Jetpack Compose mempermudah desain dan pembuatan UI aplikasi Anda. Dokumen ini menjelaskan beberapa elemen penyusun yang disediakan Compose untuk membantu Anda mengatur elemen UI, dan menunjukkan cara membuat tata letak yang lebih khusus saat Anda memerlukannya.
Sasaran tata letak di Compose
Implementasi Jetpack Compose dari sistem tata letak memiliki dua sasaran utama: menjadi berperforma tinggi, dan memudahkan penulisan tata letak kustom. Performa tinggi dicapai di Compose dengan melarang pengukuran tata letak turunan lebih dari sekali. Jika beberapa pengukuran diperlukan, Compose memiliki sistem khusus yang siap digunakan yaitu pengukuran intrinsik. Anda dapat membaca selengkapnya tentang fitur ini di bagian pengukuran intrinsik.
Dasar-dasar Fungsi yang dapat dikomposisi
Fungsi yang dapat dikomposisi adalah elemen dasar penyusun Compose. Fungsi yang dapat dikomposisi
adalah fungsi yang menampilkan Unit
yang mendeskripsikan beberapa bagian UI Anda. Fungsi ini mengambil
beberapa input dan menghasilkan apa yang ditampilkan di layar. Untuk mengetahui informasi selengkapnya tentang
composable, lihat dokumentasi Model
mental Compose.
Fungsi yang dapat dikomposisi dapat membuat beberapa elemen UI. Namun, jika Anda tidak memberikan panduan tentang cara mengaturnya, Compose mungkin akan mengatur elemen dengan cara yang tidak Anda sukai. Misalnya, kode ini menghasilkan dua elemen teks:
@Composable
fun ArtistCard() {
Text("Alfred Sisley")
Text("3 minutes ago")
}
Tanpa panduan tentang cara Anda ingin mengaturnya, Compose akan menumpuk elemen teks di atas satu sama lain, sehingga tidak dapat dibaca:
Compose menyediakan kumpulan tata letak yang siap pakai untuk membantu Anda mengatur elemen UI, dan memudahkan Anda menentukan tata letak sendiri yang lebih khusus.
Komponen tata letak standar
Sering kali, Anda cukup menggunakan elemen tata letak standar Compose.
Gunakan
Column
untuk menempatkan item secara vertikal di layar.
@Composable
fun ArtistCard() {
Column {
Text("Alfred Sisley")
Text("3 minutes ago")
}
}
Demikian pula, gunakan
Row
untuk menempatkan item secara horizontal di layar. Baik Column
maupun Row
mendukung
konfigurasi perataan elemen di dalamnya.
@Composable
fun ArtistCard(artist: Artist) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(/*...*/)
Column {
Text(artist.name)
Text(artist.lastSeenOnline)
}
}
}
Gunakan Box
untuk menempatkan satu elemen di atas elemen lainnya.
Terkadang, hanya elemen penyusun inilah yang Anda butuhkan. Anda dapat menulis fungsi yang dapat dikomposisi sendiri untuk menggabungkan tata letak ini ke dalam tata letak yang lebih rumit, yang sesuai dengan aplikasi Anda.
Untuk menyetel posisi turunan dalam Row
, setel argumen horizontalArrangement
dan verticalAlignment
. Untuk Column
, setel argumen verticalArrangement
dan
horizontalAlignment
:
@Composable
fun ArtistCard(artist: Artist) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End
) {
Image(/*...*/)
Column { /*...*/ }
}
}
Pengubah
Pengubah memungkinkan Anda mendekorasi atau meningkatkan composable. Pengubah memungkinkan Anda melakukan hal-hal berikut:
- Mengubah ukuran, tata letak, perilaku, dan tampilan yang dapat dikomposisi
- Menambahkan informasi, seperti label aksesibilitas
- Memproses input pengguna
- Menambahkan interaksi tingkat tinggi, seperti membuat elemen yang dapat diklik, dapat di-scroll, dapat ditarik, atau dapat di-zoom
Pengubah adalah objek Kotlin standar. Buat pengubah dengan memanggil salah satu fungsi class
Modifier
. Anda dapat menggabungkan fungsi-fungsi berikut untuk mengomposisikannya:
@Composable
fun ArtistCard(
artist: Artist,
onClick: () -> Unit
) {
val padding = 16.dp
Column(
Modifier
.clickable(onClick = onClick)
.padding(padding)
.fillMaxWidth()
) {
Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
Spacer(Modifier.size(padding))
Card(elevation = 4.dp) { /*...*/ }
}
}
Dalam kode di atas, perhatikan berbagai fungsi pengubah yang digunakan bersama-sama.
clickable
membuat composable bereaksi terhadap input pengguna dan menampilkan ripple.padding
menempatkan ruang di sekitar elemen.fillMaxWidth
membuat fungsi yang dapat dikomposisi mengisi lebar maksimum yang diberikan kepadanya dari induknya.size()
menentukan lebar dan tinggi yang dipilih untuk elemen.
Pentingnya urutan pengubah
Urutan fungsi pengubah signifikan. Karena setiap fungsi melakukan
perubahan pada Modifier
yang ditampilkan oleh fungsi sebelumnya, urutan tersebut
akan memengaruhi hasil akhir. Mari kita lihat contohnya:
@Composable
fun ArtistCard(/*...*/) {
val padding = 16.dp
Column(
Modifier
.clickable(onClick = onClick)
.padding(padding)
.fillMaxWidth()
) {
// rest of the implementation
}
}
Pada kode di atas, seluruh area dapat diklik, termasuk padding
di sekitarnya, karena pengubah padding
telah diterapkan setelah pengubah clickable
. Jika urutan pengubah dibalik, ruang yang ditambahkan oleh padding
tidak akan
merespons input pengguna:
@Composable
fun ArtistCard(/*...*/) {
val padding = 16.dp
Column(
Modifier
.padding(padding)
.clickable(onClick = onClick)
.fillMaxWidth()
) {
// rest of the implementation
}
}
Pengubah bawaan
Jetpack Compose menyediakan daftar pengubah bawaan untuk membantu Anda mendekorasi atau
meningkatkan composable. Beberapa pengubah seperti padding
, clickable
, dan
fillMaxWidth
telah diperkenalkan. Berikut daftar
pengubah umum lainnya:
ukuran
Secara default, tata letak yang disediakan di Compose menggabungkan turunannya. Namun,
Anda dapat menyetel ukuran dengan menggunakan pengubah size
:
@Composable
fun ArtistCard(/*...*/) {
Row(
modifier = Modifier.size(width = 400.dp, height = 100.dp)
) {
Image(/*...*/)
Column { /*...*/ }
}
}
Perhatikan bahwa ukuran yang Anda tentukan mungkin tidak diterapkan jika tidak memenuhi batasan yang berasal dari induk tata letak. Jika Anda mengharuskan ukuran composable
diperbaiki, terlepas dari batasan yang masuk,
gunakan pengubah requiredSize
:
@Composable
fun ArtistCard(/*...*/) {
Row(
modifier = Modifier.size(width = 400.dp, height = 100.dp)
) {
Image(
/*...*/
modifier = Modifier.requiredSize(150.dp)
)
Column { /*...*/ }
}
}
Dalam contoh ini, meski induk height
disetel ke 100.dp
, tinggi
Image
akan menjadi 150.dp
, karena pengubah requiredSize
lebih diutamakan.
Jika Anda ingin tata letak turunan mengisi semua tinggi yang tersedia yang diizinkan oleh
induk, tambahkan pengubah fillMaxHeight
(Compose juga menyediakan
fillMaxSize
dan fillMaxWidth
):
@Composable
fun ArtistCard(/*...*/) {
Row(
modifier = Modifier.size(width = 400.dp, height = 100.dp)
) {
Image(
/*...*/
modifier = Modifier.fillMaxHeight()
)
Column { /*...*/ }
}
}
Jika Anda ingin menambahkan padding di atas dasar pengukuran teks sehingga Anda mencapai
jarak tertentu dari bagian atas tata letak ke dasar pengukuran, gunakan
pengubah paddingFromBaseline
:
@Composable
fun ArtistCard(artist: Artist) {
Row(/*...*/) {
Column {
Text(
text = artist.name,
modifier = Modifier.paddingFromBaseline(top = 50.dp)
)
Text(artist.lastSeenOnline)
}
}
}
Offset
Untuk menempatkan tata letak relatif ke posisi aslinya, tambahkan pengubah offset
dan setel offset dalam sumbu x dan y. Offset bisa positif dan tidak positif. Perbedaan antara padding
dan offset
adalah
penambahan offset
pada composable tidak mengubah pengukurannya:
@Composable
fun ArtistCard(artist: Artist) {
Row(/*...*/) {
Column {
Text(artist.name)
Text(
text = artist.lastSeenOnline,
modifier = Modifier.offset(x = 4.dp)
)
}
}
}
Pengubah offset
diterapkan secara horizontal sesuai dengan arah tata letak.
Dalam konteks kiri-ke-kanan, offset
positif menggeser elemen ke
kanan, sedangkan dalam konteks kanan-ke-kiri, elemen akan bergeser ke kiri.
Jika Anda perlu menyetel offset tanpa mempertimbangkan arah tata letak, lihat pengubah
absoluteOffset
,
ketika nilai offset positif selalu menggeser elemen ke
kanan.
Keamanan jenis di Compose
Di Compose, ada pengubah yang hanya berfungsi saat diterapkan ke turunan
composable tertentu. Misalnya, jika Anda ingin membuat turunan sebesar induk
Box
tanpa memengaruhi ukuran Box
,
gunakan pengubah
matchParentSize
.
Compose menerapkan keamanan jenis ini melalui cakupan kustom.
Misalnya, matchParentSize
hanya tersedia di
BoxScope
.
Oleh karena itu, hanya dapat digunakan saat turunan digunakan dalam Box
.
Pengubah cakupan memberi tahu induk tentang beberapa informasi turunan yang harus diketahui induk. Ini juga biasa disebut sebagai pengubah data induk. Internalnya berbeda dari pengubah tujuan umum, tetapi dari perspektif penggunaan, perbedaan ini tidak penting.
matchParentSize di Box
Seperti yang disebutkan di atas, jika Anda ingin tata letak turunan berukuran sama dengan Box
induk
tanpa memengaruhi ukuran Box
, gunakan pengubah matchParentSize
.
Perhatikan bahwa matchParentSize
hanya tersedia dalam cakupan Box
, artinya
hanya berlaku untuk turunan langsung dari composable Box
.
Dalam contoh di bawah ini, Spacer
turunan mengambil ukurannya dari Box
induknya,
yang kemudian mengambil ukurannya dari turunan terbesar,
dalam hal ini ArtistCard
.
@Composable
fun MatchParentSizeComposable() {
Box {
Spacer(Modifier.matchParentSize().background(Color.LightGray))
ArtistCard()
}
}
Jika fillMaxSize
digunakan sebagai pengganti matchParentSize
, Spacer
akan mengambil
semua ruang tersedia yang diizinkan untuk induk, yang pada akhirnya menyebabkan induk
memperluas dan mengisi semua ruang yang tersedia.
bobot dalam Baris dan Kolom
Seperti yang telah Anda lihat di bagian sebelumnya tentang Padding dan
ukuran, secara default, ukuran composable ditentukan oleh konten
yang digabungkan. Anda dapat menyetel ukuran composable agar fleksibel dalam induknya
menggunakan Pengubah weight
yang hanya tersedia di RowScope
, dan
ColumnScope
.
Mari kita ambil Row
yang berisi dua composable Box
.
Kotak pertama diberikan dua kali weight
kotak kedua, sehingga diberi lebar
dua kali lipat. Karena Row
selebar 210.dp
, Box
pertama selebar 140.dp
, dan
kedua selebar 70.dp
:
@Composable
fun ArtistCard(/*...*/) {
Row(
modifier = Modifier.fillMaxWidth()
) {
Image(
/*...*/
modifier = Modifier.weight(2f)
)
Column(
modifier = Modifier.weight(1f)
) {
/*...*/
}
}
}
Tata letak yang dapat di-scroll
Pelajari lebih lanjut tata letak yang dapat di-scroll dalam Dokumentasi gestur Compose.
Untuk daftar dan daftar lambat, lihat Dokumentasi daftar Compose.
Layout responsif
Tata letak harus didesain dengan mempertimbangkan berbagai orientasi layar dan ukuran faktor bentuk. Compose menawarkan beberapa mekanisme untuk memfasilitasi penyesuaian tata letak composable ke berbagai konfigurasi layar.
Batasan
Untuk mengetahui batasan yang berasal dari induk dan mendesain tata letak yang sesuai, Anda dapat menggunakan BoxWithConstraints
. Batasan pengukuran dapat ditemukan dalam cakupan lambda konten. Anda dapat menggunakan batasan pengukuran ini untuk membuat tata letak yang berbeda untuk konfigurasi layar yang berbeda:
@Composable
fun WithConstraintsComposable() {
BoxWithConstraints {
Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
}
}
Tata letak berbasis slot
Compose menyediakan berbagai macam composable berdasarkan Desain Material dengan dependensi androidx.compose.material:material
(disertakan saat membuat project Compose di Android Studio) agar proses mem-build UI menjadi lebih mudah. Elemen seperti
Drawer
,
FloatingActionButton
,
dan TopAppBar
semuanya disediakan.
Komponen material banyak menggunakan API slot, yakni sebuah pola yang dibuat Compose
untuk menghadirkan lapisan penyesuaian di atas composable. Pendekatan ini membuat komponen lebih fleksibel, karena menerima elemen turunan yang dapat mengonfigurasi sendiri, bukan harus menampilkan setiap parameter konfigurasi turunan.
Slot memberikan ruang kosong di UI untuk diisi developer, sesuai keinginan mereka. Misalnya,
ini adalah slot yang dapat Anda sesuaikan dalam
TopAppBar
:
Komponen biasanya menggunakan lambda komponen content
( content: @Composable
() -> Unit
). API slot menampilkan beberapa parameter content
untuk penggunaan tertentu.
Misalnya, TopAppBar
memungkinkan Anda menyediakan konten untuk title
,
navigationIcon
, dan actions
.
Misalnya,
Scaffold
memungkinkan Anda menerapkan UI dengan struktur tata letak Desain Material dasar.
Scaffold
menyediakan slot untuk komponen Material tingkat atas yang paling umum,
seperti TopAppBar
,
BottomAppBar
,
FloatingActionButton
,
dan Drawer
. Dengan menggunakan
Scaffold
, sangat mudah untuk memastikan komponen ini diposisikan dengan benar dan
bekerja bersama dengan benar.
@Composable
fun HomeScreen(/*...*/) {
Scaffold(
drawerContent = { /*...*/ },
topBar = { /*...*/ },
content = { /*...*/ }
)
}
ConstraintLayout
ConstraintLayout
dapat membantu menempatkan composable relatif terhadap yang lain di layar,
dan merupakan alternatif untuk menggunakan beberapa elemen Row
, Column
, Box
bertingkat, dan
tata letak kustom. ConstraintLayout
berguna saat menerapkan tata letak yang lebih besar dengan
persyaratan perataan yang lebih rumit.
Untuk menggunakan ConstraintLayout
di Compose, Anda perlu menambahkan dependensi ini di
build.gradle
:
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.0-alpha08"
ConstraintLayout
di Compose berfungsi dengan DSL:
- Referensi dibuat menggunakan
createRefs()
ataucreateRefFor()
, dan setiap composable dalamConstraintLayout
harus memiliki referensi yang terkait dengannya. - Batasan diberikan menggunakan pengubah
constrainAs()
, yang menggunakan referensi sebagai parameter dan memungkinkan Anda menentukan batasannya di lambda body. - Batasan ditentukan menggunakan
linkTo()
atau metode berguna lainnya. parent
adalah referensi yang sudah ada dan dapat digunakan untuk menentukan batasan terhadap komponenConstraintLayout
itu sendiri.
Berikut ini contoh komponen yang menggunakan ConstraintLayout
:
@Composable
fun ConstraintLayoutContent() {
ConstraintLayout {
// Create references for the composables to constrain
val (button, text) = createRefs()
Button(
onClick = { /* Do something */ },
// Assign reference "button" to the Button composable
// and constrain it to the top of the ConstraintLayout
modifier = Modifier.constrainAs(button) {
top.linkTo(parent.top, margin = 16.dp)
}
) {
Text("Button")
}
// Assign reference "text" to the Text composable
// and constrain it to the bottom of the Button composable
Text("Text", Modifier.constrainAs(text) {
top.linkTo(button.bottom, margin = 16.dp)
})
}
}
Kode ini membatasi bagian atas Button
untuk induk dengan margin
16.dp
dan Text
di bagian bawah Button
, juga dengan margin
16.dp
.
Untuk contoh selengkapnya tentang cara bekerja dengan ConstraintLayout
, cobalah codelab tata letak.
API terpisah
Pada ConstraintLayout
contoh,
batasan ditentukan sebagai bagian dari tata letak, dengan pengubah dalam composable
yang diterapkan. Namun, pada beberapa situasi, lebih baik memisahkan
batasan itu dari tata letak yang diterapkan. Misalnya, Anda mungkin ingin
mengubah batasan berdasarkan konfigurasi layar, atau membuat animasi di antara dua
kumpulan batasan.
Untuk kasus seperti ini, Anda dapat menggunakan ConstraintLayout
dengan cara lain:
- Teruskan
ConstraintSet
sebagai parameter untukConstraintLayout
. - Tetapkan referensi yang dibuat di
ConstraintSet
untuk komponen menggunakan pengubahlayoutId
.
@Composable
fun DecoupledConstraintLayout() {
BoxWithConstraints {
val constraints = if (minWidth < 600.dp) {
decoupledConstraints(margin = 16.dp) // Portrait constraints
} else {
decoupledConstraints(margin = 32.dp) // Landscape constraints
}
ConstraintLayout(constraints) {
Button(
onClick = { /* Do something */ },
modifier = Modifier.layoutId("button")
) {
Text("Button")
}
Text("Text", Modifier.layoutId("text"))
}
}
}
private fun decoupledConstraints(margin: Dp): ConstraintSet {
return ConstraintSet {
val button = createRefFor("button")
val text = createRefFor("text")
constrain(button) {
top.linkTo(parent.top, margin = margin)
}
constrain(text) {
top.linkTo(button.bottom, margin)
}
}
}
Kemudian, saat Anda perlu mengubah batasan, Anda dapat meneruskan ConstraintSet
yang berbeda.
Mempelajari lebih lanjut
Pelajari ConstraintLayout di Compose lebih lanjut di bagian Tata Letak Batasan
pada codelab Tata Letak di Jetpack Compose,
dan lihat cara kerja API di
Contoh Compose yang menggunakan ConstraintLayout
.
Tata letak kustom
Dalam Compose, elemen UI diwakili oleh fungsi yang dapat dikomposisi yang memancarkan UI saat dipanggil, yang kemudian ditambahkan ke hierarki UI yang dirender di layar. Setiap elemen UI memiliki satu induk dan kemungkinan banyak turunan. Setiap elemen juga berada dalam induknya, yang ditetapkan sebagai posisi (x, y), dan ukuran, yang ditetapkan sebagai width
dan height
.
Induk menentukan batasan untuk elemen turunannya. Sebuah elemen diminta untuk menentukan ukurannya dalam batasan tersebut. Batasan membatasi width
dan height
minimum dan maksimum dari sebuah elemen. Jika suatu elemen memiliki elemen turunan, elemen tersebut dapat mengukur setiap turunan untuk membantu menentukan ukurannya. Setelah elemen menentukan dan melaporkan ukurannya sendiri, elemen tersebut memiliki peluang untuk menentukan cara menempatkan elemen turunannya secara relatif terhadap dirinya sendiri, seperti yang dijelaskan secara mendetail dalam Membuat tata letak kustom.
Pengukuran single-pass baik untuk performa, memungkinkan Compose menangani hierarki UI yang dalam secara efisien. Jika elemen tata letak mengukur turunannya dua kali dan turunan itu mengukur salah satu turunannya dua kali dan seterusnya, satu upaya untuk membuat tata letak seluruh UI harus melakukan banyak pekerjaan, sehingga sulit untuk menjaga performa aplikasi Anda tetap baik. Namun, ada kalanya Anda memerlukan informasi tambahan selain apa yang didapatkan oleh pengukuran satu turunan. Ada beberapa pendekatan yang dapat mengatasi situasi seperti tersebut secara efisien, yang dibahas di bagian pengukuran intrinsik.
Penggunaan cakupan menentukan kapan Anda dapat mengukur dan menempatkan turunan. Mengukur tata letak hanya dapat dilakukan selama pengukuran dan tata letak diteruskan, serta turunan hanya dapat ditempatkan selama tata letak diteruskan, dan hanya setelah turunan diukur. Ini diterapkan pada waktu kompilasi karena cakupan Compose seperti MeasureScope, dan PlacementScope.
Menggunakan pengubah tata letak
Anda dapat menggunakan pengubah layout
untuk mengubah cara elemen diukur dan ditata letak. Layout
adalah lambda; parameternya mencakup komponen yang dapat diukur,
diteruskan sebagai measurable
, dan batasan composable tersebut yang diteruskan, sebagai
constraints
. Pengubah tata letak kustom dapat terlihat seperti ini:
fun Modifier.customLayoutModifier(...) =
this.layout { measurable, constraints ->
...
})
Mari kita tampilkan Text
di layar dan kontrol jarak dari atas ke
bagian dasar baris pertama teks. Ini persis yang dilakukan oleh pengubah
paddingFromBaseline
. Kami menerapkannya di sini sebagai contoh.
Untuk melakukannya, gunakan pengubah layout
untuk secara manual menempatkan composable di
layar. Berikut adalah perilaku yang diinginkan saat padding atas Text
disetel 24.dp
:
Inilah kode untuk menghasilkan ruang tersebut:
fun Modifier.firstBaselineToTop(
firstBaselineToTop: Dp
) = layout { measurable, constraints ->
// Measure the composable
val placeable = measurable.measure(constraints)
// Check the composable has a first baseline
check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
val firstBaseline = placeable[FirstBaseline]
// Height of the composable with padding - first baseline
val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
val height = placeable.height + placeableY
layout(placeable.width, height) {
// Where the composable gets placed
placeable.placeRelative(0, placeableY)
}
}
Inilah yang terjadi dalam kode tersebut:
- Di parameter lambda
measurable
, Anda mengukurText
yang ditunjukkan oleh parameter terukur dengan memanggilmeasurable.measure(constraints)
. - Anda menentukan ukuran komponen dengan memanggil metode
layout(width, height)
, yang juga memberikan lambda yang digunakan untuk menempatkan elemen yang digabungkan. Dalam hal ini, tempatnya adalah tinggi antara bagian dasar pengukuran terakhir dan padding atas yang ditambahkan. - Anda dapat memosisikan elemen yang digabungkan di layar dengan memanggil
placeable.place(x, y)
. Jika elemen yang digabungkan tidak ditempatkan, elemen tersebut tidak akan terlihat. Posisiy
sesuai dengan padding atas - yaitu posisi bagian dasar pengukuran pertama teks.
Untuk memverifikasi bahwa tindakan ini berfungsi sebagaimana diharapkan, gunakan pengubah ini pada Text
:
@Preview
@Composable
fun TextWithPaddingToBaselinePreview() {
MyApplicationTheme {
Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
}
}
@Preview
@Composable
fun TextWithNormalPaddingPreview() {
MyApplicationTheme {
Text("Hi there!", Modifier.padding(top = 32.dp))
}
}
Membuat tata letak kustom
Pengubah layout
hanya mengubah composable pemanggilan. Untuk mengukur dan menata letak beberapa composable, gunakan composable Layout
. Composable ini memungkinkan Anda
mengukur dan menata letak turunan secara manual. Semua tata letak dengan tingkat lebih tinggi
seperti Column
dan Row
dibuat dengan composable Layout
.
Mari membuat versi Column
yang sangat dasar. Sebagian besar tata letak kustom mengikuti pola
ini:
@Composable
fun MyBasicColumn(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
children = content
) { measurables, constraints ->
// measure and position children given constraints logic here
}
}
Sama dengan pengubah layout
, measurables
adalah daftar turunan yang perlu diukur dan constraints
adalah pembatas dari induk.
Dengan logika yang sama seperti sebelumnya, MyBasicColumn
dapat diterapkan seperti
ini:
@Composable
fun MyBasicColumn(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// Don't constrain child views further, measure them with given constraints
// List of measured children
val placeables = measurables.map { measurable ->
// Measure each children
measurable.measure(constraints)
}
// Set the size of the layout as big as it can
layout(constraints.maxWidth, constraints.maxHeight) {
// Track the y co-ord we have placed children up to
var yPosition = 0
// Place children in the parent layout
placeables.forEach { placeable ->
// Position item on the screen
placeable.placeRelative(x = 0, y = yPosition)
// Record the y co-ord placed up to
yPosition += placeable.height
}
}
}
}
Composable turunan dibatasi oleh batasan Layout
(tanpa batasan minHeight
), dan penempatan tersebut didasarkan pada yPosition
dari composable sebelumnya.
Berikut ini cara penggunaan komponen kustom tersebut:
@Composable
fun CallingComposable(modifier: Modifier = Modifier) {
MyBasicColumn(modifier.padding(8.dp)) {
Text("MyBasicColumn")
Text("places items")
Text("vertically.")
Text("We've done it by hand!")
}
}
Cara kerja tata letak kustom
Pelajari tata letak dan pengubah kustom lebih lanjut di codelab Tata letak di Jetpack Compose, dan lihat cara kerja API di Contoh Compose yang membuat tata letak kustom.
Arah tata letak
Ubah arah tata letak composable dengan mengubah
lokal komposisi LocalLayoutDirection
.
Jika Anda menempatkan composable secara manual di layar, LayoutDirection
adalah bagian dari LayoutScope
dari pengubah layout
atau composable Layout
.
Saat menggunakan layoutDirection
, tempatkan composable menggunakan place
. Tidak seperti metode
placeRelative
,
place
tidak berubah berdasarkan arah tata letak
(dari kiri ke kanan versus kanan ke kiri).
Pengukuran intrinsik
Salah satu aturan Compose adalah Anda seharusnya hanya mengukur turunan satu kali; mengukur turunan dua kali akan memunculkan pengecualian runtime. Namun, ada kalanya Anda memerlukan beberapa informasi tentang turunan Anda sebelum mengukurnya.
Intrinsik memungkinkan Anda membuat kueri turunan sebelum benar-benar diukur.
Ke composable, Anda dapat meminta intrinsicWidth
atau intrinsicHeight
:
(min|max)IntrinsicWidth
: Dengan tinggi ini, berapa lebar minimum/maksimum yang dapat Anda gambar dengan benar?(min|max)IntrinsicHeight
: Dengan lebar ini, berapa tinggi minimum/maksimum yang dapat Anda gambar dengan benar?
Misalnya, jika Anda meminta minIntrinsicHeight
dari Text
dengan width
yang tidak terbatas, variabel ini akan menampilkan height
dari Text
seolah-olah teks digambar dalam
satu baris.
Cara kerja intrinsik
Bayangkan kita ingin membuat composable yang menampilkan dua teks di layar yang dipisahkan oleh pemisah seperti ini:
Bagaimana cara melakukannya? Kita dapat memiliki Row
dengan dua Text
di dalamnya yang meluas sebanyak mungkin dan Divider
di tengah. Kita ingin Pemisahnya setinggi
Text
tertinggi dan tipis (width = 1.dp
).
@Composable
fun TwoTexts(
text1: String,
text2: String,
modifier: Modifier = Modifier
) {
Row(modifier = modifier) {
Text(
modifier = Modifier
.weight(1f)
.padding(start = 4.dp)
.wrapContentWidth(Alignment.Start),
text = text1
)
Divider(
color = Color.Black,
modifier = Modifier.fillMaxHeight().width(1.dp)
)
Text(
modifier = Modifier
.weight(1f)
.padding(end = 4.dp)
.wrapContentWidth(Alignment.End),
text = text2
)
}
}
@Preview
@Composable
fun TwoTextsPreview() {
MaterialTheme {
Surface {
TwoTexts(text1 = "Hi", text2 = "there")
}
}
}
Jika kita melihat pratinjau ini, kita melihat bahwa Pemisah meluas ke seluruh layar dan bukan itu yang kita inginkan:
Hal ini terjadi karena Row
mengukur setiap turunan secara individual dan tinggi Text
tidak dapat digunakan untuk membatasi Divider
. Kita ingin Divider
mengisi
ruang yang tersedia dengan ketinggian tertentu. Untuk itu, kita dapat menggunakan pengubah height(IntrinsicSize.Min)
.
height(IntrinsicSize.Min)
mengukur ukuran turunannya yang dipaksa untuk setinggi instrinsik minimum mereka. Karena bersifat berulang, ini akan membuat kueri Row
dan turunannya minIntrinsicHeight
.
Menerapkannya ke kode kita akan berfungsi seperti yang diharapkan:
@Composable
fun TwoTexts(
text1: String,
text2: String,
modifier: Modifier = Modifier
) {
Row(modifier = modifier.height(IntrinsicSize.Min)) {
Text(
modifier = Modifier
.weight(1f)
.padding(start = 4.dp)
.wrapContentWidth(Alignment.Start),
text = text1
)
Divider(
color = Color.Black,
modifier = Modifier.fillMaxHeight().width(1.dp)
)
Text(
modifier = Modifier
.weight(1f)
.padding(end = 4.dp)
.wrapContentWidth(Alignment.End),
text = text2
)
}
}
Dengan pratinjau:
minIntrinsicHeight
composable Row
akan menjadi
minIntrinsicHeight
maksimum dari turunannya. Divider element's
minIntrinsicHeight
adalah 0 karena tidak menempati ruang jika tidak ada batasan
yang diberikan; Text
minIntrinsicHeight
akan menjadi teks yang diberikan
width
tertentu. Oleh karena itu, batasan height
elemen Row
akan menjadi
minIntrinsicHeight
maksimum dari Text
. Divider
kemudian akan memperluas height
ke
batasan height
yang diberikan oleh Row
.
Intrinsik di tata letak kustom
Saat membuat pengubah Layout
atau layout
kustom, pengukuran intrinsik
dihitung secara otomatis berdasarkan perkiraan. Oleh karena itu,
penghitungan mungkin tidak tepat untuk semua tata letak. API ini menawarkan opsi
untuk mengganti perilaku default tersebut.
Untuk menentukan ukuran instrinsik Layout
kustom,
ganti minIntrinsicWidth
, minIntrinsicHeight
, maxIntrinsicWidth
,
dan maxIntrinsicHeight
dari antarmuka MeasurePolicy
saat membuatnya.
@Composable
fun MyCustomComposable(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
return object : MeasurePolicy {
override fun MeasureScope.measure(
measurables: List<Measurable>,
constraints: Constraints
): MeasureResult {
// Measure and layout here
}
override fun IntrinsicMeasureScope.minIntrinsicWidth(
measurables: List<IntrinsicMeasurable>,
height: Int
) = {
// Logic here
}
// Other intrinsics related methods have a default value,
// you can override only the methods that you need.
}
}
Saat membuat pengubah layout
kustom, ganti metode terkait
di antarmuka LayoutModifier
.
fun Modifier.myCustomModifier(/* ... */) = this.then(object : LayoutModifier {
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
): MeasureResult {
// Measure and layout here
}
override fun IntrinsicMeasureScope.minIntrinsicWidth(
measurable: IntrinsicMeasurable,
height: Int
): Int = {
// Logic here
}
// Other intrinsics related methods have a default value,
// you can override only the methods that you need.
})
Mempelajari lebih lanjut
Pelajari pengukuran intrinsik lebih lanjut di bagian Intrinsik pada codelab Tata letak di Jetpack Compose.
Mempelajari lebih lanjut
Untuk mempelajari lebih lanjut, cobalah Tata letak di codelab Jetpack Compose.