Compose'da Pager

İçerikler arasında sol ve sağ veya yukarı ve aşağı oklar arasında geçiş yapmak için sırasıyla HorizontalPager ve VerticalPager composable'ları kullanabilirsiniz. Bu composable'lar, görünüm sistemindeki ViewPager ile benzer işlevlere sahiptir. Varsayılan olarak HorizontalPager, ekranın tam genişliğini kaplar, VerticalPager tüm yüksekliği kaplar ve çağrı cihazları tek seferde yalnızca bir sayfayı hızla aktarır. Bu varsayılan değerlerin tümü yapılandırılabilir.

HorizontalPager

Yatay olarak sola ve sağa kaydırılan bir sayfa cihazı oluşturmak için HorizontalPager işlevini kullanın:

Şekil 1. HorizontalPager demosu

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

VerticalPager

Yukarı ve aşağı kaydırılan bir çağrı cihazı oluşturmak için VerticalPager işlevini kullanın:

Şekil 2. VerticalPager demosu

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

Tembel oluşturma

Hem HorizontalPager hem de VerticalPager sayfalarındaki sayfalar gecikmeli ve gerektiğinde düzenlenmektedir. Kullanıcı sayfalar arasında gezinirken artık gerekli olmayan sayfaları kaldırır.

Ekran dışında daha fazla sayfa yükle

Varsayılan olarak, çağrı cihazı yalnızca ekranda görünen sayfaları yükler. Ekran dışında daha fazla sayfa yüklemek için beyondBoundsPageCount öğesini sıfırdan büyük bir değere ayarlayın.

Sayfa ayırıcıdaki bir öğeye gidin

Sayfa ayırıcıda belirli bir sayfaya gitmek için rememberPagerState() kullanarak bir PagerState nesnesi oluşturun ve bunu sayfa cihazına state parametresi olarak iletin. Bu durumda, CoroutineScope içinden PagerState#scrollToPage() numarasını arayabilirsiniz:

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")
}

Sayfaya animasyon eklemek istiyorsanız PagerState#animateScrollToPage() işlevini kullanın:

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")
}

Sayfa durumu değişiklikleri hakkında bildirim alma

PagerState, sayfalar hakkında bilgi içeren üç özelliğe sahiptir: currentPage, settledPage ve targetPage.

  • currentPage: Tutturma konumuna en yakın sayfa. Varsayılan olarak, yapışma konumu düzenin başındadır.
  • settledPage: Animasyon veya kaydırma yapılmadığında gösterilen sayfa numarası. Bu, currentPage özelliğinden farklıdır. Çünkü sayfa, tutturma konumuna yeterince yakın olduğunda currentPage anında güncellenir, ancak settledPage tüm animasyonların çalıştırılması tamamlanana kadar aynı kalır.
  • targetPage: Bir kaydırma hareketi için önerilen durma konumu.

Bu değişkenlerdeki değişiklikleri gözlemlemek ve bunlara tepki vermek için snapshotFlow işlevini kullanabilirsiniz. Örneğin, her sayfa değişikliğinde bir Analytics etkinliği göndermek için aşağıdakileri yapabilirsiniz:

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")
}

Sayfa göstergesi ekleyin

Bir sayfaya gösterge eklemek için sayfa sayısı arasından hangi sayfanın seçildiği hakkında bilgi edinmek amacıyla PagerState nesnesini kullanın ve özel göstergenizi çizin.

Örneğin, basit bir daire göstergesi istiyorsanız pagerState.currentPage kullanarak daire sayısını tekrarlayabilir ve sayfanın seçili olup olmamasına göre daire rengini değiştirebilirsiniz:

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)
        )
    }
}

İçeriğin altında bir daire göstergesini gösteren çağrı cihazı
Şekil 3. İçeriğin altında bir daire göstergesi gösteren çağrı cihazı

İçeriğe öğe kaydırma efektleri uygulayın

Yaygın kullanım alanlarından biri, efektleri çağrı cihazı öğelerinize uygulamak için kaydırma konumunu kullanmaktır. Bir sayfanın seçili olan sayfadan ne kadar uzakta olduğunu öğrenmek için PagerState.currentPageOffsetFraction değerini kullanabilirsiniz. Ardından, seçilen sayfadan uzaklığa göre içeriğinize dönüştürme efektleri uygulayabilirsiniz.

Şekil 4. Çağrı cihazı içeriğine dönüşüm uygulama

Örneğin, öğelerin opaklığını merkezden ne kadar uzakta olduklarına göre ayarlamak için sayfa ayırıcı içindeki bir öğede Modifier.graphicsLayer kullanarak alpha değerini değiştirin:

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
    }
}

Özel sayfa boyutları

Varsayılan olarak HorizontalPager ve VerticalPager, sırasıyla tam genişliği veya tam yüksekliği kullanır. pageSize değişkenini Fixed, Fill (varsayılan) veya özel boyut hesaplamasına sahip olacak şekilde ayarlayabilirsiniz.

Örneğin, 100.dp için sabit genişlikli bir sayfa ayarlamak için:

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

Sayfaları görüntü alanı boyutuna göre boyutlandırmak için özel bir sayfa boyutu hesaplaması kullanın. Özel bir PageSize nesnesi oluşturun ve öğeler arasındaki boşluğu dikkate alarak availableSpace öğesini üçe bölün:

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

İçerik dolgusu

Hem HorizontalPager hem de VerticalPager, içerik dolgusunu değiştirmeyi destekler. Böylece, sayfaların maksimum boyutunu ve hizalamasını ayarlayabilirsiniz.

Örneğin, start dolgusunu ayarlamak sayfaları sona doğru hizalar:

İçeriği sona hizalı olarak gösteren başlangıç dolgulu çağrı cihazı

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

Hem start hem de end dolgusunun aynı değere ayarlanması, öğeyi yatay olarak ortalar:

İçeriği ortalanmış olarak gösteren başlangıç ve bitiş dolgulu çağrı cihazı

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

end dolgusunu ayarlamak sayfaları başa doğru hizalar:

İçeriği başa hizalı olarak gösteren başlangıç ve bitiş dolgulu sayfa cihazı

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

VerticalPager için benzer efektler uygulamak üzere top ve bottom değerlerini ayarlayabilirsiniz. 32.dp değeri burada yalnızca örnek olarak kullanılmaktadır. Dolgu boyutlarının her birini istediğiniz değere ayarlayabilirsiniz.

Kaydırma davranışını özelleştirin

Varsayılan HorizontalPager ve VerticalPager composable'ları, kaydırma hareketlerinin çağrı cihazıyla nasıl çalışacağını belirtir. Ancak pagerSnapDistance veya flingBehavior gibi varsayılan değerleri özelleştirebilir ve değiştirebilirsiniz.

Tutturma mesafesi

Varsayılan olarak HorizontalPager ve VerticalPager, bir kaydırma hareketinin tek seferde bir sayfaya ilerleyebileceği maksimum sayfa sayısını ayarlar. Bunu değiştirmek için flingBehavior üzerinde pagerSnapDistance değerini ayarlayın:

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),
        beyondBoundsPageCount = 10,
        flingBehavior = fling
    ) {
        PagerSampleItem(page = it)
    }
}