创建自定义组件 Building Custom Components
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 方法,这应该被当作度量上的限制性要求。完整的参考在 View.onMeasure(int, int) 文档中,这篇参考文档还很好的描述了整个测量操作)。
2. 你的组件的 onMeasure() 方法应该计算一个宽度和高度用来绘制这个组件。它应该尽可能留在传递的规格所指定的范围里,尽管它可以选择超出它们(这样的话,父类可以选择处理方式,包括裁剪,滚动,抛出异常,或者要求 onMeasure() 再试一次,也许会使用不同的度量规格。)
3. 一旦宽度和高度被计算出来,这个 setMeasuredDimension(int width, int height) 方法必须以计算出来的度量来调用。如果调用不成功,则将抛出异常。
下面是框架在视图上调用的一些其他基本方法的汇总:
类别 Category |
方法 Methods |
描述 Description |
创建 |
构造器 |
有一种构造器形式是在从代码里创建视图时被调用,另一种是从一个布局文件中扩充视图时被调用。第二种形式应该解析并运用任何定义在布局文件中的属性。 |
当一个视图及其所有子项已经在 XML 中扩充好时被调用。 |
||
布局 |
用来决定这个视图及其所有子项的尺寸要求。 |
|
当这个视图应该为它所有的子项分配一个尺寸和位置的时候调用。 |
||
当这个视图的尺寸被改变时被调用。 |
||
绘画 |
当视图需要绘制其内容时被调用。 |
|
事件处理 |
当一个按键事件发生时被调用。 |
|
当一个按键释放事件发生时调用。 |
||
当一个跟踪球动作事件发生时被调用。 |
||
当一个触摸屏动作事件发生时被调用。 |
||
Focus |
当视图获取或丢失焦点时被调用。 |
|
当包含视图的窗口获取或丢失焦点时被调用。 |
||
Attaching |
当视图被附着到一个窗口时被调用。 |
|
当视图从一个窗口拆分开时被调用。 |
||
当包含视图的窗口的可见性发生改变时被调用。 |
一个自定义视图示例 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_1.xml 中查阅一些使用范例。特别是,你可以看到 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 ,该组件用来显示演讲摘录。相应的类在 List4.java 和 List6.java 里。
修改一个已有视图类型 Modifying an Existing View Type
在某些情况下有更简单实用的方法来创建自定义视图。如果有一个视图已经和你想要的很相似,你只需要在这个组件上直接扩展并重写相关行为方法即可。通过完全自定义的组件你当然可以做任何事,不过通过一个视图层次图中的特定类,你也一样可以做很多事来满足需求。
比如, SDK 在这些例子中包含了一个记事本应用程序 NotePad application 。该程序演示了 Android 平台应用的很多方面,其中之一就是扩展一个 EditText 视图来创建一个条纹记事本。这并非一个完美的例子,所使用的 APIs 可能有所变化,但它的确说明了原理。
如果你没有这么做过,导入记事本例子到 Eclipse 中。特别是看一下 NoteEditor.java 文件中的 MyEditText 定义。
下面是一些值得注意的地方:
1. 定义 The Definition
这个类以下面的代码行定义:
public static class MyEditText extends EditText
o 它被定义为 NoteEditor 活动的内部类,但它是公共的,因此它可以在 NoteEditor 类之外以 NoteEditor.MyEditText 来访问。
o 它是静态的,这意味着它不产生允许它从父类中访问数据的所谓的“伪方法”,这就意味着它的行为更像一个独立的类而不是和 NoteEditor 有着很强的关联。这是一个用来创建内部类的简洁方式,如果这些类不需要从外部类中访问状态,这使得产生的类小巧且很容易从被其它类所使用。
o 它扩展了 EditText ,我们选择基于这个视图来自定义组件。这些结束后,新的类将能够被用来代替通常的 EditText 视图。
2. 类初始化 Class Initialization
通常,先调用超类。此外,这并非缺省构造器,而是一个参数化的构造器。这个 EditText 通过扩充在 XML 布局文件中的这些参数创建,这样,我们的构造器同样需要采用它们并传递给超类构造器。
3. 重写方法 Overridden Methods
这个例子中,只有一个方法需要被重写: onDraw() - 但是当创建你自己的组件时,可能需要重写其它方法。
对于记事本 NotePad 例子,重写 onDraw() 方法可允许我们在 EditText 视图画布上绘制蓝线(这个画布 canvas 被传给这个重写的 onDraw() 方法)。这个 super.onDraw() 方法在这个方法结束前被调用。这个超类方法应该被调用,但在这个例子里,我们是在画好线后才调用它。
4. 使用自定义组件 Use the Custom Component
现在我们已经有了自定义组件,但我们该怎么用它呢?在记事本 NotePad 例子中,这个自定义组件直接在布局声明中使用,所以看一下 res/layout 目录下的 note_editor.xml 文件。
<view
class="com.android.notepad.NoteEditor$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 属性。比如:
<com.android.notepad.MyEditText
id="@+id/note"
... /> <span
发表评论
评论