一个精心设计的自定义视图就像任何其他精心设计的类一样。它用简单的接口封装了一组特定的功能,高效地使用 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
的 styleable 实体。按照惯例,styleable 实体的名称与定义自定义视图的类的名称相同。虽然不必遵循此约定,但许多流行的代码编辑器都依赖此命名约定来提供语句完成功能。
定义自定义属性后,您可以在布局 XML 文件中使用它们,就像使用内置属性一样。唯一的区别是您的自定义属性属于不同的命名空间。它们不属于 http://schemas.android.com/apk/res/android
命名空间,而是属于 http://schemas.android.com/apk/res/[您的包名]
。例如,以下是使用为 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-auto"> <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()
。这些调用对于确保视图可靠地运行至关重要。在视图属性发生任何可能改变其外观的更改后,都需要使视图失效,以便系统知道需要重新绘制。同样,如果属性的变化可能影响视图的大小或形状,则需要请求新的布局。忘记这些方法调用可能会导致难以找到的 bug。
自定义视图还必须支持事件监听器来通信重要事件。例如,PieChart
公开了一个名为 OnCurrentItemChanged
的自定义事件,以通知监听器用户旋转饼图以将焦点放在新的扇形上。
很容易忘记公开属性和事件,尤其是在您是自定义视图的唯一用户时。花时间仔细定义视图的接口可以降低未来的维护成本。一个好的经验法则是始终公开任何影响自定义视图可见外观或行为的属性。
设计无障碍功能
您的自定义视图必须支持广泛的用户。这包括因残疾而无法看到或使用触摸屏的用户。为了支持残疾用户,请执行以下操作
- 使用
android:contentDescription
属性标记您的输入字段。 - 在适当的时候通过调用
sendAccessibilityEvent()
发送无障碍事件。 - 支持替代控制器,例如方向键或轨迹球。
有关创建无障碍视图的更多信息,请参阅使应用更易于访问。