视图中的布局
布局定义了应用中用户界面的结构,例如在活动中。布局中的所有元素都是使用View
和ViewGroup
对象的层次结构构建的。View
通常绘制用户可以查看和交互的内容。ViewGroup
是一个不可见的容器,它定义了View
和其他ViewGroup
对象的布局结构,如图 1 所示。
View
对象通常称为小部件,可以是许多子类的其中之一,例如Button
或TextView
。ViewGroup
对象通常称为布局,可以是提供不同布局结构的多种类型之一,例如LinearLayout
或ConstraintLayout
。
您可以通过两种方式声明布局
- 在 XML 中声明 UI 元素。Android 提供了一个简单的 XML 词汇表,对应于
View
类及其子类,例如小部件和布局的类。您还可以使用 Android Studio 的布局编辑器使用拖放界面构建 XML 布局。 - 在运行时实例化布局元素。您的应用可以创建
View
和ViewGroup
对象并以编程方式操作其属性。
在 XML 中声明您的 UI 使您可以将应用的呈现与控制其行为的代码分离。使用 XML 文件还可以更轻松地为不同的屏幕尺寸和方向提供不同的布局。这在支持不同的屏幕尺寸中进行了进一步讨论。
Android 框架使您可以灵活地使用这两种方法中的任意一种或两种来构建应用的 UI。例如,您可以在 XML 中声明应用的默认布局,然后在运行时修改布局。
编写 XML
使用 Android 的 XML 词汇表,您可以快速设计 UI 布局及其包含的屏幕元素,就像您使用一系列嵌套元素在 HTML 中创建网页一样。
每个布局文件必须包含一个根元素,该元素必须是View
或ViewGroup
对象。定义根元素后,您可以添加其他布局对象或小部件作为子元素,以逐步构建定义布局的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 中声明布局后,将文件保存在 Android 项目的res/layout/
目录中,并使用.xml
扩展名,以便它能够正确编译。
有关布局 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()
回调方法。有关活动生命周期的更多信息,请参阅活动简介。
属性
每个View
和ViewGroup
对象都支持其自己的各种 XML 属性。某些属性特定于View
对象。例如,TextView
支持textSize
属性。但是,这些属性也会被扩展此类的任何View
对象继承。某些属性对所有View
对象都是通用的,因为它们是从根View
类继承的,例如id
属性。其他属性被视为布局参数,它们是描述View
对象某些布局方向的属性,如该对象的父ViewGroup
对象所定义。
ID
任何View
对象都可以与其关联一个整数 ID,以在树中唯一标识该View
。编译应用时,此 ID 将作为整数引用,但 ID 通常在布局 XML 文件中作为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
包命名空间表示您正在引用来自android.R
资源类而不是本地资源类的 ID。
要创建视图并从您的应用中引用它们,您可以使用以下通用模式
- 在布局文件中定义一个视图并为其分配一个唯一的 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
使用提供的布局为Cursor
中的每一行创建一个视图,方法是将每个fromColumns
项目插入到相应的toViews
视图中。
如果在应用程序生命周期中更改了适配器读取的基础数据,请调用 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 演示应用程序 中如何使用布局。