构建自定义组件
Android中,你的应用程序程序与View类组件有着一种固定的联系,例如按钮(Button)、 文本框(TextView), 可编辑文本框(EditText), 列表框(ListView), 复选框(CheckBox), 单选框(RadioButton), 滚动条(Gallery), 微调器(Spinner), 等等,还有一些比较先进的有着特殊用途的View组件,例如 AutoCompleteTextView,ImageSwitcher和 TextSwitcher。除此之外,种类繁多的像 线性布局(LinearLayout), 框架布局(FrameLayout), 这样的布局组件(Layout)也被认为是View组件,他们是从View类派生过来的。
你的应用程序就是这些控制组件和布局组件以某种方式结合显示在屏幕上,一般来说这些组件对你来说基本够用,但是你也应该知道你是可以通过类继承创建属于自己的组件,一般可以继承像View、Layouts(布局组件)这样的组件,甚至可以是一些比较高级的控制类组件。下面我们说一下为什么要继承:
- 你可以为实现某种功能创建一个完全自定义风格的组件,例如用二维的图形创建控制组件实现声音的控制,就像电子控制一样。
- 你可以把几种组件结合形成一个新的组件,你的组件可能同时包含ComboBox(一个能输入的文本列表)和dual-pane selector control(左右两个List窗口,你可以分配窗口每一项的从属关系)等等。
- 你可以创建自己的布局组件(Layout)。SDK中的布局组件已经提供了一系列的选项让你打造属于自己的应用程序,但是高级的开发人员会发现根据现有的Layout组件开发新的Layout组件是很有必要的,甚至是完全从底层开发新的组件。
- 你可以覆盖一个现有组件的显示或功能。例如,改变EditText(可编辑文本)组件在屏幕上的显示方式(可以参考Notepad的例子,里面教你如何创建一个下划线的显示页面)。
- 你可以捕获像按键按下这样的事件,以一些通用的方法来处理这些事件(一个游戏的例子)
基本方法(The Basic Approach )
完全自定义组件(Fully Customized Components )
定制组件的例子(Customized Component Example )
组件的混合(或者控制类的混合) (Compound Components (or Compound Controls) )
修改现有组件(Tweaking an Existing Component )
基本方法(The Basic Approach )
下面的一些步骤都比较概括,教你如何创建自己的组件:
1. 让你的类(Class)继承一个现有的View 类或View的子类。
2. 重载父类的一些方法:需要重载的父类方法一般以‘on’开头,如onDraw(), onMeasure()和 onKeyDown()等等。
* 这个在Activity 或则 ListActivity 派生中同样适用,你需要重载一些生命周期函数和一些其他功能性的HOOK函数。
3. 使用你的继承类:一旦你的继承类创建完成,你可以在基类能够使用的地方使用你的继承类,但完成功能就是你自己编写的了。
完全自定义组件(Fully Customized Components)
完全自定义组件的方法可以创建一些用于显示的图形组件(graphical components),也许是一个像电压表的图形计量器,或者想卡拉OK里面显示歌词的小球随着音乐滚动。无论那种方式,你也不能单纯的利用组件的结合完成,无论你怎么结合这些现有的组件。
幸运的是,你可以以你自己的要求轻松地创建完全属于自己的组件,你会发现不够用的只是你的想象力、屏幕的尺寸和处理器的性能(记住你的应用程序最后只会在那些性能低于桌面电脑的平台上面运行)。
下面简单介绍如何打造完全自定义的组件:
1. 最为通用的VIEW类的父类毫无疑问是View类,因此,最开始你要创建一个基于此类的一个子类。
2. 你可以写一个构造函数从XML文件中提取属性和参数,当然你也可以自己定义这些属性和参数(也许是图形计量器的颜色和尺寸,或者是指针的宽度和幅度等等)
3. 你可能有必要写自己的事件监听器,属性的访问和修改函数和一些组件本身的功能上的代码。
4. 如果你希望组件能够显示什么东西,你很有可能会重载 onMeasure() 函数,因而你就不得不重载 onDraw() 函数。当两个函数都用默认的,那么 onDraw() 函数将不会做任何事情,并且默认的 onMeasure() 函数自动的设置了一个100x100 —的尺寸,这个尺寸可能并不是你想要的。
5. 其他有必要重载的on... 系列函数都需要重新写一次。
onDraw()和onMeasure()
onDraw()函数将会传给你一个 Canvas 对象,通过它你可以在二维图形上做任何事情,包括其他的一些标准和通用的组件、文本的格式,任何你可以想到的东西都可以通过它实现。
注意: 这里不包括三维图形如果你想使用三维的图形,你应该把你的父类由View改为SurfaceView类,并且用一个单独的线程。可以参考GLSurfaceViewActivity 的例子。
onMeasure() 函数有点棘手,因为这个函数是体现组件和容器交互的关键部分,onMeasure()应该重载,让它能够有效而准确的表现它所包含部分的测量值。这就有点复杂了,因为我们不但要考虑父类的限制(通过onMeasure()传过来的),同时我们应该知道一旦测量宽度和高度出来后,就要立即调用setMeasuredDimension() 方法。
概括的来讲,执行onMeasure()函数分为一下几个阶段:
1. 重载的onMeasure()方法会被调用,高度和宽度参数同时也会涉及到(widthMeasureSpec 和heighMeasureSpec两个参数都是整数类型),同时你应该考虑你产品的尺寸限制。这里详细的内容可以参考View.onMeasure(int, int) (这个连接内容详细的解释了整个measurement操作)。
2. 你的组件要通过onMeasure()计算得到必要的measurement长度和宽度从而来显示你的组件,它应该与规格保持一致,尽管它可以实现一些规格以外的功能(在这个例子里,父类能够选择做什么,包括剪切、滑动、提交异常或者用不同的参数又一次调用onMeasure()函数)。
3. 一旦高度和宽度计算出来之后,必须调用setMeasuredDimension(int width, int height),否则就会导致异常。
一个自定义组件的例子(A Customized Component Example)
在 API Demos 中的CustomView提供了以一个自定义组件的例子,这个自定义组件在 LabelView 类中定义。
LabelView例子涉及到了自定义组件的方方面面:
* 首先让自定义组件从View类中派生出来。
* 编写带参数的构造函数(参数可以来源于XML文件)。这里面的一些处理都已经在View父类中完成,但是任然有些Labelview使用的自定义组件特有的新的参数需要处理。
* 一些标准的Public函数,例如setText(), setTextSize(), setTextColor()
* 重载onMeasure()方法来确定组件的尺寸(注意:在LabelView中是通过一个私有函数measureWidth()来实现的)
* 重载onDraw()函数把Lable显示在提供的canvas上。
在例子中,你可以通过custom_view_1.xml看到自定义组件LabelView的用法。在XML文件中特别要注意的是android:和app:两个参数的混合运用,app:参数表示应用程序中被认为是LabelView组件的个体,这些也会作为资源在R类中定义。
代码分析: