1. Sebelum memulai
Jetpack Compose adalah toolkit modern yang dirancang untuk menyederhanakan pengembangan UI. Toolkit ini menggabungkan model pemrograman reaktif dengan keringkasan dan kemudahan penggunaan bahasa pemrograman Kotlin. Alat ini sepenuhnya deklaratif, artinya Anda mendeskripsikan UI dengan memanggil serangkaian fungsi yang mengubah data menjadi hierarki UI. Saat data yang mendasari berubah, framework akan otomatis mengeksekusi ulang fungsi ini, yang mengupdate hierarki UI untuk Anda.
Aplikasi Compose terdiri dari fungsi composable - hanya fungsi reguler yang ditandai dengan @Composable
, yang dapat memanggil fungsi composable lainnya. Anda hanya perlu menggunakan fungsi ini untuk membuat komponen UI baru. Anotasi memberi tahu Compose untuk menambahkan dukungan khusus ke fungsi untuk mengupdate dan mengelola UI dari waktu ke waktu. Compose memungkinkan Anda menyusun kode menjadi potongan-potongan kecil. Fungsi composable sering disebut dengan "composable".
Dengan membuat composable kecil yang dapat digunakan kembali, Anda dapat dengan mudah mem-build library elemen UI yang digunakan di aplikasi. Masing-masing bertanggung jawab atas satu bagian layar dan dapat diedit secara terpisah.
Untuk mendapatkan dukungan lebih lanjut saat Anda mempelajari codelab ini, lihat video tutorial coding berikut:
Catatan: Video tutorial coding ini menggunakan Material 2 sementara codelab telah diperbarui ke Material 3. Perlu diketahui bahwa akan ada beberapa langkah yang berbeda.
Prasyarat
- Pengalaman dengan sintaksis Kotlin, termasuk lambda
Yang akan Anda lakukan
Dalam codelab ini, Anda akan mempelajari:
- Apa itu Compose
- Cara membangun UI dengan Compose
- Cara mengelola status dalam fungsi composable
- Cara membuat daftar berperforma tinggi
- Cara menambahkan animasi
- Cara menata gaya dan tema aplikasi
Anda akan mem-build aplikasi dengan layar orientasi dan daftar item animasi yang diperluas:
Yang akan Anda butuhkan
2. Memulai project Compose baru
Untuk memulai project Compose baru, buka Android Studio.
Jika baru memulai di jendela Welcome to Android Studio, klik Start a new Android Studio project. Jika sudah membuka project Android Studio, pilih File > New > New Project dari panel menu.
Untuk project baru, pilih Empty Activity dari template yang tersedia.
Klik Next lalu konfigurasikan project Anda seperti biasa, beri nama "Basics Codelab". Pastikan Anda memilih minimumSdkVersion setidaknya API level 21, yang merupakan API minimum yang didukung Compose.
Saat memilih template Empty Activity, kode berikut akan dibuat untuk Anda dalam project:
- Project sudah dikonfigurasi untuk menggunakan Compose.
- File
AndroidManifest.xml
dibuat. - File
build.gradle.kts
danapp/build.gradle.kts
berisi opsi dan dependensi yang diperlukan untuk Compose.
Setelah menyinkronkan project, buka MainActivity.kt
dan periksa kodenya.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
BasicsCodelabTheme {
Greeting("Android")
}
}
Di bagian berikutnya, Anda akan melihat fungsi setiap metode dan cara meningkatkannya untuk membuat tata letak yang fleksibel dan dapat digunakan kembali.
Solusi untuk codelab
Anda dapat memperoleh kode untuk solusi codelab ini dari GitHub:
$ git clone https://github.com/android/codelab-android-compose
Atau, Anda dapat mendownload repositori sebagai file Zip:
Anda akan menemukan kode solusi di project BasicsCodelab
. Sebaiknya ikuti codelab ini langkah demi langkah sesuai kemampuan Anda sendiri dan lihat solusi jika diperlukan. Selama codelab, Anda akan melihat cuplikan kode yang harus ditambahkan ke project.
3. Memulai Compose
Buka berbagai class dan metode yang terkait dengan Compose yang telah dibuatkan Android Studio untuk Anda.
Fungsi composable
Fungsi composable adalah fungsi reguler yang dianotasi dengan @Composable
. Hal ini memungkinkan fungsi Anda memanggil fungsi @Composable
lain di dalamnya. Anda dapat melihat cara fungsi Greeting
ditandai sebagai @Composable
. Fungsi ini akan menghasilkan bagian hierarki UI yang menampilkan input yang diberikan, yaitu String
. Text
adalah fungsi composable yang disediakan oleh library.
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
Compose di aplikasi Android
Dengan Compose, Activity
tetap menjadi titik entri ke aplikasi Android. Di project kita, MainActivity
diluncurkan saat pengguna membuka aplikasi (seperti yang ditetapkan dalam file AndroidManifest.xml
). Anda menggunakan setContent
untuk menentukan tata letak, tetapi alih-alih menggunakan file XML seperti yang biasa Anda lakukan di sistem View tradisional, Anda memanggil fungsi Composable di dalamnya.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
}
}
}
BasicsCodelabTheme
adalah cara menetapkan gaya fungsi Composable. Anda akan melihat lebih banyak tentang hal ini di bagian Tema aplikasi. Untuk melihat bagaimana teks ditampilkan di layar, Anda dapat menjalankan aplikasi di emulator atau perangkat, atau menggunakan pratinjau Android Studio.
Untuk menggunakan pratinjau Android Studio, Anda hanya perlu menandai fungsi Composable yang tidak memiliki parameter atau fungsi dengan parameter default dengan anotasi @Preview
dan mem-build project Anda. Anda sudah dapat melihat fungsi Preview Composable
di file MainActivity.kt
. Anda dapat memiliki beberapa pratinjau di file yang sama dan memberinya nama.
@Preview(showBackground = true, name = "Text preview")
@Composable
fun GreetingPreview() {
BasicsCodelabTheme {
Greeting(name = "Android")
}
}
Pratinjau mungkin tidak muncul jika Code dipilih. Klik Split untuk melihat pratinjau.
4. Menyesuaikan UI
Mari mulai dengan menetapkan warna latar belakang yang berbeda untuk Greeting
. Anda dapat melakukannya dengan menggabungkan composable Text
dengan Surface
. Surface
membutuhkan warna, jadi gunakan MaterialTheme.colorScheme.primary
.
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Surface(color = MaterialTheme.colorScheme.primary) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
}
Komponen bertingkat di dalam Surface
akan digambar di atas warna latar belakang tersebut.
Anda dapat melihat perubahan baru dalam pratinjau:
Anda mungkin melewatkan detail penting: teksnya kini berwarna putih. Kapan kita menentukannya?
Kita tidak menentukannya. Komponen Material, seperti androidx.compose.material3.Surface
, dibuat untuk membuat pengalaman Anda jadi lebih baik dengan menangani fitur umum yang mungkin Anda inginkan di aplikasi, seperti memilih warna teks yang sesuai. Kita katakan bahwa Material tidak fleksibel karena memberikan default dan pola bagus yang umum untuk sebagian besar aplikasi. Komponen Material di Compose di-build di atas komponen dasar lainnya (di androidx.compose.foundation
), yang juga dapat diakses dari komponen aplikasi Anda jika Anda memerlukan lebih banyak fleksibilitas.
Dalam hal ini, Surface
memahami bahwa, saat latar belakang ditetapkan ke warna primary
, semua teks di atasnya harus menggunakan warna onPrimary
, yang juga ditentukan di tema. Anda dapat mempelajari hal ini lebih lanjut di bagian Tema aplikasi.
Pengubah
Sebagian besar elemen UI Compose seperti Surface
dan Text
menerima parameter modifier
opsional. Pengubah memberi tahu elemen UI cara membuat tata letak, menampilkan, atau berperilaku dalam tata letak induknya. Anda mungkin sudah melihat bahwa composable Greeting
sudah memiliki pengubah default, yang kemudian diteruskan ke Text
.
Misalnya, pengubah padding
akan menerapkan jumlah ruang di sekitar elemen yang didekorasinya. Anda dapat membuat pengubah padding dengan Modifier.padding()
. Anda juga dapat menambahkan beberapa pengubah dengan merangkainya sehingga dalam kasus ini, kita dapat menambahkan pengubah padding ke pengubah default: modifier.padding(24.dp)
.
Sekarang, tambahkan padding ke Text
di layar:
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.unit.dp
// ...
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Surface(color = MaterialTheme.colorScheme.primary) {
Text(
text = "Hello $name!",
modifier = modifier.padding(24.dp)
)
}
}
Ada banyak pengubah yang dapat digunakan untuk menyelaraskan, menganimasikan, menata letak, memberikan kemampuan klik atau scroll, mengubah, dll. Untuk mengetahui daftar lengkap, lihat Daftar Pengubah Compose. Anda akan menggunakannya di langkah-langkah berikutnya.
5. Menggunakan kembali composable
Makin banyak komponen yang ditambahkan ke UI, makin banyak level bertingkat yang Anda buat. Hal ini dapat memengaruhi keterbacaan jika fungsi menjadi sangat besar. Dengan membuat komponen kecil yang dapat digunakan kembali, Anda dapat dengan mudah mem-build library elemen UI yang digunakan di aplikasi. Masing-masing bertanggung jawab atas satu bagian kecil layar dan dapat diedit secara terpisah.
Sebagai praktik terbaik, fungsi harus menyertakan parameter Pengubah yang ditetapkan sebagai Pengubah kosong secara default. Teruskan pengubah ini ke composable pertama yang Anda panggil dalam fungsi. Dengan cara ini, situs panggilan dapat menyesuaikan petunjuk tata letak dan perilaku dari luar fungsi composable Anda.
Buat Composable bernama MyApp
yang menyertakan kata sambutan.
@Composable
fun MyApp(modifier: Modifier = Modifier) {
Surface(
modifier = modifier,
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
Cara ini memungkinkan Anda menghapus callback onCreate
dan pratinjau karena kini Anda dapat menggunakan kembali composable MyApp
untuk menghindari duplikasi kode.
Dalam pratinjau, panggil MyApp
lalu hapus nama pratinjau.
File MainActivity.kt
Anda seharusnya terlihat seperti ini:
package com.example.basicscodelab
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.basicscodelab.ui.theme.BasicsCodelabTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
MyApp(modifier = Modifier.fillMaxSize())
}
}
}
}
@Composable
fun MyApp(modifier: Modifier = Modifier) {
Surface(
modifier = modifier,
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Surface(color = MaterialTheme.colorScheme.primary) {
Text(
text = "Hello $name!",
modifier = modifier.padding(24.dp)
)
}
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
BasicsCodelabTheme {
MyApp()
}
}
6. Membuat kolom dan baris
Tiga elemen tata letak standar dan dasar di Compose adalah Column
, Row
, dan Box
.
Tiga elemen tersebut adalah fungsi Composable yang menggunakan konten Composable sehingga Anda dapat menempatkan item di dalamnya. Misalnya, setiap turunan di dalam Column
akan ditempatkan secara vertikal.
// Don't copy over
Column {
Text("First row")
Text("Second row")
}
Sekarang, coba ubah Greeting
agar menampilkan kolom dengan dua elemen teks seperti dalam contoh ini:
Perhatikan bahwa Anda mungkin harus memindahkan padding.
Bandingkan hasilnya dengan solusi ini:
import androidx.compose.foundation.layout.Column
// ...
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Surface(color = MaterialTheme.colorScheme.primary) {
Column(modifier = modifier.padding(24.dp)) {
Text(text = "Hello ")
Text(text = name)
}
}
}
Compose dan Kotlin
Fungsi composable dapat digunakan seperti fungsi lainnya di Kotlin. Fungsi ini membuat versi UI sangat canggih karena Anda dapat menambahkan pernyataan untuk memengaruhi cara UI ditampilkan.
Misalnya, Anda dapat menggunakan loop for
untuk menambahkan elemen ke Column
:
@Composable
fun MyApp(
modifier: Modifier = Modifier,
names: List<String> = listOf("World", "Compose")
) {
Column(modifier) {
for (name in names) {
Greeting(name = name)
}
}
}
Anda belum menetapkan dimensi atau menambahkan batasan ke ukuran composable sehingga setiap baris memerlukan ruang minimum yang dapat diambil dan pratinjau melakukan hal yang sama. Mari kita ubah pratinjau untuk mengemulasi lebar umum ponsel kecil, 320 dp. Tambahkan parameter widthDp
ke anotasi @Preview
seperti berikut:
@Preview(showBackground = true, widthDp = 320)
@Composable
fun GreetingPreview() {
BasicsCodelabTheme {
MyApp()
}
}
Pengubah digunakan secara luas di Compose, jadi mari kita berlatih dengan latihan yang lebih canggih: Coba buat kembali tata letak berikut menggunakan pengubah fillMaxWidth
dan padding
.
Sekarang bandingkan kode Anda dengan solusinya:
import androidx.compose.foundation.layout.fillMaxWidth
@Composable
fun MyApp(
modifier: Modifier = Modifier,
names: List<String> = listOf("World", "Compose")
) {
Column(modifier = modifier.padding(vertical = 4.dp)) {
for (name in names) {
Greeting(name = name)
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Column(modifier = Modifier.fillMaxWidth().padding(24.dp)) {
Text(text = "Hello ")
Text(text = name)
}
}
}
Perhatikan bahwa:
- Pengubah dapat memiliki overload, sehingga Anda dapat menentukan cara yang berbeda untuk membuat padding.
- Untuk menambahkan beberapa pengubah ke elemen, Anda cukup menggabungkannya.
Ada beberapa cara untuk mencapai hasil ini, jadi jika kode Anda tidak cocok dengan cuplikan ini, bukan berarti kode Anda salah. Namun, salin dan tempel kode ini untuk melanjutkan codelab.
Menambahkan tombol
Pada langkah berikutnya, Anda akan menambahkan elemen yang dapat diklik yang memperluas Greeting
, sehingga kita perlu menambahkan tombol tersebut terlebih dahulu. Tujuannya adalah membuat tata letak berikut:
Button
adalah composable yang disediakan oleh paket material3 yang menggunakan composable sebagai argumen terakhir. Karena lambda akhir dapat dipindahkan di luar tanda kurung, Anda dapat menambahkan konten apa pun ke tombol sebagai turunan. Misalnya, Text
:
// Don't copy yet
Button(
onClick = { } // You'll learn about this callback later
) {
Text("Show less")
}
Untuk melakukannya, Anda perlu mempelajari cara menempatkan composable di akhir baris. Tidak ada pengubah alignEnd
, jadi sebagai gantinya, Anda memberikan beberapa weight
ke composable di awal. Pengubah weight
membuat elemen mengisi semua ruang yang tersedia, yang menjadikannya fleksibel, sehingga secara efektif mendorong elemen lain yang tidak memiliki bobot, yaitu yang tidak fleksibel. Perubahan ini juga menjadikan pengubah fillMaxWidth
redundan.
Sekarang, coba tambahkan tombol dan tempatkan seperti yang ditunjukkan pada gambar sebelumnya.
Lihat solusinya di sini:
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.ElevatedButton
// ...
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(modifier = Modifier.weight(1f)) {
Text(text = "Hello ")
Text(text = name)
}
ElevatedButton(
onClick = { /* TODO */ }
) {
Text("Show more")
}
}
}
}
7. Status dalam Compose
Di bagian ini, Anda akan menambahkan beberapa interaksi ke layar. Sejauh ini Anda telah membuat tata letak statis, tetapi sekarang Anda akan membuatnya bereaksi terhadap perubahan pengguna untuk mencapai ini:
Sebelum masuk ke cara membuat tombol yang dapat diklik dan cara mengubah ukuran item, Anda perlu menyimpan beberapa nilai di suatu tempat yang menunjukkan apakah setiap item diperluas atau tidak—status item. Karena kita harus memiliki salah satu nilai ini per kata sambutan, tempat yang logis untuk hal ini adalah dalam composable Greeting
. Lihat boolean expanded
ini dan cara penggunaannya dalam kode:
// Don't copy over
@Composable
fun Greeting(name: String) {
var expanded = false // Don't do this!
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(modifier = Modifier.weight(1f)) {
Text(text = "Hello, ")
Text(text = name)
}
ElevatedButton(
onClick = { expanded = !expanded }
) {
Text(if (expanded) "Show less" else "Show more")
}
}
}
}
Perhatikan bahwa kita juga menambahkan tindakan onClick
dan teks tombol dinamis. Selengkapnya mengenai hal itu akan dibahas nanti.
Namun, hal ini tidak akan berfungsi seperti yang diharapkan. Menetapkan nilai yang berbeda untuk variabel expanded
tidak akan membuat Compose mendeteksinya sebagai perubahan status sehingga tidak akan ada yang terjadi.
Alasan mengubah variabel ini tidak memicu rekomposisi adalah karena variabel ini tidak dilacak oleh Compose. Selain itu, setiap kali Greeting
dipanggil, variabel akan direset ke salah.
Untuk menambahkan status internal ke composable, Anda dapat menggunakan fungsi mutableStateOf
yang membuat Compose merekomposisi fungsi yang membaca State
.
import androidx.compose.runtime.mutableStateOf
// ...
// Don't copy over
@Composable
fun Greeting() {
val expanded = mutableStateOf(false) // Don't do this!
}
Namun, Anda tidak bisa hanya menetapkan mutableStateOf
ke variabel di dalam composable. Seperti yang dijelaskan sebelumnya, rekomposisi dapat terjadi kapan saja yang akan kembali memanggil composable, yang mereset status ke status baru yang dapat diubah dengan nilai false
.
Untuk mempertahankan status di seluruh rekomposisi, ingat status yang dapat diubah menggunakan remember
.
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
// ...
@Composable
fun Greeting(...) {
val expanded = remember { mutableStateOf(false) }
// ...
}
remember
digunakan untuk menjaga dari rekomposisi, sehingga status tidak direset.
Perhatikan bahwa jika Anda memanggil composable yang sama dari bagian layar yang berbeda, Anda akan membuat elemen UI yang berbeda, masing-masing dengan versi statusnya sendiri. Anda dapat menganggap status internal sebagai variabel pribadi dalam class.
Fungsi composable akan secara otomatis "berlangganan" ke status ini. Jika status berubah, composable yang membaca kolom ini akan direkomposisi untuk menampilkan update.
Mengubah status dan bereaksi terhadap perubahan status
Untuk mengubah status, Anda mungkin telah melihat bahwa Button
memiliki parameter yang disebut onClick
, tetapi tidak memerlukan nilai, parameter ini memerlukan fungsi.
Anda dapat menentukan tindakan yang akan diambil saat diklik dengan menetapkan ekspresi lambda ke tindakan tersebut. Misalnya, mari ubah nilai status yang diperluas, dan tampilkan teks berbeda bergantung pada nilai.
ElevatedButton(
onClick = { expanded.value = !expanded.value },
) {
Text(if (expanded.value) "Show less" else "Show more")
}
Jalankan aplikasi dalam mode interaktif untuk melihat perilakunya.
Saat tombol diklik, expanded
akan beralih dan memicu rekomposisi teks di dalam tombol. Setiap Greeting
mempertahankan statusnya sendiri yang diperluas karena termasuk dalam elemen UI yang berbeda.
Kode hingga tahap ini:
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
val expanded = remember { mutableStateOf(false) }
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(modifier = Modifier.weight(1f)) {
Text(text = "Hello ")
Text(text = name)
}
ElevatedButton(
onClick = { expanded.value = !expanded.value }
) {
Text(if (expanded.value) "Show less" else "Show more")
}
}
}
}
Memperluas item
Sekarang, mari kita memperluas item saat diminta. Tambahkan variabel tambahan yang bergantung pada status:
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
val expanded = remember { mutableStateOf(false) }
val extraPadding = if (expanded.value) 48.dp else 0.dp
// ...
Anda tidak perlu mengingat extraPadding
terhadap rekomposisi karena sedang melakukan penghitungan sederhana.
Selain itu, sekarang kita dapat menerapkan pengubah padding baru ke Kolom:
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
val expanded = remember { mutableStateOf(false) }
val extraPadding = if (expanded.value) 48.dp else 0.dp
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(
modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding)
) {
Text(text = "Hello ")
Text(text = name)
}
ElevatedButton(
onClick = { expanded.value = !expanded.value }
) {
Text(if (expanded.value) "Show less" else "Show more")
}
}
}
}
Jika menjalankan di emulator atau dalam mode interaktif, Anda akan melihat bahwa setiap item dapat diperluas secara terpisah:
8. Pengangkatan status
Dalam fungsi Composable, status yang dibaca atau diubah oleh beberapa fungsi harus berada di ancestor yang umum—proses ini disebut pengangkatan status. Mengangkat berarti meningkatkan atau mengembangkan.
Membuat status dapat diangkat akan menghindari status duplikat dan memperkenalkan bug, membantu menggunakan kembali composable, dan membuat composable jauh lebih mudah untuk diuji. Sebaliknya, status yang tidak perlu dikontrol oleh induk composable tidak boleh diangkat. Sumber kebenaran adalah milik siapa pun yang membuat dan mengontrol status tersebut.
Misalnya, mari kita membuat layar orientasi untuk aplikasi.
Tambahkan kode berikut ke MainActivity.kt
:
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.material3.Button
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
// ...
@Composable
fun OnboardingScreen(modifier: Modifier = Modifier) {
// TODO: This state should be hoisted
var shouldShowOnboarding by remember { mutableStateOf(true) }
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Welcome to the Basics Codelab!")
Button(
modifier = Modifier.padding(vertical = 24.dp),
onClick = { shouldShowOnboarding = false }
) {
Text("Continue")
}
}
}
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
BasicsCodelabTheme {
OnboardingScreen()
}
}
Kode ini berisi banyak fitur baru:
- Anda telah menambahkan composable baru yang disebut
OnboardingScreen
dan juga pratinjau baru. Saat mem-build project, Anda akan melihat beberapa pratinjau sekaligus. Kami juga menambahkan tinggi tetap untuk memverifikasi bahwa konten disejajarkan dengan benar. Column
dapat dikonfigurasi untuk menampilkan kontennya di tengah layar.shouldShowOnboarding
menggunakan kata kunciby
, bukan=
. Ini adalah delegasi properti agar Anda tidak perlu mengetik.value
setiap saat.- Saat tombol diklik,
shouldShowOnboarding
ditetapkan kefalse
, tetapi Anda belum membaca status dari mana pun.
Sekarang kita dapat menambahkan layar orientasi baru ini ke aplikasi. Kita ingin menampilkannya saat peluncuran, lalu menyembunyikannya saat pengguna menekan "Continue".
Di Compose, Anda tidak menyembunyikan elemen UI. Sebagai gantinya, Anda tidak menambahkannya ke komposisi, sehingga elemen tidak ditambahkan ke hierarki UI yang dihasilkan Compose. Anda melakukannya dengan logika Kotlin bersyarat yang sederhana. Misalnya, untuk menampilkan layar orientasi atau daftar kata sambutan, lakukan hal berikut:
// Don't copy yet
@Composable
fun MyApp(modifier: Modifier = Modifier) {
Surface(modifier) {
if (shouldShowOnboarding) { // Where does this come from?
OnboardingScreen()
} else {
Greetings()
}
}
}
Namun, kita tidak memiliki akses ke shouldShowOnboarding
. Jelas bahwa kita perlu membagikan status yang dibuat di OnboardingScreen
dengan composable MyApp
.
Daripada membagikan nilai status kepada induknya, kita mengangkat status. Kita hanya memindahkannya ke ancestor umum yang perlu mengaksesnya.
Pertama, pindahkan konten MyApp
ke composable baru yang disebut Greetings
. Sesuaikan juga pratinjau untuk memanggil metode Greetings
:
@Composable
fun MyApp(modifier: Modifier = Modifier) {
Greetings()
}
@Composable
private fun Greetings(
modifier: Modifier = Modifier,
names: List<String> = listOf("World", "Compose")
) {
Column(modifier = modifier.padding(vertical = 4.dp)) {
for (name in names) {
Greeting(name = name)
}
}
}
@Preview(showBackground = true, widthDp = 320)
@Composable
fun GreetingsPreview() {
BasicsCodelabTheme {
Greetings()
}
}
Tambahkan pratinjau untuk composable MyApp tingkat atas baru agar kami dapat menguji perilakunya:
@Preview
@Composable
fun MyAppPreview() {
BasicsCodelabTheme {
MyApp(Modifier.fillMaxSize())
}
}
Sekarang tambahkan logika untuk menampilkan layar yang berbeda di MyApp
, dan angkat statusnya.
@Composable
fun MyApp(modifier: Modifier = Modifier) {
var shouldShowOnboarding by remember { mutableStateOf(true) }
Surface(modifier) {
if (shouldShowOnboarding) {
OnboardingScreen(/* TODO */)
} else {
Greetings()
}
}
}
Kita juga perlu membagikan shouldShowOnboarding
ke layar orientasi, tetapi kita tidak akan meneruskannya secara langsung. Sebaiknya kita mengizinkan OnboardingScreen
memberi tahu kita saat pengguna mengklik tombol Continue, bukan membiarkannya mengubah status.
Bagaimana cara mengabaikan peristiwa? Dengan menurunkan callback. Callback adalah fungsi yang diteruskan sebagai argumen ke fungsi lainnya dan dijalankan saat peristiwa terjadi.
Coba tambahkan parameter fungsi ke layar orientasi yang ditentukan sebagai onContinueClicked: () -> Unit
sehingga Anda dapat mengubah status dari MyApp
.
Solusi:
@Composable
fun MyApp(modifier: Modifier = Modifier) {
var shouldShowOnboarding by remember { mutableStateOf(true) }
Surface(modifier) {
if (shouldShowOnboarding) {
OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
} else {
Greetings()
}
}
}
@Composable
fun OnboardingScreen(
onContinueClicked: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Welcome to the Basics Codelab!")
Button(
modifier = Modifier
.padding(vertical = 24.dp),
onClick = onContinueClicked
) {
Text("Continue")
}
}
}
Dengan meneruskan fungsi, bukan status, ke OnboardingScreen
, kita membuat composable ini dapat digunakan kembali dan melindungi status agar tidak diubah oleh composable lain. Secara umum, semuanya tetap sederhana. Contoh yang bagus adalah bagaimana pratinjau orientasi perlu dimodifikasi untuk memanggil OnboardingScreen
sekarang:
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
BasicsCodelabTheme {
OnboardingScreen(onContinueClicked = {}) // Do nothing on click.
}
}
Menetapkan onContinueClicked
ke ekspresi lambda kosong berarti "tidak melakukan apa pun", yang sangat cocok untuk pratinjau.
Ini makin tampak seperti aplikasi sebenarnya, bagus!
Dalam composable MyApp
, kita menggunakan delegasi properti by
untuk pertama kalinya agar tidak selalu menggunakan nilai. Mari kita gunakan by
, bukan =
, juga di composable Greeting untuk properti expanded
. Pastikan Anda mengubah expanded
dari val
menjadi var
.
Kode lengkap sejauh ini:
package com.example.basicscodelab
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ElevatedButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.codelab.basics.ui.theme.BasicsCodelabTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
MyApp(modifier = Modifier.fillMaxSize())
}
}
}
}
@Composable
fun MyApp(modifier: Modifier = Modifier) {
var shouldShowOnboarding by remember { mutableStateOf(true) }
Surface(modifier) {
if (shouldShowOnboarding) {
OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
} else {
Greetings()
}
}
}
@Composable
fun OnboardingScreen(
onContinueClicked: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Welcome to the Basics Codelab!")
Button(
modifier = Modifier.padding(vertical = 24.dp),
onClick = onContinueClicked
) {
Text("Continue")
}
}
}
@Composable
private fun Greetings(
modifier: Modifier = Modifier,
names: List<String> = listOf("World", "Compose")
) {
Column(modifier = modifier.padding(vertical = 4.dp)) {
for (name in names) {
Greeting(name = name)
}
}
}
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
BasicsCodelabTheme {
OnboardingScreen(onContinueClicked = {})
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
var expanded by remember { mutableStateOf(false) }
val extraPadding = if (expanded) 48.dp else 0.dp
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(
modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding)
) {
Text(text = "Hello, ")
Text(text = name)
}
ElevatedButton(
onClick = { expanded = !expanded }
) {
Text(if (expanded) "Show less" else "Show more")
}
}
}
}
@Preview(showBackground = true, widthDp = 320)
@Composable
fun GreetingPreview() {
BasicsCodelabTheme {
Greetings()
}
}
@Preview
@Composable
fun MyAppPreview() {
BasicsCodelabTheme {
MyApp(Modifier.fillMaxSize())
}
}
9. Membuat daftar lambat berperforma tinggi
Sekarang mari kita buat daftar nama yang lebih realistis. Sejauh ini Anda telah menampilkan dua sambutan dalam Column
. Namun, apakah kode tersebut dapat menangani ribuan sambutan?
Ubah nilai daftar default dalam parameter Greetings
untuk menggunakan konstruktor daftar lain yang memungkinkan untuk menetapkan ukuran daftar dan mengisinya dengan nilai yang terdapat dalam lambda-nya (di sini $it
mewakili indeks daftar):
names: List<String> = List(1000) { "$it" }
Tindakan ini akan menghasilkan 1.000 sambutan, bahkan sambutan yang tidak sesuai dengan layar. Tentu saja ini bukan performa yang bagus. Anda dapat mencoba menjalankannya di emulator (peringatan: kode ini mungkin menghentikan emulator).
Untuk menampilkan kolom yang dapat di-scroll, kita menggunakan LazyColumn
. LazyColumn
hanya merender item yang terlihat di layar, sehingga memungkinkan peningkatan performa saat merender daftar besar.
Dalam penggunaan dasarnya, LazyColumn
API menyediakan elemen items
dalam cakupannya, tempat logika rendering setiap item ditulis:
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
// ...
@Composable
private fun Greetings(
modifier: Modifier = Modifier,
names: List<String> = List(1000) { "$it" }
) {
LazyColumn(modifier = modifier.padding(vertical = 4.dp)) {
items(items = names) { name ->
Greeting(name = name)
}
}
}
10. Mempertahankan status
Aplikasi kita memiliki dua masalah:
Mempertahankan status layar orientasi
Jika Anda menjalankan aplikasi di perangkat, mengklik tombol, lalu melakukan rotasi, layar orientasi akan ditampilkan lagi. Fungsi remember
hanya berfungsi selama composable disimpan di Komposisi. Saat Anda melakukan rotasi, seluruh aktivitas dimulai ulang, sehingga semua status hilang. Hal ini juga terjadi dengan perubahan konfigurasi dan penghentian proses.
Anda dapat menggunakan rememberSaveable
, bukan remember
. Setiap perubahan konfigurasi status yang masih ada (seperti rotasi) dan penghentian proses akan disimpan.
Sekarang, ganti penggunaan remember
di shouldShowOnboarding
dengan rememberSaveable
:
import androidx.compose.runtime.saveable.rememberSaveable
// ...
var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) }
Jalankan, rotasikan, ubah ke mode gelap, atau hentikan prosesnya. Layar orientasi tidak ditampilkan kecuali Anda sebelumnya telah keluar dari aplikasi.
Mempertahankan status item daftar yang diperluas
Jika Anda memperluas item daftar lalu men-scroll daftar hingga item tidak terlihat, atau memutar perangkat lalu kembali ke item yang diperluas, Anda akan melihat bahwa item tersebut kini kembali ke keadaan awal.
Solusi untuk masalah ini adalah dengan menggunakan rememberSaveable untuk status diperluas juga:
var expanded by rememberSaveable { mutableStateOf(false) }
Dengan sekitar 120 baris kode sejauh ini, Anda dapat menampilkan daftar scroll item yang panjang dan berperforma tinggi yang masing-masing menyimpan statusnya sendiri. Selain itu, seperti yang dapat Anda lihat, aplikasi Anda memiliki mode gelap yang benar-benar sempurna tanpa baris kode tambahan. Anda akan mempelajari tema nanti.
11. Menganimasikan daftar Anda
Di Compose, ada beberapa cara untuk menganimasikan UI Anda: dari API tingkat tinggi untuk animasi sederhana hingga metode tingkat rendah untuk kontrol penuh dan transisi yang kompleks. Anda dapat membacanya di dokumentasi.
Di bagian ini, Anda akan menggunakan salah satu API tingkat rendah, tetapi jangan khawatir, API ini juga bisa menjadi sangat sederhana. Mari kita animasikan perubahan ukuran yang sudah kita implementasikan:
Untuk ini, Anda akan menggunakan composable animateDpAsState
. Metode ini menampilkan objek Status yang value
-nya akan terus diperbarui oleh animasi hingga selesai. "Nilai target" yang jenisnya adalah Dp
akan digunakan.
Buat extraPadding
animasi yang bergantung pada status yang diperluas.
import androidx.compose.animation.core.animateDpAsState
@Composable
private fun Greeting(name: String, modifier: Modifier = Modifier) {
var expanded by rememberSaveable { mutableStateOf(false) }
val extraPadding by animateDpAsState(
if (expanded) 48.dp else 0.dp
)
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding)
) {
Text(text = "Hello, ")
Text(text = name)
}
ElevatedButton(
onClick = { expanded = !expanded }
) {
Text(if (expanded) "Show less" else "Show more")
}
}
}
}
Jalankan aplikasi dan coba animasinya.
animateDpAsState
menggunakan parameter animationSpec
opsional yang memungkinkan Anda menyesuaikan animasi. Mari kita lakukan hal yang lebih menyenangkan seperti menambahkan animasi berbasis pegas:
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
@Composable
private fun Greeting(name: String, modifier: Modifier = Modifier) {
var expanded by rememberSaveable { mutableStateOf(false) }
val extraPadding by animateDpAsState(
if (expanded) 48.dp else 0.dp,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
Surface(
// ...
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding.coerceAtLeast(0.dp))
// ...
)
}
Perhatikan bahwa kita juga memastikan padding tidak pernah negatif, jika tidak, padding dapat membuat aplikasi error. Ini memperkenalkan bug animasi halus yang akan kita perbaiki nanti dalam Sentuhan akhir.
Spesifikasi spring
tidak menggunakan parameter terkait waktu. Namun, spesifikasi tersebut bergantung pada properti fisik (redaman dan kekakuan) untuk membuat animasi menjadi lebih alami. Jalankan aplikasi sekarang untuk mencoba animasi baru:
Setiap animasi yang dibuat dengan animate*AsState
dapat terganggu. Artinya, jika nilai target berubah di tengah animasi, animate*AsState
akan memulai ulang animasi dan menunjuk ke nilai baru. Gangguan terlihat sangat alami dengan animasi berbasis pegas:
Jika Anda ingin mempelajari berbagai jenis animasi, coba parameter yang berbeda untuk spring
, spesifikasi yang berbeda (tween
, repeatable
), dan fungsi yang berbeda: animateColorAsState
atau jenis animation API yang berbeda.
Kode lengkap untuk bagian ini
package com.example.basicscodelab
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
import androidx.compose.material3.ElevatedButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.codelab.basics.ui.theme.BasicsCodelabTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
MyApp(modifier = Modifier.fillMaxSize())
}
}
}
}
@Composable
fun MyApp(modifier: Modifier = Modifier) {
var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) }
Surface(modifier) {
if (shouldShowOnboarding) {
OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
} else {
Greetings()
}
}
}
@Composable
fun OnboardingScreen(
onContinueClicked: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Welcome to the Basics Codelab!")
Button(
modifier = Modifier.padding(vertical = 24.dp),
onClick = onContinueClicked
) {
Text("Continue")
}
}
}
@Composable
private fun Greetings(
modifier: Modifier = Modifier,
names: List<String> = List(1000) { "$it" }
) {
LazyColumn(modifier = modifier.padding(vertical = 4.dp)) {
items(items = names) { name ->
Greeting(name = name)
}
}
}
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
BasicsCodelabTheme {
OnboardingScreen(onContinueClicked = {})
}
}
@Composable
private fun Greeting(name: String, modifier: Modifier = Modifier) {
var expanded by rememberSaveable { mutableStateOf(false) }
val extraPadding by animateDpAsState(
if (expanded) 48.dp else 0.dp,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding.coerceAtLeast(0.dp))
) {
Text(text = "Hello, ")
Text(text = name)
}
ElevatedButton(
onClick = { expanded = !expanded }
) {
Text(if (expanded) "Show less" else "Show more")
}
}
}
}
@Preview(showBackground = true, widthDp = 320)
@Composable
fun GreetingPreview() {
BasicsCodelabTheme {
Greetings()
}
}
@Preview
@Composable
fun MyAppPreview() {
BasicsCodelabTheme {
MyApp(Modifier.fillMaxSize())
}
}
12. Menata gaya dan tema aplikasi
Hingga saat ini Anda belum menata gaya pada composable mana pun, tetapi Anda mendapatkan default yang memadai, termasuk dukungan mode gelap. Mari kita lihat apa itu BasicsCodelabTheme
dan MaterialTheme
.
Jika membuka file ui/theme/Theme.kt
, Anda akan melihat bahwa BasicsCodelabTheme
menggunakan MaterialTheme
dalam penerapannya:
// Do not copy
@Composable
fun BasicsCodelabTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
// ...
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
MaterialTheme
adalah fungsi composable yang mencerminkan prinsip gaya dari Spesifikasi desain material. Informasi gaya tersebut menurun ke komponen yang berada dalam content
-nya, yang dapat membaca informasi untuk menyesuaikan gayanya sendiri. Di UI, Anda sudah menggunakan BasicsCodelabTheme
sebagai berikut:
BasicsCodelabTheme {
MyApp(modifier = Modifier.fillMaxSize())
}
Karena BasicsCodelabTheme
menggabungkan MaterialTheme
secara internal, gaya MyApp
disesuaikan dengan properti yang ditentukan dalam tema. Dari composable turunan, Anda dapat mengambil tiga properti MaterialTheme
: colorScheme
, typography
, dan shapes
. Gunakan properti tersebut untuk menetapkan gaya header salah satu Text
Anda:
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding.coerceAtLeast(0.dp))
) {
Text(text = "Hello, ")
Text(text = name, style = MaterialTheme.typography.headlineMedium)
}
Composable Text
pada contoh di atas menetapkan TextStyle
baru. Anda dapat membuat TextStyle
Anda sendiri, atau mengambil gaya yang ditentukan tema menggunakan MaterialTheme.typography
, mana pun yang lebih disukai. Konstruksi ini memberi Anda akses ke gaya teks yang ditentukan Material, seperti displayLarge, headlineMedium, titleSmall, bodyLarge, labelMedium
, dll. Dalam contoh, Anda menggunakan gaya headlineMedium
yang ditentukan dalam tema.
Sekarang, bangun untuk melihat teks dengan gaya baru:
Secara umum, jauh lebih baik untuk mempertahankan warna, bentuk, dan gaya font Anda di dalam MaterialTheme
. Misalnya, mode gelap akan sulit diterapkan jika Anda melakukan hard code warna dan akan memerlukan banyak pekerjaan yang rentan error untuk diperbaiki.
Namun, terkadang Anda harus sedikit menyimpang dari pilihan warna dan gaya font. Dalam situasi tersebut, lebih baik warna atau gaya didasarkan pada warna atau gaya yang sudah ada.
Untuk melakukannya, Anda dapat mengubah gaya yang telah ditentukan menggunakan fungsi copy
. Buat angka lebih tebal:
import androidx.compose.ui.text.font.FontWeight
// ...
Text(
text = name,
style = MaterialTheme.typography.headlineMedium.copy(
fontWeight = FontWeight.ExtraBold
)
)
Dengan cara ini, jika harus mengubah jenis font atau atribut headlineMedium
lainnya, Anda tidak perlu khawatir dengan penyimpangan kecil.
Sekarang, hasil yang akan tampak di jendela pratinjau adalah sebagai berikut:
Menyiapkan pratinjau mode gelap
Saat ini, pratinjau hanya menampilkan tampilan aplikasi dalam mode terang. Tambahkan anotasi @Preview
tambahan ke GreetingPreview
dengan UI_MODE_NIGHT_YES
:
import android.content.res.Configuration.UI_MODE_NIGHT_YES
@Preview(
showBackground = true,
widthDp = 320,
uiMode = UI_MODE_NIGHT_YES,
name = "GreetingPreviewDark"
)
@Preview(showBackground = true, widthDp = 320)
@Composable
fun GreetingPreview() {
BasicsCodelabTheme {
Greetings()
}
}
Tindakan ini akan menambahkan pratinjau dalam mode gelap.
Menyesuaikan tema aplikasi
Anda dapat menemukan semua yang terkait dengan tema saat ini dalam file di dalam folder ui/theme
. Misalnya, warna default yang telah kita gunakan sejauh ini ditentukan di Color.kt
.
Mari kita mulai dengan menentukan warna baru. Tambahkan ini ke Color.kt
:
val Navy = Color(0xFF073042)
val Blue = Color(0xFF4285F4)
val LightBlue = Color(0xFFD7EFFE)
val Chartreuse = Color(0xFFEFF7CF)
Sekarang, tetapkan ke palet MaterialTheme
di Theme.kt
:
private val LightColorScheme = lightColorScheme(
surface = Blue,
onSurface = Color.White,
primary = LightBlue,
onPrimary = Navy
)
Jika Anda kembali ke MainActivity.kt
dan memuat ulang pratinjau, warna pratinjau tidak akan benar-benar berubah. Hal ini karena secara default, Pratinjau akan menggunakan warna dinamis. Anda dapat melihat logika untuk menambahkan pewarnaan dinamis di Theme.kt
menggunakan parameter boolean dynamicColor
.
Untuk melihat versi non-adaptif skema warna, jalankan aplikasi di perangkat dengan API level yang lebih rendah dari 31 (sesuai dengan Android S, tempat warna adaptif diperkenalkan). Anda akan melihat warna baru:
Di Theme.kt
, tentukan palet untuk warna gelap:
private val DarkColorScheme = darkColorScheme(
surface = Blue,
onSurface = Navy,
primary = Navy,
onPrimary = Chartreuse
)
Saat sekarang menjalankan aplikasi, kita akan melihat cara kerja warna gelap:
Kode akhir untuk Theme.kt
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.ViewCompat
private val DarkColorScheme = darkColorScheme(
surface = Blue,
onSurface = Navy,
primary = Navy,
onPrimary = Chartreuse
)
private val LightColorScheme = lightColorScheme(
surface = Blue,
onSurface = Color.White,
primary = LightBlue,
onPrimary = Navy
)
@Composable
fun BasicsCodelabTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
(view.context as Activity).window.statusBarColor = colorScheme.primary.toArgb()
ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
13. Sentuhan akhir
Pada langkah ini, Anda akan menerapkan hal yang sudah diketahui dan mempelajari konsep baru hanya dengan beberapa petunjuk. Anda akan membuat ini:
Mengganti tombol dengan ikon
- Gunakan composable
IconButton
bersama denganIcon
turunan. - Gunakan
Icons.Filled.ExpandLess
danIcons.Filled.ExpandMore
, yang tersedia di artefakmaterial-icons-extended
. Tambahkan baris berikut ke dependensi di fileapp/build.gradle.kts
Anda.
implementation("androidx.compose.material:material-icons-extended")
- Ubah padding untuk memperbaiki penyejajaran.
- Tambahkan deskripsi konten untuk aksesibilitas (lihat "Menggunakan resource string" di bawah).
Menggunakan resource string
Deskripsi konten untuk "Tampilkan lebih banyak" dan "tampilkan lebih sedikit" harus ada dan Anda dapat menambahkannya dengan pernyataan if
sederhana:
contentDescription = if (expanded) "Show less" else "Show more"
Namun, string hard code adalah praktik yang buruk dan Anda harus mendapatkannya dari file strings.xml
.
Anda bisa menggunakan "Extract string resource" di setiap string, yang tersedia di "Context Actions" di Android Studio untuk melakukannya secara otomatis.
Atau, buka app/src/res/values/strings.xml
dan tambahkan resource berikut:
<string name="show_less">Show less</string>
<string name="show_more">Show more</string>
Menampilkan lebih banyak
Teks "Composem ipsum" muncul dan menghilang, yang memicu perubahan ukuran setiap kartu.
- Tambahkan
Text
baru ke Kolom di dalamGreeting
yang ditampilkan saat item diperluas. - Hapus
extraPadding
dan terapkan pengubahanimateContentSize
keRow
. Hal ini akan mengotomatiskan proses pembuatan animasi, yang akan sulit dilakukan secara manual. Selain itu, tindakan ini juga menghilangkan kebutuhan akancoerceAtLeast
.
Menambahkan elevasi dan bentuk
- Anda dapat menggunakan pengubah
shadow
bersama dengan pengubahclip
untuk mendapatkan tampilan kartu. Namun, ada composable Material yang melakukan hal tersebut:Card
. Anda dapat mengubah warnaCard
dengan memanggilCardDefaults.cardColors
dan mengganti warna yang ingin diubah.
Kode akhir
package com.example.basicscodelab
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons.Filled
import androidx.compose.material.icons.filled.ExpandLess
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.basicscodelab.ui.theme.BasicsCodelabTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
MyApp(modifier = Modifier.fillMaxSize())
}
}
}
}
@Composable
fun MyApp(modifier: Modifier = Modifier) {
var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) }
Surface(modifier, color = MaterialTheme.colorScheme.background) {
if (shouldShowOnboarding) {
OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
} else {
Greetings()
}
}
}
@Composable
fun OnboardingScreen(
onContinueClicked: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Welcome to the Basics Codelab!")
Button(
modifier = Modifier.padding(vertical = 24.dp),
onClick = onContinueClicked
) {
Text("Continue")
}
}
}
@Composable
private fun Greetings(
modifier: Modifier = Modifier,
names: List<String> = List(1000) { "$it" }
) {
LazyColumn(modifier = modifier.padding(vertical = 4.dp)) {
items(items = names) { name ->
Greeting(name = name)
}
}
}
@Composable
private fun Greeting(name: String, modifier: Modifier = Modifier) {
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.primary
),
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
CardContent(name)
}
}
@Composable
private fun CardContent(name: String) {
var expanded by rememberSaveable { mutableStateOf(false) }
Row(
modifier = Modifier
.padding(12.dp)
.animateContentSize(
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
) {
Column(
modifier = Modifier
.weight(1f)
.padding(12.dp)
) {
Text(text = "Hello, ")
Text(
text = name, style = MaterialTheme.typography.headlineMedium.copy(
fontWeight = FontWeight.ExtraBold
)
)
if (expanded) {
Text(
text = ("Composem ipsum color sit lazy, " +
"padding theme elit, sed do bouncy. ").repeat(4),
)
}
}
IconButton(onClick = { expanded = !expanded }) {
Icon(
imageVector = if (expanded) Filled.ExpandLess else Filled.ExpandMore,
contentDescription = if (expanded) {
stringResource(R.string.show_less)
} else {
stringResource(R.string.show_more)
}
)
}
}
}
@Preview(
showBackground = true,
widthDp = 320,
uiMode = UI_MODE_NIGHT_YES,
name = "GreetingPreviewDark"
)
@Preview(showBackground = true, widthDp = 320)
@Composable
fun GreetingPreview() {
BasicsCodelabTheme {
Greetings()
}
}
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
BasicsCodelabTheme {
OnboardingScreen(onContinueClicked = {})
}
}
@Preview
@Composable
fun MyAppPreview() {
BasicsCodelabTheme {
MyApp(Modifier.fillMaxSize())
}
}
14. Selamat
Selamat! Anda telah mempelajari dasar-dasar Compose!
Solusi untuk codelab
Anda dapat memperoleh kode untuk solusi codelab ini dari GitHub:
$ git clone https://github.com/android/codelab-android-compose
Atau, Anda dapat mendownload repositori sebagai file Zip:
Apa selanjutnya?
Lihat codelab lain di jalur Compose:
Bacaan lebih lanjut
- Video tutorial coding dasar-dasar Jetpack Compose
- Panduan Model mental Compose
- Aplikasi contoh untuk melihat cara kerja Compose