adaptive

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Prerequisites

前提条件

The app must:
  • Use Compose for all screens. If it's still using Fragments or Views, suggest using the XML to Compose skill to migrate those screens.
  • Use Jetpack Navigation 3. If it doesn't, suggest the Navigation 3 skill to migrate the app.
应用必须满足以下要求:
  • 所有界面都使用Compose。如果仍在使用Fragments或Views,建议使用XML转Compose技能迁移这些界面。
  • 使用Jetpack Navigation 3。如果未使用,建议使用Navigation3技能迁移应用。

Workflow to make an app adaptive

打造自适应应用的工作流程

To make an app adaptive, follow these steps or a subset of them adapting to the task.
  • Step 1: Verify current UI
  • Step 2: Make the navigation bar adaptive
  • Step 3: Add multi-pane layouts
  • Step 4: Make vertical lists adaptive by changing the number of columns
  • Step 5: Hide app bars when scrolling
要打造自适应应用,请遵循以下步骤或根据任务需求选择部分步骤执行。
  • 步骤1:验证当前UI
  • 步骤2:实现自适应导航栏
  • 步骤3:添加多窗格布局
  • 步骤4:通过调整列数实现垂直列表自适应
  • 步骤5:滚动时隐藏应用栏

Step 1. Verify current UI

步骤1. 验证当前UI

Ensure that screenshot tests exist to verify the current UI on different form factors. If they don't exist, add the Compose Preview Screenshot Testing tool. Use the following annotation to create previews for all the major form factors. For example:
kotlin
@Preview(name = "Phone", device = Devices.PHONE, showBackground = true)
@Preview(name = "Foldable", device = Devices.FOLDABLE, showBackground = true)
@Preview(name = "Tablet", device = Devices.TABLET, showBackground = true)
@Preview(name = "Desktop", device = Devices.DESKTOP, showBackground = true)
annotation class FormFactorPreviews

@PreviewTest
@FormFactorPreviews
@Composable
fun FeedScreenPreview() {
    SnippetsTheme {
        Box {
            Text("My Screen")
        }
    }
}
<br />
确保存在截图测试,用于验证不同形态设备上的当前UI。如果没有,请添加Compose预览截图测试工具。使用以下注解为所有主要形态设备创建预览。例如:
kotlin
@Preview(name = "Phone", device = Devices.PHONE, showBackground = true)
@Preview(name = "Foldable", device = Devices.FOLDABLE, showBackground = true)
@Preview(name = "Tablet", device = Devices.TABLET, showBackground = true)
@Preview(name = "Desktop", device = Devices.DESKTOP, showBackground = true)
annotation class FormFactorPreviews

@PreviewTest
@FormFactorPreviews
@Composable
fun FeedScreenPreview() {
    SnippetsTheme {
        Box {
            Text("My Screen")
        }
    }
}
<br />

Step 2. Make the navigation bar adaptive

步骤2. 实现自适应导航栏

Bottom navigation bars are optimized for touch input when the user is holding a phone in portrait mode. On larger screen hand-held devices, like tablets and unfolded foldables, the navigation area must be accessible from the edge of the screen (navigation rail).
If you need to provide more screen real state for the content, hide the navigation area. Examples of this include:
  • Hiding the navigation bar when the user scrolls down and showing it again when the user scrolls up. The assumption is that when the user is scrolling down, they are consuming content but when scrolling up they are trying to navigate away from that content.
  • Hiding the navigation area when its content is distracting. For example, in camera previews or when the content is best displayed in full screen (such as a single photo screen).
When the detail screen is displayed full-screen on mobile, full-screen mode must be deactivated on larger screens.
Steps to migrate:
  • Locate the existing navigation bar.
  • Convert each item to a
    NavigationSuiteItem
    .
  • Identify whether the navigation bar's visibility changes. For example, if it is wrapped with an
    AnimatedContent
    or
    AnimatedVisibility
    composable. If so, follow the guidance in the "Control navigation area visibility".
  • Replace the container that held the navigation bar (often a
    Scaffold
    ) with
    NavigationSuiteScaffold
    from the Material 3 adaptive layouts library.
  • Supply the navigation items using the
    navigationItems
    parameter of
    NavigationSuiteScaffold
    .
底部导航栏针对用户竖屏手持手机的触摸操作进行了优化。在平板、展开的折叠屏等大屏手持设备上,导航区域必须能从屏幕边缘轻松访问(即导航轨)。
如果需要为内容提供更多屏幕空间,可以隐藏导航区域。例如:
  • 用户向下滚动时隐藏导航栏,向上滚动时重新显示。假设用户向下滚动时是在浏览内容,向上滚动时则是想要离开当前内容进行导航。
  • 当导航栏内容会分散注意力时将其隐藏。例如在相机预览界面,或内容适合全屏显示的场景(如单张照片展示界面)。
在移动设备上详情界面全屏显示时,大屏设备上必须取消全屏模式。
迁移步骤:
  • 找到现有的导航栏。
  • 将每个项转换为
    NavigationSuiteItem
  • 判断导航栏的可见性是否会变化。例如是否被
    AnimatedContent
    AnimatedVisibility
    组件包裹。如果是,请遵循「控制导航区域可见性」中的指导。
  • 将承载导航栏的容器(通常是
    Scaffold
    )替换为Material 3自适应布局库中的
    NavigationSuiteScaffold
  • 通过
    NavigationSuiteScaffold
    navigationItems
    参数提供导航项。

Step 2.1. Control navigation area visibility

步骤2.1. 控制导航区域可见性

If the navigation bar's visibility changes - it is hidden under certain scenarios or on certain screens - this behavior must be maintained with the adaptive navigation area. This is done using
NavigationSuiteScaffold
's
state
parameter.
Steps to migrate:
  • Identify the scenarios under which the navigation bar is hidden. This is usually done with a boolean variable for the visibility. It could be named something like
    isNavBarVisible
    or
    shouldShowNavBar
    .
  • Create an instance of
    NavigationSuiteScaffoldState
    using
    rememberNavigationSuiteScaffoldState()
    and pass it to
    NavigationSuiteScaffold
    .
  • When the navigation area visibility changes, use a
    LaunchedEffect
    to call
    show
    or
    hide
    on the
    NavigationSuiteScaffoldState
    .
For example:
kotlin
// Pass this variable to any composable that needs to control the navigation area visibility
var isNavBarVisible by remember { mutableStateOf(true) }
val scaffoldVisibilityState = rememberNavigationSuiteScaffoldState()

NavigationSuiteScaffold(
    navigationSuiteItems = navItems,
    state = scaffoldVisibilityState
) {
    // Main content
}

LaunchedEffect(isNavBarVisible){
    if (isNavBarVisible) {
        scaffoldVisibilityState.show()
    } else {
        scaffoldVisibilityState.hide()
    }
}
<br />
如果导航栏的可见性会变化——在某些场景或某些界面下会隐藏——这种行为必须在自适应导航区域中保留。这可以通过
NavigationSuiteScaffold
state
参数实现。
迁移步骤:
  • 确定导航栏隐藏的场景。通常会用一个布尔变量来控制可见性,变量名可能类似
    isNavBarVisible
    shouldShowNavBar
  • 使用
    rememberNavigationSuiteScaffoldState()
    创建
    NavigationSuiteScaffoldState
    实例,并将其传递给
    NavigationSuiteScaffold
  • 当导航区域可见性变化时,使用
    LaunchedEffect
    调用
    NavigationSuiteScaffoldState
    show
    hide
    方法。
示例:
kotlin
// 将此变量传递给任何需要控制导航区域可见性的组件
var isNavBarVisible by remember { mutableStateOf(true) }
val scaffoldVisibilityState = rememberNavigationSuiteScaffoldState()

NavigationSuiteScaffold(
    navigationSuiteItems = navItems,
    state = scaffoldVisibilityState
) {
    // 主内容
}

LaunchedEffect(isNavBarVisible){
    if (isNavBarVisible) {
        scaffoldVisibilityState.show()
    } else {
        scaffoldVisibilityState.hide()
    }
}
<br />

Step 3. Add multi-pane layouts using Navigation 3 Scenes

步骤3. 使用Navigation3 Scenes添加多窗格布局

Analyze the codebase looking for related screens - tapping on something in one screen opens another screen that shows information related to the first. There are two canonical screen relationships: list-detail and supporting pane.
IMPORTANT: You must use the Navigation 3
SceneStrategy
approach to implement multi-pane layouts. Do not use
ListDetailPaneScaffold
or
SupportingPaneScaffold
.
分析代码库,寻找存在关联的界面——点击某个界面中的内容会打开另一个显示相关信息的界面。典型的界面关系有两种:列表-详情和辅助窗格。
重要提示:必须使用Navigation3的
SceneStrategy
方法实现多窗格布局。请勿使用
ListDetailPaneScaffold
SupportingPaneScaffold

Step 3.1. List-detail

步骤3.1. 列表-详情

Identify the list and detail screens

识别列表和详情界面

List-detail layouts display a list of items (this is the list screen) and clicking on an item opens a new screen that shows more details about that item (the detail screen).
Typical usage includes productivity apps like email, notes, and messaging.
Unless requested explicitly, avoid this pattern when the detail content requires substantial screen space (e.g., images or media that benefits from a full-screen presentation).
列表-详情布局会显示一个项目列表(即列表界面),点击其中一项会打开一个显示该项目更多详情的新界面(即详情界面)。
典型应用场景包括邮件、笔记、消息等生产力类应用。
除非明确要求,否则当详情内容需要大量屏幕空间时(例如图片或媒体内容,全屏展示效果更佳),请避免使用此模式。

Add a Material list-detail SceneStrategy

添加Material列表-详情SceneStrategy

  • Add the
    androidx.compose.material3.adaptive:adaptive-navigation3
    library
  • Create an
    androidx.compose.material3.adaptive.navigation3.ListDetailSceneStrategy
    using
    rememberListDetailSceneStrategy
  • Pass the
    ListDetailSceneStrategy
    to
    NavDisplay
    using its
    sceneStrategies
    parameter
  • 添加
    androidx.compose.material3.adaptive:adaptive-navigation3
  • 使用
    rememberListDetailSceneStrategy
    创建
    androidx.compose.material3.adaptive.navigation3.ListDetailSceneStrategy
    实例
  • ListDetailSceneStrategy
    通过
    sceneStrategies
    参数传递给
    NavDisplay

Use metadata to identify the list and detail screens

使用元数据标识列表和详情界面

  • Add metadata using
    entry(metadata = ...)
    or
    NavEntry(metadata = ...)
    to the list entry using
    ListDetailSceneStrategy.listPane(detailPlaceholder = { <placeholder composable> })
    .
  • Use the
    detailPlaceholder
    parameter to add a placeholder on the detail screen when no list items are selected.
  • Add metadata to the detail entry using
    ListDetailSceneStrategy.detailPane()
    .
  • 使用
    entry(metadata = ...)
    NavEntry(metadata = ...)
    为列表条目添加元数据,调用
    ListDetailSceneStrategy.listPane(detailPlaceholder = { <占位符组件> })
  • 使用
    detailPlaceholder
    参数,在未选择列表项时为详情界面添加占位符。
  • 使用
    ListDetailSceneStrategy.detailPane()
    为详情条目添加元数据。

Important considerations

重要注意事项

  • When a detail screen displays its content full-screen on mobile (content fills the entire screen, bars or rails are hidden), full-screen mode must be deactivated if it's part of a list-detail layout.
  • Detail screens must not show a back arrow when on a list-detail layout.
For a reference implementation, check the Nav3 Material List Detail recipe.
  • 如果详情界面在移动设备上全屏显示(内容填满整个屏幕,隐藏栏或导航轨),当它属于列表-详情布局时必须取消全屏模式。
  • 在列表-详情布局中,详情界面不得显示返回箭头。
参考实现可查看Nav3 Material列表-详情示例

Step 3.2. Supporting pane

步骤3.2. 辅助窗格

Identify supporting pane screens where a main screen displays a single item, and selecting it opens a "supporting screen" with more details. The supporting screen complements the main screen and is shown in a supporting pane.
识别辅助窗格界面:主界面显示单个项目,选中后会打开一个展示更多详情的「辅助界面」,该辅助界面作为主界面的补充,显示在辅助窗格中。

Add a Material supporting pane
SceneStrategy

添加Material辅助窗格
SceneStrategy

  • If you haven't already, add the
    androidx.compose.material3.adaptive:adaptive-navigation3
    library
  • Create an
    androidx.compose.material3.adaptive.navigation3.SupportingPaneSceneStrategy
    using
    rememberSupportingPaneSceneStrategy
  • Pass the
    SupportingPaneSceneStrategy
    to
    NavDisplay
    using its
    sceneStrategies
    parameter
  • 如果尚未添加,引入
    androidx.compose.material3.adaptive:adaptive-navigation3
  • 使用
    rememberSupportingPaneSceneStrategy
    创建
    androidx.compose.material3.adaptive.navigation3.SupportingPaneSceneStrategy
    实例
  • SupportingPaneSceneStrategy
    通过
    sceneStrategies
    参数传递给
    NavDisplay

Use metadata to identify the main and supporting screens

使用元数据标识主界面和辅助界面

  • Add metadata using
    entry(metadata = ...)
    or
    NavEntry(metadata = ...)
    to the main entry using
    SupportingPaneSceneStrategy.mainPane()
  • Add metadata to the supporting entry using
    SupportingPaneSceneStrategy.supportingPane()
  • 使用
    entry(metadata = ...)
    NavEntry(metadata = ...)
    为主条目添加元数据,调用
    SupportingPaneSceneStrategy.mainPane()
  • 使用
    SupportingPaneSceneStrategy.supportingPane()
    为辅助条目添加元数据

Step 3.3. Run screenshot tests

步骤3.3. 运行截图测试

If you have made changes, record new reference files. Ask the user to visually verify that the new layouts are correct.
如果进行了修改,请录制新的参考文件。请用户直观验证新布局是否正确。

Step 4. Make vertical lists adaptive by changing the number of columns

步骤4. 通过调整列数实现垂直列表自适应

Step 4.1. Make lazy lists adaptive

步骤4.1. 实现懒加载列表自适应

Look for the following vertical list composables:
LazyColumn
,
LazyVerticalGrid
,
LazyVerticalStaggeredGrid
.
Steps to migrate:
  • Choose a suitable minimum width in dp for the column. It should be large enough so that item is clearly visible to the user.
  • For
    LazyColumn
    : change to a
    LazyVerticalGrid
    and follow the instruction below
  • For
    LazyVerticalGrid
    : change the
    columns
    parameter to use
    GridCells.Adaptive(<width>.dp)
  • For
    LazyVerticalStaggeredGrid
    : change the
    columns
    parameter to use
    StaggeredGridCells.Adaptive(<width>.dp)
寻找以下垂直列表组件:
LazyColumn
LazyVerticalGrid
LazyVerticalStaggeredGrid
迁移步骤:
  • 为列选择合适的最小宽度(单位dp)。宽度应足够大,确保项目对用户清晰可见。
  • 对于
    LazyColumn
    :改为使用
    LazyVerticalGrid
    ,并遵循以下说明
  • 对于
    LazyVerticalGrid
    :将
    columns
    参数改为使用
    GridCells.Adaptive(<width>.dp)
  • 对于
    LazyVerticalStaggeredGrid
    :将
    columns
    参数改为使用
    StaggeredGridCells.Adaptive(<width>.dp)

Step 4.2. Migrate non-lazy lists to Grid

步骤4.2. 将非懒加载列表迁移至Grid

WARNING: Grid is an experimental API available from Compose 1.11.0-beta01. Confirm with the user that they are happy to use an experimental API in their codebase.
Look for any
Column
that contains multiple items of the same type and replace it with
Grid
. Do not replace it with
LazyVerticalGrid
or any other lazy layout. Do not place
Grid
inside the existing
Column
. Completely replace it.
Grid
is configured by supplying a lambda (an extension function on
GridConfigurationScope
) to its
config
parameter. Inside the lambda,
constraints
provides the minimum and maximum dimensions of the grid container and can be used to change the number of rows and columns based on the available size. For example, the following code configures
Grid
such that when the available width is:
  • less than 800dp, a 2x4 grid is used
  • 800dp or more, a 4x2 grid is used
kotlin
Grid(
    config = {
        val maxWidthDp = constraints.maxWidth.toDp()
        val (cols, rows) = if (maxWidthDp < 800.dp){
            2 to 4
        } else{
            4 to 2
        }

        val gapSizeDp = 8.dp
        val cellSize = ((maxWidthDp - (gapSizeDp * (cols - 1))) / cols).coerceAtLeast(0.dp)
        repeat(cols) { column(cellSize) }
        repeat(rows) { row(cellSize) }
        gap(gapSizeDp)
    }
) { /** items **/ }
<br />
Grid
is an experimental API so add the
@OptIn(ExperimentalGridApi::class)
annotation to any function that uses it.
警告:Grid是实验性API,从Compose 1.11.0-beta01开始可用。请确认用户愿意在其代码库中使用实验性API。
寻找所有包含多个同类型项目的
Column
,将其替换为
Grid
。请勿替换为
LazyVerticalGrid
或其他懒加载布局。请勿将
Grid
放在现有
Column
内部,直接完全替换。
通过向
Grid
config
参数传入一个lambda(
GridConfigurationScope
的扩展函数)来配置它。在lambda内部,
constraints
提供了网格容器的最小和最大尺寸,可根据可用尺寸更改行列数。例如,以下代码配置
Grid
,使其在可用宽度为:
  • 小于800dp时,使用2列4行的网格
  • 800dp或更大时,使用4列2行的网格
kotlin
Grid(
    config = {
        val maxWidthDp = constraints.maxWidth.toDp()
        val (cols, rows) = if (maxWidthDp < 800.dp){
            2 to 4
        } else{
            4 to 2
        }

        val gapSizeDp = 8.dp
        val cellSize = ((maxWidthDp - (gapSizeDp * (cols - 1))) / cols).coerceAtLeast(0.dp)
        repeat(cols) { column(cellSize) }
        repeat(rows) { row(cellSize) }
        gap(gapSizeDp)
    }
) { /** 项目 **/ }
<br />
由于
Grid
是实验性API,请在所有使用它的函数上添加
@OptIn(ExperimentalGridApi::class)
注解。

Step 5: Hide App Bars when scrolling

步骤5:滚动时隐藏应用栏

In an app with multiple top-level destinations, each screen must manage its own app bar state independently. There are two main scroll behaviors:
  • exitUntilCollapsedScrollBehavior
    : Hides on scroll down, stays hidden while you scroll up until you reach the very top (0 offset).
  • enterAlwaysScrollBehavior
    : Hides on scroll down, shows immediately on scroll up.
在包含多个顶级目标页面的应用中,每个界面必须独立管理自己的应用栏状态。主要有两种滚动行为:
  • exitUntilCollapsedScrollBehavior
    :向下滚动时隐藏,向上滚动时保持隐藏,直到滚动到最顶部(偏移量为0)才显示。
  • enterAlwaysScrollBehavior
    :向下滚动时隐藏,向上滚动时立即显示。

Final step: Build and test

最终步骤:构建并测试

Build the app and run the local tests. If the project has screenshot tests, run them but DO NOT update the reference images. Prompt the user to do this after they have viewed the screenshot diffs.
构建应用并运行本地测试。如果项目包含截图测试,请运行测试但不要更新参考图片。提示用户查看截图差异后再进行更新。

Additional documentation for experimental adaptive APIs

实验性自适应API的补充文档

The following APIs are available from Compose 1.11.0-beta01.
以下API从Compose 1.11.0-beta01开始可用。

FlexBox

FlexBox

Check the FlexBox documentation:
  • Overview
  • Get started - setup
  • Set container behavior
  • Set item behavior
查看FlexBox文档:
  • 概述
  • 快速入门 - 设置
  • 设置容器行为
  • 设置项目行为

MediaQuery

MediaQuery

Check the MediaQuery documentation when you need to query the device's screen size, pointer precision, keyboard type, whether it has cameras or microphones, and other device capabilities.
当需要查询设备屏幕尺寸、指针精度、键盘类型、是否配备摄像头或麦克风及其他设备功能时,请查看MediaQuery文档

Grid

Grid

Check the Grid documentation when you need to display a fixed number of items in a grid layout:
  • Overview
  • Get started - setup
  • Set container properties
  • Set item properties
当需要在网格布局中显示固定数量的项目时,请查看Grid文档:
  • 概述
  • 快速入门 - 设置
  • 设置容器属性
  • 设置项目属性