创建一个View类
一个设计良好的自定义view和其他任何设计良好的类一样。对于一系列的特定功能进行封装,提供简单易用的接口,还需要更有效率地使用CPU与内存。为了成为一个设计良好的类,一个自定义View需要做到以下几点:
- 符合Android的标准
- 使用Android XML Layouts 提供自定义的属性
- 发送可接受的事件
- 在Android多版本具有兼容性
Android框架层提供了很多基类以及XML标签来帮助你应对创建View时所需要的东西。这篇教程将讨论如何使用Android的框架来创建View的核心功能。
继承一个View
在Android框架层定义的所有的View类都继承自View。你的自定义View同样继承自View。你也可以通过继承一些已经存在的View子类来节省时间,举个例子:Button。
为了能够允许Android Developer Tools来和你的View交互,你至少需要提供一个带Context,AttributeSet参数的构造器。这个构造器允许Layout编辑器创建以及编辑你的View。
class PieChart extends View {
public PieChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
定义自定义的属性值
为了能够使用你的View,你需要在XML元素中指明属性并用它来控制View的外表以及行为。一个已经写得很好的View同样可以通过这样方法来添加和定义属性。为了能让你的自定义View做到以上所说:
-
用<declare-styleable>
资源标签来定义你自定义View的属性 - 在你的Layout中指明属性的值
- 在运行时检索到属性值
- 将检索到的值应用到你的View中
这部分讨论了如何去定义自定义的属性值并且指明它的值。下一部分解决在运行时检索和应用这些值。
为了定义自定义属性值,添加<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>
这部分代码声明了两个自定义属性值 showText 以及 labelPosition 其属于一个名字叫做PieChart的styleable 实体里。这一实体的名字按照惯例是与你的自定义View一样的,即使不是必须严格遵守这条惯例,但是很多流行的代码编辑器都依赖于这个命名惯例来提供状态实现。
一旦你定义了自定义的属性值你就能够在Layout XML中使用它们,就像内部的属性值一样。唯一的不同之处在于,你的自定义属性属于不同的命名空间。不在http://schemas.android.com/apk/res/android
命名空间,而是在http://schemas.android.com/apk/res/[your package name]
。举个例子,以下代码展示了如何去使用定义在PieChart中的属性值。
<?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>
为了避免不得不重复the long namespace URI 使用了xmlns指令这个指令分配了别名:custom 给命名空间http://schemas.android.com/apk/res/com.example.customviews
。你可以选择任何你想的别名给你自己的命名空间。
注意到这个给你的自定义View添加的XML标签的名字。这是一个完整的自定义类的名字。假如你的View类是一个自带的内部类,你必须进一步完整写出它的外部类的名字。接下来,对于实例,PieChart 类具有一个内部类叫做PieView。使用这个类的自定义属性,你需要这么来使用:com.example.customviews.charting.PieChart$PieView。
应用你的自定义属性
当一个View在一个XML layout文件中被创建时,所有在XML标签中的属性都将会被从资源bundle中读取到view的构造器中,其载体为:AttributeSet。即使现在能够立即从AttributeSet中取出值,但是这么做有以下一些缺点:
- 属性值还没有被解引用
- Styles还没有被应用
取而代之的做法是,使用obtainStyledAttributes()来处理AttributeSet。这个方法将会返回一个TypedArray,里面有一个已经解引用且应用了Style的一组属性值。
Android资源编译器已经在你调用obtainStyledAttributes()的时候帮你做了很多工作。对于每一个在资源池中的<declare-styleable>
资源,在生成的R文件中同时定义了属性数组的id值和在数组中的index值。你需要使用来自typedArray中预先定义的常数。以下是PieChart类如何读取它的属性值的:
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();
}
}
需要注意的是TypeArray对象一个可被共享的资源,需要在使用之后进行回收。
添加内容和事件
属性值是一个很强大的工具,主要是用来控制Views的行为以及外观,但是它只能在View初始化完毕后进行读取。为了提供动态的特性,暴露View每一个属性值的内容的getter/setter。以下的代码展示了PieChart如何暴露名叫showText的内容:
public boolean isShowText() {
return mShowText;
}
public void setShowText(boolean showText) {
mShowText = showText;
invalidate();
requestLayout();
}
注意到setShowText中调用了invalidate() 以及 requestLayout()。这些调用对于确保View行为的可靠性是具有决定性作用的。你必须在View的内容发生变化且可能改变它的外观的时候,使用invalidate(),这样的话,系统就能够知道它需要被重绘。同样的,你需要在内容变化且影响到了View的大小的请求一个新的Layout。忘记这些调用会带来的“很难被发现的” bugs吧。
自定义View也需要支持事件监听器来与重要的事件进行交流。具体来说,PieChart
暴露了一个自定义的事件叫做:OnCurrentItemChanged
用来唤醒监听器,这样的话,用户就能够旋转应用。
忘记暴露内容与事件是很容易发生的,尤其是在当你是这个自定义View的唯一使用者的时候。多花一些时间来关心一下你的View的接口定义,这样可以减少未来的维护成本。有一条非常好的规则:那就是对于你的自定义View,总是暴露出任何影响外观可见性以及行为的内容。