创建视图类

设计精良的自定义视图与任何其他精心设计的类非常相似。它通过简单易用的界面封装一组特定的功能,高效地使用 CPU 和内存,诸如此类。不过,除了是精心设计的类之外,自定义视图还应该:

  • 符合 Android 标准
  • 提供适用于 Android XML 布局的自定义可设置样式属性
  • 发送无障碍事件
  • 与多种 Android 平台兼容。

Android 框架提供了一组基类和 XML 标记,帮助您创建满足所有这些要求的视图。本课将讨论如何使用 Android 框架创建视图类的核心功能。

除了本课程,您还可以在自定义组件中找到其他相关信息。

子类化 View

Android 框架中定义的所有视图类都能扩展 View。自定义视图也可以直接扩展 View,还可通过扩展某个现有视图子类(如 Button)节省时间。

为了使 Android Studio 能够与您的视图交互,您必须至少提供一个以 ContextAttributeSet 对象为参数的构造函数。此构造函数允许布局编辑器创建和编辑视图的实例。

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>
    

此代码声明了两个自定义属性 showTextlabelPosition,它们属于一个名为 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 开发者指南中的让应用易于访问