Compose'da Pager

İçeriklerde sırasıyla sola ve sağa ya da yukarı ve aşağı kaydırmak için HorizontalPager ve VerticalPager composable'larını kullanabilirsiniz. Bu composables, görünüm sistemindeki ViewPager ile benzer işlevlere sahiptir. Varsayılan olarak HorizontalPager ekranın tam genişliğini, VerticalPager ise tam yüksekliğini kaplar ve sayfalayıcılar yalnızca bir sayfayı bir kerede kaydırır. Bu varsayılanların 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 geç oluşturulur ve gerektiğinde düzenlenir. Kullanıcı sayfalar arasında kaydırma yaparken 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

Sayfalama aracında belirli bir sayfaya gitmek için rememberPagerState() kullanarak bir PagerState nesnesi oluşturun ve bunu sayfalama aracına state parametresi olarak iletin. Bu durumda, PagerState#scrollToPage() adlı işlevi CoroutineScope içinde şu şekilde ç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")
}

Sayfayı animasyonla göstermek 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 sayfalarla ilgili bilgileri içeren üç özelliğe sahiptir: 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 Analytics 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, basit bir daire göstergesi istiyorsanız daire sayısını tekrarlayabilir ve pagerState.currentPage kullanarak sayfanın seçilip seçilmediğine bağlı olarak 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 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 efekt uygulamaktır. Bir sayfanın, şu anda seçili olan 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ı olarak 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 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

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ı

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ı

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ı

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 üzerinde pagerSnapDistance ayarını yapı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 kaydırabilir. Kullanıcı, sayfalayıcıyla 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 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 biri sayfa numarasını gösteren Text composable'ı içeren bir sayfa listesi 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

  • Kök öğe olarak Box composable'ı kullanılır.
    • Box içinde, Row composable'ı, sayfa göstergelerini yatay olarak düzenler.
  • Özel sayfa göstergesi, bir daireler satırı olarak gösterilir. Box Her circle, 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:

1. Şekil. Her sayfa ilerlemesi arasında iki saniye gecikme olan, otomatik ilerleyen bir sayfalayıcı.

Ek kaynaklar