设计良好的自定义视图就像其它任何精心设计的类。它通过一个容易使用的接口封装了一个组特定的方法。它高效的使用CPU和内存,等等。除了是一个精心设计的类,然而,自定义视图应该:
遵守Android规范
提供在Android XML布局中工作的自定义属性。
发送可访问的事件。
兼容多个Android平台。
Android框架提供了一组基本类和XML标签,来帮助你创建符合所有要求的视图。这节课程讨论如何使用Android框架来创建视图类的核心功能。
子类化视图
——————————————————————————————————————————————————————————————
在Android框架中被定义的所有视图类继承View。你的自定义视图也可以直接继承View,或者你能通过继承一个已经存在的视图子类来节省时间,例如Button。
为了允许Android Developer Tool和你的视图交互,你必须至少提供一个构造方法,它使用Context和AttributeSet对象作为参数。这个构造方法允许布局编辑器来创造和编辑一个你的视图实例。
class PieChart extends View { public PieChart(Context context, AttributeSet attrs) { super(context, attrs); } }
定义自定义属性
——————————————————————————————————————————————————————————————
向你的用户界面添加一个内嵌的视图,你使用一个XML元素中指定它,并且使用元素属性控制它的显示和行为。完全自定义视图也能通过XML添加和样式化。为了在你的自定义视图中使用这种方式,你必须:
在<declare-styleable>资源元素中定义你的视图的自定义属性。
在你的XML布局中指定属性的值。
在运行时获取属性值。
将获取的属性值应用到你的视图中。
这节讨论如何定义自定义属性和指定它们的值。下一节是关于在运行时获取和应用这些值。
为定义自定义属性,向你的项目中添加<declare-styleable>资源,通常将这些资源放入/res/values/attrs.xml文件中。这里是attrs.xml文件的一个例子:
<resources> <declare-styleable name="PieChart"> <attr name="showText" format="boolean" /> <attr name="labelPosition" format="enum"> <enum name="left" value="0"/> <enum name="right" value="1"/> </attr> </declare-styleable> </resources>
这段代码声明两个自定义属性,shotText和labelPosition,它属于一个被命名为PieChart的可样式化实体。按照惯例,这个可样式化的实体的名字,和定义自定义视图类的名字相同。虽然不是非得遵守此约定,大多数程序员基于这种命名约定提供完整的描述。
一旦你定义了自定义属性,你也能和内置属性一样,在XML布局文件中使用它们。唯一的不同是你的自定义属性属于一个不同的命名空间。代替属于http://schemas.android.com/apk/res/android命名空间,它们属于http://schemas.android.com/apk/res/android/[your package name],例如,这里演示了如何使用为PieChar定义的属性:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:custom="http://schemas.android.com/apk/res/com.example.customviews"> <com.example.customviews.charting.PieChart custom:showText="true" custom:labelPosition="left" /> </LinearLayout>
为了避免重复长命名空间URI,这个例子使用xmlns指令。这个指令分配别名custom给http://schemas.android.com/apk/res/com.example.customviews命名空间。你可以使用任何你想分配给你的命名空间的别名。
注意XML标识的名称将自定义视图添加到布局中,它是自定义视图类的完整描述名。如果你的视图类是一个内部类,你必须使用外部类的名称进一步限定它。例如PieChar类有一个被命名为PiaView的内部类。为了使用这个类的属性,你应该使用com.example.customviews.charting.PiaChar$PiaView标签。
使用自定义属性
——————————————————————————————————————————————————————————————
当一个视图在一个XML布局中被创建,在这个XML标签中的所有属性都从资源束中读取,并作一个AttributeSet被传递到这个视图的构造方法。虽然可以直接从AttributeSet中读取值,但是这样做有一些缺点:
在属性值内的资源引用没有被解析。
类型没有被应用。
相反,传递AttributeSet给obtainStyledAttribute()方法,这个方法返回一个值数组TypedArray,它已经被引用和类型化。
Android资源编译器为了你更方便的调用obtainStyledAttribute()方法,做了许多工作。对于在res目录下的每个<declare-styleable>资源,在生成的R.java文件定义了一个属性id数组和一个常量集合,它定义了在这个数组中的每个属性的索引。你使用这个预定义的常量来从TypedArray中读取对应属性。下面是PieChar类如何读取它的属性:
public PieChart(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.getTheme().obtainStyledAttributes( attrs, R.styleable.PieChart, 0, 0); try { mShowText = a.getBoolean(R.styleable.PieChart_showText, false); mTextPos = a.getInteger(R.styleable.PieChart_labelPosition, 0); } finally { a.recycle(); } }
注意TypedArray对象是一个共享资源,并且在使用后必须被回收。
添加属性和事件
—————————————————————————————————————————
属性是控制视图的行为和显示的一种强有力的方式,但是它们仅仅在视图被初始化的时候被读取。为了提供动态行为,为每个自定义属性提供属性的一对getter和setter方法。下面的代码段显示PieChart如何暴露被称为showText的属性。
public boolean isShowText() { return mShowText; } public void setShowText(boolean showText) { mShowText = showText; invalidate(); requestLayout(); }
注意setShowText调用invalidate()和requestLayout()。这些调用对于确保视图的行为可靠是至关重要的。在改变任何可能改它的显示的属性之后,你必须使视图无效,以至于系统知道它需要被重绘。另一方面,如果一个可能影响这个视图的大小或者形状的属性改变,你需要请求一个新的布局。忘记了调用这个方法会导致难以寻找的Bug。
自定义视图也应该支持事件监听来沟通重要事件。例如,PieChar暴露一个被称为onCurrentItemChanged的自定义事件来通知来通知监听器,用户已经旋转了PieChar类聚焦一个新的Pie Slice。
忘记暴露属性和事件是很容易的,尤其是当你这个自定义视图的唯一用户的时候。花一些事件细心定义你的视图界面来减少将来维护的开销。遵循好的做法就是,总是暴露任何影响你的自定义视图的外表和行为可见性的属性。
可访问性设计
—————————————————————————————————————————
你的自定义视图应该支持最广泛的用户,这包括无法看见或使用触摸屏的残障用户。为了支持残障用户,你应该:
使用android:contentDescription属性标注你的输入框。
在适当的时候,通过调用setAccessibilityEvent()发送可访问事件。
支持备用控制器,例如D-pad和轨迹球。
更多关于创建可访问视图的信息,查阅在Android Developers Guide中Making Application Accessible。