Android中,你的应用程序程序与View类组件有着一种固定的联系,例如按钮(Button)、 文本框(TextView),可编辑文本框(EditText), 列表框(ListView), 复选框(CheckBox), 单选框(RadioButton), 滚动条(Gallery), 微调器(Spinner), 等等,还有一些比较先进的有着特殊用途的View组件,例如AutoCompleteTextView, ImageSwitcher和 TextSwitcher。除此之外,种类繁多的像 线性布局(LinearLayout),框架布局(FrameLayout), 这样的布局组件(Layout)也被认为是View组件,他们是从View类派生过来的。
你的应用程序就是这些控制组件和布局组件以某种方式结合显示在屏幕上,一般来说这些组件对你来说基本够用,但是你也应该知道你是可以通过类继承创建属于自己的组件,一般可以继承像View、Layouts(布局组件)这样的组件,甚至可以是一些比较高级的控制类组件。下面我们说一下为什么要继承:
为了实现某种目标你可能很有必要扩展一个已经存在的View组件,下面我们结合一些例子教你如何去做。
下面的一些步骤都比较概括,教你如何创建自己的组件:
on
’开头,如onDraw(), onMeasure()和 onKeyDown()等等。
继承类能够定义在activities里面,这样你能够方便的调用,但是这并不是必要的(或许在你的应用程序中你希望创建一个所有人都可以使用的组件)。
完全自定义组件的方法可以创建一些用于显示的图形组件(graphical components),也许是一个像电压表的图形计量器,或者想卡拉OK里面显示歌词的小球随着音乐滚动。无论那种方式,你也不能单纯的利用组件的结合完成,无论你怎么结合这些现有的组件。
幸运的是,你可以以你自己的要求轻松地创建完全属于自己的组件,你会发现不够用的只是你的想象力、屏幕的尺寸和处理器的性能(记住你的应用程序最后只会在那些性能低于桌面电脑的平台上面运行)。
下面简单介绍如何打造完全自定义的组件:
onMeasure()
函数,因而你就不得不重载 onDraw()
函数。当两个函数都用默认的,那么 onDraw()
函数将不会做任何事情,并且默认的 onMeasure()
函数自动的设置了一个100x100 —的尺寸,这个尺寸可能并不是你想要的。on...
系列函数都需要重新写一次。onDraw()
和onMeasure()
onDraw()
函数将会传给你一个 Canvas 对象,通过它你可以在二维图形上做任何事情,包括其他的一些标准和通用的组件、文本的格式,任何你可以想到的东西都可以通过它实现。
注意: 这里不包括三维图形如果你想使用三维的图形,你应该把你的父类由View改为SurfaceView类,并且用一个单独的线程。可以参考GLSurfaceViewActivity 的例子。
onMeasure()
函数有点棘手,因为这个函数是体现组件和容器交互的关键部分,onMeasure()应该重载,让它能够有效而准确的表现它所包含部分的测量值。这就有点复杂了,因为我们不但要考虑父类的限制(通过onMeasure()传过来的),同时我们应该知道一旦测量宽度和高度出来后,就要立即调用setMeasuredDimension() 方法。
概括的来讲,执行onMeasure()
函数分为一下几个阶段:
onMeasure()
方法会被调用,高度和宽度参数同时也会涉及到(widthMeasureSpec
和heighMeasureSpec
两个参数都是整数类型),同时你应该考虑你产品的尺寸限制。这里详细的内容可以参考View.onMeasure(int, int) (这个连接内容详细的解释了整个measurement操作)。onMeasure()
计算得到必要的measurement长度和宽度从而来显示你的组件,它应该与规格保持一致,尽管它可以实现一些规格以外的功能(在这个例子里,父类能够选择做什么,包括剪切、滑动、提交异常或者用不同的参数又一次调用onMeasure()
函数)。setMeasuredDimension(int width, int height)
,否则就会导致异常。在 API Demos 中的CustomView提供了以一个自定义组件的例子,这个自定义组件在 LabelView 类中定义。
LabelView例子涉及到了自定义组件的方方面面:
setText()
, setTextSize()
, setTextColor()
onMeasure()
方法来确定组件的尺寸(注意:在LabelView中是通过一个私有函数measureWidth()
来实现的)onDraw()
函数把Lable显示在提供的canvas上。在例子中,你可以通过custom_view_1.xml看到自定义组件LabelView的用法。在XML文件中特别要注意的是android:
和app:
两个参数的混合运用,app:
参数表示应用程序中被认为是LabelView组件的个体,这些也会作为资源在R类中定义。
如果你不想创建一个完全自定义的组件,而是由几个现有组件的组合产生的新的组件,那么混合组件技术就更加适合。简单的来说,这样把几个现有的组件融合到一个逻辑组合里面可以封装成一个新的组件。例如,一个Combo Box组件可以看作是是一个EditText和一个带有弹出列表的Button组件的混合体。如果你点击按钮为列表选择一项,
在Android中,其实还有其他的两个View类可以做到类似的效果: Spinner和AutoCompleteTextView,,但是Combo Box作为一个例子更容易让人理解。
下面简单的介绍如何创建组合组件:
onDraw()
和onMeasure()
方法,因为Layout会有比较好的默认处理。但是,如果你觉得有必要你也可以重载它。on
系列函数,例如通过onKeyDown()
的重载,你可以通过按某个键去选择列表中的对应的值。总之,把Layout类作为基类有下面几个优点:
onDraw()
函数和onMeasure()
函数是没有必要重载的,两个函数已经做得很好了。In the API Demos project 在API Demos工程中,有两个List类的例子——Example 4和Example 6,里面的SpeechView组件是从LinearLayout类派生过来,实现显示演讲显示功能,对应的原代码是List4.java
和List6.java
。
在某些情况下,你可能有更简单的方法去创建你的组件。如果你应经有了一个非常类似的组件,你所要做的只是简单的从这个组件派生出你的组件,重在其中一些有必要修改的方法。通过完全自定义组件的方法你也可以同样的实现,但通过冲View派生产生新的组件,你可以简单获取一些已经存在的处理机制,这些很可能是你所想要的,而没有必要从头开始。
例如,在SDK中有一个NotePad的例子(NotePad application )。该例子演示了很多Android平台实用的细节,例如你会学到从EditView派生出能够自动换行的记事本。这还不是一个完美的例子,因为相比早期的版本来说,这些API已经感变了很多,但它确实说明了一些问题。
如果你还未查看该程序,现在你就可以在Eclipse中导入记事本例程(或仅通过提供的链接查看相应的源代码)。特别是查看NoteEditor.java 中的MyEditText
的定义。
下面有几点要注意的地方:
这个类是通过下面一行代码来定义的:
public static class MyEditText extends EditText
NoteEditor activity
类里面的,但是它是共有的(public),因此如果有必要,它可以通过NoteEditor.MyEditText
从NoteEditor
外面来调用。static
类(静态类),意味着不会出现所谓的通过父类访问数据的“虚态方法”, 这样就使该类成为一个可以不严重依赖NoteEditor
的单独类。对于不需要从外部类访问的内联类的创建,这是一个很清晰地思路,保证所产生的类很小,并且允许它可以被其他的类方便的调用。EditText
类的扩展,它是我们选择的用来自定义的父类。当我们完成以后,新的类就可以作为一个普通的EditText
来使用。一般来说,父类是首先调用的。进一步来说,这不是一个默认的构造函数,而是一个带参数的构造函数。因为EditText
是使用从XML布局文件提取出来的参数进行创建,因此我们的构造函数也要取出参数并且将这些参数传递给父类。
在本例中,仅对onDraw()
一个方法进行重载。但你可以很容易地为你的定制组件重载其他需要的方法。
对于记事本例子来说,通过重载onDraw()
方法我们可以在EidtView
的画布(canvas
)上绘制蓝色的线条(canvas
类是通过重写的onDraw()
方法传递)。该函数快要结束时要调用super.onDraw()
函数。父类的方法是应该调用,但是在这个例子里面,我们是在我们划好了蓝线之后调用的。
现在,我们已经有自己定制的组件了,但是应该怎样使用它呢?在记事本例子中,定制的组件直接在预定义的布局文件中使用,让我们看一看res/layout
目录中的note_editor.xml文件。
<view xmlns:android="http://schemas.android.com/apk/res/android" 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" />
NoteEditor$MyEditText
来表示的,这是Java编程中引用内联类的标准方法。这就是你全部需要做的,诚然这是一个简单的例子。但问题的关键是:你的需求有多复杂,那么你的自定义组件就有多么复杂。
一个更为复杂的组件可能需要重载更多的on
系列函数,并且还要很多特有的函数来充分实现自定义组件的功能。唯一的限制就是你的想象力和你需要组件去执行什么工作。
如你所见,Android提供了一种精巧而又强大的组件模型,让你尽可能的完成你的工作。从简单的组件调整到组件混合,甚至完全自定义组件,灵活的运用这些技术,你应该可以得到一个完全符合你外观要求的的Android程序