设计精良的自定义视图与任何其他精心设计的类非常相似。它通过简单易用的界面封装一组特定的功能,高效地使用 CPU 和内存,诸如此类。但是,除了是精心设计的类之外,自定义视图还应该:
- 符合 Android 标准
- 提供适用于 Android XML 布局的自定义可设置样式属性
- 发送无障碍事件
- 与多种 Android 平台兼容。
Android 框架提供了一组基类和 XML 标记,帮助您创建满足所有这些要求的视图。本课将介绍如何使用 Android 框架创建视图类的核心功能。
除了本课程,您还可以在自定义组件中找到其他相关信息。
子类化视图
Android 框架中定义的所有视图类都能扩展 View
。自定义视图也可以直接扩展 View
,还可通过扩展某个现有视图子类(如 Button
)节省时间。
为了使 Android Studio 能够与您的视图交互,您必须至少提供一个以 Context
和 AttributeSet
对象为参数的构造函数。此构造函数允许布局编辑器创建和编辑视图的实例。
Kotlin
class PieChart(context: Context, attrs: AttributeSet) : View(context, attrs)
Java
class PieChart extends View { public PieChart(Context context, AttributeSet attrs) { super(context, attrs); } }
定义自定义属性
如需向界面添加内置 View
,请在 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>
此代码声明了两个自定义属性(即 showText
和 labelPosition
),它们属于一个名为 PieChart
的可设样式实体。按照惯例,这个可设样式实体的名称与定义自定义视图的类的名称相同。尽管不严格要求遵守此惯例,但许多常见的代码编辑器依据此命名惯例提供语句补全功能。
定义了自定义属性后,便可像内置属性一样在布局 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>
为避免反复使用冗长的命名空间 URI,该示例使用了 xmlns
指令。此指令将别名 custom
分配给命名空间 http://schemas.android.com/apk/res/com.example.customviews
。您可以为命名空间选用任何别名。
注意将自定义视图添加到布局的 XML 标记的名称。它是自定义视图类的完全限定名称。如果视图类是内部类,您必须使用视图外部类的名称进一步限定。例如,PieChart
类有一个名为 PieView
的内部类。如需使用此类中的自定义属性,您需要使用 com.example.customviews.charting.PieChart$PieView
标记。
应用自定义属性
通过 XML 布局创建视图时,XML 标记中的所有属性都会从资源包读取,并作为 AttributeSet
传递到视图的构造函数中。虽然可以直接从 AttributeSet
读取值,但这样做有一些弊端:
- 不解析属性值中的资源引用
- 不应用样式
请改为将 AttributeSet
传递给 obtainStyledAttributes()
。此方法会传回一个 TypedArray
数组,其中包含已解除引用并设置了样式的值。
Android 资源编译器做了大量工作,以便您更轻松地调用 obtainStyledAttributes()
。对于 res 目录中的各个 <declare-styleable>
资源,生成的 R.java 定义一个由属性 ID 组成的数组,同时定义一组常量,用于定义该数组中各属性的索引。您可以使用预定义的常量从 TypedArray
读取属性。以下代码段展示了 PieChart
类如何读取其属性:
Kotlin
init { context.theme.obtainStyledAttributes( attrs, R.styleable.PieChart, 0, 0).apply { try { mShowText = getBoolean(R.styleable.PieChart_showText, false) textPos = getInteger(R.styleable.PieChart_labelPosition, 0) } finally { recycle() } } }
Java
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); textPos = a.getInteger(R.styleable.PieChart_labelPosition, 0); } finally { a.recycle(); } }
请注意,TypedArray
对象是共享资源,必须在使用后回收。
添加属性和事件
属性是控制视图行为和外观的强大方式,但只能在视图初始化时读取。如需提供动态行为,请为每个自定义属性公开一个 getter 与 setter 属性对。以下代码段展示了 PieChart
如何公开名为 showText
的属性:
Kotlin
fun isShowText(): Boolean { return mShowText } fun setShowText(showText: Boolean) { mShowText = showText invalidate() requestLayout() }
Java
public boolean isShowText() { return mShowText; } public void setShowText(boolean showText) { mShowText = showText; invalidate(); requestLayout(); }
请注意,setShowText
会调用 invalidate()
和 requestLayout()
。这些调用对于确保视图可靠运行至关重要。您必须在视图属性发生任何可能改变其外观的更改后使该视图失效,以便系统知道需要重新绘制该视图。同样,如果属性更改可能会影响视图的大小或形状,则需要请求新的布局。遗漏这些方法调用可能会导致难以发现的错误。
自定义视图还应支持用于传达重要事件的事件监听器。例如,PieChart
公开名为 OnCurrentItemChanged
的自定义事件,从而告知监听器,用户旋转了饼图以将焦点放在新的饼图分区上。
公开属性和事件是很容易忘记的,尤其是当您是自定义视图的唯一用户时。花点时间仔细定义视图界面可以降低未来的维护成本。一种好的做法是始终公开任何会影响自定义视图的可见外观或行为的属性。
无障碍设计
自定义视图应支持最广泛的用户。包括无法正常看到或使用触摸屏的残障用户。如需为残障用户提供支持,您应该:
- 使用
android:contentDescription
属性为输入字段添加标签 - 根据需要调用
sendAccessibilityEvent()
以发送无障碍事件。 - 支持替代控制器,例如方向键和轨迹球
如需详细了解如何创建易于访问的视图,请参阅 Android 开发者指南中的让应用易于访问。