Điều hướng trên giao diện người dùng thích ứng

Điều hướng là quá trình tương tác với giao diện người dùng của ứng dụng để truy cập vào đích đến nội dung của ứng dụng. Nguyên tắc điều hướng của Android giúp bạn xây dựng việc điều hướng trong ứng dụng một cách nhất quán và trực quan.

Giao diện người dùng thích ứng cung cấp đích đến nội dung thích ứng và thường bao gồm nhiều loại phần tử điều hướng để đáp ứng các thay đổi về kích thước hiển thị, ví dụ: thanh điều hướng ở cuối màn hình nhỏ, dải điều hướng trên màn hình trung bình hoặc ngăn điều hướng cố định trên màn hình lớn, nhưng giao diện người dùng thích ứng vẫn phải tuân thủ các nguyên tắc điều hướng.

Thành phần điều hướng Jetpack triển khai các nguyên tắc điều hướng và có thể được dùng để hỗ trợ phát triển các ứng dụng có giao diện người dùng thích ứng.

Hình 1. Màn hình mở rộng, trung bình và nhỏ gọn có ngăn điều hướng, dải điều hướng và thanh dưới cùng màn hình.

Điều hướng giao diện người dùng thích ứng

Kích thước của cửa sổ hiển thị mà một ứng dụng hiện diện ảnh hưởng đến tính tiện dụng và khả năng hữu dụng. Các lớp kích thước cửa sổ cho phép bạn xác định các phần tử điều hướng thích hợp (chẳng hạn như thanh điều hướng, ray hoặc ngăn) và đặt các phần tử đó ở nơi người dùng có thể truy cập dễ dàng nhất. Trong nguyên tắc về bố cục của Material Design, các phần tử điều hướng chiếm một không gian ổn định ở cạnh trên của màn hình và có thể di chuyển sang cạnh dưới cùng khi chiều rộng của ứng dụng nhỏ gọn. Lựa chọn phần tử điều hướng phụ thuộc phần lớn vào kích thước của cửa sổ ứng dụng và số mục mà phần tử phải chứa.

Lớp kích thước cửa sổ Một vài mục Nhiều mục
chiều rộng thu gọn thanh điều hướng ở dưới cùng ngăn điều hướng (cạnh trên hoặc cuối)
chiều rộng trung bình ray điều hướng ngăn điều hướng (cạnh trên)
chiều rộng được mở rộng dải điều hướng ngăn điều hướng cố định (cạnh trên)

Trong bố cục dựa trên chế độ xem, tệp tài nguyên bố cục có thể đáp ứng điều kiện theo điểm ngắt lớp kích thước cửa sổ để sử dụng các phần tử điều hướng riêng cho từng kích thước màn hình. Jetpack Compose có thể dùng các điểm ngắt do API lớp kích thước cửa sổ cung cấp để xác định phần tử điều hướng phù hợp nhất với cửa sổ ứng dụng theo phương thức lập trình.

Số lượt xem

<!-- res/layout/main_activity.xml -->

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        ... />

    <!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>

<!-- res/layout-w600dp/main_activity.xml -->

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.navigationrail.NavigationRailView
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        ... />

    <!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>

<!-- res/layout-w1240dp/main_activity.xml -->

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.navigation.NavigationView
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        ... />

    <!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>

Compose

// This method should be run inside a Composable function.
val widthSizeClass = calculateWindowSizeClass(this).widthSizeClass
// You can get the height of the current window by invoking heightSizeClass instead.

@Composable
fun MyApp(widthSizeClass: WindowWidthSizeClass) {
    // Select a navigation element based on window size.
    when (widthSizeClass) {
        WindowWidthSizeClass.Compact -> { CompactScreen() }
        WindowWidthSizeClass.Medium -> { MediumScreen() }
        WindowWidthSizeClass.Expanded -> { ExpandedScreen() }
    }
}

@Composable
fun CompactScreen() {
    Scaffold(bottomBar = {
                BottomNavigation {
                    icons.forEach { item ->
                        BottomNavigationItem(
                            selected = isSelected,
                            onClick = { ... },
                            icon = { ... })
                    }
                }
            }
        ) {
        // Other content
    }
}

@Composable
fun MediumScreen() {
    Row(modifier = Modifier.fillMaxSize()) {
        NavigationRail {
            icons.forEach { item ->
                NavigationRailItem(
                    selected = isSelected,
                    onClick = { ... },
                    icon = { ... })
            }
        }
        // Other content
    }
}

@Composable
fun ExpandedScreen() {
    PermanentNavigationDrawer(
        drawerContent = {
            icons.forEach { item ->
                NavigationDrawerItem(
                    icon = { ... },
                    label = { ... },
                    selected = isSelected,
                    onClick = { ... }
                )
            }
        },
        content = {
            // Other content
        }
    )
}

Đích đến của nội dung thích ứng

Trong giao diện người dùng thích ứng, bố cục của từng đích đến nội dung phải thích ứng với sự thay đổi về kích thước cửa sổ. Ứng dụng của bạn có thể điều chỉnh khoảng cách bố cục, phần tử vị trí, thêm/xóa nội dung hoặc thay đổi các phần tử giao diện người dùng, bao gồm cả các phần tử điều hướng. (Xem Di chuyển giao diện người dùng sang bố cục thích ứngTạo bố cục thích ứng.)

Khi mỗi đích đến riêng lẻ xử lý thành công các sự kiện đổi kích thước, các thay đổi sẽ được tách riêng khỏi giao diện người dùng. Phần còn lại của trạng thái ứng dụng bao gồm cả trạng thái điều hướng sẽ không bị ảnh hưởng.

Hoạt động điều hướng không nên xuất hiện như tác dụng phụ của việc thay đổi kích thước cửa sổ. Không tạo đích đến nội dung chỉ để phù hợp với kích thước cửa sổ khác nhau. Ví dụ: không tạo các đích đến khác nhau cho nội dung cho các màn hình khác nhau của thiết bị có thể gập.

Điều hướng như tác dụng phụ của việc thay đổi kích thước cửa sổ sẽ có các vấn đề sau đây:

  • Đích đến cũ (đối với kích thước cửa sổ trước đó) có thể hiển thị tạm thời trước khi điều hướng đến đích đến mới
  • Để duy trì khả năng đảo ngược (ví dụ: khi thiết bị gập và mở) thì phải buộc điều hướng cho mỗi kích thước cửa sổ
  • Việc duy trì trạng thái ứng dụng giữa các đích đến có thể khó khăn do điều hướng có thể phá hủy trạng thái khi bật ngăn xếp lui

Ngoài ra, ứng dụng của bạn thậm chí có thể không chạy ở nền trước trong khi các thay đổi về kích thước cửa sổ đang diễn ra. Bố cục của ứng dụng có thể yêu cầu nhiều không gian hơn ứng dụng trên nền trước. Và khi người dùng quay lại ứng dụng của bạn, hướng và kích thước cửa sổ đều có thể đã thay đổi.

Nếu ứng dụng của bạn yêu cầu đích đến nội dung duy nhất dựa trên kích thước cửa sổ, hãy cân nhắc kết hợp các đích đến liên quan thành một đích đến duy nhất bao gồm các bố cục thay thế.

Đích đến của nội dung có bố cục thay thế

Là một phần của thiết kế đáp ứng, một đích đến điều hướng duy nhất có thể có các bố cục thay thế tuỳ thuộc vào kích thước cửa sổ ứng dụng. Bố cục nào cũng chiếm toàn bộ cửa sổ, nhưng mỗi bố cục lại xuất hiện cho từng kích thước cửa sổ riêng.

Một ví dụ chuẩn là chế độ xem chi tiết danh sách (list-detail). Đối với các kích thước cửa sổ nhỏ, ứng dụng sẽ hiện một bố cục nội dung cho danh sách và một bố cục cho thông tin chi tiết. Ban đầu, việc điều hướng tới đích đến của chế độ xem chi tiết danh sách chỉ cho thấy bố cục danh sách. Khi một mục trong danh sách được chọn, ứng dụng sẽ hiện bố cục chi tiết thay cho danh sách. Khi bạn chọn nút điều khiển quay lại, bố cục danh sách sẽ hiển thị thay thế cho thông tin chi tiết. Tuy nhiên, đối với kích thước cửa sổ mở rộng, bố cục danh sách và chi tiết sẽ xuất hiện bên cạnh nhau.

Số lượt xem

SlidingPaneLayout cho phép tạo một đích đến điều hướng duy nhất cho thấy hai ngăn nội dung cạnh nhau trên màn hình lớn, nhưng chỉ cho thấy một ngăn trên thiết bị màn hình nhỏ chẳng hạn như điện thoại.

<!-- Single destination for list and detail. -->

<navigation ...>

    <!-- Fragment that implements SlidingPaneLayout. -->
    <fragment
        android:id="@+id/article_two_pane"
        android:name="com.example.app.ListDetailTwoPaneFragment" />

    <!-- Other destinations... -->
</navigation>

Hãy xem nội dung Tạo bố cục hai ngăn để nắm được chi tiết về cách triển khai bố cục chi tiết danh sách bằng SlidingPaneLayout.

Compose

Trong Compose, chế độ xem chi tiết danh sách có thể được triển khai bằng cách kết hợp các thành phần kết hợp (composable) thay thế trong một tuyến (route) duy nhất sử dụng các lớp kích thước cửa sổ để phát ra thành phần kết hợp thích hợp cho từng lớp kích thước.

Tuyến là đường dẫn điều hướng tới đích đến nội dung, thường là một thành phần kết hợp, nhưng cũng có thể là thành phần kết hợp thay thế. Logic kinh doanh xác định thành phần kết hợp thay thế nào được hiển thị. Thành phần kết hợp lấp đầy cửa sổ ứng dụng bất kể phiên bản thay thế nào được hiển thị.

Ví dụ về chế độ xem chi tiết danh sách bao gồm ba thành phần kết hợp:

/* Displays a list of items. */
@Composable
fun ListOfItems(
    onItemSelected: (String) -> Unit,
) { /*...*/ }

/* Displays the detail for an item. */
@Composable
fun ItemDetail(
    selectedItemId: String? = null,
) { /*...*/ }

/* Displays a list and the detail for an item side by side. */
@Composable
fun ListAndDetail(
    selectedItemId: String? = null,
    onItemSelected: (String) -> Unit,
) {
  Row {
    ListOfItems(onItemSelected = onItemSelected)
    ItemDetail(selectedItemId = selectedItemId)
  }
}

Một tuyến điều hướng đơn cấp quyền truy cập vào chế độ xem chi tiết danh sách:

@Composable
fun ListDetailRoute(
    // Indicates that the display size is represented by the expanded window size class.
    isExpandedWindowSize: Boolean = false,
    // Identifies the item selected from the list. If null, a item has not been selected.
    selectedItemId: String?,
) {
  if (isExpandedWindowSize) {
    ListAndDetail(
      selectedItemId = selectedItemId,
      /*...*/
    )
  } else {
    // If the display size cannot accommodate both the list and the item detail,
    // show one of them based on the user's focus.
    if (selectedItemId != null) {
      ItemDetail(
        selectedItemId = selectedItemId,
        /*...*/
      )
    } else {
      ListOfItems(/*...*/)
    }
  }
}

ListDetailRoute (đích đến điều hướng) xác định thành phần kết hợp nào trogn 3 thành phần kết hợp có thể phát ra: ListAndDetail cho kích thước cửa sổ mở rộng; ListOfItems hoặc ItemDetail cho kích thước cửa sổ thu gọn, tuỳ thuộc việc một mục trong danh sách đã được chọn hay chưa.

Tuyến bao gồm trong NavHost, ví dụ:

NavHost(navController = navController, startDestination = "listDetailRoute") {
  composable("listDetailRoute") {
    ListDetailRoute(isExpandedWindowSize = isExpandedWindowSize,
                    selectedItemId = selectedItemId)
  }
  /*...*/
}

Bạn có thể cung cấp đối số isExpandedWindowSize bằng cách kiểm tra WindowMetrics của ứng dụng.

Đối số selectedItemId có thể được cung cấp bởi một ViewModel có chức năng duy trì trạng thái trên tất cả các kích thước cửa sổ. Khi người dùng chọn một mục trong danh sách, biến trạng thái selectedItemId sẽ được cập nhật:

class ListDetailViewModel : ViewModel() {

  data class ListDetailUiState(
      val selectedItemId: String? = null,
  )

  private val viewModelState = MutableStateFlow(ListDetailUiState())

  fun onItemSelected(itemId: String) {
    viewModelState.update {
      it.copy(selectedItemId = itemId)
    }
  }
}

val listDetailViewModel = ListDetailViewModel()

@Composable
fun ListDetailRoute(
    isExpandedWindowSize: Boolean = false,
    selectedItemId: String?,
    onItemSelected: (String) -> Unit = { listDetailViewModel.onItemSelected(it) },
) {
  if (isExpandedWindowSize) {
    ListAndDetail(
      selectedItemId = selectedItemId,
      onItemSelected = onItemSelected,
      /*...*/
    )
  } else {
    if (selectedItemId != null) {
      ItemDetail(
        selectedItemId = selectedItemId,
        /*...*/
      )
    } else {
      ListOfItems(
        onItemSelected = onItemSelected,
        /*...*/
      )
    }
  }
}

Tuyến cũng bao gồm một BackHandler tuỳ chỉnh khi thành phần kết hợp chi tiết của mục chiếm toàn bộ cửa sổ ứng dụng:

class ListDetailViewModel : ViewModel() {

  data class ListDetailUiState(
      val selectedItemId: String? = null,
  )

  private val viewModelState = MutableStateFlow(ListDetailUiState())

  fun onItemSelected(itemId: String) {
    viewModelState.update {
      it.copy(selectedItemId = itemId)
    }
  }

  fun onItemBackPress() {
    viewModelState.update {
      it.copy(selectedItemId = null)
    }
  }
}

val listDetailViewModel = ListDetailViewModel()

@Composable
fun ListDetailRoute(
    isExpandedWindowSize: Boolean = false,
    selectedItemId: String?,
    onItemSelected: (String) -> Unit = { listDetailViewModel.onItemSelected(it) },
    onItemBackPress: () -> Unit = { listDetailViewModel.onItemBackPress() },
) {
  if (isExpandedWindowSize) {
    ListAndDetail(
      selectedItemId = selectedItemId,
      onItemSelected = onItemSelected,
      /*...*/
    )
  } else {
    if (selectedItemId != null) {
      ItemDetail(
        selectedItemId = selectedItemId,
        /*...*/
      )
      BackHandler {
        onItemBackPress()
      }
    } else {
      ListOfItems(
        onItemSelected = onItemSelected,
        /*...*/
      )
    }
  }
}

Việc kết hợp trạng thái ứng dụng từ một ViewModel với thông tin lớp kích thước cửa sổ giúp việc chọn thành phần kết hợp trở thành logic đơn giản. Bằng cách duy trì luồng dữ liệu một chiều, ứng dụng có thể sử dụng trọn vẹn không gian hiển thị có sẵn trong khi vẫn duy trì được trạng thái của ứng dụng.

Để triển khai chế độ xem chi tiết danh sách đầy đủ trong Compose, xem mẫu JetNews trên GitHub.

Biểu đồ điều hướng

Để mang đến trải nghiệm nhất quán cho người dùng trên mọi kích thước thiết bị hoặc cửa sổ, hãy sử dụng một biểu đồ điều hướng duy nhất, trong đó bố cục của mỗi đích đến nội dung đều có tính thích ứng.

Nếu sử dụng biểu đồ điều hướng riêng cho từng lớp kích thước cửa sổ, mỗi khi ứng dụng chuyển đổi từ một lớp kích thước này sang lớp kích thước khác, thì bạn phải xác định đích đến hiện tại của người dùng trong các sơ đồ khác, tạo ngăn xếp lui và điều chỉnh thông tin trạng thái khác biệt giữa các sơ đồ.

Lưu trữ điều hướng lồng

Ứng dụng của bạn có thể bao gồm một đích đến nội dung có các đích đến nội dung riêng. Ví dụ: trong chế độ xem chi tiết danh sách, ngăn chi tiết mục có thể bao gồm các thành phần trên giao diện người dùng điều hướng đến nội dung thay thế cho chi tiết của mục.

Để triển khai loại hình điều hướng phụ này, ngăn chi tiết có thể là một lưu trữ điều hướng được lồng với sơ đồ điều hướng riêng và chỉ định các đích đến được truy cập qua ngăn chi tiết:

Số lượt xem

<!-- layout/two_pane_fragment.xml -->

<androidx.slidingpanelayout.widget.SlidingPaneLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/sliding_pane_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/list_pane"
        android:layout_width="280dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"/>

    <!-- Detail pane is a nested navigation host. Its graph is not connected
         to the main graph that contains the two_pane_fragment destination. -->
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/detail_pane"
        android:layout_width="300dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/detail_pane_nav_graph" />
</androidx.slidingpanelayout.widget.SlidingPaneLayout>

Compose

NavHost(navController = navController, startDestination = "listDetailRoute") {
  composable("listDetailRoute") {
    ListDetailRoute(isExpandedWindowSize = isExpandedWindowSize,
                    selectedItemId = selectedItemId)
  }
  navigation(startDestination = "itemSubdetail1", route = "itemSubDetail") {
    composable("itemSubdetail1") { ItemSubdetail1(/*...*/) }
    composable("itemSubdetail2") { ItemSubdetail2(/*...*/) }
    composable("itemSubdetail3") { ItemSubdetail3(/*...*/) }
  }
  /*...*/
}

Biểu đồ này khác với biểu đồ điều hướng lồng nhau vì biểu đồ điều hướng của NavHost lồng nhau không kết nối với biểu đồ điều hướng chính; nghĩa là bạn không thể chuyển trực tiếp từ các đích đến trong một biểu đồ sang các đích đến ở một biểu đồ khác.

Để biết thêm thông tin, vui lòng xem phần nội dung Biểu đồ điều hướng lồng nhauĐiều hướng bằng Compose.

Trạng thái lưu giữ

Để cung cấp đích đến nội dung có tính thích ứng, ứng dụng của bạn phải duy trì trạng thái khi xoay hoặc gập thiết bị hoặc đổi kích thước cửa sổ ứng dụng. Theo mặc định, các thay đổi về cấu hình, chẳng hạn như thay đổi về hoạt động, phân mảnh, phân cấp chế độ xem và thành phần kết hợp (composable) của ứng dụng. Nên lưu trạng thái giao diện người dùng bằng cách sử dụng ViewModel hoặc rememberSaveable vốn vẫn tồn tại khi bạn thay đổi cấu hình. (Xem phần Lưu trạng thái giao diện người dùng Trạng thái và Jetpack Compose.)

Các thay đổi về kích thước phải đảo ngược được – ví dụ: khi người dùng xoay thiết bị, sau đó xoay trở lại.

Bố cục thích ứng có thể cho thấy những phần nội dung riêng biệt tuỳ theo kích thước cửa sổ, và do đó bố cục thích ứng thường cần lưu trạng thái bổ sung liên quan đến nội dung, ngay cả khi trạng thái đó không áp dụng cho kích thước cửa sổ hiện tại. Ví dụ: có thể một bố cục có không gian trình bày một tiện ích cuộn bổ sung chỉ ở chiều rộng cửa sổ lớn. Nếu sự kiện đổi kích thước khiến chiều rộng của cửa sổ trở nên quá nhỏ, thì tiện ích sẽ bị ẩn. Khi ứng dụng trở lại kích thước trước đó, tiện ích cuộn này sẽ xuất hiện lại và vị trí cuộn ban đầu sẽ được khôi phục.

Phạm vi ViewModel

Hướng dẫn dành cho nhà phát triển Di chuyển sang thành phần Điều hướng đề xuất cấu trúc hoạt động đơn trong đó các đích đến được triển khai dưới dạng các phân mảnh và mô hình dữ liệu của chúng được triển khai sử dụng ViewModel.

ViewModel luôn thuộc phạm vi một vòng đời và sau khi vòng đời đó kết thúc vĩnh viễn, ViewModel sẽ bị xóa và có thể bị loại bỏ. Vòng đời chứa ViewModel và do đó ViewModel có thể được chia sẻ rộng rãi phụ thuộc vào việc ủy quyền thuộc tính nào được sử dụng để lấy ViewModel.

Trong trường hợp đơn giản nhất, mỗi đích điều hướng đều là một phân mảnh duy nhất có trạng thái giao diện người dùng hoàn toàn tách biệt; và do đó, mỗi phân mảnh có thể sử dụng ủy quyền thuộc tính viewModels() để có được ViewModel xác định phạm vi theo phân mảnh đó.

Để chia sẻ trạng thái giao diện người dùng giữa các phân mảnh, hãy đặt phạm vi ViewModel cho hoạt động bằng cách gọi activityViewModels() trong các phân mảnh (giá trị tương đương với hoạt động chỉ là viewModels()). Việc này cho phép hoạt động và mọi phân mảnh đính kèm với hoạt động này chia sẻ phiên bản ViewModel. Tuy nhiên, trong một cấu trúc hoạt động đơn, phạm vi ViewModel này cũng tồn tại lâu như ứng dụng, cũng giống như ViewModel tồn tại trong bộ nhớ ngay cả khi không có mảnh nào đang sử dụng nó.

Giả sử biểu đồ điều hướng của bạn có một chuỗi các đích đến phân mảnh đại diện cho một luồng thanh toán và trạng thái hiện tại cho toàn bộ trải nghiệm thanh toán nằm trong một ViewModel được chia sẻ giữa các phân mảnh. Việc đưa ViewModel vào hoạt động không chỉ quá rộng mà còn thực sự làm phát sinh một vấn đề khác: nếu người dùng trải qua quy trình thanh toán cho một đơn đặt hàng và sau đó thực hiện lại đơn đặt hàng thứ hai, thì cả hai đơn đặt hàng đều sử dụng cùng một phiên bản thanh toán ViewModel. Trước lần thanh toán đơn đặt hàng thứ hai, bạn sẽ phải tự tay xoá dữ liệu của đơn đặt hàng đầu tiên và mọi sai sót đều có thể gây tiêu tốn cho người dùng.

Thay vào đó, hãy đặt phạm vi ViewModel vào một biểu đồ điều hướng trong NavController hiện tại. Tạo một biểu đồ điều hướng lồng nhau để đóng gói các đích đến thuộc quy trình thanh toán. Khi ấy trong mỗi đích đến phân mảnh đó, hãy sử dụng ủy quyền thuộc tính navGraphViewModels() và chuyển mã của biểu đồ điều hướng để lấy ViewModel được chia sẻ. Điều này đảm bảo rằng khi người dùng thoát khỏi quy trình thanh toán và biểu đồ điều hướng được lồng nằm ngoài phạm vi, phiên bản tương ứng của ViewModel sẽ được loại bỏ và không được dùng cho lần thanh toán tiếp theo.

Phạm vi Ủy quyền thuộc tính Có thể chia sẻ ViewModel với
Mảnh Fragment.viewModels() Chỉ phân mảnh hiện tại
Hoạt động Activity.viewModels()

Fragment.activityViewModels()

Hoạt động và tất cả các phân mảnh đính kèm với hoạt động đó
Biểu đồ điều hướng Fragment.navGraphViewModels() Tất cả các phân mảnh trong cùng một biểu đồ điều hướng

Lưu ý nếu đang sử dụng lưu trữ điều hướng được lồng (xem ở trên), thì các đích đến trong lưu trữ đó không thể chia sẻ ViewModel với các đích đến ngoài lưu trữ khi sử dụng navGraphViewModels() vì các biểu đồ chưa được kết nối. Trong trường hợp này, bạn có thể dùng phạm vi của hoạt động để thay thế.

Trạng thái được nâng

Trong mục Compose, bạn có thể lưu giữ trạng thái trong quá trình thay đổi kích thước cửa sổ bằng cách nâng trạng thái. Bằng cách nâng trạng thái của các thành phần kết hợp lên vị trí cao hơn trong cây thành phần, trạng thái có thể được bảo toàn ngay cả khi các thành phần kết hợp không còn xuất hiện nữa.

Trong phần Compose của Đích đến nội dung với bố cục thay thế ở trên, chúng ta đã nâng trạng thái của thành phần kết hợp chế độ xem chi tiết danh sách thành ListDetailRoute để trạng thái đó được bảo toàn bất kể thành phần kết hợp nào xuất hiện:

@Composable
fun ListDetailRoute(
    // Indicates that the display size is represented by the expanded window size class.
    isExpandedWindowSize: Boolean = false,
    // Identifies the item selected from the list. If null, a item has not been selected.
    selectedItemId: String?,
) { /*...*/ }