解读创建自定义组件

来源:互联网 发布:曲面电视的缺点 知乎 编辑:程序博客网 时间:2024/06/05 14:31
自定义组件
   Android系统为用户创建自己的UI提供了功能强大的组件模型,这个模型是基于View和ViewGroup这些基本的布局类。Android系统包含了预先制作好的View和ViewGroup的子类————分别是widgets(窗口部件)和layouts(布局)————你可以使用这些已经提供的子类构建自己的UI,在刚开始接触Android开发时,我们都是使用这些系统提供的,然而,随着项目需要,这些基本的UI组件已经不能满足我们的需要了,这个时候就需要我们遵循组件模型来创建自定义UI组件。
  widgets(窗口部件)主要包含了如下部分:Button、TextView、EditView、ListView、CheckBox、RadioButton、Gallery、Spinner,当然,也包括一些具有特殊用途的widgets:AutoCompleteTextView、ImageSwitcher和TextSwitcher。
  layouts(布局)主要包含了:LinearLayout、FrameLayout、RelativeLayout和其他的。更多的示例,请看Common Layout Objects。
  当已经存在的组件不能满足需求,我们就需要创建自己的View子类。如果,你仅仅是需要对存在的widget或layout进行一些小的调整,你可以简单的创建该widget或layout的子类并且覆盖其中的方法。
  通过创建自己的View子类可以使自己精确的控制屏幕上每个元素的显示和其功能。这里列出了一些示例,给出了如何控制自定义组件的思路:
  *你可以创建一个完全自定义渲染的View类型,例如:使用2D图形渲染“音量控制”旋钮,使其看起来像是一个模拟电量控制。
  *你可以将一组View组件结合起来形成一个新的单个的组件,可能是使其像一个ComboBox(是由popup list和一个没有任何条目的text组合而成),一个dual-pane选择控制器(左右各有一个面板,并且每个面板中都有一个list,你可以通过指定一个pane中的内容使另一个pane随之改变,说了这么多,其实就是大家经常使用的天气预报中第一个下拉框中选择省份,随之第二个下拉框会随之变为该省份中包含的城市,这下懂了吧,呵呵!)等等示例。
  *你可以覆盖EditeText组件在屏幕上渲染的方式(NotePad Tutorial使用这个方法创建一个lined-notepad页面)。
  *你可以捕获其他一些事件,例如按键并且通过自定义的方式处理这些事件(例如在一个游戏中)。
  下面的内容解释了如何创建自定义的Views并且在应用程序中使用这个自定义组件。对于详尽的参考信息,请看View类。
  
基本的方法
   站在一个较高的层次上来看,要创建一个自定义的View组件,那么你必须知道如下内容:
   1、让自己的类继承自存在的View类或者是View的子类。
   2、覆盖超类的一些方法。要覆盖的超类的方法都是以”on“开头的,例如:onDraw()、ONMeasure()和onKeyDown()。这有点类似于Acitivity或ListActivity的on...事件,你为它们的生命周期和完成其他的功能而覆盖这些方法(on...事件).
   3、使用你自己新建的扩展类。一旦完成,你可以在能够使用基类的地方使用自定义的这个扩展类。
   提示:扩展类可以作为内部类在要使用这个扩展类的activities中定义。这一点不是必须的,但是却是非常有用的,因为它控制对扩展类的使用权限。
完全自定义的组件
   完全自定义的组件可以用来创建图形组件在任何你想要显示的地方显示。可能是一个看起来像老式模拟计的图形VU计,或者是一个带有前进的球的长文本框,球随着字符移动,使得你可以用一台卡拉OK机唱歌。还有,你需要做一些事情,可是无论怎么组合现有的组件都无法达到想要的效果(这个时候就需要完全自定义的组件)。
   幸运地是,你可以很容易创建出样式和功能都符合自己需求的组件,限制的因素仅仅是你的想象力、屏幕的尺寸和可用的电量(必须记住,你的应用程序通常都是在比桌面环境电量低得多的设备上运行)。
   创建一个完全自定义的组件:
   1、毫无疑问,最常继承的就是View。因此,你通常是继承这个类来实现自定义组建的;
   2、你可以提供一个构造器来从XML文件中获取和解析属性及其参数,并且你可以自定义属性及参数(可能是VU表的颜色和取值范围或者是面条的宽度等等);
   3、你可能希望创建自己的事件监听器,属性的accessors(获取器)和modifiers(修改器)或者是其他更复杂的动作。
   4、如果你希望通过自定义组件显示一些事物,那么你几乎总是需要覆盖onMeasure()并且经常需要覆盖onDraw()。以上两者都包含默认的动作,onDraw()的默认动作是不执行任何操作,onMeasure()的默认动作是设置显示尺寸是100X100————而这个默认尺寸可能并不是你所想要的(尺寸)。
   5、其他的on...的方法在需要的时候也必须覆盖。
扩展onDraw()和onMeasure()
   onDraw()方法传递一个Canvas给你,你可以在这个Canvas上面完成自己想要的效果:2D图形、其他标准的或自定义的组件、styled text、或者其他任何你想要的事物。
   注意:这个并不能用来完成3D图形。如果你想使用3D图形,那么你必须扩展SurfaceView来取代View,并且在一个独立的进程中完成绘制。对于细节部分可以查看GLSurfaceViewActivity示例。
   onMeasure()要涉及得更多一点,onMeasure()是自定义组件和包含它的容器间的rendering contract非常严格的一个部分。onMeasure()必须被覆盖来非常高效和准确的报告它所包含部分的尺寸。来自父组件(传递进onMeasure方法的部分)的限制和通过测量的尺寸来调用setMeasureDimension()方法都将会使得这变得有一点点复杂。如果你从一个覆盖的onMeasure()方法中调用这个方法(setMeasureDimension())失败,那么在测量的时候将会引起异常。
   站在一个较高的层次来看,实现onMeasure()就如同下面这样:
   1、覆盖的onMeasure()方法是随着宽度和高度测量规范(widthMeasureSpec和heightMeasureSpec参数,都是代表尺寸的整型数)来调用的。对于这些规范可以获取的限制种类的完整参考可以在View.onMeasure(int, int)这个文档中找到(这个参考文档对完整的测量操作作出了比较好的解释)。
   2、你自定义组件的onMeasure()方法必须计算出一个宽度和高度值,这两个值是用来渲染组件用来。这两个值必须处于传递进去的测量规范之内,尽管它可以选择扩展它们(宽度和高度这两个值)(在这种情况下,父组件可以选择如何处理:包括可以翻转、滚动、抛出一场或者是让onMeasure()以不同的测量规范重试)。
   3、一旦宽度和高度计算好了之后,必须用得到的这两个计算值作为参数来调用setMeasureDimension(int width, int height)方法。如果这个操作失败了,那么将会引起抛出异常。
   下面是对应用程序框架将会在views上面调用的标准方法的一个总结,如下图:
 
一个自定义View的例子
   在API Demos子中提供的 Custom View例子是解释创建自定义View的很好的例子。Custom View例子中自定义的View是定义再LabelView这个类中的。
   LabelView展示了一个自定义组件很多不同的方面:
   *通过扩展View类来创建一个完全自定义的组件;
   *参数化的构造器,使得可以解析参数(参数定义在XML文件中)。参数中的一部分是要传递给View这个基类的,但是更重要的,这个例子里还为LabelView这个组件定义了一些自定义属性。
   *使用了label这个组件类型的标准公共方法,例如:setText()、setTextSize()、setTextColor()等等。
   *使用了覆盖的onDraw()方法,将这个label画在提供的canvas上。
   你可以在custom_view_1.xml这个文件中看到LabelView的一些示例用法。特别的是,你将会看到混合使用了android:namespace参数和自定义的app:namespace参数。LabelView可以识别这些app:参数,并工作良好,这些参数都定义在示例的R资源定义类的一个styleable内部类中。
<!-- Demonstrates defining custom views in a layout file. --><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"        xmlns:app="http://schemas.android.com/apk/res/com.example.android.apis"        android:orientation="vertical"        android:layout_width="match_parent"        android:layout_height="wrap_content">    <com.example.android.apis.view.LabelView            android:background="@drawable/red"            android:layout_width="match_parent"            android:layout_height="wrap_content"             app:text="Red"/>    <com.example.android.apis.view.LabelView            android:background="@drawable/blue"            android:layout_width="match_parent"            android:layout_height="wrap_content"             app:text="Blue" app:textSize="20dp"/>    <com.example.android.apis.view.LabelView            android:background="@drawable/green"            android:layout_width="match_parent"            android:layout_height="wrap_content"             app:text="Green" app:textColor="#ffffffff" /></LinearLayout>
组合控制
   如果你不想创建一个完全自定义的组件,而是想将已经存在的一组组件组合在一起使用,这个时候就应该创建一个组合组件(或者叫组合控制),来满足需求。概括地说,就是将一组组件已经逻辑放在一起,然后将这一组组件当作一个整体来操作。例如:一个Combo Box可以通过一个单行的EditText和一个带有PopupList的按钮组合而成。如果按下按钮,并且从list中选择了一个项,那么你选择的项就会填充这个EditText,但是如果用户愿意,他们也可以直接再EditText中手动输入内容而不是从list中选择。
   在Android系统中,实际上已经提供了另外两个组件可以完成这件事:Spinner和AutoCompleteTextView,但是,以一个Combo Box作为一个例子更容易理解。
   要创建一个组合组件:
   1、通常是从某种Layout入手,因此创建一个类扩展一个Layout。在之前举的Combo Box例子中,我们可以扩展一个水平放置的LinearLayout。要记住的是其他的layouts可以嵌套在其中,因此组合组件可以任意构造和及其复杂。注意,就好像Activity一样,你可以通过XML文件类创建包含的组件,也可以通过编码实现布局。
   2、在新类的的构造器中,接收基类可以接收的任意参数,并且首先将他们传递给基类的构造器。这样你就可以在自己的组件中使用其他的组件了;这里就是你可以创建EditText和PopupList的地方了。注意:你同样可以在XML文件中定义自己的属性和参数,这些参数可以被你的构造器使用。
   3、如果你包含的组件可以产生事件,那么你同样可以为这些事件创建事件监听器,例如:给一个被选中的列表项设置事件监听器来更新EditText的内容。
   4、你同样可以为accessors(获取器)和modifiers(修改器)定义自己的属性,例如:可以在组件中为EditText指定一个初始值,并且在需要的时候可以查询它的值。
   5、在扩展一个Layout的情况下,你不需要覆盖onDraw()和onMeasure()方法,因为layout已经包含了默认的行为,可以使其正常工作。然而,如果需要,你同样可以覆盖他们。
   6、你也可以覆盖其他以on..开头的方法,例如:onKeyDown(),当一个特定的键被按下的时候,可以从一个combo box的popup list中选取一个默认的值。
   总结一下:用Layout作为Custom Control的基础有一系列的好处,包括:
   *你可以像一个Activity一样使用XML文件来定义布局,也可以通过编码来实现布局
   *onDraw()和onMeasure()方法(大多数的以on..开头的方法)将会有了合适的动作,因此你不必覆盖他们
   *最后,你可以快速的构建任何复杂的组件,并且可以将他们作为一个当个的组件复用。
组合控制的示例:
   在随着SDK发布的API Dmeos中,包含了两个List示例————在Views/Lists中的Example4和Example6展示了一个扩展自LinearLayout的SpeechView来显示Speech quotes。相应的类包含在示例代码的List4.java和List6.java中。
修改一个已经存在的View类型
   在特地的环境下,这里还有一种非常简单的创建自定义View的方法。如果已经存在一种和你想要的组件非常相近的组件,你可以仅仅扩展这个组件并且只覆盖其中你想改变的部分。你可以通过创建一个完全自定义组件来完成所有这些事情,但是如果你从存在于View层级结构中的特殊的类入手,你就可以获得许多你可能想得到的效果,这将大大减小自己的工作量。
   例如:SDK包含了一个NotePad 的示例应用程序。这个应用程序展示了Android平台的许多特性,在这个示例中通过扩展一个EditText View来创建一个lined notepad。这不是一个特别恰当的示例,用来完成这个操作的APIs也许已经随着系统版本的变化而被改变了,但是它已经足够表明期中的原理了。
   如果你没这样做,那么将NotePad示例导入Eclipse中(或者是通过下面提供的链接来查看源文件)。特别是要仔细看看定义在NoteEditor.java文件中的MyEditText。
   一些需要注意的点如下:
   1、定义:
   这个类是像下面这样定义的:
   public static class MyEditText extends EditText
   *它是作为NoteEditor的一个内部类定义的,但是它的访问权限是public,所以你可以在NoteEditor类的外部通过NoteEditor.MyEditText来使用这个类。
   *这个类也是静态的,意味着它不用产生所谓的允许父类操作数据的"synthetic methods",这也意味着它是作为一个独立的类而不是和NoteEditor强关联的类。如果它们不需要从外部类获取状态,那么这就是一种干净的创建内部类的方法,确保生成的类是十分小巧的,并且可以在其他类中十分方便的使用。
   *它继承自EditText,我们在这里选择它来进行我们的自定义View。当我们完成了自定义工作后,这个新类就可以代替普通的EditText了。
   2、类初始化:
   一如往常,最先调用的是超类。但是,这不是默认构造器,而是包含一个参数的构造器。EditText是通过从XML layout文件中解析这些参数来创建的,因此,我们的构造器需要接收这些参数并且将他们传递给超类的构造器。
   3、覆盖的方法:
   在这个例子中,这里仅仅有一个被覆盖的方法:onDraw()————但是,当你需要的时候,你也可以很容易覆盖其他的方法来创建自定义的组件。
   在NotePad示例中,覆盖onDraw()方法允许我们在EditText view的canvas(传递进onDraw()方法的canvas)上面画绿色的线。在这个方法完成之前,我们要掉用super.onDraw()这个方法。超类的方法应该被调用,但是在这种情况下,我们是在完成绘制我们想要包含的部分后再去调用这个方法的。
   4、使用自定义组件:
   此时,我们已经完成了自定义组件,但是我们怎样来使用这个自定义组件呢?在NotePad这个例子中,我们是直接在layout布局文件中使用的,因此看一下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文件中创建的,并且自定义组件类是通过完整的包名来指定的。注意:我们是通过NoteEditor$MyEditText这个符号来引用内部类的。这是JAVA语言中使用内部类的标准方法。
  如果你的自定义组件不是作为一个内部类定义的,这是你也可以选择使用XML元素的名字(但是要去掉class这个属性)来声明自定义的View组件,例如:
  <com.android.notepad.MyEditText  id="@+id/note"  ... />
  注意,此时MyEditText类是一个单独的类文件。当这个类嵌套在NoteEditor类中,这个技术就不会工作了,所以一定要是单独的文件。
  *在定义中的其他属性和参数都是传递给自定义组建的构造器,然后传递给EditText构造器,因此,他们和你使用EditText的参数都是一样的。注意:你也可以添加自己的参数,我们将会在下面介绍这个方面的内容。
  这就是所有的内容了,诚然这是比较简单的事情,但是这一点很重要————依据自己的需要来创建自定义组件(满足需求即可,不必创建过于复杂的自定义组件)。
  一个功能更加强大的组件可能需要覆盖更多以on..开头的方法并且需要引进一些自己的方法,实现自定义的属性的动作。惟一的限制就是你的想象力和你需要这个组件组的工作。
   
原创粉丝点击