Pengubah scroll

Pengubah verticalScroll dan horizontalScroll memberikan cara termudah untuk memungkinkan pengguna men-scroll elemen jika batas kontennya lebih besar dari batasan ukuran maksimumnya. Dengan pengubah verticalScroll dan horizontalScroll, Anda tidak perlu menerjemahkan atau melakukan offset pada konten tersebut.

@Composable
private fun ScrollBoxes() {
    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .verticalScroll(rememberScrollState())
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

Daftar vertikal sederhana yang merespons gestur scroll
Gambar 1. Daftar vertikal sederhana yang merespons gestur scroll.

The ScrollState memungkinkan Anda mengubah posisi scroll atau mendapatkan status saat ini. Untuk membuatnya dengan parameter default, gunakan rememberScrollState().

@Composable
private fun ScrollBoxesSmooth() {
    // Smoothly scroll 100px on first composition
    val state = rememberScrollState()
    LaunchedEffect(Unit) { state.animateScrollTo(100) }

    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .padding(horizontal = 8.dp)
            .verticalScroll(state)
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

Pengubah area yang dapat di-scroll

Pengubah scrollableArea adalah elemen penyusun dasar untuk membuat penampung yang dapat di-scroll kustom. Pengubah ini menyediakan abstraksi tingkat yang lebih tinggi atas pengubah scrollable, yang menangani persyaratan umum seperti interpretasi delta gestur, kliping konten, dan efek overscroll.

Meskipun scrollableArea digunakan untuk penerapan kustom, Anda umumnya harus memilih solusi siap pakai seperti verticalScroll, horizontalScroll, atau composable seperti LazyColumn untuk daftar scroll standar. Komponen tingkat yang lebih tinggi ini lebih sederhana untuk kasus penggunaan umum dan dibuat menggunakan scrollableArea.

Perbedaan antara scrollableArea dan scrollable pengubah

Perbedaan utama antara scrollableArea dan scrollable terletak pada cara keduanya menafsirkan gestur scroll pengguna:

  • scrollable (delta mentah): Delta secara langsung mencerminkan gerakan fisik input pengguna (misalnya, pointer drag) di layar.
  • scrollableArea (delta berorientasi konten): delta secara semantik dibalik untuk mewakili perubahan yang dipilih dalam posisi scroll agar konten tampak bergerak dengan gestur pengguna, yang biasanya berlawanan dengan gerakan pointer.

Bayangkan seperti ini: scrollable memberi tahu Anda cara pointer bergerak, sedangkan scrollableArea menerjemahkan gerakan pointer tersebut ke dalam cara konten harus bergerak dalam tampilan yang dapat di-scroll. Inversi ini membuat scrollableArea terasa lebih alami saat menerapkan penampung yang dapat di-scroll standar.

Tabel berikut merangkum tanda delta untuk skenario umum:

Gestur pengguna

delta yang dilaporkan ke dispatchRawDelta oleh scrollable

delta yang dilaporkan ke dispatchRawDelta oleh scrollableArea*

Pointer bergerak KE ATAS

Negatif

Positif

Pointer bergerak KE BAWAH

Positif

Negatif

Pointer bergerak KE KIRI

Negatif

Positif (Negatif untuk RTL)

Pointer bergerak KE KANAN

Positif

Negatif (Positif untuk RTL)

(*) Catatan tentang tanda delta scrollableArea: Tanda delta dari scrollableArea bukan hanya inversi sederhana. Tanda ini mempertimbangkan secara cerdas:

  1. Orientasi: Vertikal atau horizontal.
  2. LayoutDirection: LTR atau RTL (terutama penting untuk scroll horizontal).
  3. Flag reverseScrolling: Apakah arah scroll dibalik.

Selain membalikkan delta scroll, scrollableArea juga memangkas konten ke batas tata letak dan menangani rendering efek overscroll. Secara default, efek yang disediakan oleh LocalOverscrollFactory akan digunakan. Anda dapat menyesuaikan atau menonaktifkannya menggunakan scrollableArea overload yang menerima parameter OverscrollEffect.

Kapan harus menggunakan pengubah scrollableArea

Anda harus menggunakan pengubah scrollableArea saat perlu membuat komponen scroll kustom yang tidak dilayani secara memadai oleh pengubah horizontalScroll atau verticalScroll atau tata letak Lazy. Hal ini sering kali melibatkan kasus dengan:

  • Logika tata letak kustom: Saat pengaturan item berubah secara dinamis berdasarkan posisi scroll.
  • Efek visual unik: Menerapkan transformasi, penskalaan, atau efek lainnya ke turunan saat di-scroll.
  • Kontrol langsung: Memerlukan kontrol terperinci atas mekanisme scroll di luar yang diekspos oleh verticalScroll atau tata letak Lazy.

Membuat daftar kustom seperti roda menggunakan scrollableArea

Contoh berikut menunjukkan penggunaan scrollableArea untuk membuat daftar vertikal kustom tempat item diperkecil saat bergerak menjauh dari tengah, sehingga membuat efek visual "seperti roda". Jenis transformasi yang bergantung pada scroll ini adalah kasus penggunaan yang sempurna untuk scrollableArea.

Gambar 2. Daftar vertikal yang disesuaikan menggunakan scrollableArea.

@Composable
private fun ScrollableAreaSample() {
    // ...
    Layout(
        modifier =
            Modifier
                .size(150.dp)
                .scrollableArea(scrollState, Orientation.Vertical)
                .background(Color.LightGray),
        // ...
    ) { measurables, constraints ->
        // ...
        // Update the maximum scroll value to not scroll beyond limits and stop when scroll
        // reaches the end.
        scrollState.maxValue = (totalHeight - viewportHeight).coerceAtLeast(0)

        // Position the children within the layout.
        layout(constraints.maxWidth, viewportHeight) {
            // The current vertical scroll position, in pixels.
            val scrollY = scrollState.value
            val viewportCenterY = scrollY + viewportHeight / 2

            var placeableLayoutPositionY = 0
            placeables.forEach { placeable ->
                // This sample applies a scaling effect to items based on their distance
                // from the center, creating a wheel-like effect.
                // ...
                // Place the item horizontally centered with a layer transformation for
                // scaling to achieve wheel-like effect.
                placeable.placeRelativeWithLayer(
                    x = constraints.maxWidth / 2 - placeable.width / 2,
                    // Offset y by the scroll position to make placeable visible in the viewport.
                    y = placeableLayoutPositionY - scrollY,
                ) {
                    scaleX = scaleFactor
                    scaleY = scaleFactor
                }
                // Move to the next item's vertical position.
                placeableLayoutPositionY += placeable.height
            }
        }
    }
}
// ...

Pengubah yang dapat di-scroll

Pengubah scrollable berbeda dengan pengubah scroll dalam scrollable yang mendeteksi gestur scroll dan mengambil delta, tetapi tidak mengimbangi kontennya secara otomatis. Hal ini didelegasikan kepada pengguna melalui ScrollableState , yang diperlukan agar pengubah ini berfungsi dengan benar.

Saat membuat ScrollableState, Anda harus menyediakan fungsi consumeScrollDelta yang akan dipanggil pada setiap langkah scroll (dengan input gestur, scroll halus, atau lempar) dengan delta dalam piksel. Fungsi ini harus menampilkan jumlah jarak scroll yang digunakan untuk memastikan peristiwa disebarkan dengan benar jika ada elemen bertingkat yang memiliki pengubah scrollable.

Cuplikan berikut mendeteksi gestur dan menampilkan nilai numerik untuk offset, tetapi tidak melakukan offset pada elemen apa pun:

@Composable
private fun ScrollableSample() {
    // actual composable state
    var offset by remember { mutableFloatStateOf(0f) }
    Box(
        Modifier
            .size(150.dp)
            .scrollable(
                orientation = Orientation.Vertical,
                // Scrollable state: describes how to consume
                // scrolling delta and update offset
                state = rememberScrollableState { delta ->
                    offset += delta
                    delta
                }
            )
            .background(Color.LightGray),
        contentAlignment = Alignment.Center
    ) {
        Text(offset.toString())
    }
}

Elemen UI mendeteksi penekanan jari dan menampilkan nilai numerik untuk lokasi jari
Gambar 3. Elemen UI mendeteksi penekanan jari dan menampilkan nilai numerik untuk lokasi jari.