第三部分:Android 应用程序接口指南---第二节:UI---第十二章 自定义组件

第12章 自定义组件

Android平台提供了一套完备的、功能强大的组件化模型用于搭建用户界面,这套组件化模型以View和 ViewGroup这两个基础布局类为基础。平台本身已预先实现了多种用于构建界面的View子类和ViewGroup子类,他们被分别称为widget和layout。

界面工具集(widget)包括Button、TextView、EditText、ListView、CheckBox、RadioButton、Gallery、Spinner等这些常用部件以及有着专门用途的AutoCompleteTextView、ImageSwitcher和TextSwitcher。布局(layout)包括:LinearLayout、FrameLayout、RelativeLayout等。 如果这些已有部件和布局不能满足需求,您可以按自己的需要来实现View子类。如果对已有的部件和布局进行小调整就能满足需求,可以通过继承部件或布局并重载特定方法的方式轻松实现。开发者通过构造View子类可以对屏幕元素的样式及功能进行精确控制。为了使您对定制View的可控性有一个直观了解,下面给出可以对定制View进行操作的几个实例:

◆你可以将View定制成特别样式,比如一个使用2D图片渲染的音量调节器可以做成模拟电路控制的样子。

◆你可以将一组View组件合成为一个新的独立组件,比如制作一个下拉列表框(弹出列表和输入框的组合)、双区域选择控制器(有左、右两个选择区域,选择框中的元素可随意切换其左右位置)等等。

◆你可以重载EditText组件的屏幕绘制方式(simple里面的NotePad很好地利用了这一点,使之产生了带有下划线的记事本页面)。

◆你可以监听多种事件(包括按下按键事件),并可以定制这些事件的处理方式。

12.1 基本方法

以下是定制View组件的步骤总览:

1. 使新类继承自View类或其子类;

2. 重载父类特定方法。被重载的父类方法通常以“on”开头,例如:onDraw()、onMeasure()和onKeyDown(),这与你重载Activity或ListActivity中的事件用以控制生命周期和钩子函数的做法类似;

3. 使用该扩展类。完成后,这个扩展类便可代替其父类使用,并体现出新的特性。

小提示:扩展类可以被定义为使用它的Activity的内部类,这样做可以控制扩展类的访问权限,但这并不是必须的(有可能需要创建一个可以在App中多处用到的扩展类)。

12.2 完全自定义组件

不论您对于视图组件的期望多么夸张,完全定制的组件都可以实现,包括实现一个看起来像旧模拟仪表盘的图形化音量指示器,或者实现一个有着进度标记的长文本使之就像一台卡拉ok机的字幕。总之,这些特殊功能不可以通过已有组件实现,而且组合使用已有组件也不能满足需求。幸运的是,您可以轻松构建一个样式、外观完全满足您需要的新组件,包括控制新组件在屏幕上所占区域大小和在运行时的耗电量。

创建一个完全定制的组件需要以下几步:

1. 完全定制的组件通常继承自View类,所以搭建完全定制的组件的第一步通常是继承该类。

2. 可以向完全定制的组件加入一个接收XML属性和参数的构造函数,当然,您也可以为其指定属性和参数(比如音量指示器的颜色和范围、音量指示器指针的宽度或阻尼系数)。

3. 可以为完全定制组件创建新的事件监听器、属性访问修改器以及更为复杂的行为。

4. 基本上都会重载onMeasure()方法,在控制组件的显示时会重载onDraw()方法。这两个方法都有其默认行为,onDraw()方法默认情况下什么都不做,onMeasure()方法默认情况下会设置100*100的区域大小--而这并不一定是您所需的。

5. 对形如“on...”的方法按需进行重载。

12.2.1扩展onDraw()与onMeasure()方法

onDraw()方法提供了Canvas对象的引用,你可以在其上实现各种需求,包括:绘制2D图片或特殊风格的文本、添加其他标准组件或定制组件等。

注意:Canvas不支持3D绘图,如果要绘制3D图片,您需要继承SurfaceView类而不是View类,并在一个单独的线程中绘制图像。具体细节请查看GLSurefaceViewActivity示例程序。onMeasure()方法囊括的东西更多一些,onMeasure()方法是定制组件在其容器中正确呈现的关键。重载的onMeasure()方法应能够及时准确地反映外界对其的操作。如果定制组件的父组件对定制组件有某些限制(通过onMeasure()附加限制),或者在对组件大小经过计算后调用setMeasuredDimension()对定制组件的操作范围加以限制,在onMeasure()方法中的控制就会变得更复杂一些。如果您未能正确调用重载后的onMeasure()方法,程序就会在进行操作的时候抛出异常。

重载onMeasure()方法大致要达到以下几个要求:

1. 重载的后onMeasure()方法应该在指定操作区域被调用,而这个指定区域应该是由你加以限制(通过两个代表尺寸的整形参数:widthMeasureSpec和heightMeasureSpec)。如果需要查看对于指定区域尺寸的要求可以参考View.onMeasure(int, int)的文档(该参考文档也很好地解释了所有触屏操作)。

2. 您定制组件的onMeasure()方法应该计算出触控操作的区域大小,在绘制组件时会用到该区域大小。操作应该都限于该指定区域之内,虽然在该指定区域外也可以进行操作(这种情况下,定制组件的父组件对操作进行处理,包括:切屏、滚动、抛出异常或是调用其他特殊操作区域的onMeasure()尝试处理)。

3. 在完成对操作区域长宽的计算之后,必须调用setMeasuredDimension(int width, int height)方法,调用该方法时的参数必不可少。

下面是Framework生成View时调用的标准方法汇总,如表格12-1所示:

种类(Category)

方法

描述

Creation

Constructors

组件有两种调用构造函数的形式:1.使用代码调用;2.组件在通过布局文件进行绘制时调用。其中第二种情况,需要对布局文件中定义的全部属性进行解析和应用。

onFinishInflate()

Layout

onMeasure(int, int)

用于确定视图及其所有子视图的尺寸。

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

在根视图指定其所有子视图的尺寸和位置时调

onSizeChanged(int, int, int, int)

在视图尺寸发生变化时调用。

Drawing

onDraw(Canvas)

在绘制视图内容时调用

Event processing

onKeyDown(int, KeyEvent)

在发生按下按钮事件时被调用。

onKeyUp(int, KeyEvent)

在发生抬起按钮事件时被调用。

onTrackballEvent(MotionEvent)

在发生轨迹球移动事件时调用。

onTouchEvent(MotionEvent)

在发生轨迹球移动事件时调用。

Focus

onFocusChanged(boolean, int, Rect)

在视图得到或失去焦点时调用。

在包含该视图的窗口得到或失去焦点时调用。

onWindowFocusChanged(boolean)

Attaching

onAttachedToWindow()

在视图附加到窗口上时调用。

onDetachedFromWindow()

在视图从窗口上分离时调用。

onWindowVisibilityChanged(int)

当视图所在窗口可见性发生变化时调用。

表格12-1

12.2.2定制View控件的示例

Api Demos提供了定制View控件的示例程序,被定义为LabelView类。 从LabelView示例可以看出定制View控件与定制组件之间还是有很多不同的:

◆直接继承View类可以对定制组件有完全的控制;

◆含参的构造方法用于接收绘制属性(在XML中定义的参数)。这些参数中有一部分会传给其父类View,但更为重要的是,一部分参数会用于定义LabelView的属性;

◆定义了标准的外部方法,例如:setText(),setTextSize(),setTextColor()等等;

◆重载的onMeasure方法确定了了组件的绘制区域大小(注意,在LabelView中通过私有的measurewidth()方法完成);

◆重载的onDraw()方法会将组件在方法提供的Canvas对象上绘制。

你可以在custom_view_1.xml中看到如何使用LabelView定制组件。特别需要注意的是:你可以看到"android:"命名空间与"app:"定制组件命名空间的混合使用。以"app:"开头的参数会被定制组件(本例中时LabelView)识别使用,并会在R资源文件中以内部类的形式定义。

12.3 复合控件

如果你并不需要创建一个完全定制的组件,仅通过将已有的组件组合到一起便可满足需求,那么就创建一个复合组件(控件)吧。通过将一些小的操作(或视图)按一定逻辑组合到一起使其对外表现为单一组件。例如,组合列表框可以看成是文本输入框、选项按钮和弹出列表的组合。如果你按下备选项按钮并从列表中选择某一项,文本输入框将会显示你的选择,当然,用户也可以直接向文本输入框输入。实际上,Android平台已经提供了具有下拉列表框功能的组件:Spinner和AutoCompleteTextView,但请先假设他们并不存在,因为通过下拉列表框来讲解复合控件比较通俗易懂。构造复合控件需要以下步骤:

1. 通常首先要实现一个继承自Layout的类。对于下拉列表框,水平方向的LinearLayout是不错的选择。由于其他的布局(Layout)可以嵌入其中,所以复合组件可以在具体实现很复杂的同时保证其结构无误。你可以使用声明(通过XML文件)或编码两种方式使用你的复合组件,与在Activity的使用方法没有区别。

2. 新创建的复合组件类的构造方法需要接收其父类需要的参数,在接收到这些参数后,应首先将这些参数传给其父类构造方法用于初始化父类对象。接着你就可以向在构造函数中添加其他新视图,比如文本输入域和弹出列表。当然,你也可以在XML中引入自有的属性和参数,并在构造函数中取出并使用。

3. 你可也为复合组件中的视图(view)添加事件监听器,例如,用于选择列表项后更新文本输入框的事件监听器;

4. 可以为自有的属性创建访问控制器,比如,设置文本编辑框的初始值并可以在需要时取出它的内容。

5. 在继承Layout类的情况下,由于Layout默认行为可能刚好满足您的需求,所以修改onDraw()和onMeasure()方法并不是必须的,当然,你也可以按需修改这两个方法;

6. 可以重载其他"on..."方法,例如,可以通过重载onKeyDown()方法来决定按键与备选列表的对应项。

总而言之,以Layout为基础搭建定制组件有很多优点,包括:

◆可以像Activity那样使用声明式XML文件作为布局文件,或是通过代码创建视图部件(view)并将其嵌入布局之中。

◆onDraw()与onMeasure()方法(以及其他绝对多数"on..."方法)的默认行为都很可能满足需要,这种情况下就不需要重载这些方法了。

◆您可以非常容易地构造复杂的组件视图并实现复用,就如同在操作单一组件。

API Demos示例中有两个列表示例 — Views/Lists下的例4和例6演示了如何构造继承自LinearLayout的SpeechView类,该类用于列举演讲名言。相关代码位于List4.java、List6.java。

12.4 修改已有View

还有一种更为简单地创建定制视图(View)的备选方式,这种方式在某些情况下很实用。如果已经存在一个与需求十分相似的组件,可以继承它并按需重载该组件的某些行为。完全定制的组件也可完成相同的工作,但如果能继承自一个更具体的View子类,那么很多功能使用默认即可而不用您亲自实现。 例如,SDK中有NotePad的示例程序。从多个方面演示了如何使用Android平台,其中有介绍了如何扩展EditText视图类来绘制带线的记事本页面。这并不是一个绝佳示例,另外API也可能发生变化了,但是它确实证明了上述原则。 您可以查看NotePad示例程序的源代码或是在Eclipse中引入,尤其需要注意的是NoteEditor.java文件中LinedEditText的定义。 有几点需要注意:

1.类的定义

该类通过下述代码进行定义:

public static class LinedEditText extends EditText

◆它被定义为NoteEditor的内部类,由于被定义为公开的内部类,所以如有需要,可以在NoteEditor类外通过NoteEditor.MyEditText对其进行访问;

◆它是一个静态类,也就是说它不会产生“合成方法”来访问其所在类的数据,换言之,LinedEditText 类具有很强的独立性且与NoteEditor类的耦合性很低。如果内部类并不需要访问其外部类,将其设成静态内部类是一种较为简洁的方式,这样做可以保证生成类体积较小,同时,也可被其他类轻松使用。

◆该内部类继承自EditText,在本例中我们以EditText来定制我们需要的组件。在完成之后,这个经过定制的新类就可以替换EditText视图使用。

2.类的初始化

任何情况下,父类构造函数都会被最先调用。需要注意的是,这是一个含参构造函数而非无参构造函数。EditText视图组件会在绘制时从XML布局文件中取出必要参数,因此,定制组件的构造函数需要接收必要参数并将参数传给其基类构造函数。

3.重载方法

在本例中,只有onDraw()方法被重载,您在创建自有的定制组件时,可以按需重载其他方法。

对于NotePad示例,重写onDraw()方法允许我们在EditText视图的Canvas(Canvas对象会通过onDraw()方法传入)上绘制蓝线。另外,super.onDraw()会在重载的onDraw()方法结束前被调用。在本例中,我们在绘制完蓝线后调用基类的方法。

4.使用定制组件

现在我们有了定制组件,但是我们如何使用呢?在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文件中以扩展视图(generic view)的形式创建,类定义通过完整包名进行指明。需要注意的是我们通过NoteEditor$MyEditText句式来引用内部类,这也是Java语言引用内部类的标准方法。

如果定制视图组件不是以内部类的形式定义,那么,可以使用类的完整路径来声明视图组件,并去掉class属性 。如下所示:

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

请注意MyEditText并定义在一个单独的类文件中,如果将该类嵌入NoteEditor类中,就不能使用这种方法了。

定制组件接收定义的其他属性和参数,并将其传给EditText的构造方法,所以定制组件接收的参数与EditText视图接收的参数是相同的。当然我们也可以添加自己的参数。

 本文来自jy02432443,是本人辛辛苦苦一个个字码出来的,转载请保留出处,并保留追究法律责任的权利 QQ78117253 

posted @ 2013-12-23 11:16  jy02432443  阅读(574)  评论(0编辑  收藏  举报