Mendukung berbagai ukuran layar

Dukungan untuk berbagai ukuran layar memungkinkan akses ke aplikasi Anda oleh beragam perangkat dan jumlah pengguna terbanyak.

Untuk mendukung sebanyak mungkin ukuran layar, desain tata letak aplikasi Anda agar responsif dan adaptif. Tata letak responsif/adaptif memberikan pengalaman pengguna yang dioptimalkan terlepas dari ukuran layar pengguna sehingga memungkinkan aplikasi Anda mengakomodasi ponsel, tablet, perangkat foldable, perangkat ChromeOS, orientasi potret dan lanskap, serta konfigurasi yang dapat diubah ukurannya seperti mode multi-aplikasi.

Tata letak responsif/adaptif berubah berdasarkan ruang tampilan yang tersedia. Perubahan mulai dari penyesuaian tata letak kecil yang mengisi ruang (desain responsif) hingga benar-benar mengganti satu tata letak dengan tata letak lain sehingga aplikasi Anda dapat mengakomodasi berbagai ukuran tampilan secara optimal (desain adaptif).

Sebagai toolkit UI deklaratif, Jetpack Compose ideal untuk mendesain dan mengimplementasikan tata letak yang berubah secara dinamis untuk merender konten secara berbeda di berbagai ukuran tampilan.

Membuat perubahan tata letak yang besar untuk composable tingkat layar eksplisit

Saat menggunakan Compose untuk menata letak seluruh aplikasi, composable tingkat aplikasi dan tingkat layar akan menempati semua ruang yang diberikan untuk merender aplikasi Anda. Pada tingkat ini dalam desain Anda, mungkin perlu mengubah tata letak layar secara keseluruhan untuk memanfaatkan layar yang lebih besar.

Hindari menggunakan nilai fisik dan hardware untuk membuat keputusan tata letak. Anda mungkin ingin membuat keputusan berdasarkan nilai nyata yang tetap (Apakah perangkat tersebut adalah tablet? Apakah layar fisik memiliki rasio aspek tertentu?), tetapi jawaban atas pertanyaan ini mungkin tidak berguna untuk menentukan ruang yang dapat digunakan UI Anda.

Diagram yang menunjukkan beberapa faktor bentuk perangkat yang berbeda, termasuk ponsel, perangkat foldable, tablet, dan laptop.
Gambar 1. Faktor bentuk ponsel, perangkat foldable, tablet, dan laptop

Pada tablet, aplikasi mungkin berjalan dalam mode multi-aplikasi, yang berarti aplikasi mungkin memisahkan layar dengan aplikasi lain. Di ChromeOS, aplikasi mungkin berada di jendela yang dapat diubah ukurannya. Mungkin ada lebih dari satu layar fisik, misalnya dengan perangkat foldable. Dalam semua kasus ini, ukuran layar fisik tidak relevan untuk memutuskan cara menampilkan konten.

Sebagai gantinya, Anda harus membuat keputusan berdasarkan bagian layar sebenarnya yang dialokasikan ke aplikasi, seperti metrik jendela saat ini yang disediakan oleh library WindowManager Jetpack. Untuk melihat cara menggunakan WindowManager di aplikasi Compose, lihat contoh JetNews.

Dengan mengikuti pendekatan ini, aplikasi Anda akan lebih fleksibel karena akan berperilaku baik dalam semua skenario di atas. Membuat tata letak yang adaptif dengan ruang layar yang tersedia untuknya juga akan mengurangi jumlah penanganan khusus untuk mendukung platform seperti ChromeOS, dan faktor bentuk seperti tablet dan perangkat foldable.

Setelah Anda mengamati ruang relevan yang tersedia untuk aplikasi, sebaiknya konversi ukuran mentah menjadi class ukuran yang bermakna, seperti yang dijelaskan dalam Class ukuran jendela. Kebijakan ini mengelompokkan ukuran ke dalam bucket ukuran standar, yang merupakan titik henti sementara yang dirancang untuk menyeimbangkan kemudahan dengan fleksibilitas untuk mengoptimalkan aplikasi Anda bagi sebagian besar kasus yang unik. Class ukuran ini merujuk pada jendela aplikasi Anda secara keseluruhan. Jadi, gunakan class ini untuk keputusan tata letak yang memengaruhi tata letak layar secara keseluruhan. Anda dapat meneruskan class ukuran ini ke bawah sebagai status, atau Anda dapat menjalankan logika tambahan untuk membuat status turunan agar diteruskan ke composable bertingkat.

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun MyApp(
    windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
) {
    // Perform logic on the size class to decide whether to show the top app bar.
    val showTopAppBar = windowSizeClass.windowHeightSizeClass != WindowHeightSizeClass.COMPACT

    // MyScreen knows nothing about window sizes, and performs logic based on a Boolean flag.
    MyScreen(
        showTopAppBar = showTopAppBar,
        /* ... */
    )
}

Pendekatan berlapis ini membatasi logika ukuran layar ke satu lokasi, bukan menyebarkannya ke seluruh aplikasi Anda di banyak tempat yang perlu tetap sinkron. Lokasi tunggal ini menghasilkan status, yang dapat secara eksplisit diteruskan ke composable lain seperti yang Anda lakukan untuk status aplikasi lainnya. Meneruskan status secara eksplisit menyederhanakan composable individual, karena hanya berupa fungsi composable normal yang menggunakan class ukuran atau konfigurasi yang ditentukan bersama data lainnya.

Composable bertingkat yang fleksibel dapat digunakan kembali

Composable lebih dapat digunakan kembali saat dapat ditempatkan di berbagai tempat. Jika suatu composable berasumsi bahwa composable tersebut akan selalu ditempatkan di lokasi tertentu dengan ukuran tertentu, akan lebih sulit untuk menggunakannya kembali di tempat lain pada lokasi yang berbeda, atau dengan jumlah ruang yang tersedia berbeda. Ini juga berarti bahwa composable individual yang dapat digunakan kembali harus menghindari ketergantungan pada informasi ukuran "global" secara implisit.

Pertimbangkan contoh berikut: Bayangkan composable bertingkat yang mengimplementasikan tata letak daftar-detail, yang dapat menampilkan satu atau dua panel secara berdampingan.

Screenshot aplikasi yang menampilkan dua panel secara berdampingan.
Gambar 2. Screenshot aplikasi yang menampilkan tata letak daftar-detail standar—1 adalah area daftar; 2, area detail.

Kita ingin keputusan ini menjadi bagian dari tata letak keseluruhan untuk aplikasi, jadi kita meneruskan keputusan dari composable tingkat layar seperti yang kita lihat di atas:

@Composable
fun AdaptivePane(
    showOnePane: Boolean,
    /* ... */
) {
    if (showOnePane) {
        OnePane(/* ... */)
    } else {
        TwoPane(/* ... */)
    }
}

Bagaimana jika kita ingin composable mengubah tata letaknya secara independen berdasarkan ruang yang tersedia? Misalnya, kartu yang ingin menampilkan detail tambahan jika ruang memungkinkan. Kita ingin melakukan beberapa logika berdasarkan ukuran yang tersedia, tetapi ukuran mana yang spesifik?

Contoh dua kartu yang berbeda.
Gambar 3. Kartu sempit yang hanya menampilkan ikon dan judul, serta kartu yang lebih lebar yang menampilkan ikon, judul, dan deskripsi singkat.

Seperti yang kita lihat di atas, kita harus menghindari mencoba menggunakan ukuran layar perangkat yang sebenarnya. Nilai ini tidak akan akurat untuk beberapa layar, dan juga tidak akan akurat jika aplikasi tidak dalam mode layar penuh.

Karena composable ini bukan composable tingkat layar, kita juga tidak boleh menggunakan metrik jendela saat ini secara langsung untuk memaksimalkan penggunaan kembali. Jika komponen ditempatkan dengan padding (seperti untuk inset), atau jika terdapat komponen seperti kolom samping navigasi atau panel aplikasi, jumlah ruang yang tersedia untuk composable mungkin berbeda secara signifikan dari keseluruhan ruang yang tersedia untuk aplikasi.

Oleh karena itu, kita harus menggunakan lebar yang benar-benar disediakan composable untuk merendernya sendiri. Kita memiliki dua opsi untuk mendapatkan lebar tersebut:

Jika ingin mengubah tempat atau cara konten ditampilkan, Anda dapat menggunakan koleksi pengubah atau tata letak kustom agar tata letak menjadi responsif. Ini dapat semudah memiliki beberapa turunan yang mengisi semua ruang yang tersedia, atau menata letak turunan dengan beberapa kolom jika ada cukup ruang.

Jika ingin mengubah item yang ditampilkan, Anda dapat menggunakan BoxWithConstraints sebagai alternatif yang lebih efektif. Composable ini memberikan batasan pengukuran yang dapat Anda gunakan untuk memanggil berbagai composable berdasarkan ruang yang tersedia. Namun, hal ini memiliki konsekuensi, karena BoxWithConstraints menunda komposisi hingga fase Tata Letak, saat batasan ini diketahui, menyebabkan lebih banyak pekerjaan yang harus dilakukan selama tata letak.

@Composable
fun Card(/* ... */) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(/* ... */)
                Title(/* ... */)
            }
        } else {
            Row {
                Column {
                    Title(/* ... */)
                    Description(/* ... */)
                }
                Image(/* ... */)
            }
        }
    }
}

Memastikan semua data tersedia untuk berbagai ukuran

Saat memanfaatkan ruang layar tambahan, pada layar besar Anda dapat menyediakan ruang untuk menampilkan lebih banyak konten kepada pengguna daripada di layar kecil. Saat mengimplementasikan komponen dengan perilaku ini, Anda mungkin ingin menjadi efisiensi, dan memuat data sebagai efek samping ukuran saat ini.

Namun, hal ini bertentangan dengan prinsip aliran data searah, tempat data dapat diangkat dan diberikan ke composable agar dirender dengan tepat. Data yang cukup harus diberikan ke composable sehingga composable selalu memiliki apa yang diperlukannya untuk ditampilkan di berbagai ukuran, meskipun beberapa bagian data mungkin tidak selalu digunakan.

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(description)
                }
                Image(imageUrl)
            }
        }
    }
}

Berdasarkan contoh Card, perhatikan bahwa kita selalu meneruskan description ke Card. Meskipun description hanya digunakan saat lebar mengizinkan untuk menampilkannya, Card selalu memerlukannya, terlepas dari lebar yang tersedia.

Selalu meneruskan data akan menyederhanakan tata letak adaptif dengan membuatnya kurang stateful, dan menghindari pemicu efek samping saat beralih antar-ukuran (yang dapat terjadi karena perubahan ukuran jendela, perubahan orientasi, atau perangkat melipat dan membentangkan).

Prinsip ini juga memungkinkan untuk mempertahankan status di seluruh perubahan tata letak. Dengan menarik informasi yang mungkin tidak digunakan di semua ukuran, kita dapat mempertahankan status pengguna saat ukuran tata letak berubah. Misalnya, kita dapat mengangkat flag Boolean showMore sehingga status pengguna dipertahankan saat perubahan ukuran menyebabkan tata letak beralih antara menyembunyikan dan menampilkan deskripsi:

@Composable
fun Card(
    imageUrl: String,
    title: String,
    description: String
) {
    var showMore by remember { mutableStateOf(false) }

    BoxWithConstraints {
        if (maxWidth < 400.dp) {
            Column {
                Image(imageUrl)
                Title(title)
            }
        } else {
            Row {
                Column {
                    Title(title)
                    Description(
                        description = description,
                        showMore = showMore,
                        onShowMoreToggled = { newValue ->
                            showMore = newValue
                        }
                    )
                }
                Image(imageUrl)
            }
        }
    }
}

Pelajari lebih lanjut

Untuk mempelajari tata letak kustom di Compose lebih lanjut, lihat referensi tambahan berikut.

Aplikasi contoh

  • Tata letak kanonis perangkat layar besar adalah repositori pola desain yang telah terbukti memberikan pengalaman pengguna yang optimal di perangkat layar besar
  • JetNews menunjukkan cara mendesain aplikasi yang menyesuaikan UI-nya untuk memanfaatkan ruang yang tersedia
  • Reply adalah contoh adaptif untuk mendukung perangkat seluler, tablet, dan perangkat foldable
  • Now in Android adalah aplikasi yang menggunakan tata letak adaptif untuk mendukung berbagai ukuran layar

Video