Android开发指南-用户界面-绘制视图

Android开发指南-用户界面-绘制视图

2023年7月15日发(作者:)

Android开发指南-用户界面-绘制视图

Android怎么绘画视图How Android Draws Views

当一个活动接收到焦点时,它将被要求绘制它的布局。Android框架将处理这个绘画的过程,但是活动必须提供它的布局层次的根节点。

绘画从布局的根节点开始。它被要求来测量和绘制布局树。绘画通过遍历布局树并渲染每个和失效区域相交的视图来处理。相应的,每个视图组负责请求绘制它的子视图(通过draw() 方法)而每个视图负责画它自己。因为这个树是顺序遍历的,这意味着先画父节点(也就是在屏幕后面),然后按照树中出现的顺序画其同层次节点。

框架将不会画不在失效区域的视图,而且还将会帮你画视图背景。

你可以强制一个视图被重画,通过调用invalidate()。

绘画布局共有两步:一个度量过程和一个布局过程。度量过程在measure(int, int)里实现且是一个自顶向下的视图树遍历。每个视图在递归时往下推送尺寸规格。在度量过程的最后,每个视图都已经保存了自己的度量。第二个过程发生在layout(int, int, int, int) 中并且也是自顶向下。在这个过程中,每个父节点负责定位它的所有子节点,通过使用在度量过程中计算得到的尺寸。

measure()方法返回时,它的getMeasuredWidth()和getMeasuredHeight() 值必须被设置,以及所有这个视图子节点的值。一个视图的度量的当一个视图的宽度和高度值必须符合父视图引入的限制。这确保在度量过程之后,所有父节点接受所有它们的子节点的度量值。一个父视图可能会在其子视图上多次调用measure()方法。比如,父视图可能会通过未指定的尺寸调用measure来发现它们的大小,然后使用实际数值再次调用measure(),如果所有子视图未做限制的尺寸总合过大或过小(也即是,如果子视图之间不能对各自占据的空间达成共识的话,父视图将会干预并设置第二个过程的规则)。

要开始一个布局,可调用requestLayout()。这个方法通常在视图认为它自己不再适合它当前的边界的情况下被调用。

度量过程使用两个类来交流尺寸。eSpec类被视图用来告诉它们的父视图它们想如何被度量和定位。基础的LayoutParams类仅仅描述了视图想有多大(高和宽)。对于每个维度,它可以指定下面之一:

一个准确的数值

FILL_PARENT, 这意味着视图想和父视图一样大(减掉填充padding)。

WRAP_CONTENT, 这意味着视图只想有刚好包装其内容那么大(加上填充)

对于不同的ViewGroup子类,有相应的LayoutParams子类。比如,相对布局RelativeLayout有它自己的LayoutParams子类,这包含了能够让子视图横向和竖向居中显示的能力。

度量规格(MeasureSpecs)被用来沿着树从父到子的下传度量需求。一个MeasureSpecs可以是下面三种模式之一: 

UNSPECIFIED: 这被父视图用来决定其子视图期望的尺寸。比如,一个线性布局可能在它的子视图上调用measure() on its child,通过设置其高度为UNSPECIFIED 以及一个宽度为EXACTLY 240,来找出这个子视图在给定240像素宽度的情况下需要显示多高。

EXACTLY: 这被父视图用来给子视图强加一个准确的尺寸。子视图必须使用这个大小,并确保其所有的后代将适合这个尺寸。

AT_MOST: 这被父视图用来给子视图强加一个最大尺寸。子视图必须确保它自己以及所有的后代都适合这个尺寸。

Android开发指南-用户界面-创建自定义组件

Android提供了一个精致而强大的组件化模式来创建你的用户界面,基于基础的布局类:视图

View和视图组ViewGroup。平台包含了多种预定义视图和视图组子类-分别称为部件和布局-这些可以用来构造你的用户界面。

一部分可用部件包括按钮Button,文本视图TextView,编辑文本框EditText,列表视图ListView,组合框CheckBox, 单选按钮RadioButton, 画廊Gallery, 微调器Spinner, 以及一些用于特定场合的自动补全文本视图AutoCompleteTextView, 图片切换器ImageSwitcher, 和文本切换器TextSwitcher.

可用布局有线性布局LinearLayout, 框架布局FrameLayout, 相对布局RelativeLayout, 以及其他。更多例子,参见常用布局对象Common Layout Objects.

如果这些预定义的部件或布局不能满足你的需求,你可以创建你自己的视图类。如果你只需要在现有的部件或布局上做些调整,你只需要子类化这个部件或布局并重写它的方法。

创建自己的视图子类让你可以精确控制界面元素的外形和功能。为了对这种控制有个大概的印象,下面是一些例子说明你可以用它们做什么:

你可以创建一个完全自定义绘制的视图类型,比如一个用2D图形绘制的“音量控制”旋钮,使它看上去像一个模拟电子件。

你可以把一组视图组件组合进一个单独的组件里,也许像一个组合框(是弹出列表和自由输入的文本段的组合),一个双窗格选择器控件(一个左窗格和右窗格,里面各有一个列表,你可以选择哪个项应该在哪个列表中),如此等等。

你可以重写一个编辑文本EditText组件的绘制方式(记事本指南Notepad Tutorial中使用这个方法创建了一个条纹状的记事本页面效果)。

你可以捕获其他事件如按键然后以自定义的方式处理。(比如一个游戏所做的那样)

下面的章节解释了怎么去创建自定义视图和在你的应用程序中使用它们。详细的参考信息,请查看视图View 类。

基本方法The Basic Approach

下面是关于怎么样开始创建自定义视图组件的一个概要性的总体描述: 1.

扩展一个现有的视图类然后子类化它。

2.

重写父类中的一些方法。这些方法以“on”开始,比如onDraw(), onMeasure(),和

onKeyDown()。这和活动或列表活动中为生命周期和其他功能钩子重写on…事件类似。

3.

使用你的新扩展类。这些完成后,你的扩展类就可以替代那个基础视图了。

提示: 扩展类可以被定义成使用它们的活动的内部类。这样活动可以控制对它们的访问,这很有用但并非必须如此(也许你想在应用程序里创建一个使用更广的公共视图)。

完全自定义组件Fully Customized Components

完全自定义组件可以用来创建你想要的图形组件。也许是一个图形VU表看起来像一个老式的模拟计量器,或者是一个伴唱字幕,上面有一个弹球随着文字移动这样你就可以在卡拉OK机上跟唱。无论哪种方式,你想要的东西都是内置组件所不能完成的,不管你怎么组合它们。

幸运的是,你可以简单的按照你的意愿来创建组件,除非你想不到,或者受限于屏幕尺寸,以及可用电源(记住,最终你的应用程序得运行在比桌面工作站电源少得多的设备上)。

要创建一个完全自定义组件:

1.

毫无意外,你可以扩展的最通用的视图是View, 因此你通常从扩展它开始创建你的超级组件。

2.

你可以供应一个构造器从XML中读取属性和参数,你也可以消费你自己的属性和参数(也许是VU表的颜色和范围,或者指针的宽度和阻尼,等)

3.

你可能也想在你的组件中创建自己的事件侦听器,属性访问和修改器,以及其它可能更复杂的行为。

4.

你将几乎肯定要重写onMeasure() 而且也很可能需要重写onDraw()。如果你希望这个组件显示一些东西。两者都有缺省行为,缺省的onDraw()方法不做任何事情,而缺省onMeasure()方法将总是设置一个100x100的尺寸-这或许不是你想要的。

5.

其他on...方法也可以按照要求重写。

扩展onDraw()和onMeasure() Extend onDraw() and onMeasure()

onDraw()方法传给你一个画布Canvas对象,你可以在上面实现任何你想要的东西:2D图形,其它基础或自定义组件,风格文本,或其他任何你能想到的。

注意: 这不适用于3D图形。如果你想使用3D图形,你必须扩展SurfaceView而不是View,并且从一个单独的线程中绘制。参见GLSurfaceViewActivity示例以了解更多细节。.

onMeasure()会被用得更多一点。onMeasure()是你的组件和它的容器之间绘制约定的关键部分。onMeasure()应该被重写来有效和准确的报告它所包含部分的尺寸。但是由于父类的一些限制性要求(被传递给onMeasure()方法)和以宽度和高度(一旦已经被计算出来)调用setMeasuredDimension()方法的要求,这将变得稍微复杂一些。如果你从一个重写的onMeasure()方法中调用这个方法失败,结果将返回一个测量时异常。

概要而言,实现onMeasure()看起来如下:

1.

给定宽度和高度测量规格(widthMeasureSpec 和heightMeasureSpec,两者都是代表维度的整数编码)来调用重写的onMeasure方法,这应该被当作度量上的限制性要求。完整的参考在ure(int, int)文档中,这篇参考文档还很好的描述了整个测量操作)。

2.

你的组件的onMeasure()方法应该计算一个宽度和高度用来绘制这个组件。它应该尽可能留在传递的规格所指定的范围里,尽管它可以选择超出它们(这样的话,父类可以选择处理方式,包括裁剪,滚动,抛出异常,或者要求onMeasure()再试一次,也许会使用不同的度量规格。)

3.

一旦宽度和高度被计算出来,这个setMeasuredDimension(int width, int

height)方法必须以计算出来的度量来调用。如果调用不成功,则将抛出异常。

下面是框架在视图上调用的一些其他基本方法的汇总:

一个自定义视图示例A Custom View Example

这个在API Demos中的CustomView例子提供了自定义视图的示范。这个自定义视图定义在

LabelView 类中。

LabelView例子说明了自定义组件的很多不同方面:

为一个完全自定义组件扩展视图类。

参数化的构造器,采用视图扩充参数(这些参数定义在XML中)。其中一些被传递给视图超类,但更重要的是,有一些自定义属性被LabelView所用。

基本的公共方法来设置一个label组件你所期望的类型,比如setText(), setTextSize(),

setTextColor() 等等。

一个重写的onMeasure方法来决定和设置这个组件的绘制尺寸。(注意在LabelView中,实际工作是通过一个私有的measureWidth()方法来完成的。)

一个重写的onDraw()方法来把标签(label)画到提供的画布canvas中。 method to draw the

label onto the provided canvas.

你可以在custom_view_中查阅一些使用范例。特别是,你可以看到android:命名空间参数和自定义app: 命名空间参数的一个混合。这些app:参数是LabelView认识和使用的,并被定义在R资源类的一个可格式化的内部类里。

复合控件Compound Controls

如 果你不想创建一个完全自定义的组件,但希望由一组已有控件来组装成一个可复用组件,那么创建一个复合组件(或复合控件)可以满足要求。在一个小容器里,把 一些更原子的控件(或视图)整合进一个逻辑项目组中,从而可以被当作单个控件来对待。比如,一个组合框,可以被看作是一个单行文本编辑控制和一个附有弹出 列表的相邻按钮的组合。如果你按下这个按钮并从列表中选择一些项,它将被生成到文本编辑域中,不过用户也可以直接在文本编辑域进行输入如果愿意的话。

在Android里,事实上有另外两种视图也是这样做的: Spinner和AutoCompleteTextView,不过,组合框的概念比较容易理解。

为了创建一个复合组件:

1.

通常的起点是某种类型的布局,因此创建一个扩展布局的类。可能在组合框例子中,我们会使用一个水平方向的线性布局LinearLayout。记住其它布局可以被嵌套在里面,这样这个复合组件可以任意复杂和结构化。注意就像一个视图,你既可以使用声明(基于XML)方式来创建这个包含的组件,也可以通过编码在程序中嵌入它们。

2. 在这个新类的构造函数中,采用任何超类期望的参数,并首先传递给超类构造函数。然后你可以建立用于新组件的其它视图;这就是你将创建EditText域和PopupList的地方。注意你还可能需要引入你自己的属性和参数到XML中,这些将被提取出来并为你的构造函数所用。

3. 你还可以为包含的视图可能产生的事件创建侦听器,比如,一个列表项点击侦听器的方法,用来在一个列表项被选中时更新相应的文本编辑框内容。

4. 你可能还想通过访问和修改函数来创建自己的特性,比如,允许EditText值可以被初始化并在需要时被查询到。

5. 在扩展一个布局时,你不需要重写onDraw()和onMeasure()方法,因为布局会有缺省行为。不过,如果需要的话,你同样可以重写它们。

6. 你可能会重写其它on... 方法,比如onKeyDown(),当一个特定键被按下时可能会从弹出列表中选择某个特定的默认值。

总之,基于布局创建自定义控件有如下一些好处,包括:

你可以像活动屏幕一样使用声明性的XML来指定布局,或者通过编程来实现视图嵌套。 

onDraw()和onMeasure()方法(加上大多数其它on... 方法)将可能有合适的行为,这样你就不需要重写它们。

最后,你可以很快地构建任意复杂的复合视图以及把它们当作单个组件来重用。

复合控件的例子Examples of Compound Controls

在随SDK发布的API Demos项目中,有两个列表例子-例4和例6,在Views/Lists下面,演示了一个从线性布局扩展的SpeechView,该组件用来显示演讲摘录。相应的类在

和里。

修改一个已有视图类型Modifying an Existing View Type

在某些情况下有更简单实用的方法来创建自定义视图。如果有一个视图已经和你想要的很相似,你只需要在这个组件上直接扩展并重写相关行为方法即可。通过完全自定义的组件你当然可以做任何事,不过通过一个视图层次图中的特定类,你也一样可以做很多事来满足需求。

比如,SDK在这些例子中包含了一个记事本应用程序NotePad application 。该程序演示了Android平台应用的很多方面,其中之一就是扩展一个EditText视图来创建一个条纹记事本。这并非一个完美的例子,所使用的APIs可能有所变化,但它的确说明了原理。

如果你没有这么做过,导入记事本例子到Eclipse中。特别是看一下文件中的MyEditText定义。

下面是一些值得注意的地方:

1.

定义The Definition

这个类以下面的代码行定义:

public static class MyEditText extends EditText

o

它被定义为NoteEditor活动的内部类,但它是公共的,因此它可以在NoteEditor类之外以Text来访问。

o

它是静态的,这意味着它不产生允许它从父类中访问数据的所谓的“伪方法”,这就意味着它的行为更像一个独立的类而不是和NoteEditor有着很强的关联。这是一个用来创建内部类的简洁方式,如果这些类不需要从外部类中访问状态,这使得产生的类小巧且很容易从被其它类所使用。

o

它扩展了EditText,我们选择基于这个视图来自定义组件。这些结束后,新的类将能够被用来代替通常的EditText视图。

2.

类初始化Class Initialization

通常,先调用超类。此外,这并非缺省构造器,而是一个参数化的构造器。这个EditText通过扩充在XML布局文件中的这些参数创建,这样,我们的构造器同样需要采用它们并传递给超类构造器。

3.

重写方法Overridden Methods

这个例子中,只有一个方法需要被重写:onDraw()- 但是当创建你自己的组件时,可能需要重写其它方法。 对于记事本NotePad例子,重写onDraw()方法可允许我们在EditText视图画布上绘制蓝线(这个画布canvas被传给这个重写的onDraw()方法)。这个()方法在这个方法结束前被调用。这个超类方法应该被调用,但在这个例子里,我们是在画好线后才调用它。

4.

使用自定义组件Use the Custom Component

现在我们已经有了自定义组件,但我们该怎么用它呢?在记事本NotePad例子中,这个自定义组件直接在布局声明中使用,所以看一下res/layout目录下的note_文件。

class="itor$MyEditText"

id="@+id/note"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:background="@android:drawable/empty"

android:padding="10dip"

android:scrollbars="vertical"

android:fadingEdge="vertical" />

o

这个自定义组件在XML中以一个通用视图创建,而这个类用包全名来指定。还需要注意到这个我们定义的内部类通过NoteEditor$MyEditText 标记来引用,这在Java编程语言中是一个引用内部类的标准方式。

如果你的自定义视图组件不是定义为一个内部类,那么你可以,有选择的,以XML元素名来声明这个视图组件,并且不需要包含class属性。比如:

id="@+id/note"

... />

注意MyEditText 类现在是一个独立的类文件,当这个类被嵌入到NoteEditor类里时,这个技术无效。

o

定义中的其它属性和参数被传递给自定义组件的构造函数,然后传递给EditText的构造函数,因此这些是和你用于EditText视图的相同的参数。注意你同样可以添加自己的参数,下面会再谈到这一点。

这就是所有的内容。我承认这只是一个简单的例子,但关键是-创建自定义组件的复杂度只和你的需求有关。

一个更复杂的组件可以重写更多的on... 方法并引入一些它自己的帮助(helper)方法,大量的定制它的特性和行为。唯一的限制在于你的想象力和你想让这个组件做什么。

发布者:admin,转转请注明出处:http://www.yc00.com/web/1689428352a246572.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信