1. Sebelum memulai
Perangkat Android hadir dalam berbagai bentuk dan ukuran sehingga Anda perlu membuat tata letak aplikasi yang mengakomodasi berbagai ukuran layar untuk membuatnya tersedia bagi pengguna dan perangkat berbeda dengan satu Paket Android (APK) atau Android App Bundle (AAB). Untuk melakukannya, Anda harus menentukan aplikasi dengan tata letak responsif dan adaptif, bukan menentukannya dengan dimensi statis yang mengasumsikan ukuran dan rasio aspek layar tertentu. Tata letak adaptif berubah berdasarkan ruang layar yang tersedia.
Codelab ini mengajarkan dasar-dasar cara membangun UI adaptif dan menyesuaikan aplikasi yang menampilkan daftar olahraga serta detail setiap olahraga untuk mendukung perangkat layar besar. Aplikasi olahraga terdiri dari tiga layar: utama, favorit, dan setelan. Layar utama menampilkan daftar olahraga dan placeholder untuk berita saat Anda memilih olahraga dari daftar. Layar favorit dan setelan juga menampilkan teks placeholder. Anda memilih item terkait di menu navigasi bawah untuk beralih layar.
Aplikasi dimulai dengan masalah tata letak ini pada layar besar:
- Anda tidak dapat menggunakannya dalam orientasi potret.
- Aplikasi menampilkan banyak ruang kosong pada layar besar.
- Aplikasi selalu menampilkan menu navigasi bawah pada layar besar.
Anda akan membuat aplikasi menjadi adaptif agar aplikasi:
- Mendukung orientasi lanskap dan potret.
- Menampilkan daftar olahraga dan berita tentang setiap olahraga secara berdampingan jika ada cukup ruang horizontal untuk melakukannya.
- Menampilkan komponen navigasi sesuai dengan pedoman Desain Material.
Aplikasi ini adalah aplikasi satu aktivitas dengan beberapa fragmen. Anda menggunakan file berikut:
- File
AndroidManifest.xml, yang memberikan metadata tentang aplikasi olahraga. - File
MainActivity.kt, yang berisi kode yang dihasilkan dengan fileactivity_main.xml, anotasioverride, classenumyang mewakili ukuran class jendela lebar, dan definisi metode untuk mengambil class ukuran jendela lebar untuk jendela aplikasi. Menu navigasi bawah diinisialisasi saat aktivitas dibuat. - File
activity_main.xml, yang menentukan tata letak default untuk aktivitasMain. - File
layout-sw600dp/activity_main.xml, yang menentukan tata letak alternatif untuk aktivitasMain. Tata letak alternatif akan efektif jika lebar jendela aplikasi lebih besar dari atau sama dengan nilai600dp. Konten sama dengan tata letak default di titik awal. - File
SportsListFragment.kt, yang berisi implementasi daftar olahraga dan navigasi kembali kustom. - File
fragment_sports_list.xml, yang menentukan tata letak daftar olahraga. - File
navigation_menu.xml, yang menentukan item menu navigasi bawah.
Gambar 1. Aplikasi olahraga mendukung berbagai ukuran jendela dengan satu APK atau AAB.
Prasyarat
- Pengetahuan dasar tentang pengembangan UI berbasis tampilan
- Pengalaman dengan sintaksis Kotlin, termasuk fungsi lambda
- Penyelesaian codelab dasar-dasar Jetpack Compose
Yang akan Anda pelajari
- Cara mendukung perubahan konfigurasi.
- Cara menambahkan tata letak alternatif dengan lebih sedikit modifikasi kode.
- Cara mengimplementasikan UI detail daftar yang berperilaku berbeda untuk berbagai ukuran jendela.
Yang akan Anda bangun
Aplikasi Android yang mendukung:
- Orientasi perangkat lanskap
- Tablet, desktop, dan perangkat seluler
- Perilaku detail daftar untuk berbagai ukuran layar
Yang akan Anda butuhkan
- Android Studio Bumblebee | 2021.1.1 atau yang lebih tinggi
- Tablet Android atau emulator
2. Memulai persiapan
Download kode untuk codelab ini dan siapkan project:
- Dari command line, clone kode di repositori GitHub ini:
$ git clone https://github.com/android/add-adaptive-layouts
- Di Android Studio, buka project
AddingAdaptiveLayout. Project ini dibuat di beberapa cabang git:
- Cabang
mainberisi kode awal untuk project ini. Anda membuat perubahan pada cabang ini untuk menyelesaikan codelab. - Cabang
endberisi solusi untuk codelab ini.
3. Memigrasikan komponen navigasi atas ke Compose
Aplikasi olahraga menggunakan menu navigasi bawah sebagai komponen navigasi atas. Komponen navigasi ini diimplementasikan dengan class BottomNavigationView. Di bagian ini, Anda akan memigrasikan komponen navigasi atas ke Compose.
Menampilkan konten resource menu yang terkait sebagai class tertutup
- Buat class
MenuItemtertutup untuk menampilkan konten filenavigation_menu.xml, lalu teruskan parametericonId, parameterlabelId, dan parameterdestinationId. - Tambahkan objek
Home, objekFavorite, dan objekSettingssebagai subclass yang sesuai dengan tujuan Anda.
MenuItem.kt
sealed class MenuItem(
// Resource ID of the icon for the menu item
@DrawableRes val iconId: Int,
// Resource ID of the label text for the menu item
@StringRes val labelId: Int,
// ID of a destination to navigate users
@IdRes val destinationId: Int
) {
object Home: MenuItem(
R.drawable.ic_baseline_home_24,
R.string.home,
R.id.SportsListFragment
)
object Favorites: MenuItem(
R.drawable.ic_baseline_favorite_24,
R.string.favorites,
R.id.FavoritesFragment
)
object Settings: MenuItem(
R.drawable.ic_baseline_settings_24,
R.string.settings,
R.id.SettingsFragment
)
}
Mengimplementasikan menu navigasi bawah sebagai fungsi composable
- Tentukan fungsi
BottomNavigationBarcomposable yang menggunakan tiga parameter ini: objekmenuItemsyang disetel ke nilaiList<MenuItem>, objekmodifieryang disetel ke nilaiModifier = Modifier, dan objekonMenuSelectedyang disetel ke fungsi lambda(MenuItem) -> Unit = {}. - Dalam isi fungsi
BottomNavigationBarcomposable, panggil fungsiNavigationBar()yang menggunakan parametermodifier. - Dalam fungsi lambda yang diteruskan ke fungsi
NavigationBar(), panggil metodeforEach()pada parametermenuItems, lalu panggil fungsiNavigationBarItem()dalam fungsi lambda yang disetel ke panggilan metodeforeach(). - Teruskan fungsi
NavigationBarItem()parameterselectedyang disetel ke nilaifalse; parameteronClickyang disetel ke fungsi lambda yang berisi fungsionMenuSelecteddengan parameterMenuItem; parametericonyang disetel ke fungsi lambda yang berisi fungsiIconyang menggunakan parameterpainter = painterResource(id = menuItem.iconId)dan parametercontentDescription = null; dan parameterlabelyang disetel ke fungsi lambda yang berisi fungsiTextyang menggunakan parameter(text = stringResource(id = menuItem.labelId).
Navigation.kt
@Composable
fun BottomNavigationBar(
menuItems: List<MenuItem>,
modifier: Modifier = Modifier,
onMenuSelected: (MenuItem) -> Unit = {}
) {
NavigationBar(modifier = modifier) {
menuItems.forEach { menuItem ->
NavigationBarItem(
selected = false,
onClick = { onMenuSelected(menuItem) },
icon = {
Icon(
painter = painterResource(id = menuItem.iconId),
contentDescription = null)
},
label = { Text(text = stringResource(id = menuItem.labelId))}
)
}
}
}
Mengganti elemen BottomNavigationView dengan elemen ComposeView di file resource tata letak
- Dalam file
activity_main.xml, ganti elemenBottomNavigationViewdengan elemenComposeView. Tindakan ini akan menyematkan komponen navigasi bawah ke tata letak UI berbasis tampilan dengan Compose.
activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment_content_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/top_navigation"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/top_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
Mengintegrasikan menu navigasi bawah berbasis Compose dengan tata letak UI berbasis tampilan
- Di file
MainActivity.kt, tentukan variabelnavigationMenuItemsyang ditetapkan ke daftar objekMenuItem. ObjekMenuItemsmuncul di menu navigasi bawah dalam urutan daftar. - Panggil fungsi
BottomNavigationBar()untuk menyematkan menu navigasi bawah di objekComposeView. - Buka tujuan yang terkait dengan item yang dipilih oleh pengguna dalam fungsi callback yang diteruskan ke fungsi
BottomNavigationBar.
MainActivity.kt
val navigationMenuItems = listOf(
MenuItem.Home,
MenuItem.Favorites,
MenuItem.Settings
)
binding.navigation.setContent {
MaterialTheme {
BottomNavigationBar(menuItems = navigationMenuItems){ menuItem ->
navController.navigate(screen.destinationId)
}
}
}
4. Mendukung orientasi horizontal
Jika aplikasi Anda mendukung perangkat layar besar, aplikasi tersebut diharapkan dapat mendukung orientasi lanskap dan orientasi potret. Saat ini, aplikasi Anda hanya memiliki satu aktivitas: aktivitas MainActivity.
Orientasi tampilan aktivitas pada perangkat ditetapkan di file AndroidManifest.xml dengan atribut android:screenOrientation, yang ditetapkan ke nilai portrait.
Buat aplikasi Anda mendukung orientasi lanskap:
- Tetapkan atribut
android:screenOrientationke nilaifullUser. Dengan konfigurasi ini, pengguna dapat mengunci orientasi layarnya. Orientasi ditentukan berdasarkan sensor orientasi perangkat untuk setiap empat orientasi.
AndroidManifest.xml
<activity
android:name=".MainActivity"
android:exported="true"
android:screenOrientation="fullUser">

Gambar 2. Aplikasi berjalan dalam orientasi horizontal setelah Anda memperbarui file AndroidManifest.xml.
5. Class ukuran jendela
Ini adalah nilai titik henti sementara yang membantu mengklasifikasikan ukuran jendela ke dalam class ukuran yang telah ditentukan sebelumnya—rapat, sedang, dan diperluas—dengan ukuran jendela mentah yang tersedia untuk aplikasi Anda. Anda akan menggunakan class ukuran ini saat mendesain, mengembangkan, dan menguji tata letak adaptif.
Lebar dan tinggi yang tersedia dipartisi satu per satu sehingga aplikasi Anda selalu dikaitkan dengan dua class ukuran jendela: class ukuran jendela lebar dan class ukuran jendela tinggi.

Gambar 3. Class ukuran jendela lebar dan titik henti sementara yang terkait.

Gambar 4. Class ukuran jendela tinggi dan titik henti sementara yang terkait.
Class ukuran jendela mewakili ukuran jendela saat ini dari aplikasi Anda. Dengan kata lain, Anda tidak dapat menentukan class ukuran jendela berdasarkan ukuran perangkat fisik. Meskipun aplikasi Anda berjalan di perangkat yang sama, class ukuran jendela terkait akan berubah berdasarkan konfigurasi, seperti saat Anda menjalankan aplikasi dalam mode layar terpisah. Hal ini memiliki dua konsekuensi penting:
- Perangkat fisik tidak menjamin class ukuran jendela tertentu.
- Class ukuran jendela dapat berubah sepanjang masa penggunaan aplikasi Anda.
Setelah menambahkan tata letak adaptif ke aplikasi, Anda akan menguji aplikasi di semua rentang ukuran jendela, terutama pada class ukuran jendela rapat, sedang, dan diperluas. Pengujian untuk setiap class ukuran jendela diperlukan, tetapi tidak memadai dalam banyak kasus. Penting untuk menguji aplikasi pada berbagai ukuran jendela guna memastikan bahwa UI diskalakan dengan benar. Untuk mengetahui informasi selengkapnya, lihat Tata letak layar besar dan Kualitas aplikasi layar besar.
6. Menata letak panel daftar dan detail secara berdampingan di layar besar
UI detail daftar mungkin perlu berperilaku berbeda sesuai dengan class ukuran jendela lebar saat ini. Jika class ukuran jendela lebar sedang atau diperluas dikaitkan dengan aplikasi Anda, itu berarti aplikasi tersebut dapat memiliki cukup ruang untuk menampilkan panel daftar dan panel detail secara berdampingan, sehingga pengguna dapat melihat daftar item dan detail item yang dipilih tanpa transisi layar. Namun, panel ini bisa menjadi terlalu padat pada layar yang lebih kecil, yang akan lebih baik jika menampilkan panel satu per satu dengan panel daftar ditampilkan di awal. Panel detail menampilkan detail item yang dipilih saat pengguna mengetuk item dalam daftar. Class SlidingPaneLayout mengelola logika untuk menentukan mana dari dua pengalaman pengguna ini yang sesuai dengan ukuran jendela saat ini.
Mengonfigurasi tata letak panel daftar
Class SlidingPaneLayout adalah komponen UI berbasis tampilan. Anda akan mengubah file resource tata letak untuk panel daftar, yang merupakan file fragment_sports_list.xml dalam codelab ini.
Class SlidingPaneLayout menggunakan dua elemen turunan. Atribut lebar dan berat setiap elemen turunan adalah faktor utama class SlidingPaneLayout untuk menentukan apakah jendela cukup besar untuk menampilkan dua tampilan secara berdampingan. Jika tidak, daftar layar penuh akan diganti dengan UI detail layar penuh. Nilai berat merujuk pada ukuran dua panel secara proporsional jika ukuran jendela lebih besar dari persyaratan minimum untuk menampilkan panel secara berdampingan.
Class SlidingPaneLayout telah diterapkan ke file fragment_sports_list.xml. Panel daftar dikonfigurasi agar memiliki lebar 1280dp. Inilah alasan panel daftar dan detail tidak muncul secara berdampingan.
Buat panel ditampilkan secara berdampingan saat layar lebih besar dari lebar 580dp:
- Tetapkan
RecyclerViewke lebar280dp.
fragment_sports_list.xml
<androidx.slidingpanelayout.widget.SlidingPaneLayout
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/sliding_pane_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SportsListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="280dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:clipToPadding="false"
android:padding="8dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<androidx.fragment.app.FragmentContainerView
android:layout_height="match_parent"
android:layout_width="300dp"
android:layout_weight="1"
android:id="@+id/detail_container"
android:name="com.example.android.sports.NewsDetailsFragment"/>
</androidx.slidingpanelayout.widget.SlidingPaneLayout>
Gambar 5. Panel daftar dan detail muncul berdampingan setelah Anda memperbarui file resource tata letak.
Menukar panel detail
Sekarang panel daftar dan detail muncul berdampingan ketika class ukuran jendela lebar sedang atau diperluas dikaitkan dengan aplikasi Anda. Namun, layar sepenuhnya bertransisi ke panel detail terjadi ketika pengguna memilih item dari panel daftar.

Gambar 6. Layar bertransisi ke panel detail setelah Anda memilih olahraga dari daftar.
Masalah ini disebabkan oleh navigasi yang dipicu saat pengguna memilih item dari panel daftar. Kode yang relevan tersedia di file SportsListFragment.kt.
SportsListFragment.kt
val adapter = SportsAdapter {
sportsViewModel.updateCurrentSport(it)
// Navigate to the details pane.
val action =
SportsListFragmentDirections.actionSportsListFragmentToNewsFragment()
this.findNavController().navigate(action)
}
Pastikan layar hanya sepenuhnya bertransisi ke panel detail saat tidak ada cukup ruang untuk menampilkan panel daftar dan detail secara berdampingan:
- Dalam variabel fungsi
adapter, tambahkan pernyataanifyang memeriksa apakah atributisSlidabledari classSlidingPaneLayoutbernilai benar dan atributisOpendari classSlidingPaneLayoutbernilai salah.
SportsListFragment.kt
val adapter = SportsAdapter {
sportsViewModel.updateCurrentSport(it)
if(slidingPaneLayout.isSlidable && !slidingPaneLayout.isOpen){
// Navigate to the details pane.
val action =
SportsListFragmentDirections.actionSportsListFragmentToNewsFragment()
this.findNavController().navigate(action)
}
}
7. Memilih komponen navigasi yang tepat berdasarkan class ukuran jendela lebar
Desain Material mengharapkan aplikasi Anda memilih komponen secara adaptif. Di bagian ini, Anda memilih komponen navigasi untuk menu navigasi atas berdasarkan class ukuran jendela lebar saat ini. Tabel ini menjelaskan komponen navigasi yang diharapkan untuk setiap class ukuran jendela:
Class ukuran jendela lebar | Komponen navigasi |
Rapat | Menu navigasi bawah |
Sedang | Kolom samping navigasi |
Diperluas | Panel navigasi permanen |
Mengimplementasikan kolom samping navigasi
- Buat fungsi
NavRail()composable yang menggunakan tiga parameter: objekmenuItemsyang disetel ke nilaiList<MenuItem>, objekmodifieryang disetel ke nilaiModifier, dan fungsi lambdaonMenuSelected. - Dalam isi fungsi, panggil fungsi
NavigationRail()yang menggunakan objekmodifiersebagai parameter. - Panggil fungsi
NavigationRailItem()untuk setiap objekMenuItemdi objekmenuItemsseperti yang Anda lakukan dengan fungsiNavigationBarItemdi fungsiBottomNavigationBar().
Mengimplementasikan panel navigasi permanen
- Buat fungsi
NavigationDrawer()composable yang menggunakan tiga parameter ini: objekmenuItemsyang disetel ke nilaiList<MenuItem>, objekmodifieryang disetel ke nilaiModifier, dan fungsi lambdaonMenuSelected. - Dalam isi fungsi, panggil fungsi
Column()yang menggunakan objekmodifiersebagai parameter. - Panggil fungsi
Row()untuk setiap objekMenuItempada objekmenuItems. - Dalam isi fungsi
Row(), tambahkan labelicondantextseperti yang Anda lakukan pada fungsiNavigationBarItemdi fungsiBottomNavigationBar().
Memilih komponen navigasi yang tepat berdasarkan class ukuran jendela lebar
- Untuk mengambil class ukuran jendela lebar saat ini, panggil fungsi
rememberWidthSizeClass()di dalam fungsi composable yang diteruskan ke metodesetContent()pada objekComposeView. - Buat cabang bersyarat untuk memilih komponen navigasi berdasarkan class ukuran jendela lebar yang diambil, lalu panggil komponen yang dipilih.
- Teruskan objek
Modifierke fungsiNavigationDraweruntuk menentukan lebarnya sebagai nilai256dp.
ActivityMain.kt
binding.navigation.setContent {
MaterialTheme {
when(rememberWidthSizeClass()){
WidthSizeClass.COMPACT ->
BottomNavigationBar(menuItems = navigationMenuItems){ menuItem ->
navController.navigate(screen.destinationId)
}
WidthSizeClass.MEDIUM ->
NavRail(menuItems = navigationMenuItems){ menuItem ->
navController.navigate(screen.destinationId)
}
WidthSizeClass.EXPANDED ->
NavigationDrawer(
menuItems = navigationMenuItems,
modifier = Modifier.width(256.dp)
) { menuItem ->
navController.navigate(screen.destinationId)
}
}
}
}
Menempatkan komponen navigasi di posisi yang tepat
Sekarang aplikasi Anda dapat memilih komponen navigasi yang tepat berdasarkan class ukuran jendela lebar saat ini, tetapi komponen yang dipilih tidak dapat ditempatkan seperti yang Anda harapkan. Hal ini karena elemen ComposeView ditempatkan di bawah elemen FragmentViewContainer.
Update resource tata letak alternatif untuk class MainActivity:
- Buka file
layout-sw600dp/activity_main.xml. - Perbarui batasan untuk menata letak elemen
ComposeViewdan elemenFragmentContainersecara horizontal.
layout-sw600dp/activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment_content_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintLeft_toRightOf="@+id/top_navigation"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:navGraph="@navigation/nav_graph" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/top_navigation"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/top_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
Setelah modifikasi, aplikasi memilih komponen navigasi yang tepat berdasarkan class ukuran jendela lebar yang terkait. Screenshot pertama menampilkan layar untuk class ukuran jendela lebar sedang. Screenshot kedua menampilkan layar untuk class ukuran jendela lebar yang diluaskan.

Gambar 7. Layar untuk class ukuran jendela lebar sedang dan diluaskan.
8. Selamat
Selamat! Anda telah menyelesaikan codelab ini dan mempelajari cara menambahkan tata letak adaptif ke aplikasi Android berbasis Tampilan dengan Compose. Dengan demikian, Anda telah mempelajari class SlidingPaneLayout, class ukuran jendela, dan pemilihan komponen navigasi berdasarkan class ukuran jendela lebar.
