Android 提供了一个复杂而强大的组件化模型,用于基于基本布局类 View
和 ViewGroup
构建界面。该平台包含各种预构建的 View
和 ViewGroup
子类(分别称为 widget 和布局),可供您用来构建界面。
可用的部分微件包括 Button
、TextView
、EditText
、ListView
、CheckBox
、RadioButton
、Gallery
、Spinner
,以及具有特殊用途的 AutoCompleteTextView
、ImageSwitcher
和 TextSwitcher
。
可用布局包括 LinearLayout
、FrameLayout
、RelativeLayout
等。如需查看更多示例,请参阅常见布局。
如果预构建的 widget 或布局都不能满足您的需求,您可以创建自己的 View
子类。如果您只需要对现有 widget 或布局进行细微调整,则可以创建相应 widget 或布局的子类并替换其方法。
通过创建自己的 View
子类,您可以精确控制屏幕元素的外观和功能。为了让您了解自定义视图可以实现哪些控制,下面列举了一些示例来说明您可以如何使用自定义视图:
-
您可以创建一个完全自定义渲染的
View
类型,例如,使用 2D 图形渲染的“音量控制”旋钮,类似于模拟电子控件。 -
您可以将一组
View
组件组合成一个新的组件,例如制作组合框(弹出式列表和自由输入文本字段的组合)、双窗格选择器控件(左右窗格,其中每个窗格都有一个列表,您可以在其中重新分配哪个列表中的项)等。 -
您可以替换
EditText
组件在屏幕上的渲染方式。 NotePad 示例应用使用此效果有效地创建了一个带线条的记事本页面。 - 您可以捕获其他事件(例如按键),并以自定义方式(例如在游戏中)处理这些事件。
以下部分介绍了如何创建自定义视图并在应用中使用它们。如需了解详细的参考信息,请参阅 View
类。
基本方法
下面简要介绍了创建您自己的 View
组件需要了解的内容:
-
使用您自己的类扩展现有的
View
类或子类。 -
替换父类中的某些方法。要替换的父类方法以
on
开头,例如onDraw()
、onMeasure()
和onKeyDown()
。 这类似于您为生命周期和其他功能钩子替换的Activity
或ListActivity
中的on
事件。 - 使用您的新扩展类。完成后,您可以使用新的扩展类来代替其所基于的视图。
完全自定义的组件
您可以创建外观完全自定义的图形组件。您可能想要一个看起来像旧模拟量表的图形声量计,或者想要一个跟唱文本视图(在您跟着卡拉 OK 机唱歌时,一个弹力球会随着歌词移动。您可能会需要内置组件无法执行的操作,无论您以何种方式组合使用它们。
幸运的是,您可以根据自己的想象力、屏幕大小和可用处理能力来创建外观和行为符合自己要求的组件。但请注意,应用可能需要在比桌面工作站低得多的功耗上运行。
如需创建完全自定义的组件,请考虑以下事项:
-
您可以扩展的最通用的视图是
View
,因此您通常首先需要扩展此视图来创建新的超级组件。 - 您可以提供一个构造函数,它可以从 XML 获取属性和参数,并且可以使用您自己的此类属性和参数,例如 VU 计的颜色和范围或指针的宽度和阻尼。
- 您可能需要创建自己的事件监听器、属性存取器和修饰符,以及组件类中更复杂的行为。
-
您几乎肯定需要替换
onMeasure()
;如果您希望组件显示某些内容,也可能需要替换onDraw()
。虽然两者都具有默认行为,但默认onDraw()
不会执行任何操作,并且默认onMeasure()
始终将尺寸设置为 100x100,您可能并不希望这样设置。 -
您还可以根据需要替换其他
on
方法。
扩展 onDraw() 和 onMeasure()
onDraw()
方法提供了一个 Canvas
,您可以在其上实现所需的任何内容:2D 图形、其他标准或自定义组件、样式文本或您能想到的任何其他内容。
onMeasure()
涉及更多。onMeasure()
是组件与其容器之间渲染协定的关键部分。必须替换 onMeasure()
,才能高效且准确地报告其所含部分的测量结果。父级的限制要求(传递到 onMeasure()
方法)以及计算后使用测量的宽度和高度调用 setMeasuredDimension()
方法的要求,让这变得稍微有些复杂。如果您不通过已替换的 onMeasure()
方法调用此方法,则会导致测量时出现异常。
概括来讲,实现 onMeasure()
如下所示:
-
系统会使用宽度和高度规范调用替换的
onMeasure()
方法,这些规范被视为对您生成的宽度和高度的限制要求。widthMeasureSpec
和heightMeasureSpec
参数都是表示维度的整数代码。有关这些规范可能要求的限制的完整参考,请参阅View.onMeasure(int, int)
下的参考文档。此参考文档还介绍了整个测量操作。 -
组件的
onMeasure()
方法会计算渲染组件所需的测量宽度和高度。它必须尽量符合传入的规范,但也可能会超过这些规范。在这种情况下,父级可以选择要执行的操作,包括裁剪、滚动、抛出异常或要求onMeasure()
重试,或许使用不同的测量规范。 -
计算宽度和高度后,使用计算出的测量值调用
setMeasuredDimension(int width, int height)
方法。否则会导致异常。
下面总结了框架对视图调用的其他标准方法:
类别 | 方法 | 说明 |
---|---|---|
创建 | 构造函数 | 构造函数有两种形式:从代码创建视图时调用,另一种形式在从布局文件膨胀视图时调用。第二种形式解析并应用布局文件中定义的属性。 |
|
在视图及其所有子项都从 XML 扩充之后调用。 | |
布局 |
|
调用以确定此视图及其所有子级的大小要求。 |
|
在此视图必须为其所有子视图分配大小和位置时调用。 | |
|
在此视图的大小发生更改时调用。 | |
绘制 |
|
在视图必须渲染其内容时调用。 |
事件处理 |
|
在发生按键按下事件时调用。 |
|
在发生 key up 事件时调用 | |
|
在发生轨迹球动作事件时调用。 | |
|
在发生触摸屏动作事件时调用。 | |
侧重点 |
|
在视图获得或失去焦点时调用。 |
|
在包含视图的窗口获得或失去焦点时调用。 | |
附加 |
|
在视图附加到窗口时调用。 |
|
在视图与其窗口分离时调用。 | |
|
在包含视图的窗口的可见性发生更改时调用。 |
复合控件
如果您不想创建完全自定义的组件,而是希望将可重复使用的组件(由一组现有控件组成)组合在一起,那么创建复合组件(或复合控件)可能是最好的选择。总而言之,这会将许多原子性控件或视图整合到可视为一项的逻辑项组中。
例如,组合框可以是单行 EditText
字段和附有弹出式列表的相邻按钮的组合。如果用户点按该按钮并从列表中选择了内容,系统会填充 EditText
字段,但用户也可以根据需要直接在 EditText
中输入内容。
在 Android 中,还有另外两个视图可用于执行此操作:Spinner
和 AutoCompleteTextView
。无论如何,这个组合框概念都是一个很好的例子。
如需创建复合组件,请执行以下操作:
-
与
Activity
一样,使用声明式(基于 XML)方法创建包含的组件,或者以程序化方式从代码中嵌套组件。通常的起点是某种类型的Layout
,因此请创建一个扩展Layout
的类。对于组合框,您可以使用水平方向的LinearLayout
。您可以在里面嵌套其他布局,使复合组件可以任意复杂化和结构化。 -
在新类的构造函数中,获取父类所需的任何参数,并先将它们传递给父类构造函数。然后,您可以设置其他视图,以便在新组件中使用。您可以在这里创建
EditText
字段和弹出式列表。您可以在 XML 中引入您自己的属性和参数,以便构造函数可以提取和使用。 -
(可选)为包含的视图可能生成的事件创建监听器。例如,在选择了列表的情况下,列表项点击监听器会更新
EditText
的内容。 -
(可选)使用访问器和修饰符创建自己的属性。例如,最初在组件中设置
EditText
值,并在需要时查询其内容。 -
(可选)替换
onDraw()
和onMeasure()
。在扩展Layout
时通常没有必要这样做,因为布局具有可能正常运行的默认行为。 -
(可选)替换其他
on
方法(如onKeyDown()
),例如,在点按某个键时,从组合框的弹出式列表中选择特定默认值。
使用 Layout
作为自定义控件的基础具有如下优势:
- 您可以使用声明式 XML 文件指定布局(就像使用 activity 屏幕一样),也可以以编程方式创建视图并将其从代码嵌套到布局中。
-
onDraw()
和onMeasure()
方法以及大多数其他on
方法具有合适的行为,因此您无需替换它们。 - 您可以快速构建任意复杂的复合视图,并像使用单个组件一样重复使用它们。
修改现有视图类型
如果存在与您所需的组件类似的组件,您可以扩展该组件并替换您想要更改的行为。您可以使用完全自定义的组件执行所有操作,但通过从 View
层次结构中更专用的类着手,您可以免费获得一些执行您所需要的行为。
例如,NotePad 示例应用演示了使用 Android 平台的多个方面。其中包括扩展 EditText
视图,使之成为带线条的记事本。这并不是一个完美的示例,并且用于执行此操作的 API 可能会发生变化,但它演示了相关原则。
如果您尚未执行此操作,请将 NotePad 示例导入 Android Studio,或使用提供的链接查看源代码。请特别留意 NoteEditor.java
文件中 LinedEditText
的定义。
下面是此文件中的一些注意事项:
-
定义
该类使用以下行进行定义:
public static class LinedEditText extends EditText
LinedEditText
定义为NoteEditor
Activity 中的一个内部类,但它是公共类,因此可以作为NoteEditor.LinedEditText
从NoteEditor
类外部进行访问。此外,
LinedEditText
为static
,这意味着它不会生成允许其访问父类数据的所谓“合成方法”。这意味着它的行为表现为一个单独的类,而不是与NoteEditor
密切相关的类。如果内部类不需要从外部类访问状态,则这是一种更简洁的方法来创建内部类。它使生成的类保持较小,并便于其他类使用。LinedEditText
扩展了EditText
(在本例中是要自定义的视图)。完成后,新类可以代替普通的EditText
视图。 -
类初始化
与往常一样,首先调用父类。这不是默认构造函数,而是参数化构造函数。
EditText
在从 XML 布局文件膨胀时是使用这些参数创建的。因此,构造函数需要获取这些方法并将其传递给父类构造函数。 -
替换的方法
此示例仅替换
onDraw()
方法,但您可能需要在创建自己的自定义组件时替换其他方法。在此示例中,通过替换
onDraw()
方法,您可以在EditText
视图画布上绘制蓝色线条。系统会将画布传入被替换的onDraw()
方法。系统会在super.onDraw()
方法结束之前调用该方法。必须调用父类方法。在本例中,请在绘制要包含的行后调用该函数。 -
自定义组件
现在,您已经有了自定义组件,但如何使用它呢?在记事本示例中,自定义组件直接从声明式布局中使用,因此请查看
res/layout
文件夹中的note_editor.xml
:<view xmlns:android="http://schemas.android.com/apk/res/android" class="com.example.android.notepad.NoteEditor$LinedEditText" android:id="@+id/note" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/transparent" android:padding="5dp" android:scrollbars="vertical" android:fadingEdge="vertical" android:gravity="top" android:textSize="22sp" android:capitalize="sentences" />
将自定义组件创建为 XML 中的通用视图,并使用完整软件包指定类。您定义的内部类使用
NoteEditor$LinedEditText
表示法引用,这是以 Java 编程语言引用内部类的标准方式。如果您的自定义视图组件未定义为内部类,您可以使用 XML 元素名称声明视图组件,并排除
class
属性。例如:<com.example.android.notepad.LinedEditText id="@+id/note" ... />
请注意,
LinedEditText
类现在是一个单独的类文件。当该类嵌套在NoteEditor
类中时,此方法不起作用。定义中的其他属性和参数是传入自定义组件构造函数中和再传递到
EditText
构造函数的,因此它们与您用于EditText
视图的参数相同。您也可以添加自己的参数。
您可以根据自己的需要创建自定义组件。
更复杂的组件可以替换更多的 on
方法并引入自己的辅助方法,从而充分地自定义其属性和行为。唯一的限制是您的想象力以及您需要组件执行的操作。