Menangani perubahan konfigurasi

UI dan navigasi responsif

Untuk memberikan pengalaman navigasi terbaik kepada pengguna, Anda harus menyediakan UI navigasi yang disesuaikan dengan lebar, tinggi, dan lebar terkecil perangkat pengguna. Anda mungkin ingin menggunakan panel aplikasi bawah, panel navigasi yang selalu ada atau dapat diciutkan, kolom samping, atau mungkin sesuatu yang benar-benar baru berdasarkan ruang layar yang tersedia dan gaya unik aplikasi Anda.

contoh kolom samping, panel navigasi, dan panel aplikasi bawah
Gambar 1. Contoh kolom samping, panel navigasi, dan panel aplikasi bawah.

Desain material panduan arsitektur produk memberikan konteks dan pertimbangan tambahan untuk membuat UI responsif, yaitu UI yang secara dinamis menyesuaikan dengan perubahan lingkungan. Beberapa contoh perubahan lingkungan mencakup penyesuaian lebar, tinggi, orientasi, dan preferensi bahasa pengguna. Properti lingkungan ini secara keseluruhan disebut sebagai konfigurasi perangkat.

Jika satu atau beberapa properti ini berubah saat runtime, Android OS akan merespons dengan menghancurkan lalu membuat ulang aktivitas dan fragmen aplikasi Anda. Oleh karena itu, hal terbaik yang dapat Anda lakukan untuk mendukung UI responsif di Android adalah memastikan bahwa Anda menggunakan pengontrol kualitas konfigurasi resource yang sesuai dan menghindari penggunaan ukuran tata letak hard code.

Menerapkan navigasi global dalam UI responsif

Menerapkan navigasi global sebagai bagian dari UI responsif dimulai dengan aktivitas yang menghosting grafik navigasi Anda. Untuk contoh praktis, lihat Codelab Navigation. Codelab menggunakan NavigationView untuk menampilkan menu navigasi, seperti yang ditunjukkan pada gambar 2. Saat dijalankan di perangkat yang merender dengan lebar minimal 960 dp, NavigationView ini selalu ada di layar.

codelab navigasi menggunakan tampilan navigasi yang selalu terlihat ketika lebar perangkat minimal 960 dp
Gambar 2. Codelab Navigasi menggunakan NavigationView untuk menampilkan menu navigasi.

Ukuran dan orientasi perangkat lainnya secara dinamis beralih antara DrawerLayout atau BottomNavigationView sesuai kebutuhan.

tampilan navigasi bawah dan tata letak panel samping, yang digunakan untuk menu navigasi sesuai kebutuhan dalam tata letak perangkat yang lebih kecil
Gambar 3. Codelab Navigasi menggunakan BottomNavigationView dan DrawerLayout untuk menampilkan menu navigasi pada perangkat yang lebih kecil.

Anda dapat menerapkan perilaku ini dengan membuat tiga tata letak yang berbeda, dengan setiap tata letak menentukan elemen navigasi dan hierarki tampilan yang diinginkan berdasarkan konfigurasi perangkat saat ini.

Konfigurasi tempat setiap tata letak diterapkan ditentukan oleh struktur direktori tempat file tata letak ditempatkan. Misalnya, file tata letak NavigationView ditemukan di direktori 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>

Tampilan navigasi bawah ditemukan di direktori 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>

Tata letak panel samping ditemukan di direktori res/layout. Gunakan direktori ini untuk tata letak default tanpa penentu khusus konfigurasi:

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

Android mengikuti urutan prioritas saat menentukan resource yang akan diterapkan. Khusus untuk contoh ini, -w960dp (atau lebar yang tersedia >= 960dp) lebih diutamakan daripada -h470dp (atau tinggi yang tersedia >= 470). Jika konfigurasi perangkat tidak cocok dengan salah satu kondisi tersebut, resource tata letak default (res/layout/navigation_activity.xml) akan digunakan.

Dalam menangani peristiwa navigasi, Anda hanya perlu menyiapkan peristiwa yang sesuai dengan widget yang ada saat ini, seperti yang ditunjukkan dalam contoh berikut.

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

   }
}

Jika konfigurasi perangkat berubah, kecuali jika secara eksplisit dikonfigurasi sebaliknya, Android akan menghapus aktivitas dari konfigurasi sebelumnya bersama dengan tampilan terkait. Kemudian, aktivitas akan dibuat ulang dengan resource yang didesain untuk konfigurasi baru. Aktivitas tersebut, yang dihancurkan dan dibuat ulang, kemudian secara otomatis menyambungkan elemen navigasi global yang tepat di onCreate().

Pertimbangkan alternatif untuk tata letak tampilan terpisah

Tata letak tampilan terpisah, atau tata letak master/detail, pernah menjadi cara yang sangat populer dan direkomendasikan untuk mendesain tablet dan perangkat layar besar lainnya.

Sejak diperkenalkannya tablet Android, ekosistem perangkat telah berkembang dengan pesat. Salah satu faktor yang sangat memengaruhi ruang desain untuk perangkat layar besar adalah pengenalan mode multi-aplikasi, terutama jendela bentuk bebas yang sepenuhnya dapat diubah ukurannya, seperti pada perangkat ChromeOS. Hal ini memberikan penekanan yang jauh lebih tinggi di setiap layar aplikasi yang responsif, bukan mengubah struktur navigasi berdasarkan ukuran layar.

Meskipun Anda dapat menerapkan antarmuka tata letak tampilan terpisah menggunakan library Navigasi, Anda harus mempertimbangkan alternatif lain.

Nama tujuan

Jika Anda memberikan nama tujuan pada grafik menggunakan atribut android:label, pastikan untuk selalu menggunakan nilai resource agar konten Anda tetap dapat dilokalkan.

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

Dengan nilai resource, tujuan Anda akan otomatis menerapkan resource yang paling sesuai setiap kali konfigurasi Anda berubah.