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.
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.
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.
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.