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
, classenum
yang 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
main
berisi kode awal untuk project ini. Anda membuat perubahan pada cabang ini untuk menyelesaikan codelab. - Cabang
end
berisi 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
MenuItem
tertutup untuk menampilkan konten filenavigation_menu.xml
, lalu teruskan parametericonId
, parameterlabelId
, dan parameterdestinationId
. - Tambahkan objek
Home
, objekFavorite
, dan objekSettings
sebagai 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
BottomNavigationBar
composable yang menggunakan tiga parameter ini: objekmenuItems
yang disetel ke nilaiList<MenuItem>
, objekmodifier
yang disetel ke nilaiModifier = Modifier
, dan objekonMenuSelected
yang disetel ke fungsi lambda(MenuItem) -> Unit = {}
. - Dalam isi fungsi
BottomNavigationBar
composable, 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()
parameterselected
yang disetel ke nilaifalse
; parameteronClick
yang disetel ke fungsi lambda yang berisi fungsionMenuSelected
dengan parameterMenuItem
; parametericon
yang disetel ke fungsi lambda yang berisi fungsiIcon
yang menggunakan parameterpainter = painterResource(id = menuItem.iconId)
dan parametercontentDescription = null
; dan parameterlabel
yang disetel ke fungsi lambda yang berisi fungsiText
yang 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 elemenBottomNavigationView
dengan 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 variabelnavigationMenuItems
yang ditetapkan ke daftar objekMenuItem
. ObjekMenuItems
muncul 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:screenOrientation
ke 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
RecyclerView
ke 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 pernyataanif
yang memeriksa apakah atributisSlidable
dari classSlidingPaneLayout
bernilai benar dan atributisOpen
dari classSlidingPaneLayout
bernilai 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 | Navigasi bawah |
Sedang | Kolom samping navigasi |
Diperluas | Panel navigasi permanen |
Mengimplementasikan kolom samping navigasi
- Buat fungsi
NavRail()
composable yang menggunakan tiga parameter: objekmenuItems
yang disetel ke nilaiList<MenuItem>
, objekmodifier
yang disetel ke nilaiModifier
, dan fungsi lambdaonMenuSelected
. - Dalam isi fungsi, panggil fungsi
NavigationRail()
yang menggunakan objekmodifier
sebagai parameter. - Panggil fungsi
NavigationRailItem()
untuk setiap objekMenuItem
di objekmenuItems
seperti yang Anda lakukan dengan fungsiNavigationBarItem
di fungsiBottomNavigationBar()
.
Mengimplementasikan panel navigasi permanen
- Buat fungsi
NavigationDrawer()
composable yang menggunakan tiga parameter ini: objekmenuItems
yang disetel ke nilaiList<MenuItem>
, objekmodifier
yang disetel ke nilaiModifier
, dan fungsi lambdaonMenuSelected
. - Dalam isi fungsi, panggil fungsi
Column()
yang menggunakan objekmodifier
sebagai parameter. - Panggil fungsi
Row()
untuk setiap objekMenuItem
pada objekmenuItems
. - Dalam isi fungsi
Row()
, tambahkan labelicon
dantext
seperti yang Anda lakukan pada fungsiNavigationBarItem
di 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
Modifier
ke fungsiNavigationDrawer
untuk 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
ComposeView
dan elemenFragmentContainer
secara 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.