View 中的布局
布局定义了应用中(例如 activity)界面的结构。布局中的所有元素均使用 View
和 ViewGroup
对象的层次结构进行构建。View
通常会绘制用户可以看到并与之互动的内容。ViewGroup
是一个不可见容器,用于定义 View
和其他 ViewGroup
对象的布局结构,如图 1 所示。

View
对象通常称为 widget,可以是许多子类之一,例如 Button
或 TextView
。ViewGroup
对象通常称为布局,可以是提供不同布局结构的众多类型之一,例如 LinearLayout
或 ConstraintLayout
。
您可通过两种方式声明布局:
- 在 XML 中声明界面元素。Android 提供了对应于
View
类及其子类的简明 XML 词汇,如用于微件和布局的词汇。您还可以使用 Android Studio 的布局编辑器,通过拖放界面构建 XML 布局。 - 在运行时实例化布局元素。您的应用可以创建
View
和ViewGroup
对象,并以编程方式操控其属性。
通过在 XML 中声明界面,您可以将应用的外观与控制其行为的代码分开。使用 XML 文件还可让您更轻松地为不同屏幕尺寸和方向提供不同的布局。支持不同的屏幕尺寸中对此进行了进一步讨论。
Android 框架可让您灵活地使用这两种或其中一种方法来构建应用界面。例如,您可以在 XML 中声明应用的默认布局,然后在运行时修改布局。
编写 XML
借助 Android 的 XML 词汇,您可以快速设计界面布局及其包含的屏幕元素,方法与在 HTML 中创建包含一系列嵌套元素的网页一样。
每个布局文件必须正好包含一个根元素,该元素必须是 View
或 ViewGroup
对象。定义根元素后,您可以将其他布局对象或 widget 添加为子元素,以逐步构建定义布局的 View
层次结构。例如,以下 XML 布局使用垂直 LinearLayout
来保存 TextView
和 Button
:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello, I am a TextView" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello, I am a Button" /> </LinearLayout>
在 XML 中声明布局后,使用 .xml
扩展名将文件保存在 Android 项目的 res/layout/
目录中,以便其正确编译。
如需详细了解布局 XML 文件的语法,请参阅布局资源。
加载 XML 资源
当您编译应用时,系统会将每个 XML 布局文件编译成 View
资源。在应用的 Activity.onCreate()
回调实现中加载布局资源。为此,您可以调用 setContentView()
,并以 R.layout.layout_file_name
格式向其传递对布局资源的引用。例如,如果您的 XML 布局保存为 main_layout.xml
,请为 Activity
加载该布局,如下所示:
Kotlin
fun onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) setContentView(R.layout.main_layout) }
Java
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_layout); }
当 Activity
启动时,Android 框架会调用 Activity
中的 onCreate()
回调方法。如需详细了解 activity 生命周期,请参阅 activity 简介。
属性
每个 View
和 ViewGroup
对象都支持自己的各种 XML 属性。某些属性专用于某个 View
对象。例如,TextView
支持 textSize
属性。不过,扩展此类的任何 View
对象也会继承这些属性。某些属性是所有 View
对象通用的,因为它们继承自 View
根类,例如 id
属性。其他属性被视为布局参数,即描述 View
对象某些布局方向的属性,如该对象的父级 ViewGroup
对象所定义。
ID
任何 View
对象都可以具有与其关联的整数 ID,用于唯一标识树中的 View
。编译应用时,系统会以整数形式引用此 ID,但在布局 XML 文件中,该 ID 通常作为 id
属性中的字符串进行分配。这是所有 View
对象通用的 XML 属性,由 View
类定义。您经常使用它。XML 标记内 ID 的语法如下:
android:id="@+id/my_button"
字符串开头的 at 符号 (@) 表示 XML 解析器解析并展开 ID 字符串的其余部分,并将其标识为 ID 资源。加号 (+) 表示这是一个新资源名称,必须创建该名称并将其添加到 R.java
文件中的资源。
Android 框架提供了许多其他 ID 资源。引用 Android 资源 ID 时,无需加号,但必须添加 android
软件包命名空间,如下所示:
android:id="@android:id/empty"
android
软件包命名空间表明您所引用的 ID 来自 android.R
资源类,而不是本地资源类。
如需创建视图并从应用中引用它们,您可以使用如下通用模式:
- 在布局文件中定义一个视图并为其分配唯一 ID,如以下示例所示:
<Button android:id="@+id/my_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/my_button_text"/>
- 创建视图对象的实例,并从布局中捕获该实例(通常使用
onCreate()
方法),如以下示例所示:Kotlin
val myButton: Button = findViewById(R.id.my_button)
Java
Button myButton = (Button) findViewById(R.id.my_button);
创建 RelativeLayout
时,请务必为视图对象定义 ID。在相对布局中,同级视图可以定义其相对于由唯一 ID 引用的另一个同级视图的布局。
ID 在整个结构树中不必是唯一的,但在您搜索的树部分中必须是唯一的。它通常可能是整个树,因此最好尽可能使其具有唯一性。
布局参数
名为 layout_something
的 XML 布局属性会为 View
定义适合其所在 ViewGroup
的布局参数。
每个 ViewGroup
类都会实现一个扩展 ViewGroup.LayoutParams
的嵌套类。此子类包含的属性类型会根据需要为视图组定义每个子视图的尺寸和位置。如图 2 所示,父视图组会为每个子视图(包括子视图组)定义布局参数。

每个 LayoutParams
子类都有自己的值设置语法。每个子元素都必须定义适合其父元素的 LayoutParams
,不过它也可以为自己的子元素定义不同的 LayoutParams
。
所有视图组都使用 layout_width
和 layout_height
包含宽度和高度,并且每个视图都需要定义它们。许多 LayoutParams
包含可选的外边距和边框。
您可以指定具有确切尺寸的宽度和高度,但最好不要经常这样做。更常见的情况是,您可以使用以下常量之一来设置宽度或高度:
wrap_content
:告知视图将其大小调整为内容所需的尺寸。match_parent
:指示您的视图尽可能采用其父视图组所允许的最大尺寸。
一般情况下,我们不建议使用绝对单位(如像素)指定布局宽度和高度,更好的方法是使用相对测量值,例如密度无关像素单位 (dp)、wrap_content
或 match_parent
,因为它有助于应用在各种尺寸的设备屏幕尺寸上正确显示。布局资源中定义了接受的测量类型。
布局位置
视图具有矩形几何图形。该坐标有一个位置(以一对“水平向左”和“垂直向上”的坐标表示)和两个维度(以宽度和高度表示)。位置和尺寸的单位是像素。
您可以通过调用 getLeft()
和 getTop()
方法来检索视图的位置。前者会返回表示视图的矩形的左 (x) 坐标。后者会返回表示视图的矩形的顶部 (y) 坐标。这些方法会返回视图相对于其父项的位置。例如,如果 getLeft()
返回 20,则表示视图位于其直接父项左边缘距左边缘 20 个像素处。
此外,您还可以使用一些便捷方法避免不必要的计算,即 getRight()
和 getBottom()
。这些方法会返回表示视图的矩形的右边缘和下边缘的坐标。例如,调用 getRight()
类似于进行以下计算:getLeft() + getWidth()
。
尺寸、内边距和外边距
视图的大小通过宽度和高度表示。视图具有两对宽度和高度值。
第一对称为“测量宽度”和“测量高度”。这些尺寸定义了视图想要在其父项中具有的大小。您可以通过调用 getMeasuredWidth()
和 getMeasuredHeight()
获取测量尺寸。
第二对称为“宽度”和“高度”,有时称为“绘制宽度”和“绘制高度”。这些尺寸定义绘制时和布局之后,视图在屏幕上的实际尺寸。这些值可以(但不必)与测量的宽度和高度不同。您可以通过调用 getWidth()
和 getHeight()
获取宽度和高度。
为了测量尺寸,视图需将其内边距考虑在内。内边距以视图左侧、顶部、右侧和底部各部分的像素数表示。您可以使用内边距将视图的内容偏移特定像素。例如,左侧内边距为 2,因此视图内容会向左边缘右侧展示 2 个像素。您可以使用 setPadding(int, int, int, int)
方法设置内边距,并通过调用 getPaddingLeft()
、getPaddingTop()
、getPaddingRight()
和 getPaddingBottom()
进行查询。
虽然视图可以定义内边距,但不支持外边距。不过,视图组支持外边距。如需了解详情,请参阅 ViewGroup
和 ViewGroup.MarginLayoutParams
。
如需详细了解维度,请参阅维度。
除了以编程方式设置外边距和内边距之外,您还可以在 XML 布局中设置外边距和内边距,如以下示例所示:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:padding="8dp" android:text="Hello, I am a TextView" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:paddingBottom="4dp" android:paddingEnd="8dp" android:paddingStart="8dp" android:paddingTop="4dp" android:text="Hello, I am a Button" /> </LinearLayout>
上面的示例展示了应用的外边距和内边距。TextView
的各处采用统一的外边距和内边距,Button
则显示了如何将它们独立应用于不同的边缘。
常见布局
ViewGroup
类的每个子类都提供了一种独特的方式来显示您在其中嵌套的视图。ConstraintLayout
是最灵活的布局类型,也是使布局层次结构保持浅层的最佳工具类型。
以下是 Android 平台内置的一些常见布局类型。
构建动态列表
如果布局的内容是动态的或未预先确定的内容,您可以使用 RecyclerView
或 AdapterView
的子类。
RecyclerView
通常是更好的选择,因为它使用内存的效率比 AdapterView
更高。
RecyclerView
和 AdapterView
的常见布局包括:
RecyclerView
提供了更多可能性以及用于创建自定义布局管理器的选项。
使用数据填充适配器视图
您可以通过将 AdapterView
实例与 Adapter
绑定来填充 AdapterView
(例如 ListView
或 GridView
),此操作会从外部来源检索数据,并创建表示每个数据条目的 View
。
Android 提供了几个 Adapter
子类,用于检索不同类型的数据以及为 AdapterView
构建视图。两种最常见的适配器是:
ArrayAdapter
- 请在数据源为数组时使用此适配器。默认情况下,
ArrayAdapter
会通过对每个数组项调用toString()
并将内容放入TextView
来为每个数组项创建视图。例如,如果您有要在
ListView
中显示的字符串数组,请使用构造函数初始化新的ArrayAdapter
,为每个字符串和字符串数组指定布局:Kotlin
val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, myStringArray)
Java
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, myStringArray);
此构造函数的参数如下:
- 您的应用
Context
- 包含数组中每个字符串的
TextView
的布局 - 字符串数组
然后,对您的
ListView
调用setAdapter()
:Kotlin
val listView: ListView = findViewById(R.id.listview) listView.adapter = adapter
Java
ListView listView = (ListView) findViewById(R.id.listview); listView.setAdapter(adapter);
如需自定义每个项的外观,您可以替换数组中各个对象的
toString()
方法。或者,如需为TextView
以外的每个项创建视图(例如,如果您想为每个数组项创建一个ImageView
),请扩展ArrayAdapter
类并替换getView()
以返回您想要为每个项返回的视图类型。 - 您的应用
SimpleCursorAdapter
- 请在数据来自
Cursor
时使用此适配器。 使用SimpleCursorAdapter
时,请指定要为Cursor
中的每一行使用的布局,以及要插入到所需布局视图中的Cursor
中的哪些列。 例如,如果您想创建人员姓名和电话号码的列表,则可以执行返回Cursor
(包含对应每个人的行,以及对应姓名和号码的列)的查询。然后,您需要创建一个字符串数组,用于指定想要在每个结果的布局中包含Cursor
中的哪些列,并创建一个整数数组,用于指定需要放置每列的相应视图:Kotlin
val fromColumns = arrayOf(ContactsContract.Data.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER) val toViews = intArrayOf(R.id.display_name, R.id.phone_number)
Java
String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER}; int[] toViews = {R.id.display_name, R.id.phone_number};
当您实例化
SimpleCursorAdapter
时,请传递要用于每个结果的布局、包含结果的Cursor
以及以下两个数组:Kotlin
val adapter = SimpleCursorAdapter(this, R.layout.person_name_and_number, cursor, fromColumns, toViews, 0) val listView = getListView() listView.adapter = adapter
Java
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.person_name_and_number, cursor, fromColumns, toViews, 0); ListView listView = getListView(); listView.setAdapter(adapter);
然后,
SimpleCursorAdapter
会使用提供的布局,将每个fromColumns
项插入相应的toViews
视图,为Cursor
中的每一行创建一个视图。
如果您在应用的生命周期中更改了适配器读取的底层数据,请调用 notifyDataSetChanged()
。这会通知附加的视图数据已被更改,并且它会自行刷新。
处理点击事件
您可以通过实现 AdapterView.OnItemClickListener
接口来响应 AdapterView
中每一项上的点击事件。例如:
Kotlin
listView.onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id -> // Do something in response to the click. }
Java
// Create a message handling object as an anonymous class. private OnItemClickListener messageClickedHandler = new OnItemClickListener() { public void onItemClick(AdapterView parent, View v, int position, long id) { // Do something in response to the click. } }; listView.setOnItemClickListener(messageClickedHandler);
其他资源
如需了解如何使用布局,请参阅 GitHub 上的 Sunflower 演示版应用。