Pager di Compose

Untuk membalik konten secara kiri dan kanan atau atas dan bawah, Anda dapat menggunakan composable HorizontalPager dan VerticalPager. Composable ini memiliki fungsi yang serupa dengan ViewPager dalam sistem tampilan. Secara default, HorizontalPager menggunakan lebar layar penuh, VerticalPager menggunakan tinggi penuh, dan penggeser hanya menggeser satu halaman dalam satu waktu. Semua default ini dapat dikonfigurasi.

HorizontalPager

Untuk membuat penomoran halaman yang men-scroll secara horizontal ke kiri dan kanan, gunakan HorizontalPager:

Gambar 1. Demo HorizontalPager

// Display 10 items
val pagerState = rememberPagerState(pageCount = {
    10
})
HorizontalPager(state = pagerState) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier.fillMaxWidth()
    )
}

VerticalPager

Untuk membuat penomoran halaman yang men-scroll ke atas dan ke bawah, gunakan VerticalPager:

Gambar 2. Demo VerticalPager

// Display 10 items
val pagerState = rememberPagerState(pageCount = {
    10
})
VerticalPager(state = pagerState) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier.fillMaxWidth()
    )
}

Pembuatan lambat

Halaman di HorizontalPager dan VerticalPager dikomposisikan secara lambat dan ditata saat diperlukan. Saat pengguna men-scroll halaman, composable akan menghapus halaman yang tidak lagi diperlukan.

Memuat halaman lainnya di luar layar

Secara default, penomoran halaman hanya memuat halaman yang terlihat di layar. Untuk memuat lebih banyak halaman di luar layar, tetapkan beyondBoundsPageCount ke nilai yang lebih tinggi dari nol.

Men-scroll ke item di penomoran halaman

Untuk men-scroll ke halaman tertentu di pager, buat objek PagerState menggunakan rememberPagerState() dan teruskan sebagai parameter state ke pager. Anda dapat memanggil PagerState#scrollToPage() pada status ini, di dalam CoroutineScope:

val pagerState = rememberPagerState(pageCount = {
    10
})
HorizontalPager(state = pagerState) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier
            .fillMaxWidth()
            .height(100.dp)
    )
}

// scroll to page
val coroutineScope = rememberCoroutineScope()
Button(onClick = {
    coroutineScope.launch {
        // Call scroll to on pagerState
        pagerState.scrollToPage(5)
    }
}, modifier = Modifier.align(Alignment.BottomCenter)) {
    Text("Jump to Page 5")
}

Jika Anda ingin menganimasikan ke halaman, gunakan fungsi PagerState#animateScrollToPage():

val pagerState = rememberPagerState(pageCount = {
    10
})

HorizontalPager(state = pagerState) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier
            .fillMaxWidth()
            .height(100.dp)
    )
}

// scroll to page
val coroutineScope = rememberCoroutineScope()
Button(onClick = {
    coroutineScope.launch {
        // Call scroll to on pagerState
        pagerState.animateScrollToPage(5)
    }
}, modifier = Modifier.align(Alignment.BottomCenter)) {
    Text("Jump to Page 5")
}

Mendapatkan notifikasi tentang perubahan status halaman

PagerState memiliki tiga properti dengan informasi tentang halaman: currentPage, settledPage, dan targetPage.

  • currentPage: Halaman terdekat dengan posisi pengepasan. Secara default, posisi penyambungan berada di awal tata letak.
  • settledPage: Nomor halaman saat tidak ada animasi atau scrolling yang berjalan. Hal ini berbeda dengan properti currentPage karena currentPage langsung diperbarui jika halaman cukup dekat dengan posisi penarikan, tetapi settledPage tetap sama hingga semua animasi selesai berjalan.
  • targetPage: Posisi berhenti yang diusulkan untuk gerakan men-scroll.

Anda dapat menggunakan fungsi snapshotFlow untuk mengamati perubahan pada variabel ini dan bereaksi terhadapnya. Misalnya, untuk mengirim peristiwa analisis pada setiap perubahan halaman, Anda dapat melakukan hal berikut:

val pagerState = rememberPagerState(pageCount = {
    10
})

LaunchedEffect(pagerState) {
    // Collect from the a snapshotFlow reading the currentPage
    snapshotFlow { pagerState.currentPage }.collect { page ->
        // Do something with each page change, for example:
        // viewModel.sendPageSelectedEvent(page)
        Log.d("Page change", "Page changed to $page")
    }
}

VerticalPager(
    state = pagerState,
) { page ->
    Text(text = "Page: $page")
}

Menambahkan indikator halaman

Untuk menambahkan indikator ke halaman, gunakan objek PagerState untuk mendapatkan informasi tentang halaman mana yang dipilih dari sejumlah halaman, dan gambar indikator kustom Anda.

Misalnya, jika Anda menginginkan indikator lingkaran sederhana, Anda dapat mengulangi jumlah lingkaran dan mengubah warna lingkaran berdasarkan apakah halaman dipilih, menggunakan pagerState.currentPage:

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    modifier = Modifier.fillMaxSize()
) { page ->
    // Our page content
    Text(
        text = "Page: $page",
    )
}
Row(
    Modifier
        .wrapContentHeight()
        .fillMaxWidth()
        .align(Alignment.BottomCenter)
        .padding(bottom = 8.dp),
    horizontalArrangement = Arrangement.Center
) {
    repeat(pagerState.pageCount) { iteration ->
        val color = if (pagerState.currentPage == iteration) Color.DarkGray else Color.LightGray
        Box(
            modifier = Modifier
                .padding(2.dp)
                .clip(CircleShape)
                .background(color)
                .size(16.dp)
        )
    }
}

Pengatur halaman yang menampilkan indikator lingkaran di bawah konten
Gambar 3. Pengatur halaman yang menampilkan indikator lingkaran di bawah konten

Menerapkan efek scroll item ke konten

Kasus penggunaan umum adalah menggunakan posisi scroll untuk menerapkan efek ke item pager Anda. Untuk mengetahui seberapa jauh halaman dari halaman yang saat ini dipilih, Anda dapat menggunakan PagerState.currentPageOffsetFraction. Kemudian, Anda dapat menerapkan efek transformasi ke konten berdasarkan jarak dari halaman yang dipilih.

Gambar 4. Menerapkan transformasi pada konten Pager

Misalnya, untuk menyesuaikan keburaman item berdasarkan seberapa jauh item tersebut dari tengah, ubah alpha menggunakan Modifier.graphicsLayer pada item di dalam pager:

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(state = pagerState) { page ->
    Card(
        Modifier
            .size(200.dp)
            .graphicsLayer {
                // Calculate the absolute offset for the current page from the
                // scroll position. We use the absolute value which allows us to mirror
                // any effects for both directions
                val pageOffset = (
                    (pagerState.currentPage - page) + pagerState
                        .currentPageOffsetFraction
                    ).absoluteValue

                // We animate the alpha, between 50% and 100%
                alpha = lerp(
                    start = 0.5f,
                    stop = 1f,
                    fraction = 1f - pageOffset.coerceIn(0f, 1f)
                )
            }
    ) {
        // Card content
    }
}

Ukuran halaman kustom

Secara default, HorizontalPager dan VerticalPager masing-masing menggunakan lebar penuh atau tinggi penuh. Anda dapat menetapkan variabel pageSize agar memiliki Fixed, Fill (default), atau penghitungan ukuran kustom.

Misalnya, untuk menetapkan halaman lebar tetap 100.dp:

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    pageSize = PageSize.Fixed(100.dp)
) { page ->
    // page content
}

Untuk menentukan ukuran halaman berdasarkan ukuran area pandang, gunakan perhitungan ukuran halaman kustom. Buat objek PageSize kustom dan bagi availableSpace dengan tiga, dengan mempertimbangkan jarak antar-item:

private val threePagesPerViewport = object : PageSize {
    override fun Density.calculateMainAxisPageSize(
        availableSpace: Int,
        pageSpacing: Int
    ): Int {
        return (availableSpace - 2 * pageSpacing) / 3
    }
}

Padding konten

HorizontalPager dan VerticalPager mendukung perubahan padding konten, yang memungkinkan Anda memengaruhi ukuran dan perataan maksimum halaman.

Misalnya, menyetel padding start akan menyelaraskan halaman ke arah akhir:

Pengatur halaman dengan padding awal yang menampilkan konten yang disejajarkan ke arah akhir

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    contentPadding = PaddingValues(start = 64.dp),
) { page ->
    // page content
}

Menetapkan padding start dan end ke nilai yang sama akan memusatkan item secara horizontal:

Pager dengan padding awal dan akhir yang menampilkan konten di tengah

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    contentPadding = PaddingValues(horizontal = 32.dp),
) { page ->
    // page content
}

Menyetel padding end akan menyelaraskan halaman ke arah awal:

Paginator dengan padding awal dan akhir yang menampilkan konten yang disejajarkan dengan awal

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    contentPadding = PaddingValues(end = 64.dp),
) { page ->
    // page content
}

Anda dapat menetapkan nilai top dan bottom untuk mendapatkan efek serupa untuk VerticalPager. Nilai 32.dp hanya digunakan di sini sebagai contoh; Anda dapat menetapkan setiap dimensi padding ke nilai apa pun.

Menyesuaikan perilaku scroll

Composable HorizontalPager dan VerticalPager default menentukan cara gestur men-scroll berfungsi dengan pager. Namun, Anda dapat menyesuaikan dan mengubah setelan default seperti pagerSnapDistance atau flingBehavior.

Jarak penempelan

Secara default, HorizontalPager dan VerticalPager menetapkan jumlah maksimum halaman yang dapat di-scroll oleh gestur menggeser ke satu halaman dalam satu waktu. Untuk mengubahnya, tetapkan pagerSnapDistance di flingBehavior:

val pagerState = rememberPagerState(pageCount = { 10 })

val fling = PagerDefaults.flingBehavior(
    state = pagerState,
    pagerSnapDistance = PagerSnapDistance.atMost(10)
)

Column(modifier = Modifier.fillMaxSize()) {
    HorizontalPager(
        state = pagerState,
        pageSize = PageSize.Fixed(200.dp),
        beyondViewportPageCount = 10,
        flingBehavior = fling
    ) {
        PagerSampleItem(page = it)
    }
}

Membuat penomoran halaman yang otomatis berpindah

Bagian ini menjelaskan cara membuat pager yang maju otomatis dengan indikator halaman di Compose. Kumpulan item otomatis men-scroll secara horizontal, tetapi pengguna juga dapat menggeser item secara manual. Jika pengguna berinteraksi dengan penomoran halaman, progres otomatis akan berhenti.

Contoh dasar

Bersama-sama, cuplikan berikut membuat penerapan pager maju otomatis dasar dengan indikator visual, yang setiap halamannya dirender sebagai warna yang berbeda:

@Composable
fun AutoAdvancePager(pageItems: List<Color>, modifier: Modifier = Modifier) {
    Box(modifier = Modifier.fillMaxSize()) {
        val pagerState = rememberPagerState(pageCount = { pageItems.size })
        val pagerIsDragged by pagerState.interactionSource.collectIsDraggedAsState()

        val pageInteractionSource = remember { MutableInteractionSource() }
        val pageIsPressed by pageInteractionSource.collectIsPressedAsState()

        // Stop auto-advancing when pager is dragged or one of the pages is pressed
        val autoAdvance = !pagerIsDragged && !pageIsPressed

        if (autoAdvance) {
            LaunchedEffect(pagerState, pageInteractionSource) {
                while (true) {
                    delay(2000)
                    val nextPage = (pagerState.currentPage + 1) % pageItems.size
                    pagerState.animateScrollToPage(nextPage)
                }
            }
        }

        HorizontalPager(
            state = pagerState
        ) { page ->
            Text(
                text = "Page: $page",
                textAlign = TextAlign.Center,
                modifier = modifier
                    .fillMaxSize()
                    .background(pageItems[page])
                    .clickable(
                        interactionSource = pageInteractionSource,
                        indication = LocalIndication.current
                    ) {
                        // Handle page click
                    }
                    .wrapContentSize(align = Alignment.Center)
            )
        }

        PagerIndicator(pageItems.size, pagerState.currentPage)
    }
}

Poin penting tentang kode

  • Fungsi AutoAdvancePager membuat tampilan penomoran halaman horizontal dengan peralihan otomatis. Fungsi ini mengambil daftar objek Color sebagai input, yang digunakan sebagai warna latar belakang untuk setiap halaman.
  • pagerState dibuat menggunakan rememberPagerState, yang menyimpan status pager.
  • pagerIsDragged dan pageIsPressed melacak interaksi pengguna.
  • LaunchedEffect otomatis memajukan penomoran halaman setiap dua detik, kecuali jika pengguna menarik penomoran halaman atau menekan salah satu halaman.
  • HorizontalPager menampilkan daftar halaman, yang masing-masing memiliki composable Text yang menampilkan nomor halaman. Pengubah mengisi halaman, menetapkan warna latar belakang dari pageItems, dan membuat halaman dapat diklik.

@Composable
fun PagerIndicator(pageCount: Int, currentPageIndex: Int, modifier: Modifier = Modifier) {
    Box(modifier = Modifier.fillMaxSize()) {
        Row(
            modifier = Modifier
                .wrapContentHeight()
                .fillMaxWidth()
                .align(Alignment.BottomCenter)
                .padding(bottom = 8.dp),
            horizontalArrangement = Arrangement.Center
        ) {
            repeat(pageCount) { iteration ->
                val color = if (currentPageIndex == iteration) Color.DarkGray else Color.LightGray
                Box(
                    modifier = modifier
                        .padding(2.dp)
                        .clip(CircleShape)
                        .background(color)
                        .size(16.dp)
                )
            }
        }
    }
}

Poin penting tentang kode

  • Composable Box digunakan sebagai elemen root.
    • Di dalam Box, composable Row mengatur indikator halaman secara horizontal.
  • Indikator halaman kustom ditampilkan sebagai baris lingkaran, dengan setiap Box yang di-clip ke circle merepresentasikan satu halaman.
  • Lingkaran halaman saat ini diberi warna DarkGray, sedangkan lingkaran lainnya berwarna LightGray. Parameter currentPageIndex menentukan lingkaran mana yang dirender dalam warna abu-abu tua.

Hasil

Video ini menampilkan penggeser maju otomatis dasar dari cuplikan sebelumnya:

Gambar 1. Pager yang maju otomatis dengan penundaan dua detik di antara setiap progres halaman.

Referensi lainnya