Xử lý các thay đổi về cấu hình

Tính năng điều hướng và giao diện người dùng thích ứng

Để mang lại trải nghiệm điều hướng tốt nhất có thể cho người dùng, bạn nên thiết kế giao diện điều hướng phù hợp với chiều rộng, chiều cao và chiều rộng nhỏ nhất của thiết bị người dùng sử dụng. Bạn nên sử dụng thanh ứng dụng ở dưới cùng, ngăn điều hướng luôn hiển thị hoặc có thể thu gọn, thanh điều hướng dọc hay có thể là thứ gì đó hoàn toàn mới tuỳ theo không gian màn hình có sẵn và phong cách riêng của ứng dụng.

ví dụ về thanh điều hướng dọc, ngăn điều hướng và thanh ứng dụng ở dưới cùng
Hình 1. Ví dụ về thanh điều hướng dọc, ngăn điều hướng và thanh ứng dụng ở dưới cùng.

Hướng dẫn Material Design về cấu trúc sản phẩm sẽ cung cấp thêm ngữ cảnh và những lưu ý khi dựng giao diện người dùng thích ứng – tức là giao diện người dùng có khả năng điều chỉnh linh hoạt theo các thay đổi về môi trường. Một vài ví dụ cho các thay đổi về môi trường: việc điều chỉnh chiều rộng, chiều cao, hướng và lựa chọn ưu tiên của người dùng về ngôn ngữ. Các thuộc tính môi trường này được gọi chung là cấu hình của thiết bị.

Khi một hoặc nhiều thuộc tính trong số này thay đổi trong thời gian chạy, hệ điều hành Android sẽ phản hồi bằng cách huỷ rồi tái tạo các hoạt động và mảnh của ứng dụng. Do đó, điều tốt nhất bạn có thể làm để hỗ trợ giao diện người dùng thích ứng trên Android là đảm bảo rằng bạn đang sử dụng chuỗi định tính cấu hình tài nguyên nếu thích hợp và tránh cố định giá trị kích thước bố cục trong mã.

Triển khai điều hướng chung trong giao diện người dùng thích ứng

Việc triển khai điều hướng chung trong khuôn khổ giao diện người dùng thích ứng bắt đầu từ hoạt động lưu trữ biểu đồ điều hướng của bạn. Để xem ví dụ thực tế, hãy tham khảo Lớp học lập trình về điều hướng. Lớp học lập trình sử dụng NavigationView để hiển thị trình đơn điều hướng, như minh họa trong hình 2. Khi chạy trên thiết bị hiển thị ở chiều rộng tối thiểu là 960 dp, NavigationView này luôn hiện trên màn hình.

lớp học lập trình điều hướng sử dụng thành phần hiển thị điều hướng luôn hiển thị
            khi thiết bị có chiều rộng tối thiểu là 960 dp
Hình 2. Lớp học lập trình điều hướng sử dụng NavigationView để hiển thị trình đơn điều hướng.

Các kích thước và hướng thiết bị khác sẽ linh động chuyển đổi giữa DrawerLayout hoặc BottomNavigationView nếu cần.

bottomnavigationview và drawerlayout, được sử dụng cho trình đơn điều hướng
            nếu cần trong bố cục thiết bị nhỏ hơn
Hình 3. Lớp học lập trình về điều hướng sử dụng BottomNavigationViewDrawerLayout để hiển thị trình đơn điều hướng trên các thiết bị nhỏ hơn.

Bạn có thể triển khai hành vi này bằng cách tạo 3 bố cục, trong đó, mỗi bố cục xác định các phần tử điều hướng và hệ phân cấp thành phần hiển thị mong muốn căn cứ theo cấu hình thiết bị hiện tại.

Cấu hình mà mỗi bố cục áp dụng được xác định theo cấu trúc thư mục chứa tệp bố cục. Ví dụ: bạn có thể tìm thấy tệp bố cục NavigationView trong thư mục res/layout-w960dp.

<!-- res/layout-w960dp/navigation_activity.xml -->
<RelativeLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.example.android.codelabs.navigation.MainActivity">

   <com.google.android.material.navigation.NavigationView
       android:id="@+id/nav_view"
       android:layout_width="wrap_content"
       android:layout_height="match_parent"
       android:layout_alignParentStart="true"
       app:elevation="0dp"
       app:headerLayout="@layout/nav_view_header"
       app:menu="@menu/nav_drawer_menu" />

   <View
       android:layout_width="1dp"
       android:layout_height="match_parent"
       android:layout_toEndOf="@id/nav_view"
       android:background="?android:attr/listDivider" />

   <androidx.appcompat.widget.Toolbar
       android:id="@+id/toolbar"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_alignParentTop="true"
       android:layout_toEndOf="@id/nav_view"
       android:background="@color/colorPrimary"
       android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />

   <androidx.fragment.app.FragmentContainerView
       android:id="@+id/my_nav_host_fragment"
       android:name="androidx.navigation.fragment.NavHostFragment"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:layout_below="@id/toolbar"
       android:layout_toEndOf="@id/nav_view"
       app:defaultNavHost="true"
       app:navGraph="@navigation/mobile_navigation" />
</RelativeLayout>

Bạn có thể xem thành phần hiển thị thanh điều hướng ở dưới cùng trong thư mục res/layout-h470dp:

<!-- res/layout-h470dp/navigation_activity.xml -->
<LinearLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical"
   tools:context="com.example.android.codelabs.navigation.MainActivity">

   <androidx.appcompat.widget.Toolbar
       android:id="@+id/toolbar"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:background="@color/colorPrimary"
       android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />

   <androidx.fragment.app.FragmentContainerView
       android:id="@+id/my_nav_host_fragment"
       android:name="androidx.navigation.fragment.NavHostFragment"
       android:layout_width="match_parent"
       android:layout_height="0dp"
       android:layout_weight="1"
       app:defaultNavHost="true"
       app:navGraph="@navigation/mobile_navigation" />

   <com.google.android.material.bottomnavigation.BottomNavigationView
       android:id="@+id/bottom_nav_view"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       app:menu="@menu/bottom_nav_menu" />
</LinearLayout>

Bạn có thể xem bố cục ngăn điều hướng trong thư mục res/layout. Hãy sử dụng thư mục này cho bố cục mặc định không có bộ hạn định cấu hình cụ thể:

<!-- res/layout/navigation_activity.xml -->
<androidx.drawerlayout.widget.DrawerLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/drawer_layout"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.example.android.codelabs.navigation.MainActivity">

   <LinearLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:orientation="vertical">

       <androidx.appcompat.widget.Toolbar
           android:id="@+id/toolbar"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:background="@color/colorPrimary"
           android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />

       <androidx.fragment.app.FragmentContainerView
           android:id="@+id/my_nav_host_fragment"
           android:name="androidx.navigation.fragment.NavHostFragment"
           android:layout_width="match_parent"
           android:layout_height="match_parent"
           app:defaultNavHost="true"
           app:navGraph="@navigation/mobile_navigation" />
   </LinearLayout>

   <com.google.android.material.navigation.NavigationView
       android:id="@+id/nav_view"
       android:layout_width="wrap_content"
       android:layout_height="match_parent"
       android:layout_gravity="start"
       app:menu="@menu/nav_drawer_menu" />
</androidx.drawerlayout.widget.DrawerLayout>

Khi xác định tài nguyên nào cần áp dụng, Android tuân theo thứ tự ưu tiên. Riêng trong ví dụ này, -w960dp (hoặc chiều rộng có thể dùng >= 960 dp) được ưu tiên hơn -h470dp (hay chiều cao có thể dùng >= 470). Nếu cấu hình thiết bị không khớp với điều kiện nào trong các điều kiện đó, thì tài nguyên bố cục mặc định (res/layout/navigation_activity.xml) sẽ được sử dụng.

Trong quá trình xử lý sự kiện điều hướng, bạn chỉ cần liên kết các sự kiện tương ứng với tiện ích đang hiển thị, như minh họa trong ví dụ sau.

Kotlin

class MainActivity : AppCompatActivity() {

   private lateinit var appBarConfiguration : AppBarConfiguration

   override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.navigation_activity)
      val drawerLayout : DrawerLayout? = findViewById(R.id.drawer_layout)
      appBarConfiguration = AppBarConfiguration(
                  setOf(R.id.home_dest, R.id.deeplink_dest),
                  drawerLayout)

      ...

      // Initialize the app bar with the navigation drawer if present.
      // If the drawerLayout is not null here, a Navigation button will be added
      // to the app bar whenever the user is on a top-level destination.
      setupActionBarWithNavController(navController, appBarConfig)

      // Initialize the NavigationView if it is present,
      // so that clicking an item takes
      // the user to the appropriate destination.
      val sideNavView = findViewById<NavigationView>(R.id.nav_view)
      sideNavView?.setupWithNavController(navController)

      // Initialize the BottomNavigationView if it is present,
      // so that clicking an item takes
      // the user to the appropriate destination.
      val bottomNav = findViewById<BottomNavigationView>(R.id.bottom_nav_view)
      bottomNav?.setupWithNavController(navController)

      ...
    }

    ...
}

Java

public class MainActivity extends AppCompatActivity {

   private AppBarConfiguration appBarConfiguration;

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.navigation_activity);
       NavHostFragment host = (NavHostFragment) getSupportFragmentManager()
               .findFragmentById(R.id.my_nav_host_fragment);
       NavController navController = host.getNavController();

       DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
       appBarConfiguration = new AppBarConfiguration.Builder(
               R.id.home_dest, R.id.deeplink_dest)
               .setDrawerLayout(drawerLayout)
               .build();

       // Initialize the app bar with the navigation drawer if present.
       // If the drawerLayout is not null here, a Navigation button will be added to
       // the app bar whenever the user is on a top-level destination.
       NavigationUI.setupActionBarWithNavController(
               this, navController, appBarConfiguration);


       // Initialize the NavigationView if it is present,
       // so that clicking an item takes
       // the user to the appropriate destination.
       NavigationView sideNavView = findViewById(R.id.nav_view);
       if(sideNavView != null) {
           NavigationUI.setupWithNavController(sideNavView, navController);
       }

       // Initialize the BottomNavigationView if it is present,
       // so that clicking an item takes
       // the user to the appropriate destination.
       BottomNavigationView bottomNav = findViewById(R.id.bottom_nav_view);
       if(bottomNav != null) {
           NavigationUI.setupWithNavController(bottomNav, navController);
       }

   }
}

Nếu cấu hình thiết bị thay đổi, Android sẽ huỷ bỏ hoạt động từ cấu hình trước cùng với các thành phần hiển thị liên kết của cấu hình đó, trừ khi được định cấu hình rõ ràng theo cách khác. Sau đó, Android sẽ tái tạo hoạt động bằng tài nguyên được thiết kế cho cấu hình mới. Sau khi bị huỷ và tái tạo, hoạt động tự động liên kết phần tử điều hướng chung phù hợp trong onCreate().

Cân nhắc các phương án thay thế cho bố cục thành phần hiển thị phân tách

Bố cục thành phần hiển thị phân tách hay bố cục chính/chi tiết từng là cách rất phổ biến và được khuyên dùng để thiết kế cho máy tính bảng và thiết bị màn hình lớn khác.

Kể từ khi máy tính bảng Android ra đời, hệ sinh thái thiết bị đã phát triển nhanh chóng. Có một yếu tố ảnh hưởng đáng kể đến không gian thiết kế dành cho thiết bị có màn hình lớn, đó là sự ra mắt của chế độ nhiều cửa sổ, đặc biệt là cửa sổ dạng tự do hoàn toàn có thể đổi kích thước, ví dụ như cửa sổ trên thiết bị ChromeOS. Điều này nhấn mạnh hơn vào việc mỗi màn hình trong ứng dụng của bạn đều nên có khả năng thích ứng thay vì thay đổi cấu trúc điều hướng theo kích thước màn hình.

Mặc dù có thể triển khai giao diện bố cục thành phần hiển thị phân tách nhờ Thư viện điều hướng, bạn vẫn nên xem xét các giải pháp thay thế khác.

Tên đích đến

Nếu bạn cung cấp tên đích đến trong biểu đồ bằng thuộc tính android:label, hãy đảm bảo luôn sử dụng các giá trị tài nguyên sao cho nội dung của bạn vẫn có thể được bản địa hóa.

<navigation ...>
    <fragment
        android:id="@+id/my_dest"
        android:name="com.example.MyFragment"
        android:label="@string/my_dest_label"
        tools:layout="@layout/my_fragment" />
    ...

Bằng giá trị tài nguyên, đích đến sẽ tự động áp dụng các tài nguyên phù hợp nhất bất cứ khi nào cấu hình của bạn thay đổi.