精心设计的自定义视图就像任何其他精心设计的类一样。它封装了一组特定的功能,并具有简单的界面,高效地使用 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-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()
。这些调用对于确保视图可靠地运行至关重要。您需要在对其属性进行任何可能更改其外观的更改后使视图无效,以便系统知道需要重新绘制它。同样,如果属性以可能影响视图大小或形状的方式发生更改,则需要请求新的布局。忘记这些方法调用会导致难以发现的错误。
自定义视图还必须支持事件侦听器以传达重要事件。例如,PieChart
公开了名为 OnCurrentItemChanged
的自定义事件,以通知侦听器用户旋转了饼图以关注新的饼图切片。
很容易忘记公开属性和事件,尤其是在您是自定义视图的唯一用户时。花时间仔细定义视图的界面可以降低未来的维护成本。一个好的规则是始终公开任何影响自定义视图可见外观或行为的属性。
设计辅助功能
您的自定义视图必须支持广泛的用户。这包括无法看到或使用触摸屏的残疾用户。要支持残疾用户,请执行以下操作
- 使用
android:contentDescription
属性为您的输入字段添加标签。 - 在适当的时候通过调用
sendAccessibilityEvent()
发送辅助功能事件。 - 支持替代控制器,例如方向键或轨迹球。
有关创建辅助功能视图的更多信息,请参阅 使应用更易于访问。