视图中的布局

尝试 Compose 方法
Jetpack Compose 是 Android 推荐的 UI 工具包。了解如何在 Compose 中使用布局。

布局定义了应用中用户界面的结构,例如在 活动 中。布局中的所有元素都是使用 ViewViewGroup 对象的层次结构构建的。一个 View 通常绘制用户可以看到并与其交互的内容。一个 ViewGroup 是一个不可见的容器,它定义了 View 和其他 ViewGroup 对象的布局结构,如图 1 所示。

图 1. 视图层次结构的图示,它定义了 UI 布局。

View 对象通常称为小部件,可以是许多子类的其中之一,例如 ButtonTextView。The ViewGroup 对象通常称为布局,可以是许多类型之一,它们提供不同的布局结构,例如 LinearLayoutConstraintLayout.

您可以通过两种方式声明布局

  • 在 XML 中声明 UI 元素。 Android 提供了一个简单的 XML 词汇表,它对应于 View 类和子类,例如用于小部件和布局的类。您还可以使用 Android Studio 的 布局编辑器 使用拖放界面构建您的 XML 布局。

  • 在运行时实例化布局元素。 您的应用可以创建 ViewViewGroup 对象,并以编程方式操作它们的属性。

在 XML 中声明您的 UI 使您可以将应用的演示文稿与其控制行为的代码分开。使用 XML 文件还可以更轻松地为不同的屏幕尺寸和方向提供不同的布局。这在 支持不同的屏幕尺寸 中有更详细的讨论。

Android 框架让您灵活地使用这些方法中的任何一种或两种来构建应用的 UI。例如,您可以在 XML 中声明应用的默认布局,然后在运行时修改布局。

编写 XML

使用 Android 的 XML 词汇表,您可以像在 HTML 中使用一系列嵌套元素创建网页一样快速地设计 UI 布局及其包含的屏幕元素。

每个布局文件必须包含一个根元素,该元素必须是 ViewViewGroup 对象。定义根元素后,您可以添加其他布局对象或小部件作为子元素,以逐步构建定义布局的 View 层次结构。例如,以下是一个 XML 布局,它使用垂直 LinearLayout 来容纳 TextViewButton

<?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);
}

Android 框架会在 Activity 启动时调用 Activity 中的 onCreate() 回调方法。有关活动生命周期的更多信息,请参阅 活动简介

属性

每个 ViewViewGroup 对象都支持其自己的各种 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 资源。plus 符号 (+) 表示这是一个必须创建并添加到 R.java 文件中资源的新资源名称。

Android 框架提供许多其他 ID 资源。引用 Android 资源 ID 时,您不需要plus 符号,但必须添加 android 包名称空间,如下所示

android:id="@android:id/empty"

android 包名称空间表示您正在引用来自 android.R 资源类而不是本地资源类的 ID。

要创建视图并从应用中引用它们,您可以使用以下常见模式

  1. 在布局文件中定义视图并为其分配一个唯一的 ID,如以下示例所示
    <Button android:id="@+id/my_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/my_button_text"/>
    
  2. 创建视图对象的实例并从布局中捕获它,通常在 onCreate() 方法中,如以下示例所示

    Kotlin

    val myButton: Button = findViewById(R.id.my_button)
    

    Java

    Button myButton = (Button) findViewById(R.id.my_button);
    

为视图对象定义 ID 在创建 RelativeLayout 时非常重要。在相对布局中,兄弟视图可以相对于另一个兄弟视图定义其布局,该兄弟视图由唯一 ID 引用。

ID 不需要在整棵树中都是唯一的,但它必须在您搜索的树的一部分中是唯一的。它通常是整棵树,因此最好在可能的情况下使其唯一。

布局参数

名为 layout_something 的 XML 布局属性定义了适合其所在的 ViewGroupView 的布局参数。

每个 ViewGroup 类都实现一个扩展 ViewGroup.LayoutParams 的嵌套类。此子类包含定义每个子视图的大小和位置的属性类型,这些类型适合视图组。如图 2 所示,父视图组定义了每个子视图的布局参数,包括子视图组。

图 2. 具有与每个视图关联的布局参数的视图层次结构的可视化。

每个 LayoutParams 子类都有自己的语法来设置值。每个子元素必须定义一个适合其父元素的 LayoutParams,尽管它也可能为自己的子元素定义不同的 LayoutParams

所有视图组都包含宽度和高度,使用 layout_widthlayout_height,并且每个视图都必须定义它们。许多 LayoutParams 包含可选的边距和边框。

您可以使用精确的测量值来指定宽度和高度,但您可能不希望经常这样做。更常见的是,您使用以下常量之一来设置宽度或高度

  • wrap_content: 告知您的视图根据其内容所需的大小调整自身尺寸。
  • match_parent: 告知您的视图尽可能大,以适应其父视图组允许的尺寸。

通常,我们不建议使用像素等绝对单位来指定布局宽度和高度。更好的方法是使用相对测量,例如与密度无关的像素单位 (dp)、wrap_contentmatch_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() 来查询填充。

虽然视图可以定义填充,但它不支持边距。但是,视图组支持边距。有关详细信息,请参阅 ViewGroupViewGroup.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 平台中内置的一些常用布局类型。

**创建线性布局**

将子视图组织成单行水平或垂直排列,并在窗口长度超过屏幕长度时创建滚动条。

**在 WebView 中构建 Web 应用**

显示网页。

构建动态列表

当布局的内容是动态的或未预先确定的时,可以使用 RecyclerViewAdapterView 的子类。 RecyclerView 通常是更好的选择,因为它比 AdapterView 更有效地使用内存。

使用 RecyclerViewAdapterView 可以实现的常用布局包括以下内容

**列表**

显示滚动单列列表。

**网格**

显示滚动列和行网格。

RecyclerView 提供更多可能性,以及创建自定义布局管理器 的选项。

使用数据填充适配器视图

可以通过将 AdapterView 实例绑定到 Adapter 来填充 AdapterView(例如 ListViewGridView),该适配器从外部源检索数据并创建表示每个数据条目的 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 演示应用程序 中如何使用布局。