Scroll

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.

Dengan ScrollState, Anda dapat 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 memberikan abstraksi tingkat yang lebih tinggi atas pengubah scrollable, yang menangani persyaratan umum seperti interpretasi delta gestur, pemangkasan konten, dan efek overscroll.

Meskipun scrollableArea digunakan untuk penerapan kustom, Anda umumnya harus lebih 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 dibangun sendiri menggunakan scrollableArea.

Perbedaan antara pengubah scrollableArea dan scrollable

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, penarikan pointer) di layar.
  • scrollableArea (delta berorientasi konten): delta secara semantik dibalik untuk merepresentasikan perubahan yang dipilih dalam posisi scroll agar konten tampak bergerak dengan gestur pengguna, yang biasanya berlawanan dengan gerakan pointer.

Anggap saja seperti ini: scrollable memberi tahu Anda cara kursor bergerak, sementara scrollableArea menerjemahkan gerakan kursor tersebut ke cara konten harus bergerak dalam tampilan yang dapat di-scroll secara umum. Inversi ini adalah alasan mengapa scrollableArea terasa lebih alami saat menerapkan penampung yang dapat di-scroll standar.

Tabel berikut merangkum tanda delta untuk skenario umum:

Gestur pengguna

perubahan dilaporkan ke dispatchRawDelta oleh scrollable

delta yang dilaporkan ke dispatchRawDelta oleh scrollableArea*

Pointer bergerak UP

Negatif

Positif

Pointer bergerak DOWN

Positif

Negatif

Pointer bergerak ke KIRI

Negatif

Positif (Negatif untuk RTL)

Kursor bergerak ke KANAN

Positif

Negatif (Positif untuk RTL)

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

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

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

Kapan harus menggunakan pengubah scrollableArea

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

  • Logika tata letak kustom: Saat susunan item berubah secara dinamis berdasarkan posisi scroll.
  • Efek visual yang unik: Menerapkan transformasi, penskalaan, atau efek lain pada anak-anak saat mereka men-scroll.
  • Kontrol langsung: Memerlukan kontrol terperinci atas mekanisme scrolling 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 dengan item yang diperkecil saat bergerak menjauh dari tengah, sehingga menciptakan efek visual "seperti roda". Transformasi yang bergantung pada scroll semacam 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 karena scrollable mendeteksi gestur scroll dan mengambil delta, tetapi tidak mengimbangi kontennya secara otomatis. Sebagai gantinya, 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.