Android 提供了一个复杂且强大的组件化模型来构建您的 UI,该模型基于基本布局类 View
和 ViewGroup
。该平台包含各种预构建的 View
和 ViewGroup
子类(分别称为小部件和布局),您可以使用它们来构建您的 UI。
可用小部件的部分列表包括 Button
、TextView
、EditText
、ListView
、CheckBox
、RadioButton
、Gallery
、Spinner
,以及更专业的 AutoCompleteTextView
、ImageSwitcher
和 TextSwitcher
。
可用的布局包括 LinearLayout
、FrameLayout
、RelativeLayout
等。有关更多示例,请参阅 常见布局。
如果没有任何预构建的小部件或布局满足您的需求,您可以创建自己的 View
子类。如果您只需要对现有的小部件或布局进行少量调整,您可以对小部件或布局进行子类化并覆盖其方法。
创建自己的 View
子类可以让您精确控制屏幕元素的外观和功能。为了说明使用自定义视图可以获得的控制权,以下是一些您可以使用它们执行的操作示例
- 您可以创建一个完全自定义渲染的
View
类型,例如,使用 2D 图形渲染的“音量控制”旋钮,类似于模拟电子控制。 - 您可以将一组
View
组件组合成一个新的单个组件,也许是为了创建类似于组合框(弹出列表和自由输入文本字段的组合)、双窗格选择器控件(左右窗格,每个窗格中都有一个列表,您可以在其中重新分配哪个项目位于哪个列表中)等等。 - 您可以覆盖
EditText
组件在屏幕上渲染的方式。 记事本 示例应用有效地利用了这一点来创建带线的记事本页面。 - 您可以捕获其他事件(例如按键)并以自定义方式处理它们,例如用于游戏。
以下部分说明如何创建自定义视图并在您的应用程序中使用它们。有关详细参考信息,请参阅 View
类。
基本方法
以下是您需要了解的有关创建自己的 View
组件的高级概述
- 使用您自己的类扩展现有的
View
类或子类。 - 覆盖超类中的一些方法。要覆盖的超类方法以
on
开头,例如onDraw()
、onMeasure()
和onKeyDown()
。这类似于您为生命周期和其他功能挂钩覆盖的Activity
或ListActivity
中的on
事件。 - 使用您的新扩展类。完成后,您可以使用新的扩展类来代替它所基于的视图。
完全自定义的组件
您可以创建完全自定义的图形组件,以您想要的方式显示。也许您想要一个看起来像旧模拟仪表的图形 VU 表,或者一个随着您与卡拉 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 加载视图及其所有子视图后调用。 | |
布局 |
|
用于确定此视图及其所有子视图的大小需求。 |
|
当此视图必须为其所有子视图分配大小和位置时调用。 | |
|
当此视图的大小发生变化时调用。 | |
绘制 |
|
当视图必须呈现其内容时调用。 |
事件处理 |
|
当发生键盘按下事件时调用。 |
|
当发生键盘抬起事件时调用。 | |
|
当发生轨迹球运动事件时调用。 | |
|
当发生触摸屏运动事件时调用。 | |
焦点 |
|
当视图获得或失去焦点时调用。 |
|
当包含该视图的窗口获得或失去焦点时调用。 | |
附加 |
|
当视图附加到窗口时调用。 |
|
当视图从其窗口分离时调用。 | |
|
当包含该视图的窗口的可见性发生变化时调用。 |
复合控件
如果您不想创建完全自定义的组件,而是希望将一组现有的控件组合成一个可重用的组件,那么创建复合组件(或复合控件)可能是最佳选择。总而言之,它将许多更基本的控件或视图组合成一个逻辑项组,这些项可以作为一个整体进行处理。例如,组合框可以是单行EditText
字段和一个带有附加弹出列表的相邻按钮的组合。如果用户点击按钮并从列表中选择某些内容,它将填充EditText
字段,但如果他们愿意,他们也可以直接在EditText
中输入内容。
在 Android 中,还有另外两个视图可以轻松实现此目的:Spinner
和AutoCompleteTextView
。无论如何,组合框的这个概念提供了一个很好的例子。
要创建复合组件,请执行以下操作
- 就像使用
Activity
一样,使用声明性(基于 XML)方法创建包含的组件,或者从代码中以编程方式嵌套它们。通常的起点是某种Layout
,因此创建一个扩展Layout
的类。对于组合框,您可以使用水平方向的LinearLayout
。您可以在其中嵌套其他布局,因此复合组件可以具有任意复杂性和结构。 - 在新类的构造函数中,获取超类期望的任何参数,并将它们首先传递给超类构造函数。然后,您可以设置要在新组件中使用的其他视图。在这里,您创建
EditText
字段和弹出列表。您可以在 XML 中引入您自己的属性和参数,以便构造函数可以提取和使用它们。 - 可选地,为包含的视图可能生成的事件创建侦听器。例如,列表项点击侦听器的侦听器方法,用于在进行列表选择时更新
EditText
的内容。 - 可选地,使用访问器和修改器创建您自己的属性。例如,让
EditText
值最初在组件中设置,并在需要时查询其内容。 - 可选地,覆盖
onDraw()
和onMeasure()
。当扩展Layout
时,这通常不是必需的,因为布局具有可能正常工作的默认行为。 - 可选地,覆盖其他
on
方法,例如onKeyDown()
,例如在点击某个键时从组合框的弹出列表中选择某些默认值。
使用Layout
作为自定义控件的基础有一些优势,包括以下几点
- 您可以使用声明性 XML 文件指定布局,就像使用活动屏幕一样,或者您可以以编程方式创建视图并将它们嵌套到代码中的布局中。
onDraw()
和onMeasure()
方法以及大多数其他on
方法都具有合适的行为,因此您无需覆盖它们。- 您可以快速构建任意复杂的复合视图,并像使用单个组件一样重用它们。
修改现有视图类型
如果有一个与您想要的类似的组件,您可以扩展该组件并覆盖您想要更改的行为。您可以对完全自定义的组件执行所有操作,但是通过从View
层次结构中更专业的类开始,您可以获得一些免费实现您想要的行为。
例如,NotePad示例应用演示了使用 Android 平台的许多方面。其中包括扩展EditText
视图以创建带线的记事本。这不是一个完美的示例,执行此操作的 API 可能会发生变化,但它演示了原理。
如果您还没有这样做,请将 NotePad 示例导入 Android Studio 或使用提供的链接查看源代码。特别是,请参阅NoteEditor.java
文件中LinedEditText
的定义。
以下是一些需要注意的事项
-
定义
类使用以下行定义
public static class LinedEditText extends EditText
LinedEditText
在NoteEditor
活动中定义为内部类,但它是公共的,因此可以从NoteEditor
类外部作为NoteEditor.LinedEditText
访问。此外,
LinedEditText
是static
的,这意味着它不会生成所谓的“合成方法”,这些方法允许它访问父类中的数据。这意味着它表现为一个单独的类,而不是与NoteEditor
紧密相关的类。如果内部类不需要访问外部类的状态,这是一种更简洁的创建内部类的方法。它使生成的类保持较小,并允许其他类轻松使用它。LinedEditText
扩展了EditText
,在本例中,它是要自定义的视图。完成后,新类可以替代普通的EditText
视图。 -
类初始化
与往常一样,首先调用超类。这不是默认构造函数,而是一个参数化构造函数。
EditText
使用这些参数在从 XML 布局文件加载时创建。因此,构造函数需要获取它们并将它们也传递给超类构造函数。 -
覆盖的方法
此示例仅覆盖
onDraw()
方法,但您可能需要在创建自己的自定义组件时覆盖其他方法。对于此示例,覆盖
onDraw()
方法允许您在EditText
视图画布上绘制蓝线。画布被传递到覆盖的onDraw()
方法中。在方法结束之前调用super.onDraw()
方法。必须调用超类方法。在本例中,在绘制要包含的线条后,在最后调用它。 -
自定义组件
您现在有了自定义组件,但是如何使用它呢?在 NotePad 示例中,自定义组件直接从声明性布局中使用,因此请查看
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
方法并引入其自己的帮助器方法,从而大幅自定义其属性和行为。唯一的限制是您的想象力和您需要组件执行的操作。