Compose'da Pager

İçerikleri yatay veya dikey olarak kaydırmak için HorizontalPager ve VerticalPager composable'larını kullanabilirsiniz. Bu özellikler, görünüm sistemindeki ViewPager ile benzer işlevlere sahiptir. Varsayılan olarak, HorizontalPager tam ekran genişliğini, VerticalPager ise tam ekran yüksekliğini kaplar. Ayrıca, çağrı cihazları da aynı anda yalnızca bir sayfa çevirir. Bu varsayılan değerlerin tümü yapılandırılabilir.

HorizontalPager

Yatay olarak sola ve sağa kayan bir sayfalayıcı oluşturmak için HorizontalPager öğesini kullanın:

1. Şekil. HorizontalPager demosunu izleyin

// 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ğı kayan bir sayfalayıcı oluşturmak için VerticalPager kullanın:

Şekil 2. VerticalPager demosunu izleyin

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

Geç oluşturma

Hem HorizontalPager hem de VerticalPager içindeki sayfalar, gerektiğinde geç oluşturulur ve düzenlenir. Kullanıcı sayfalar arasında kaydırarak gezinirken composable, artık gerekli olmayan sayfaları kaldırır.

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

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

Sayfalayıcıda bir öğeye kaydırma

Sayfalayıcıda belirli bir sayfaya gitmek için PagerState nesnesi oluşturun rememberPagerState() kullanarak ve bunu sayfalayıcıya state parametresi olarak iletin. Bu durumda, CoroutineScope içinde PagerState#scrollToPage() işlevini çağırabilirsiniz:

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'da sayfalarla ilgili bilgilerin yer aldığı üç özellik bulunur: currentPage, settledPage ve targetPage.

  • currentPage: Sabitleme konumuna en yakın sayfa. Varsayılan olarak, tutturma konumu düzenin başlangıcındadır.
  • settledPage: Animasyon veya kaydırma işlemi çalışmadığında sayfa numarası. Bu, currentPage özelliğinden farklıdır. currentPage, sayfa tutturma konumuna yeterince yakınsa hemen güncellenir ancak settledPage, tüm animasyonlar çalışmayı bitirene kadar aynı kalır.
  • targetPage: Kaydırma hareketi için önerilen durdurma 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 analiz etkinliği göndermek için şunları 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 ekleme

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

Örneğin, daire göstergesi oluşturmak için daire sayısını tekrarlayabilir ve sayfanın seçilip seçilmediğine bağlı olarak daire rengini değiştirebilirsiniz. Bunun için pagerState.currentPage kullanın:

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 daire göstergesi bulunan sayfalayıcı
3. Şekil. İçeriğin altında daire göstergesi bulunan sayfalayıcı

İçeriğe öğe kaydırma efektleri uygulama

Yaygın bir kullanım alanı, kaydırma konumunu kullanarak sayfalama öğelerinize efektler uygulamaktır. Bir sayfanın seçili sayfadan ne kadar uzakta olduğunu öğrenmek için PagerState.currentPageOffsetFraction simgesini kullanabilirsiniz. Ardından, seçilen sayfaya olan mesafeye göre içeriğinize dönüştürme efektleri uygulayabilirsiniz.

Şekil 4. Pager içeriğine dönüşümler uygulama

Örneğin, öğelerin opaklığını merkezden ne kadar uzakta olduklarına göre ayarlamak için sayfalayıcıdaki 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 kaplar. pageSize değişkenini Fixed, Fill (varsayılan) veya özel bir boyut hesaplaması içerecek şekilde ayarlayabilirsiniz.

Örneğin, 100.dp genişliğinde sabit 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 değerini üç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

HorizontalPager ve VerticalPager, içerik dolgusunun değiştirilmesini destekler. Bu sayede sayfaların maksimum boyutunu ve hizalamasını etkileyebilirsiniz.

Örneğin, start dolgusunun ayarlanması sayfaları sona doğru hizalar:

İçeriği sona doğru hizalanmış şekilde gösteren, başlangıçta dolgu bulunan sayfalayıcı
5. Şekil Başlangıç dolgusu olan sayfalayıcı.

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

Hem start hem de end dolgusunu aynı değere ayarlamak öğeyi yatay olarak ortalar:

İçeriği ortalanmış olarak gösteren, başlangıç ve bitiş dolgulu sayfalayıcı
6. Şekil. Yatay dolgulu sayfa ayırıcı.

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

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

İçeriğin başlangıca göre hizalandığını gösteren, başlangıç ve bitiş dolgulu sayfalayıcı
7. Şekil. Sonunda dolgu olan sayfalayıcı.

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

top ve bottom değerlerini ayarlayarak VerticalPager için benzer efektler elde edebilirsiniz. 32.dp değeri burada yalnızca örnek olarak kullanılmıştır. Dolgu boyutlarının her birini istediğiniz değere ayarlayabilirsiniz.

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

Varsayılan HorizontalPager ve VerticalPager composable'ları, kaydırma hareketlerinin sayfalayıcıyla nasıl çalıştığını belirtir. Ancak pagerSnapDistance veya flingBehavior gibi varsayılanları özelleştirebilir ve değiştirebilirsiniz.

Tutturma mesafesi

Varsayılan olarak, HorizontalPager ve VerticalPager, bir kaydırma hareketinin kaydırabileceği maksimum sayfa sayısını her seferinde bir sayfa olarak ayarlar. Bunu değiştirmek için flingBehavior bölümünde pagerSnapDistance seçeneğini 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),
        beyondViewportPageCount = 10,
        flingBehavior = fling
    ) {
        PagerSampleItem(page = it)
    }
}

Otomatik ilerleyen sayfalayıcı oluşturma

Bu bölümde, Compose'da sayfa göstergeleri içeren otomatik ilerleyen bir sayfalayıcı oluşturma işlemi açıklanmaktadır. Öğeler koleksiyonu otomatik olarak yatay kaydırılır ancak kullanıcılar öğeler arasında manuel olarak da geçiş yapabilir. Kullanıcı kaydırma çubuğuyla etkileşimde bulunursa otomatik ilerleme durdurulur.

Temel örnek

Aşağıdaki snippet'ler birlikte, her sayfanın farklı bir renkte oluşturulduğu, görsel göstergeli temel bir otomatik ilerleyen sayfalayıcı uygulaması oluşturur:

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

Kodla ilgili önemli noktalar

  • AutoAdvancePager işlevi, otomatik ilerlemeyle yatay olarak sayfalandırılan bir görünüm oluşturur. Her sayfanın arka plan rengi olarak kullanılan Color nesnelerinin listesini giriş olarak alır.
  • pagerState, sayfalayıcının durumunu tutan rememberPagerState kullanılarak oluşturulur.
  • pagerIsDragged ve pageIsPressed, kullanıcı etkileşimini izler.
  • Kullanıcı, sayfalayı sürüklemediği veya sayfalardan birine basmadığı sürece LaunchedEffect, sayfalandırıcıyı iki saniyede bir otomatik olarak ilerletir.
  • HorizontalPager, her birinde sayfa numarasını gösteren Text composable'ın bulunduğu bir sayfa listesini gösterir. Değiştirici, sayfayı doldurur, arka plan rengini pageItems olarak ayarlar ve sayfayı tıklanabilir hale getirir.

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

Kodla ilgili önemli noktalar

  • Bir Box composable, kök öğe olarak işlev görür ve sayfa göstergelerini yatay olarak düzenlemek için bir Row içerir.
  • Özel sayfa göstergesi, bir daireler satırı olarak gösterilir. Box Her CircleShape, bir sayfayı temsil eder.
  • Geçerli sayfanın dairesi DarkGray renginde, diğer daireler ise LightGray rengindedir. currentPageIndex parametresi, hangi dairenin koyu gri renkte oluşturulacağını belirler.

Sonuç

Bu videoda, önceki snippet'lerdeki temel otomatik ilerleyen sayfalayıcı gösterilmektedir:

Şekil 8. Sayfalar arasında iki saniye gecikmeyle otomatik olarak ilerleyen bir sayfalayıcı.

Ek kaynaklar