Evernote Export
为构建你自己的UI,安卓提供了一个成熟的强大的组件化模型,基于View和ViewGroup这两个基类。在一开始,你可以用一些预建的View和ViewGroup子类(即控件和布局)来构建你的UI。
可用的控件包括Button,TextView,EditText,ListView,CheckBox,RadioButton,Gallery,Spinner还有些面向特殊需求的AutoCompleteTextView,ImageSwitcher,TextSwitcher等等。

而可用布局包括LinearLayout, FrameLayout, RelativeLayout, 等等。

如果这些预建的控件和布局满足不了你的需要,你可以创建你自己的View子类,或者如果你只需要对现有的控件做一些小改动就可以使用的话,那就直接继承那个控件重写一些方法好了。

创建自己的View子类可以让你更精确的控制一个屏幕元素的各种功能表现。怎么个精确控制法呢,下面是一些举例:
~你可以创建一个完全自定义画法的View类,如一个2D图形绘制的“音量控制“球,集成一个模拟电子信号控制。
~同样可以把一组View控件集成到一个新的自定义控件中,比如做一个ComboBox(弹出列表和随意点击的文本框的组合),或者做一个双面板选择器(左右各一个列表框,你可以自己决定哪个项出现在哪个列表框里)等等。
~你可以重新定制EditText在屏幕上出现的方式。
~你可以捕获按键事件或者触摸事件并且以自己的方式处理这些事件(比如在游戏中的各种操作)。

下面的章节讲述如何创建并在应用中使用自定义的View。

基本方式:
以下是一些创建自定义控件所需的步骤的概括:
1. 创建一个类,继承View类或其子类。
2. 重写一些父类的方法。这些方法都以‘on’开头,如onDraw(),onMeasure(),onKeyDown()。 有点像活动生命周期那里的方法。
3. 使用你新创建的类。完成之后,所有父类适用的地方你都可以用你的新创建的类。

提示:自定义控件类可以在需要用到View的活动内部定义,做为内部类,这样你可以更灵活的控制自定义控件类的访问权限(可以把内部类定义为public让别人随意访问,也可以作为private只有自己用。所以比较灵活)。

完全自定义的控件:
完全自定义控件可以用来创建图形控件,控件的显示属性完全由你来定制。比如一个复古风格的声量计,或者一个跟随文本而移动的小弹球,可以用来唱卡拉OK的时候看歌词。。不管怎么说,你就是想实现一些功能,而内建的控件怎么也实现不了,那就用完全自定义控件。

幸运的是,你可以很轻松的创建一个自定义控件,让它按照你的方式来显示,只有想不到,没有做不到~。当然还是要受屏幕大小,低功率的运行环境的制约(你的应用可不会运行在像工作站那种大功率的环境下)。
具体步骤:
1. 最常见的可继承视图就是View类了,所以我们从继承它开始创建自己的控件。
2. 可以提供一个构造方法,从XML文件里接收属性和参数,你同样可以定义自己的属性和参数(声量计的颜色和表示范围啊,指针的宽度和阻尼啊什么的)。
3. 你还可以创建自己的时间监听器,属性访问和修改方法,或者给控件实现一些复杂的功能。
4. onMeasure()方法基本上是一定要重写的,如果想让控件显示些什么东西的话那onDraw()也要重写。这俩方法都有默认的实现:默认的onDraw()什么也没干,默认的onMeasure()会把尺寸写死在100x100,估计你也不想要这么个尺寸。
5. 其他onXXXX()方法视需要重写。

重写onDraw()和onMeasure():
onDraw()方法会给传入一个Canvas作为参数,你可以在上面画你想要的东西:图形,文字,并且可以带一些自定义的样式什么的。

注意:onDraw()不适用于3D图形。想要显示3D画面你需要继承SurfaceView类(而不是View),然后在独立的线程中把画面画出来。

onMeasure()要复杂一些。onMeasure()是一个介于你的控件和控件的容器之间的非常关键的绘画约束(rendering contract)。你应该重写onMeasure()方法去更有效更准确地描述所含控件的具体尺寸。而控件的父元素对其的限制,以及调用setMeasuredDimension()将已经算出的长宽数值进行设定,这些需求都让测量控件具体尺寸变得更加复杂。而如果你没有在你重写的onMeasure()方法里调用setMeasuredDimension()的话在测量尺寸期间会报异常。

大体来看,实现onMeasure()方法基本是这么个情况:
1. onMeasure()被调用时一般会传入测量宽高时的一些设定参数(widthMeasureSpec和heightMeasureSpec参数,代表尺寸的整型变量,且经过了编码?),这两个参数可以看成是对你测量宽高时做出的一些限制。
2. 你的控件的onMeasure()方法里应该测量一下要画这个控件所需的宽高参数。而且宽高需在上面那两个参数的限制范围内,当然你可以选择超出它们限制的范围,这种情况下,控件的父元素来决定怎么搞,比如裁剪啊,滚动啊,或者抛异常,或者改变一下限制条件让onMeasure()重测一下。
3. 测出来宽高参数之后,必须调用setMeasuredDimension(int width, int height)并将宽高对应传入。不调的话会报异常。

下面总结了部分framework会调到的view方法:
种类方法名描述
创建构造函数在代码中创建视图(就是View类或其子类的对象)时会调到一种形式的构造函数,而从layout文件中填充视图的时候会调另外一种形式的构造函数,后者会解析xml文件将所得的属性给到构造函数。
创建 onFinishInflate()当一个视图和他的子视图都被从xml文件中加载完毕时,调用此方法。
布局onMeasure(int , int)调用此方法来确定视图和它的子视图所需的尺寸。
布局onLayout(boolean, int , int, int, int)当某视图需要给子视图分配大小和位置参数时调用此方法。
布局onSizeChanged(int, int, int, int)当某视图的大小改变的时候调用此方法。
描绘onDraw(Canvas)当某视图需要被画出它的内容是调用
事件处理onKeyDown(int, KeyEvent)按键事件发生时调用
事件处理 onKeyUp(int, KeyEvent)按键弹起发生时调用
事件处理 onTrackballEvent(MotionEvent)轨迹球动作事件发生时调用
事件处理 onTouchEvent(MotionEvent)触摸动作事件发生时调用
焦点onFocusChanged(boolean, int, Rect)视图获得/失去焦点的时候调用
焦点onWindowFocusChanged(boolean)当包含视图的窗体获得/失去焦点时调用
附着/挂载(Attaching)onAttachedToWindow()当视图被挂载(attach)到一个窗体下时调用
附着/挂载(Attaching) onDetachedFromWindow()当视图从一个窗体被解除的时候调用
附着/挂载(Attaching) onWindowVisibilityChanged()当视图所属的窗体的可见性发生改变时调用

一个自定义视图的例子:(视图就是View或其子类的对象,前面说的控件都属于视图。文档里的词一会component一会view,所以。。)

APIDemo中有个自定义视图的例子,视图以LabelView定义。
该例子展示了自定义控件的的一些不同方面的内容:
~继承了View类
~提供构造函数,接收在XML文件中定义的视图填充参数。其中的部分参数传给了父类View,不过,有一些给LabelView专用的自定义属性值得关注。
~还有正常人都希望一个标签控件拥有的一些标准方法,setText(),setTextSize(),setTextColor()等等。
~一个重写的onMeasure方法用来设定描绘控件时的大小参数(例子中,实际是用measureWidth()方法来进行此工作)。
~一个重写的onDraw()方法,将标签画在提供的canvas上。
关于如何使用自定义的这个LabelView,例子中有custom_view_1.xml文件。文件中可以看到android:命名空间和app:命名空间。app:的参数都是LabelView可以识别的自定义参数,定义在R下面的一个子类中。

组合控制:
如果你不想创建一个完全自定义的控件,而是想把现有的控件组合起来作为一个可重用的控件。那么创建一个组合控件(或者组合控制)可能会比较合适。总的来说,组合控制把一些原子性的控制(或者视图/控件)聚集到一个逻辑项目组(或者控件组)中,该项目组可以作为一个单独的控件使用。如,一个ComboBox可被视为是一个单行的EditText旁边加一个button,button上绑定一个PopupList而成。如果你按了按钮,选择了弹出列表中的某项,然后就会直接在EditText里生成对应的文本,不过用户依然可以直接在EditText里输入文字。

在安卓系统中,有两个控件可以很方便的拿来做上面的任务,Spinner和AutoCompleteTextView,但是先不管它,ComboBox的概念可以为组合控制提供一个不错的示例。

创建一个组合控件,大体步骤:
1. 通常的入手点是创建一个类,继承某Layout类。在ComboBox的例中我们子或许会用到一个水平方向的LinearLayout。同时记得布局是可以有嵌套布局的,所以组合控件的结构,可以想多复杂就多复杂。就像活动一样,你可以用“声明式”(即在xml中声明)方法来创建控件,也可以在代码中实现组合控件的层层嵌套。
2. 在这个组合控件类的构造方法中,把拿到的参数先传给父类的构造函数。然后就可以做一些其他的视图初始化工作,比如回到ComboBox的例子,这是就可以创建一个EditText和PopupList。注意到你可以在xml文件中创建自定义属性&参数,然后在构造方法中获取并设定他们。
3. 你还可以为你的组合控件中包含的视图可能产生的事件注册事件监听器。比如还是ComboBox,你就可以为那个弹出列表注册一个ListItemClickListener然后点击的时候就去修改对应的EditText内容。
4. 可以给你的控件创建自定义属性,生成getter和setter。比如,给EditText设定一个初始默认值,也可以设定根据EditText内容进行查询。
5. 在继承某Layout类的情况下,omMeasure()和onDraw()方法都没必要重写了,因为Layout本身的默认实现就挺好的。当然,需要的话你也可以重写。
6. 你或许需要重写其他的onXXXX()方法比如onKeyDown(),然后当某个键被按下的时候在ComboBox的下拉列表中选择一个默认值。

总而言之,以Layout作为组合控制的基类有很多好处,包括:
~你可以像给活动写布局那样在xml文件中给组合控件写布局,或者也可以在代码中创建子视图嵌套到布局中。
~onDraw()和onMeasure()方法默认的实现基本适合需求,所以不再需要重写。
~你可以方便地创建出任意复杂的控件并作为一个单一的控件来重用。

组合控制示例:
SDK的APIDemos中有两示例:Views/Lists下的Example4 和 Example6展示了一个继承LinearLayout的SpeechView,是一个用来显示Speech引用的控件。对应到代码中的类文件是List4.java和List6.java。

修改现有的View类:
有一个更加简便的方法可以创建一个自定义的View,该方法使用于某些特定场景。即如果已有的某个控件已经和你想要的效果非常接近,你可以直接继承这个控件然后重写你想要的方法。所以在完全自定义控件里能重写的东西,这种方法下都可以重写,就是在视图结构中的起点不同。并且继承某控件你可以更加精确的得到你想要控件表现的效果。

比如,SDK在samples中有一个NotePad应用的例子,展示了使用Androidplatform的多方面内容,其中便有继承EditText视图来制作一个记事本。这并不是一个多么完美的示例,而且使用的API可能有点过时了,不过重在理解原理。

接下来,找到Notepad示例,导入到Eclipse然后重点观察一下NoteEditor.java中MyEditText的定义。
注意一下几点:
1. 定义:
public static class MyEditText extends EditText
~这个类是定义在NoteEditor内部作为内部类的,但是是public的类所以可以其他类里以NoteEditor.MyEditText的形式访问。
~它是一个静态类,意味着它没有那些所谓的“synthetic methods”去访问外部类的数据,也就是说MyEditText类是一个独立的类而不是一个与NoteEditor关系很紧密的类。如果某内部类不需要访问外部类的状态,那么采用静态的这种方式是比较好的,可以使内部类更小巧,并且方便其他的类来使用这个内部类。
~它继承了EditText,这个例子里我们正是选择EditText来做优化。创建完毕后,这个类可以在任何适用于EditText的场景下使用。
2. 类的初始化(构造方法)
首先还是先调父类的构造方法。这不是一个默认的构造方法,而是带参的。创建EditText的时候需要用到xml文件中定义的参数,所以,我们的构造方法需要接受这些参数并且把它传给父类。
3. 重写方法:
这个例子当中,仅需重写一个onDraw()方法,当然你创建自己的控件的时候可能还需重写一些其他方法。
本例中,重写onDraw()方法让我们可以在EditText的canvas上画蓝线(canvas会作为onDraw()的参数传给你),在方法结束前需要调一下super.onDraw()。当然需要是需要,不过在本例中,我们在画完想要的线之后才调的super。
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中是以一个普通的View元素来创建的,然后给元素指定了类名(要带包名的)。同时注意一下内部类的访问方式,NoteEditor$MyEditText,Java语言中的引用内部类的标准形式。
如果你自定义控件不是以内部类的方式定义的,那就可以用下面这种方式来访问,即在xml中直接以类名来声明元素,并且不需要class属性:
<com.android.notepad.MyEditText
id="@+id/note"
...
/>
这里的MyEditText是一个独立的类文件才可以这样用,如果它还是NoteEditor的内部类的话这种方法就行不通了。
~其他的属性参数会传到自定义控件的构造方法里,进而传到父类即EditText的构造方法里,所以其实就是EditText的属性,怎么给EditText设置属性就怎么给这个自定义控件设置属性。同时别忘了,你还可以设置一些自定义的属性。

嗯,这就差不多了。虽然这个例子确实很简单,不过创建自定义控件就是这样,你想让它复杂它就复杂,想让它简单点也没问题。
想创建更复杂的控件的话,需要重写更多的onXXX方法并且写一些属于子类自己的方法,充分的定制控件的一些表现或行为。Again,只有想不到,没有做不到~~~



























 posted on 2015-07-29 15:40  絮状颗粒  阅读(181)  评论(0)    收藏  举报