Tata letak flow di Compose

FlowRow dan FlowColumn adalah composable yang mirip dengan Row dan Column, tetapi berbeda dalam item tersebut mengalir ke baris berikutnya saat penampung kehabisan ruang. Ini menghasilkan beberapa baris atau kolom. Jumlah item dalam baris juga dapat dikontrol dengan menyetel maxItemsInEachRow atau maxItemsInEachColumn. Anda sering dapat menggunakan FlowRow dan FlowColumn untuk membuat tata letak responsif. Konten tidak akan terpotong jika item terlalu besar untuk satu dimensi, dan menggunakan kombinasi maxItemsInEach* dengan Modifier.weight(weight) dapat membantu membangun tata letak yang mengisi/memperluas lebar baris atau kolom saat diperlukan.

Contoh umumnya adalah untuk chip atau UI pemfilteran:

5 chip di FlowRow yang menampilkan tambahan ke baris berikutnya jika tidak ada
lagi ruang yang tersedia.
Gambar 1. Contoh FlowRow

Penggunaan dasar

Untuk menggunakan FlowRow atau FlowColumn, buat composable ini dan tempatkan item di dalamnya yang harus mengikuti alur standar:

@Composable
private fun FlowRowSimpleUsageExample() {
    FlowRow(modifier = Modifier.padding(8.dp)) {
        ChipItem("Price: High to Low")
        ChipItem("Avg rating: 4+")
        ChipItem("Free breakfast")
        ChipItem("Free cancellation")
        ChipItem("£50 pn")
    }
}

Cuplikan ini menghasilkan UI yang ditampilkan di atas, dengan item yang otomatis mengalir ke baris berikutnya jika tidak ada lagi ruang di baris pertama.

Fitur tata letak alur

Tata letak flow memiliki fitur dan properti berikut yang dapat Anda gunakan untuk membuat berbagai tata letak di aplikasi Anda.

Pengaturan sumbu utama: pengaturan horizontal atau vertikal

Sumbu utama adalah sumbu tempat item ditata (misalnya, dalam FlowRow, item disusun secara horizontal). Parameter horizontalArrangement di FlowRow mengontrol cara ruang kosong didistribusikan di antara item.

Tabel berikut menunjukkan contoh setelan horizontalArrangement pada item untuk FlowRow:

Pengaturan horizontal ditetapkan pada FlowRow

Hasil

Arrangement.Start (Default)

Item yang disusun dengan awal

Arrangement.SpaceBetween

Pengaturan item dengan spasi di antaranya

Arrangement.Center

Item disusun di tengah

Arrangement.End

Item yang disusun di akhir

Arrangement.SpaceAround

Benda-benda yang diatur dengan ruang di sekelilingnya

Arrangement.spacedBy(8.dp)

Item yang diberi jarak dengan dp tertentu

Untuk FlowColumn, opsi serupa tersedia dengan verticalArrangement, dengan default Arrangement.Top.

Pengaturan sumbu silang

Sumbu silang adalah sumbu yang berlawanan arah dengan sumbu utama. Misalnya, dalam FlowRow, ini adalah sumbu vertikal. Untuk mengubah cara pengaturan keseluruhan konten dalam penampung dalam sumbu silang, gunakan verticalArrangement untuk FlowRow, dan horizontalArrangement untuk FlowColumn.

Untuk FlowRow, tabel berikut menunjukkan contoh penetapan verticalArrangement yang berbeda pada item:

Pengaturan vertikal ditetapkan di FlowRow

Hasil

Arrangement.Top (Default)

Pengaturan bagian atas container

Arrangement.Bottom

Pengaturan bawah container

Arrangement.Center

Pengaturan pusat container

Untuk FlowColumn, opsi serupa tersedia dengan horizontalArrangement. Pengaturan sumbu silang default adalah Arrangement.Start.

Perataan item individual

Anda dapat memosisikan setiap item dalam baris dengan perataan yang berbeda. Ini berbeda dengan verticalArrangement dan horizontalArrangement karena menyelaraskan item dalam baris saat ini. Anda dapat menerapkannya dengan Modifier.align().

Misalnya, jika item dalam FlowRow memiliki tinggi yang berbeda, baris akan mengambil tinggi item terbesar dan menerapkan Modifier.align(alignmentOption) ke item:

Perataan vertikal ditetapkan pada FlowRow

Hasil

Alignment.Top (Default)

Item sejajar dengan bagian atas

Alignment.Bottom

Item sejajar dengan bagian bawah

Alignment.CenterVertically

Item sejajar dengan bagian tengah

Untuk FlowColumn, opsi serupa tersedia. Perataan default adalah Alignment.Start.

Item maks dalam baris atau kolom

Parameter maxItemsInEachRow atau maxItemsInEachColumn menentukan jumlah item maksimum di sumbu utama untuk diizinkan dalam satu baris sebelum digabungkan ke baris berikutnya. Defaultnya adalah Int.MAX_INT, yang memungkinkan item sebanyak mungkin, selama ukurannya memungkinkan item tersebut sesuai dengan baris.

Misalnya, menyetel maxItemsInEachRow akan memaksa tata letak awal untuk hanya memiliki 3 item:

Tidak ada batas maksimum

maxItemsInEachRow = 3

Tidak ada batas maksimum yang ditetapkan pada baris alur Jumlah item maksimum yang ditetapkan di baris alur

Item alur pemuatan lambat

ContextualFlowRow dan ContextualFlowColumn adalah versi khusus dari FlowRow dan FlowColumn yang memungkinkan Anda memuat lambat konten baris atau kolom flow. File ini juga memberikan informasi di sekitar posisi item (indeks, nomor baris, dan ukuran yang tersedia), seperti apakah item berada di baris pertama. Hal ini berguna untuk set data besar dan jika Anda memerlukan informasi kontekstual tentang suatu item.

Parameter maxLines membatasi jumlah baris yang ditampilkan, dan parameter overflow menentukan apa yang harus ditampilkan saat tambahan item tercapai sehingga Anda dapat menentukan expandIndicator atau collapseIndicator kustom.

Misalnya, untuk menampilkan tombol "+ (jumlah item yang tersisa)" atau "Tampilkan Lebih Sedikit":

val totalCount = 40
var maxLines by remember {
    mutableStateOf(2)
}

val moreOrCollapseIndicator = @Composable { scope: ContextualFlowRowOverflowScope ->
    val remainingItems = totalCount - scope.shownItemCount
    ChipItem(if (remainingItems == 0) "Less" else "+$remainingItems", onClick = {
        if (remainingItems == 0) {
            maxLines = 2
        } else {
            maxLines += 5
        }
    })
}
ContextualFlowRow(
    modifier = Modifier
        .safeDrawingPadding()
        .fillMaxWidth(1f)
        .padding(16.dp)
        .wrapContentHeight(align = Alignment.Top)
        .verticalScroll(rememberScrollState()),
    verticalArrangement = Arrangement.spacedBy(4.dp),
    horizontalArrangement = Arrangement.spacedBy(8.dp),
    maxLines = maxLines,
    overflow = ContextualFlowRowOverflow.expandOrCollapseIndicator(
        minRowsToShowCollapse = 4,
        expandIndicator = moreOrCollapseIndicator,
        collapseIndicator = moreOrCollapseIndicator
    ),
    itemCount = totalCount
) { index ->
    ChipItem("Item $index")
}

Contoh baris alur kontekstual.
Gambar 2. Contoh ContextualFlowRow

Berat item

Berat menambah item berdasarkan faktornya dan ruang yang tersedia di garis tempatnya berada. Yang penting, ada perbedaan antara FlowRow dan Row terkait cara bobot digunakan untuk menghitung lebar item. Untuk Rows, bobot didasarkan pada semua item di Row. Dengan FlowRow, bobot didasarkan pada item dalam baris tempat item ditempatkan, bukan semua item dalam penampung FlowRow.

Misalnya, jika Anda memiliki 4 item yang semuanya berada dalam satu baris, masing-masing dengan bobot 1f, 2f, 1f dan 3f yang berbeda, berat totalnya adalah 7f. Ruang yang tersisa dalam baris atau kolom akan dibagi 7f. Kemudian, setiap lebar item akan dihitung menggunakan: weight * (remainingSpace / totalWeight).

Anda dapat menggunakan kombinasi item Modifier.weight dan max dengan FlowRow atau FlowColumn untuk membuat tata letak seperti petak. Pendekatan ini berguna untuk membuat tata letak responsif yang menyesuaikan dengan ukuran perangkat Anda.

Ada beberapa contoh tentang hal yang dapat Anda capai menggunakan bobot. Salah satu contohnya adalah petak tempat item berukuran sama, seperti yang ditunjukkan di bawah ini:

Petak dibuat dengan baris alur
Gambar 3. Menggunakan FlowRow untuk membuat petak

Untuk membuat petak dengan ukuran item yang sama, Anda dapat melakukan hal berikut:

val rows = 3
val columns = 3
FlowRow(
    modifier = Modifier.padding(4.dp),
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    maxItemsInEachRow = rows
) {
    val itemModifier = Modifier
        .padding(4.dp)
        .height(80.dp)
        .weight(1f)
        .clip(RoundedCornerShape(8.dp))
        .background(MaterialColors.Blue200)
    repeat(rows * columns) {
        Spacer(modifier = itemModifier)
    }
}

Yang penting, jika Anda menambahkan item lain dan mengulanginya 10 kali, bukan 9, item terakhir akan mengisi seluruh kolom terakhir, karena bobot total untuk seluruh baris adalah 1f:

Ukuran penuh item terakhir di petak
Gambar 4. Menggunakan FlowRow untuk membuat petak dengan item terakhir menggunakan lebar penuh

Anda dapat menggabungkan bobot dengan Modifiers lain seperti Modifier.width(exactDpAmount), Modifier.aspectRatio(aspectRatio), atau Modifier.fillMaxWidth(fraction). Semua pengubah ini berfungsi bersama untuk memungkinkan ukuran item responsif dalam FlowRow (atau FlowColumn).

Anda juga dapat membuat petak alternatif dengan ukuran item yang berbeda, dengan masing-masing dua item berukuran setengah lebar, dan satu item menempati lebar penuh kolom berikutnya:

Petak bergantian dengan baris alur
Gambar 5. FlowRow dengan ukuran baris bergantian

Anda dapat melakukannya dengan kode berikut:

FlowRow(
    modifier = Modifier.padding(4.dp),
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    maxItemsInEachRow = 2
) {
    val itemModifier = Modifier
        .padding(4.dp)
        .height(80.dp)
        .clip(RoundedCornerShape(8.dp))
        .background(Color.Blue)
    repeat(6) { item ->
        // if the item is the third item, don't use weight modifier, but rather fillMaxWidth
        if ((item + 1) % 3 == 0) {
            Spacer(modifier = itemModifier.fillMaxWidth())
        } else {
            Spacer(modifier = itemModifier.weight(0.5f))
        }
    }
}

Ukuran pecahan

Dengan Modifier.fillMaxWidth(fraction), Anda dapat menentukan ukuran penampung yang harus digunakan oleh item. Hal ini berbeda dengan cara kerja Modifier.fillMaxWidth(fraction) saat diterapkan ke Row atau Column, karena item Row/Column menempati persentase lebar yang tersisa, bukan lebar keseluruhan penampung.

Misalnya, kode berikut memberikan hasil yang berbeda saat menggunakan FlowRow vs Row:

FlowRow(
    modifier = Modifier.padding(4.dp),
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    maxItemsInEachRow = 3
) {
    val itemModifier = Modifier
        .clip(RoundedCornerShape(8.dp))
    Box(modifier = itemModifier.height(200.dp).width(60.dp).background(Color.Red))
    Box(modifier = itemModifier.height(200.dp).fillMaxWidth(0.7f).background(Color.Blue))
    Box(modifier = itemModifier.height(200.dp).weight(1f).background(Color.Magenta))
}

FlowRow: Item tengah dengan pecahan 0,7 dari lebar container secara keseluruhan.

Lebar pecahan dengan baris alur

Row: Item tengah menempati 0,7 persen dari lebar Row yang tersisa.

Lebar pecahan dengan baris

fillMaxColumnWidth() dan fillMaxRowHeight()

Menerapkan Modifier.fillMaxColumnWidth() atau Modifier.fillMaxRowHeight() ke item di dalam FlowColumn atau FlowRow akan memastikan bahwa item di kolom atau baris yang sama memiliki lebar atau tinggi yang sama dengan item terbesar di kolom/baris.

Misalnya, contoh ini menggunakan FlowColumn untuk menampilkan daftar makanan penutup Android. Anda dapat melihat perbedaan lebar setiap item saat Modifier.fillMaxColumnWidth() diterapkan pada item dibandingkan saat tidak dan item digabungkan.

FlowColumn(
    Modifier
        .padding(20.dp)
        .fillMaxHeight()
        .fillMaxWidth(),
    horizontalArrangement = Arrangement.spacedBy(8.dp),
    verticalArrangement = Arrangement.spacedBy(8.dp),
    maxItemsInEachColumn = 5,
) {
    repeat(listDesserts.size) {
        Box(
            Modifier
                .fillMaxColumnWidth()
                .border(1.dp, Color.DarkGray, RoundedCornerShape(8.dp))
                .padding(8.dp)
        ) {

            Text(
                text = listDesserts[it],
                fontSize = 18.sp,
                modifier = Modifier.padding(3.dp)
            )
        }
    }
}

Modifier.fillMaxColumnWidth() diterapkan ke setiap item

fillMaxColumnWidth

Tidak ada perubahan lebar yang ditetapkan (menggabungkan item)

Lebar kolom maksimum untuk pengisian tidak ditetapkan