定制组件(Custom Components)

基于布局类View和ViewGroup的基本功能,Android为创建自己的UI界面提供了先进和强大的定制化模式。首先,平台包含了各种预置的View和ViewGroup子类---Widget和layout,可以使用它们来构造自己的UI界面。

部分的可以利用的widget包括:Button、TextView、EditText、ListView、CheckBox、RadioButton、Gallery、Spinner、以及比较特殊用途的AutoCompleteTextView、ImageSwitcher和TextSwitcher。

其中可利用的布局是:LinearLayout、FrameLayout、RelativeLayout以及其他的布局。

如果遇到了没有预置的widget或layout的需求,可以创建自己的View子类。如果只需要对既存的widget或layout进行小的调整,那么只需简单的继承widget或layout,并且重写它们的方法。

创建自己的View子类,以便能够精准的控制屏幕元素的外观和功能。以下是用定制View对象来实现这种控制想法的一些例子:

  • 创建一个完全的定制化渲染的View类型,如用类似模拟电子控制的2D图形来渲染的音量控制按钮。

  • 把一组View组件组合成一个新的单一组件,制作一些像ComboBox(一个下拉列表和文本输入域的组合)、双面板选择器(左右两个列表面板,右边的列表面板中的项目与左边列表面板中的一个项目相关联)等组件。

  • 重写一个EditText组件在屏幕上的渲染的方法。

  • 捕获一些像按键一样的事件,并在某些定制的方法中处理它们(如游戏)。

基本方法

以下是创建自定义View组件需要要了解基本概要:

  • 自定义的View类要继承一个既存的View类或其子类;

  • 在子类重写父类的一些方法。要覆写的父类方法是用‘on’开头的,例如,onDraw()、onMeasure()和onKeyDown()等,这有点类似于重写Activity或ListActivity的生存周期回调的on…事件。

  • 使用新的扩展类,一旦完成,新扩展的类就能被用于替换基本的View对象。

提示:扩展类能够作为使用它们的Acticity的内部类来定义。这样对控制对它们的访问是有益的,当然可以创建一个新的公共的View类,这样就可以在应用程序范围内来使用。

完全定制化的组件

完全定制化的组件能够用于创建你所期望的显示效果的图形化组件。可以是看上去像旧的模拟仪表的图形化VU仪表,或者是一个长的歌词视图,有一个跳动的球沿着歌词移动,以便跟着这卡拉OK机歌唱,这两种情况,无论如何组织内置的组件都无法满足要求。

幸运的是,能够使用任意自己喜欢的方法来创建组件的外观和行为,唯一的限制就是你的想象力、屏幕的尺寸和可利用的处理能力(因为应用程序最终可能运行在比桌面工作站处理能力要弱的设备上)。
以下是创建完全定制组件的步骤:

  • 毋庸置疑,能够扩展的最通用的视图是View类,因此通常是继承这个View类来创建自己的新的组件;

  • 提供一个能够从XML中获取属性和参数的构造器,并且也能够使用自己属性和参数(如VU仪表的颜色和范围,指针的宽度和阻尼等);

  • 创建组件中可能的事件监听器、属性访问器和修饰符以及尽可能准确的行为等;

  • 覆写onMeasure()回调方法,如果想要组件显示一些东西,也要覆写onDraw()回调。虽然它们都有默认的行为,onDraw()回调默认什么也不做,onMeasure()方法默认的要设置组件的尺寸为100x100;

  • 覆写其他的需要on…方法。

扩展onDraw()和onMeasure()

onDraw()方法会把能够实现的任何想要的东西放到一个Canvas对象上,如2D图形、标准或定制的组件、样式化的文本、或其他任何能够想到的东西。

注意:View类不能使用3D图形。如果要使用3D图形,必须继承SurfaceView类,而不是View类,并且要在一个独立的线程中描画。

onMeasure()方法有点复杂,它是组件和它的容器之间的渲染约束的关键部分。覆写onMeasure(),以便准确高效的报告组件被包含部分的尺寸。由于来自父容器限制的要求,使得尺寸的测量有些复杂,并且组件的尺寸一旦被计算完成,就要调用setMeasureDimension()方法来保存测量的宽度和高度。如果在onMeasure()方法中调用setMeasureDimension()方法失败,这个结果在测量时将是一个异常的值。

在上层看,实现onMeasure()方法的步骤如下:

  • 要用父容器的宽度和高度的计量规格来调用被覆写的onMensure()方法(widthMeasureSpec和heightMeasureSpec参数都是代表了尺寸的整数),这两个参数应该作为生成组件的宽度和高度的约束要求。对于这些规格约束类型的完整说明可以在View类说明的View.onMeasure(int,int)方法中找到。

  • 组件的onMeasure()方法应该计算用于渲染组件所需的尺寸(宽度和高度)。组件应该尽量保留在被传入的规格范围内,尽管它能够选择超出规格范围(在这种情况下,父容器能够选择做的事情包括:裁剪、滚动、抛出异常、或者要求onMeasure()方法用不同的尺寸规格再试)。

  • 一旦组件的宽度和高度被计算完成,就必须调用setMeasuredDimension(int width, int height)方法来保存计算结果。不这样做就会抛出一个异常。

下表是framework调用View类的其他标准方法:

分类

方法

说明

Creation

Constructors

构造器的调用有两种类型:1.在代码中创建View对象;2.用布局文件填充View对象。第二种类型应该解析和应用布局文件中的任何属性定义。

onFinishInflate()

View对象和它的所有子对象都用XML填充完之后,调用这个方法。

Layout

onMeasure(int, int)

调用这个方法决定View对象及其所有子对象的尺寸要求。

onLayout(boolean,int,int,int,int)

View对象给它的所有子对象分配尺寸和位置时,调用这个方法。

onSizeChanged(int,int,int,int)

View对象的尺寸发生改变时,调用这个方法。

Drawing

onDraw(Canvas)

View对象渲染它的内容时,调用这个方法。

Event

onKeyDown(int,KeyEvent)

当一个键的按下事件发生时,调用这个方法

onKeyUp(int,KeyEvent)

当一个键弹起事件发生时,调用这个方法

onTrackballEvent(MotionEvent)

当鼠标轨迹球滚动事件发生时,调用这个方法。

onTouchEvent(MotionEvent)

当触屏事件发生时,调用这个方法。

Focus

onFocusChanged(boolean,int,Rect)

View对象获取或失去焦点时,调用这个方法。

onWindowFocusChanged(boolean)

当包含View对象的窗口获得或失去焦点时,调用这个方法。

Attaching

onAttachedToWindow()

View对象被绑定到一个窗口时,调用这个方法。

onDetachedFromWindow()

View对象被从它的窗口中分离的时候,调用这个方法。

onWindowVisibilityChanged(int)

当包含View对象的窗口的可见性发生改变时,调用这个方法。

定制View的例子

在API Demos中提供了一个定制的View对象的例子:CustomView。这个定制的View定义在LabelView类中。

LabelView示例展示了很多定制组件的不同特征:

  • 继承View类的完全定制化的组件;

  • 参数化的带有View填充参数(在XML中定义的参数)方式构造View对象。有一些填充参数使用通过这个View的父类传递过来的,还有一些用于labelView对象而定义的定制的属性;

  • 你所期望看到的标准的公共类型的方法,如setText()、setTextSize()、setTextColor()等等;

  • 一个重写的onMeasure()方法,它决定和设置了组件的渲染尺寸。(注意:在LabelView类中,实际的工作是由一个私有的measureWidth()方法来做的。)

  • 一个重写的onDraw()方法,它在提供的Canvas上描画标签。

从这个示例的custom_view_1.xml中,能够看到一些LabelView定制View的用法。实际上,可以看到android:命名空间参数和定制的app:命名空间的组合。这些app:参数是LabelView类所承认的并用于工作的一些定制化的属性,并且这些参数在示例的R资源定义类的styleable内部类中被定义。

复合控件

如果不想创建一个完全定制的组件,而是想要把一组既存的控件放到一起,形成一个可重用的组件,那么创建一个复合组件(或复合控件)是一个合适的选择。在一个容器中,复合组件把多个原子化的控件(或View)组合成一个逻辑组,它能够处置一件单一的事情。例如,ComboBox控件,就是通过一个单行文本域、一个按钮和一个弹出列表组合而成的,如果按下按钮并且从列表中选择了一项,那么选择项就会填入单行文本域,而且如果用户喜欢,也可以直接把内容输入到文本域中。

在Android中,实际上有另外两View可以很容易的完成ComboBox的工作:Spinner和AutoCompleteTextView,但是不管怎样,使用ComboBox的例子会使得概念更容易理解。

以下是创建复合组件的步骤:

  • 通常的起点是用某种类型布局,创建一个扩展布局的类。在ComboBox的例子中,可以使用水平方向的LinearLayout布局。其他布局也可以嵌套到内部,以便复合组件能够随意的组合。注意,跟Activity一样,既可以使用基于XML声明的方法来创建一个复合组件,也可以编程的方式把它们嵌入到代码中。

  • 在新类的构造器中,可以带入任何子类需要的参数,并且首先要通过子类的构造器来传递它们。然后,就可以创建新组件中使用的其他的View了;ComboBox组件就是在这个时候创建了文本域和弹出列表。注意,也可以把自己的属性和参数引入到XML声明中,这些属性和参数能够被构造器使用。

  • 还可以给被包含的View所可能产生的事件创建监听器,例如,针对列表项的Click监听器的方法,如果一个列表项被选择了,那么这个监听器方法就会更新文本域的内容。

  • 创建自定义带有访问器和编辑器的属性,例如,ComboBox组件中针对文本域的get…和set…方法。

  • 在扩展一个Layout的情况下,不需要重写onDraw和onMeasure()方法,因为布局的默认行为就可以很好的工作。但是,如果需要你依然可以重写它们。

  • 还可以重写其他的on…方法,如可以重写onKeyDown()方法,当某个键被按下时,可以从ComboBox的弹出列表中选择某些默认值。

总结,使用布局作为定制控件的基础有以下好处:

  • 能够像Activity一样使用声明的XML文件来指定布局,也能够用编程的方式创建View,并且从代码中把它们嵌入到布局中。

  • onDraw()和onMeasure()方法(还有其他的大多on…方法)都有适当的行为,因此不用重写它们。

  • 最后,可以快速的构造任务复杂的复合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

需要注意的一些问题:

  • 定义

    使用下面方法来定义类:

    public static class LinedEditTextextends EditText

    • 它是作为NoteEditorActivity的一个内部类来定义的,但是它是公共的,这样如果需要,就可以在NoteEditor类的外部用NoteEditor.MyEditText方式来访问。

    • 它是静态的,这就;意味着不会产生所谓的允许访问来自父类的数据的合成方法,反过来意味着它实际是作为一个独立的类,而没有跟NoteEditor进行强制的关联。如果不需要从类的外部访问状态,要保持生成小的类,并且还要允许在其他类中能够容易的使用它,那么这是一种创建内部类的比较清晰的方式。

    • 它扩展了EditText类,它是我们选择的要定制的View。定制完成后,新的类可以替代普通的EditText类。

  • 类初始化

    始终首先要调用的是super方法,它不是默认的构造器,而是参数化的一种方式。EditText是用XML布局文件中的这些参数来创建的,因此,我们的构造器需要带入这些参数,并把它们传递给父类的构造器。

  • 重写方法

    这个例子中,仅重写了一个方法:onDraw().

    对于NotePad示例,重写onDraw()方法允许我们在EditText的画布上画蓝线(画布是有重写的onDraw()方法传入的)。在这个方法的最后,调用了super.onDraw()方法。父类的方法应该被调用,但是在这个例子中,我们在画完我们想要的线之后调用了父类的onDraw()方法。

  • 使用定制组件

    组件定制完之后,如何使用它呢?在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" />

定制的组件是用XML中的一个一般的View来创建,并且使用完整的包名来指定类。还要注意定义中引用内部类的方法:NoteEditor$LinedEditText,这是Java编程语言标准的引用内部类的方法。如果定制View组件没有作为内部类来定义,那么就要选择用XML元素名来声明View组件,并且要去除类的属性,如:

<com.android.notepad.LinedEditText
  id="@+id/note"
  ... />

注意,LinedEditText类是一个独立的类文件,当这个类被嵌套在NoteEditor类中时,这种技术是不会工作的。

定义中的其他属性和参数被传入定制组件的构造器中,然后传递给EditText类的构造器,因此这些属性和参数与EditText类使用的是相同的。注意,也可以添加自己的参数。

Copyright© 2020-2022 li-xyz 冀ICP备2022001112号-1