复合控件
如果不想创建一个完全定制的组件,而是想要把一组既存的控件放到一起,形成一个可重用的组件,那么创建一个复合组件(或复合控件)是一个合适的选择。在一个容器中,复合组件把多个原子化的控件(或View)组合成一个逻辑组,它能够处置一件单一的事情。例如,ComboBox控件,就是通过一个单行文本域、一个按钮和一个弹出列表组合而成的,如果按下按钮并且从列表中选择了一项,那么选择项就会填入单行文本域,而且如果用户喜欢,也可以直接把内容输入到文本域中。
在Android中,实际上有另外两View可以很容易的完成ComboBox的工作:Spinner和AutoCompleteTextView,但是不管怎样,使用ComboBox的例子会使得概念更容易理解。
以下是创建复合组件的步骤:
1. 通常的起点是用某种类型布局,创建一个扩展布局的类。在ComboBox的例子中,可以使用水平方向的LinearLayout布局。其他布局也可以嵌套到内部,以便复合组件能够随意的组合。注意,跟Activity一样,既可以使用基于XML声明的方法来创建一个复合组件,也可以编程的方式把它们嵌入到代码中。
2. 在新类的构造器中,可以带入任何子类需要的参数,并且首先要通过子类的构造器来传递它们。然后,就可以创建新组件中使用的其他的View了;ComboBox组件就是在这个时候创建了文本域和弹出列表。注意,也可以把自己的属性和参数引入到XML声明中,这些属性和参数能够被构造器使用。
3. 还可以给被包含的View所可能产生的事件创建监听器,例如,针对列表项的Click监听器的方法,如果一个列表项被选择了,那么这个监听器方法就会更新文本域的内容。
4. 创建自定义带有访问器和编辑器的属性,例如,ComboBox组件中针对文本域的get…和set…方法。
5. 在扩展一个Layout的情况下,不需要重写onDraw和onMeasure()方法,因为布局的默认行为就可以很好的工作。但是,如果需要你依然可以重写它们。
6. 还可以重写其他的on…方法,如可以重写onKeyDown()方法,当某个键被按下时,可以从ComboBox的弹出列表中选择某些默认值。
总结,使用布局作为定制控件的基础有以下好处:
1. 能够像Activity一样使用声明的XML文件来指定布局,也能够用编程的方式创建View,并且从代码中把它们嵌入到布局中。
2. onDraw()和onMeasure()方法(还有其他的大多on…方法)都有适当的行为,因此不用重写它们。
3. 最后,可以快速的构造任务复杂的复合View,并且可以把它们作为一个单一的组件来重用。
复合控件的例子
在SDK的API Demos工程中,有两个List的例子---在Views/Lists目录下示例4和示例6演示了一个SpeechView组件,它扩展了LineraLayout布局让组件显示语音查询结果。对应的类在List4.java和List6.java的示例代码中。
修改既存的View类型
在某些情况中,对于创建有用的定制的View甚至有更容易的选项。如果有一个既存的组件与想要的非常相似,就可以简单的扩展这个组件,并且重写想要改变的行为。可以用完全定制的组件做所有想做的事情,但是通过从View层次树中的一个更特殊的类开始,还能够获得更多的你所期望的已经实现的行为。
例如,在SDK包含的NotePad应用程序的示例中,有许多使用Android平台的特征,其中有扩展EditText控件的单行记事本。这不是一个完美的例子,但是它演示了组件定制的一些原则。
使用下面链接,把NotePad示例的代码导入到Eclipse中,实际看一下NoteEditor.java文件中LinedEditText类的定义。
http://developer.android.com/resources/samples/NotePad/index.html
需要注意的一些问题:
1. 定义
使用下面方法来定义类:
public static class LinedEditTextextends EditText
A. 它是作为NoteEditorActivity的一个内部类来定义的,但是它是公共的,这样如果需要,就可以在NoteEditor类的外部用NoteEditor.MyEditText方式来访问。
B. 它是静态的,这就;意味着不会产生所谓的允许访问来自父类的数据的合成方法,反过来意味着它实际是作为一个独立的类,而没有跟NoteEditor进行强制的关联。如果不需要从类的外部访问状态,要保持生成小的类,并且还要允许在其他类中能够容易的使用它,那么这是一种创建内部类的比较清晰的方式。
C. 它扩展了EditText类,它是我们选择的要定制的View。定制完成后,新的类可以替代普通的EditText类。
2. 类初始化
始终首先要调用的是super方法,它不是默认的构造器,而是参数化的一种方式。EditText是用XML布局文件中的这些参数来创建的,因此,我们的构造器需要带入这些参数,并把它们传递给父类的构造器。
3. 重写方法
这个例子中,仅重写了一个方法:onDraw().
对于NotePad示例,重写onDraw()方法允许我们在EditText的画布上画蓝线(画布是有重写的onDraw()方法传入的)。在这个方法的最后,调用了super.onDraw()方法。父类的方法应该被调用,但是在这个例子中,我们在画完我们想要的线之后调用了父类的onDraw()方法。
4. 使用定制组件
组件定制完之后,如何使用它呢?在NotePad例子中,定制组件在声明的布局中被直接使用,因此需要看一下res/layout文件中的note_editor.xml文件中的声明:
class="com.example.android.notepad.NoteEditor$LinedEditText"
android:id="@+id/note"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@android:color/transparent"
android:padding="5dp"
android:scrollbars="vertical"
android:fadingEdge="vertical"
android:gravity="top"
android:textSize="22sp"
android:capitalize="sentences" />
定制的组件是用XML中的一个一般的View来创建,并且使用完整的包名来指定类。还要注意定义中引用内部类的方法:NoteEditor$LinedEditText,这是Java编程语言标准的引用内部类的方法。如果定制View组件没有作为内部类来定义,那么就要选择用XML元素名来声明View组件,并且要去除类的属性,如:
id="@+id/note"
... />
注意,LinedEditText类是一个独立的类文件,当这个类被嵌套在NoteEditor类中时,这种技术是不会工作的。
定义中的其他属性和参数被传入定制组件的构造器中,然后传递给EditText类的构造器,因此这些属性和参数与EditText类使用的是相同的。注意,也可以添加自己的参数。