Pager di Compose

Untuk membalik konten secara horizontal atau vertikal, Anda dapat menggunakan HorizontalPager dan VerticalPager composable. Keduanya memiliki fungsi yang mirip dengan ViewPager dalam sistem tampilan. Secara default, HorizontalPager menggunakan lebar layar penuh dan VerticalPager menggunakan tinggi penuh. Pager juga hanya menggeser satu halaman dalam satu waktu. Semua default ini dapat dikonfigurasi.

HorizontalPager

Untuk membuat pager yang di-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 pager yang di-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 disusun secara lambat dan ditata saat diperlukan. Saat pengguna men-scroll halaman, composable akan menghapus halaman yang tidak lagi diperlukan.

Memuat lebih banyak halaman di luar layar

Secara default, pager 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 pager

Untuk men-scroll ke halaman tertentu di pager, buat PagerState objek menggunakan rememberPagerState() dan teruskan sebagai parameter state ke pager. Anda dapat memanggil PagerState#scrollToPage() pada status ini, di dalam a 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 membuat animasi 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 snap. Secara default, posisi snap berada di awal tata letak.
  • settledPage: Nomor halaman saat tidak ada animasi atau scrolling yang berjalan. Hal ini berbeda dengan properti currentPage karena currentPage akan segera diperbarui jika halaman cukup dekat dengan posisi snap, tetapi settledPage tetap sama hingga semua animasi selesai berjalan.
  • targetPage: Posisi berhenti yang diusulkan untuk gerakan scrolling.

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 jumlah halaman, dan gambar indikator kustom Anda.

Misalnya, untuk membuat indikator lingkaran, Anda dapat mengulangi jumlah lingkaran dan mengubah warna lingkaran berdasarkan apakah halaman dipilih atau tidak, 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)
        )
    }
}

Paginator menampilkan indikator lingkaran di bawah konten
Gambar 3. Pager 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 dipilih, Anda dapat menggunakan PagerState.currentPageOffsetFraction. Kemudian, Anda dapat menerapkan efek transformasi ke konten berdasarkan jarak dari halaman yang dipilih.

Gambar 4. Menerapkan transformasi ke konten Pager

Misalnya, untuk menyesuaikan opasitas 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 menggunakan lebar penuh atau tinggi penuh. Anda dapat menetapkan variabel pageSize agar memiliki Fixed, Fill (default), atau perhitungan 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, menetapkan padding start akan meratakan halaman ke akhir:

Pengatur halaman dengan padding awal yang menampilkan konten yang disejajarkan ke arah akhir
Gambar 5. Pager dengan padding awal.

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
Gambar 6. Pager dengan padding horizontal.

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

Menetapkan padding end akan meratakan halaman ke awal:

Paginator dengan padding awal dan akhir yang menampilkan konten yang disejajarkan dengan awal
Gambar 7. Pager dengan padding akhir.

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

Anda dapat menetapkan nilai top dan bottom untuk mencapai 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 scrolling berfungsi dengan pager. Namun, Anda dapat menyesuaikan dan mengubah default seperti pagerSnapDistance atau flingBehavior.

Jarak snap

Secara default, HorizontalPager dan VerticalPager menetapkan jumlah maksimum halaman yang dapat di-scroll oleh gestur geser ke satu halaman dalam satu waktu. Untuk mengubah ini, tetapkan pagerSnapDistance pada 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 pager yang otomatis maju

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

Contoh dasar

Bersama-sama, cuplikan berikut membuat implementasi pager otomatis maju 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-poin penting tentang kode

  • Fungsi AutoAdvancePager membuat tampilan halaman horizontal dengan kemajuan 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.
  • The LaunchedEffect otomatis memajukan pager setiap dua detik kecuali jika pengguna menarik pager 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-poin penting tentang kode

  • Composable Box bertindak sebagai elemen root dan berisi Row untuk mengatur indikator halaman secara horizontal.
  • Indikator halaman kustom ditampilkan sebagai baris lingkaran, dengan setiap Box yang dipangkas ke CircleShape mewakili halaman.
  • Lingkaran halaman saat ini diberi warna DarkGray, sedangkan lingkaran lainnya adalah LightGray. Parameter currentPageIndex menentukan lingkaran mana yang dirender dalam warna abu-abu gelap.

Hasil

Video ini menampilkan pager otomatis maju dasar dari cuplikan sebelumnya:

Gambar 8. Pager otomatis maju dengan penundaan dua detik antara setiap progres halaman.

Referensi lainnya