原文地址:  http://marshal.easymorse.com/archives/2062

android提供了精巧和有力的组件化模型构建用户的UI部分。主要是基于布局类:View和ViewGroup。在此基础上,android平 台提供了大量的预制的View和ViewGroup子类,即布局(layout)和窗口小部件(widget)。可以用它们构建自己的UI。

如果没有符合你需求的预制窗口小部件,你可以创建自己的视图子类。如果只是对已存在的窗口小部件或者布局做小的调整,只需继承该类,覆盖相关的方法。

创建你自己的View子类可以更精确控制视图元素的外观和功能。

  • 可创建完整的自定义渲染视图类型,比如创建一个2d的控制条;
  • 可将一组视图组件合成为一个新的单一组件,比如双选的列表,选择省和市;
  • 覆盖EditText组件,比如notepad tutorial中的示例;
  • 捕捉其他事件比如按键事件,并执行自定义的处理方式,比如在游戏中。


基本方法

总的来说,创建自定义的视图组件步骤是:

  1. 创建自己的类,继承已经存在的View类或者子类;
  2. 覆盖超类的一些方法。这些超类的方法一般以“on”开头,比如onDraw()方法等等;
  3. 使用新创建的扩展类。一旦完成,你的新扩展类就可以用于所有View使用的地方。

注意:扩展类可以定义为内部类,在你创建的Activity类之中。这很有用,因为这样可以控制外界的访问,但是这不是必须的,因为你可能需要一个public的自定义View类供更广泛的使用。

完全自定义组件

完全自定义的组件可以创建图形组件显示在你需要的任何地方。

步骤如下:

  1. 可以继承的最通用的视图类是View,可以继承它创建自定义的组件超类;
  2. 可以提供构造方法,并通过xml文件获取属性值和参数;
  3. 创建自己的事件监听器,属性访问器和编辑器等等;
  4. 一般情况下会覆盖onMeasure()方法和onDraw()方法,这会让组件显示一些东西。如果都用默认的行为,onDraw()方法不做任何事情,onMeasure()方法设置一个100×100的区域;
  5. 根据需求覆盖其他on…方法。

扩展onDraw()和onMeasure()方法

onDraw()方法提供给你一个Canvas对象,在它之上可以实现任何你想要的东西,通过2d图形api。比如其他标准的后者自定义的组件,风格化的文字后者其他。

注意:这里不提供3d图形api的支持。如果你需要3d图形支持,必须继承SurfaceView而不是View,并且通过单独的线程画图。可以通过GLSurfaceViewActivity实例查看详细信息。

onMeasure()方法有些麻烦。该方法是在容器和自定义组件之间渲染的重要部分。该方法覆盖,要高效率的和精确的报告被包含区域的测量值。

总的来看,实现onMeasure()方法类似如下步骤:

  1. 调用已经覆盖的onMeasure()方法,传递长和宽规范参数;
  2. 自定义组件在onMeasure()方法中计算需要渲染的组件的长和宽,应该在规范参数的范围内;
  3. 一旦长和宽计算出来,必须调用setMeasuredDimension(int width, int height)方法,这步失败会导致异常的抛出。

一个自定义视图的示例

自定义视图的示例,见:LabelView

该示例演示了一些自定义组件的不同方面:

  • 继承View类,用于完全自定义组件;
  • 参数化的构造方法,提供更多的参数,定义在xml文件中;
  • 标准的公开方法,用于设置标签,比如setText()方法等;
  • 覆盖onMeasure方法确定渲染的组件尺寸;
  • 覆盖onDraw方法,在提供的canvas中画标签。

可以找到对示例的一些使用,在custom_view_1.xml文件中。

该示例运行效果:


android示例是混在一起的,比较乱,我这里改写了一下,只有相关示例的代码和配置。看起来比较简单:

http://easymorse.googlecode.com/svn/tags/android.customer.view.demo_1.0


合成控制器

合成控制器,即不是完全自定义一个新的视图组件,而是,将现有的原子级控制器(控件?)或者视图组件组合在一起,处理共同的业务逻辑。比如,一个combo box可以被看做,一个单行的EditText和一个相邻的按钮,带一个弹出列表。

在android中还有很多其他的示例,比如Spinner,AutoComleteTextView。

创建合成组件的步骤:

  1. 通常的起始步骤是,创建某种类型的Layout,即创建一个类继承一个Layout。比如上述的combo box,可能会使用到基于垂直布局的LinearLayout。其他布局也可以嵌套在其中,因此合成组件可以任意复杂结构。和activity类似,你可 以用基于xml的声明方式创建容器组件,也可以嵌入到程序代码中;
  2. 在新类的构造方法中,得到超类所需的参数,并传递给超类的构造方法。另外,也可设置其他在这个心组件当中的视图组件,比如创建一个EditText和PopupList。注意,你也可以引入自己的参数和属性到xml文件中,这样会被取出并用于你的构造方法;
  3. 还可以创建事件监听器,用于容器中的视图组件,比如一个监听器方法,用于处理列表点击的监听器,更新EditText的文本内容;
  4. 创建自己的属性访问器和编辑器,比如,EditText的值可以在组件中初始设置,并能在需要的时候获取它的值;
  5. 在继承Layout类时,不需要覆盖onDraw()和onMeasure()方法,因为它们可能已经符合你的要求,当然,也可以覆盖它们实现自己特定的需求;
  6. 可能需要覆盖其他on…方法,比如onKeyDown()方法。

总之,使用Layout作为基础合成自定义的控件,有一些优点:

  • 可以通过xml文件的方式声明指定的布局,和activity类似,或者可以通过编程的方式嵌入到你的代码中;
  • onDraw()方法和onMeasure()等一般可适合需求,因此不必一定要覆盖它们;
  • 可以快速的构建任何复杂的合成视图,重用它们为一个单一的组件。

合成控件的示例

在ApiDemos示例中,演示了SpeechView,它继承了LinearLayout,并创建了一个组件,用于显示谈话中的引号。相关的类见:

samples/ApiDemos/src/com/example/android/apis/view/List4.java

samples/ApiDemos/src/com/example/android/apis/view/List6.java

List4示例截图,见:

List6示例截图,可以点击条目,出现内容,见:

修改已存在的视图类型

如果已存在的视图组件已经和你的需求相差不远,你可以只是简单的扩展该组件,只覆盖需要改变的行为。

比如示例中的NotePad应用(platforms/android-1.5/samples/NotePad)。

效果如下:


在文本框视图组件(EditText)基础上,增加了横线。

本文主要参考:

http://developer.android.com/guide/topics/ui/custom-components.html