Compose'da Pager

İçerikler arasında yatay veya dikey olarak gezinmek için HorizontalPager ve VerticalPager bileşenlerini kullanabilirsiniz. Bunlar, görünüm sistemindeki ViewPager ile benzer işlevlere sahiptir. Varsayılan olarak HorizontalPager tam ekran genişliğini, VerticalPager ise tam yüksekliği kaplar. Sayfa gezginleri de tek seferde yalnızca bir sayfa kaydırır. Bu varsayılanların tümü yapılandırılabilir.

HorizontalPager

Yatay olarak sola ve sağa kaydırılan bir sayfa gezgini oluşturmak için HorizontalPager 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 sayfa gezgini oluşturmak için VerticalPager 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()
    )
}

Geç oluşturma

Hem HorizontalPager hem de VerticalPager'deki sayfalar gecikmeli olarak oluşturulur ve gerektiğinde düzenlenir. Kullanıcı sayfalar arasında gezinirken composable, artık gerekli olmayan sayfaları kaldırır.

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

Varsayılan olarak sayfa gezgini 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.

Sayfa gezginindeki bir öğeye kaydırma

Sayfa gezgininde belirli bir sayfaya gitmek için rememberPagerState() kullanarak bir PagerState nesnesi oluşturun ve bunu sayfa gezginine state parametresi olarak iletin. Bu durumu, bir CoroutineScope içinde PagerState#scrollToPage() ile ç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şiklikleriyle ilgili bildirim alma

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

  • currentPage: Yakalama konumuna en yakın sayfa. Varsayılan olarak, tutturma konumu düzenin başındadır.
  • settledPage: Animasyon veya kaydırma işlemi yapılmadığında sayfa numarası. Bu, currentPage özelliğinden farklıdır. Sayfa, tutturma konumuna yeterince yakınsa currentPage hemen güncellenir ancak settledPage, tüm animasyonlar tamamlanana kadar aynı kalır.
  • targetPage: 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 analiz 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 ekleme

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

Örneğin, daire göstergesi oluşturmak için daire sayısını tekrarlayabilir ve sayfanın seçili olup olmamasına göre daire rengini değiştirebilirsiniz. Bunun için pagerState.currentPage kullanabilirsiniz:

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

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

Yaygın kullanım alanlarından biri, sayfa gezgini öğelerinize efekt uygulamak için kaydırma konumunu kullanmaktır. Bir sayfanın seçilen 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üşüm efektleri uygulayabilirsiniz.

Şekil 4. Sayfa gezgini içeriğine dönüşüm uygulama

Örneğin, öğelerin opaklığını merkeze olan uzaklıklarına göre ayarlamak için sayfa gezginindeki 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 boyut hesaplaması olacak şekilde ayarlayabilirsiniz.

Örneğin, 100.dp boyutunda 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 hesaba katarak 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 dolgusunu değiştirmeyi destekler. Bu sayede sayfaların maksimum boyutunu ve hizalamasını etkileyebilirsiniz.

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

İçeriği sona doğru hizalanmış şekilde gösteren, başlangıç dolgulu sayfa gezgini
5. şekil. Başlangıç dolgusu olan sayfa gezgini.

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 sayfa gezgini
Şekil 6. Yatay dolgulu sayfa gezgini.

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ği başlangıca hizalanmış şekilde gösteren, başlangıç ve bitiş dolgulu sayfa gezgini
Şekil 7. Son dolgulu sayfa gezgini.

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

VerticalPager için benzer efektler elde etmek üzere top ve bottom değerlerini ayarlayabilirsiniz. 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 sayfa gezginiyle nasıl çalıştığını belirtir. Ancak pagerSnapDistance veya flingBehavior gibi varsayılan ayarları özelleştirip değiştirebilirsiniz.

Tutturma mesafesi

Varsayılan olarak HorizontalPager ve VerticalPager, kaydırma hareketinin tek seferde kaydırabileceği maksimum sayfa sayısını bir sayfa olarak 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),
        beyondViewportPageCount = 10,
        flingBehavior = fling
    ) {
        PagerSampleItem(page = it)
    }
}

Otomatik ilerleyen sayfa gezgini oluşturma

Bu bölümde, Compose'da sayfa göstergeleri içeren otomatik ilerleyen bir sayfa gezgini oluşturma işlemi açıklanmaktadır. Öğe koleksiyonu otomatik olarak yatay şekilde kaydırılır ancak kullanıcılar öğeler arasında manuel olarak da kaydırma yapabilir. Kullanıcı sayfa gezginiyle etkileşim kurarsa 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 sayfa gezgini 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 ilerleme özelliğine sahip yatay sayfalama görünümü oluşturur. Giriş olarak, her sayfanın arka plan rengi olarak kullanılan Color nesnelerinin listesini alır.
  • pagerState, sayfa gezgininin durumunu tutan rememberPagerState kullanılarak oluşturulur.
  • pagerIsDragged ve pageIsPressed, kullanıcı etkileşimini izler.
  • LaunchedEffect, kullanıcı sayfa gezginini sürüklemediği veya sayfalardan birine basmadığı sürece sayfa gezginini her iki saniyede bir otomatik olarak ilerletir.
  • HorizontalPager, her biri sayfa numarasını gösteren bir Text composable'ı içeren sayfaların 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

  • Box composable, kök öğe görevi görür ve sayfa göstergelerini yatay olarak düzenlemek için bir Row içerir.
  • Özel sayfa göstergesi, her bir Box öğesinin bir CircleShape ile kırpılarak bir sayfayı temsil ettiği bir daire satırı olarak gösterilir.
  • Geçerli sayfanın dairesi DarkGray renkte, diğer daireler ise LightGray renktedir. currentPageIndex parametresi, hangi dairenin koyu gri renkte oluşturulacağını belirler.

Sonuç

Bu videoda, önceki snippet'lerde yer alan temel otomatik ilerleyen sayfa gezgini gösterilmektedir:

8. şekil. Her sayfa ilerlemesi arasında iki saniyelik gecikme olan, otomatik ilerleyen bir sayfa gezgini.

Ek kaynaklar