Compose में पेजर

कॉन्टेंट को बाएं से दाएं या ऊपर से नीचे की ओर फ़्लिप करने के लिए, HorizontalPager और VerticalPager कंपोज़ेबल का इस्तेमाल किया जा सकता है. इन कंपोज़ेबल के फ़ंक्शन, व्यू सिस्टम में मौजूद ViewPager के फ़ंक्शन से मिलते-जुलते हैं. डिफ़ॉल्ट रूप से, HorizontalPager स्क्रीन की पूरी चौड़ाई लेता है, VerticalPager पूरी ऊंचाई लेता है, और पेजर्स एक बार में सिर्फ़ एक पेज फ़्लिंग करते हैं. इन डिफ़ॉल्ट सेटिंग को कॉन्फ़िगर किया जा सकता है.

HorizontalPager

बाईं और दाईं ओर स्क्रोल करने वाला पेजर बनाने के लिए, HorizontalPager का इस्तेमाल करें:

पहली इमेज. HorizontalPager का डेमो

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

VerticalPager

ऊपर और नीचे की ओर स्क्रोल करने वाला पेजर बनाने के लिए, VerticalPager का इस्तेमाल करें:

दूसरी इमेज. VerticalPager का डेमो

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

आसानी से कॉन्टेंट बनाना

HorizontalPager और VerticalPager, दोनों में मौजूद पेजों को ज़रूरत के मुताबिक लेज़ी कंपोज़िशन और लेआउट किया जाता है. उपयोगकर्ता के पेजों को स्क्रोल करने पर, कंपोज़ेबल उन पेजों को हटा देता है जिनकी अब ज़रूरत नहीं है.

स्क्रीन पर न दिखने वाले ज़्यादा पेज लोड करें

डिफ़ॉल्ट रूप से, पेजर सिर्फ़ स्क्रीन पर दिखने वाले पेजों को लोड करता है. स्क्रीन से बाहर मौजूद ज़्यादा पेजों को लोड करने के लिए, beyondBoundsPageCount को शून्य से ज़्यादा पर सेट करें.

पेजर में किसी आइटम पर स्क्रोल करना

पेजर में किसी पेज पर स्क्रोल करने के लिए, rememberPagerState() का इस्तेमाल करके PagerState ऑब्जेक्ट बनाएं. इसके बाद, इसे पेजर में state पैरामीटर के तौर पर पास करें. इस स्थिति में, CoroutineScope के अंदर PagerState#scrollToPage() को कॉल किया जा सकता है:

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

अगर आपको पेज को ऐनिमेट करना है, तो 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")
}

पेज की स्थिति में बदलाव होने पर सूचना पाना

PagerState में पेजों के बारे में जानकारी देने वाली तीन प्रॉपर्टी होती हैं: currentPage, settledPage, और targetPage.

  • currentPage: स्नैप की गई जगह के सबसे नज़दीक वाला पेज. डिफ़ॉल्ट रूप से, स्नैप पोजीशन लेआउट की शुरुआत में होती है.
  • settledPage: जब कोई ऐनिमेशन या स्क्रोलिंग नहीं चल रही हो, तब पेज नंबर. यह currentPage प्रॉपर्टी से अलग है. ऐसा इसलिए, क्योंकि अगर पेज स्नैप पोज़िशन के काफ़ी करीब है, तो currentPage तुरंत अपडेट हो जाता है. हालांकि, settledPage तब तक एक जैसा रहता है, जब तक सभी ऐनिमेशन चलना बंद नहीं हो जाते.
  • targetPage: स्क्रोलिंग के लिए सुझाई गई स्टॉप पोज़िशन.

इन वैरिएबल में हुए बदलावों को देखने और उन पर प्रतिक्रिया देने के लिए, snapshotFlow फ़ंक्शन का इस्तेमाल किया जा सकता है. उदाहरण के लिए, हर पेज बदलने पर कोई Analytics इवेंट भेजने के लिए, यह तरीका अपनाया जा सकता है:

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

पेज इंडिकेटर जोड़ना

किसी पेज में इंडिकेटर जोड़ने के लिए, PagerState ऑब्जेक्ट का इस्तेमाल करें. इससे आपको यह जानकारी मिलेगी कि कई पेजों में से कौनसा पेज चुना गया है. साथ ही, इससे आपको अपना कस्टम इंडिकेटर बनाने में मदद मिलेगी.

उदाहरण के लिए, अगर आपको एक सामान्य सर्कल इंडिकेटर चाहिए, तो सर्कल की संख्या को दोहराया जा सकता है. साथ ही, पेज के चुने जाने के आधार पर सर्कल का रंग बदला जा सकता है. इसके लिए, 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)
        )
    }
}

कॉन्टेंट के नीचे सर्कल इंडिकेटर दिखाने वाला पेजर
तीसरी इमेज. कॉन्टेंट के नीचे सर्कल इंडिकेटर दिखाने वाला पेजर

कॉन्टेंट पर आइटम स्क्रोल इफ़ेक्ट लागू करना

इसका इस्तेमाल आम तौर पर, स्क्रोल की पोज़िशन के हिसाब से पेजर आइटम पर इफ़ेक्ट लागू करने के लिए किया जाता है. चुने गए मौजूदा पेज से कोई पेज कितनी दूर है, यह जानने के लिए PagerState.currentPageOffsetFraction का इस्तेमाल किया जा सकता है. इसके बाद, चुने गए पेज से दूरी के आधार पर, अपने कॉन्टेंट पर ट्रांसफ़ॉर्मेशन इफ़ेक्ट लागू किए जा सकते हैं.

चौथी इमेज. पेजर कॉन्टेंट में बदलाव करना

उदाहरण के लिए, आइटम की ओपैसिटी को इस आधार पर अडजस्ट करने के लिए कि वे सेंटर से कितनी दूर हैं, पेजर में मौजूद किसी आइटम पर Modifier.graphicsLayer का इस्तेमाल करके alpha को बदलें:

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

पसंद के मुताबिक पेज के साइज़

डिफ़ॉल्ट रूप से, HorizontalPager और VerticalPager पूरी चौड़ाई या पूरी ऊंचाई लेते हैं. pageSize वैरिएबल को Fixed, Fill (डिफ़ॉल्ट) या कस्टम साइज़ कैलकुलेशन पर सेट किया जा सकता है.

उदाहरण के लिए, 100.dp की तय चौड़ाई वाला पेज सेट करने के लिए:

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

व्यूपोर्ट के साइज़ के आधार पर पेजों का साइज़ तय करने के लिए, पेज के साइज़ का हिसाब लगाने का कस्टम तरीका इस्तेमाल करें. कस्टम PageSize ऑब्जेक्ट बनाएं और आइटम के बीच की दूरी को ध्यान में रखते हुए, availableSpace को तीन हिस्सों में बांटें:

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

कॉन्टेंट पैडिंग

HorizontalPager और VerticalPager, दोनों में कॉन्टेंट पैडिंग बदलने की सुविधा होती है. इससे पेजों के ज़्यादा से ज़्यादा साइज़ और अलाइनमेंट पर असर डाला जा सकता है.

उदाहरण के लिए, start पैडिंग सेट करने से, पेज आखिर की ओर अलाइन हो जाते हैं:

स्टार्ट पैडिंग वाला पेजर, जिसमें कॉन्टेंट आखिर की ओर अलाइन किया गया है

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

start और end, दोनों के लिए एक जैसी पैडिंग सेट करने से आइटम, हॉरिज़ॉन्टल तौर पर बीच में आ जाता है:

कॉन्टेंट को बीच में दिखाने वाला, शुरू और आखिर में पैडिंग वाला पेजर

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

end पैडिंग सेट करने से, पेज शुरुआत की ओर अलाइन हो जाते हैं:

शुरू और आखिर में पैडिंग वाला पेजर, जिसमें कॉन्टेंट को शुरुआत में अलाइन किया गया है

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

VerticalPager के लिए एक जैसे इफ़ेक्ट पाने के लिए, top और bottom वैल्यू सेट की जा सकती हैं. यहां 32.dp वैल्यू का इस्तेमाल सिर्फ़ उदाहरण के तौर पर किया गया है. पैडिंग के हर डाइमेंशन के लिए कोई भी वैल्यू सेट की जा सकती है.

स्क्रोल करने के तरीके को पसंद के मुताबिक बनाना

डिफ़ॉल्ट HorizontalPager और VerticalPager कंपोज़ेबल से यह तय होता है कि स्क्रोल करने के जेस्चर, पेजर के साथ कैसे काम करते हैं. हालांकि, आपके पास डिफ़ॉल्ट सेटिंग को अपनी पसंद के मुताबिक बनाने और बदलने का विकल्प होता है. जैसे, pagerSnapDistance या flingBehavior.

स्नैप की दूरी

डिफ़ॉल्ट रूप से, HorizontalPager और VerticalPager, फ़्लिंग जेस्चर से एक बार में स्क्रोल किए जा सकने वाले पेजों की ज़्यादा से ज़्यादा संख्या को एक पेज पर सेट करते हैं. इसे बदलने के लिए, flingBehavior पर pagerSnapDistance सेट करें:

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

अपने-आप आगे बढ़ने वाला पेजर बनाना

इस सेक्शन में, Compose में पेज इंडिकेटर के साथ अपने-आप आगे बढ़ने वाला पेज व्यू बनाने का तरीका बताया गया है. आइटम का कलेक्शन, हॉरिज़ॉन्टल तौर पर अपने-आप स्क्रोल होता है. हालांकि, उपयोगकर्ता आइटम के बीच मैन्युअल तरीके से भी स्वाइप कर सकते हैं. अगर कोई उपयोगकर्ता पेजर से इंटरैक्ट करता है, तो यह अपने-आप आगे बढ़ने की सुविधा को बंद कर देता है.

सामान्य उदाहरण

नीचे दिए गए स्निपेट, एक साथ मिलकर ऑटो-ऐडवांसिंग पेजर को लागू करने का एक बुनियादी तरीका बनाते हैं. इसमें विज़ुअल इंडिकेटर भी शामिल होता है. इसमें हर पेज अलग-अलग रंग में रेंडर होता है:

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

कोड के बारे में अहम जानकारी

  • AutoAdvancePager फ़ंक्शन, हॉरिज़ॉन्टल पेजिंग व्यू बनाता है. इसमें पेज अपने-आप आगे बढ़ता है. यह Color ऑब्जेक्ट की सूची को इनपुट के तौर पर लेता है. इनका इस्तेमाल हर पेज के बैकग्राउंड कलर के तौर पर किया जाता है.
  • pagerState को rememberPagerState का इस्तेमाल करके बनाया जाता है. यह पेजर की स्थिति को सेव करता है.
  • pagerIsDragged और pageIsPressed कुकी, उपयोगकर्ता के इंटरैक्शन को ट्रैक करती हैं.
  • LaunchedEffect हर दो सेकंड में पेजर को अपने-आप आगे बढ़ाता है. हालांकि, ऐसा तब तक होता है, जब तक उपयोगकर्ता पेजर को खींचकर न ले जाए या किसी पेज को न दबाए.
  • HorizontalPager में पेजों की सूची दिखती है. हर पेज में Text कंपोज़ेबल होता है, जिसमें पेज नंबर दिखता है. मॉडिफ़ायर, पेज को भरता है. साथ ही, pageItems से बैकग्राउंड का रंग सेट करता है और पेज को क्लिक करने लायक बनाता है.

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

कोड के बारे में अहम जानकारी

  • रूट एलिमेंट के तौर पर, Box कंपोज़ेबल का इस्तेमाल किया जाता है.
    • Box के अंदर, Row कंपोज़ेबल, पेज इंडिकेटर को हॉरिजॉन्टल तरीके से व्यवस्थित करता है.
  • कस्टम पेज इंडिकेटर को सर्कल की एक लाइन के तौर पर दिखाया जाता है. इसमें हर Box क्लिप किए गए circle का मतलब एक पेज होता है.
  • मौजूदा पेज के सर्कल को DarkGray के तौर पर रंगा गया है, जबकि अन्य सर्कल LightGray हैं. currentPageIndex पैरामीटर से यह तय होता है कि कौनसी सर्कल गहरे स्लेटी रंग में रेंडर होगी.

नतीजा

इस वीडियो में, पिछले स्निपेट से मिले बुनियादी ऑटो-ऐडवांसिंग पेजर को दिखाया गया है:

पहली इमेज. अपने-आप आगे बढ़ने वाला पेजर, जिसमें हर पेज के बीच दो सेकंड का अंतर होता है.

अन्य संसाधन