设计良好的自定义视图与任何其他精心设计的类一样。它通过一个简单的接口封装一组特定的功能,高效使用 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()
。这些调用对于确保视图可靠运行至关重要。在对视图属性进行任何可能会改变其外观的更改后,您需要使该视图失效,以便系统知道需要重新绘制该视图。同样,如果属性的变化方式可能会影响视图的大小或形状,您需要请求新的布局。忘记这些方法调用可能会导致难以发现的 bug。
自定义视图还必须支持事件监听器来传达重要事件。例如,PieChart
公开了一个名为 OnCurrentItemChanged
的自定义事件,以通知监听器用户旋转了饼图以将焦点放在新的饼图切片上。
公开属性和事件是很容易忘记的,尤其是当您是自定义视图的唯一用户时。花点时间仔细定义视图界面可以降低未来的维护成本。一种好的做法是始终公开任何会影响自定义视图的可见外观或行为的属性。
在设计时充分考虑无障碍功能
您的自定义视图必须支持众多用户。包括妨碍他们看到或使用触摸屏的残障用户。如需为残障用户提供支持,请执行以下操作:
- 使用
android:contentDescription
属性为输入字段添加标签。 - 根据需要调用
sendAccessibilityEvent()
来发送无障碍事件。 - 支持备用控制器,例如方向键或轨迹球。
如需详细了解如何创建易于访问的视图,请参阅 让应用使用起来更没有障碍。