پی‌جو در «نوشتن»

برای ورق زدن محتوا به‌صورت افقی یا عمودی، می‌توانید از عناصر ترکیبی 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 را روی مقداری بزرگ‌تر از صفر تنظیم کنید.

پیمایش به موردی در صفحه‌بندی

برای پیمایش به صفحه‌ای خاص در صفحه‌بندی، شیء PagerState را بااستفاده از rememberPagerState() ایجاد کنید و آن را به‌عنوان پارامتر state به صفحه‌بندی منتقل کنید. در این وضعیت، می‌توانید PagerState#scrollToPage() را در داخل CoroutineScope فراخوانی کنید:

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 استفاده کنید. سپس می‌توانید براساس فاصله از صفحه انتخاب‌شده، جلوه‌های تبدیل را به محتوایتان اعمال کنید.

شکل ۴. درحال اعمال تبدیل‌ها روی محتوای «صفحه‌بندی»

برای مثال، برای تنظیم میزان شفافیت عناصر براساس فاصله آن‌ها از مرکز، alpha را بااستفاده از Modifier.graphicsLayer در عنصر داخل صفحه‌بندی تغییر دهید:

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
}

می‌توانید مقادیر top و bottom را تنظیم کنید تا جلوه‌های مشابهی برای VerticalPager به‌دست آورید. مقدار 32.dp فقط در اینجا به‌عنوان مثال استفاده می‌شود؛ می‌توانید هریک از ابعاد حاشیه‌گذاری را روی هر مقداری تنظیم کنید.

سفارشی‌سازی رفتار پیمایش

ترکیب‌پذیرهای پیش‌فرض HorizontalPager و VerticalPager مشخص می‌کنند اشاره‌های پیمایش چگونه با صفحه‌گرد کار می‌کنند. بااین‌حال، می‌توانید پیش‌فرض‌ها مثل pagerSnapDistance یا flingBehavior را سفارشی‌سازی و تغییر دهید.

فاصله چفت کردن

به‌طور پیش‌فرض، HorizontalPager و VerticalPager حداکثر تعداد صفحه‌هایی را که اشاره پرتاب می‌تواند از آن‌ها عبور کند روی یک صفحه در هر بار تنظیم می‌کنند. برای تغییر دادن این تنظیم، pagerSnapDistance را در flingBehavior تنظیم کنید:

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 composable دارد که شماره صفحه را نمایش می‌دهد. اصلاح‌گر صفحه را پر می‌کند، رنگ پس‌زمینه را از 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 به‌عنوان عنصر ریشه عمل می‌کند و حاوی Row برای چیدن نشانگرهای صفحه به‌صورت افقی است.
  • نشانگر صفحه سفارشی به‌صورت ردیفی از دایره‌ها نمایش داده می‌شود که هر Box کلیپ‌شده به CircleShape نشان‌دهنده یک صفحه است.
  • دایره صفحه کنونی به‌صورت DarkGray رنگ‌آمیزی شده است، درحالی‌که دایره‌های دیگر LightGray هستند. پارامتر currentPageIndex تعیین می‌کند کدام دایره به رنگ خاکستری تیره رندر شود.

نتیجه

این ویدیو صفحه‌گردان پیش‌رونده خودکار پایه را از گزیده‌های قبلی نمایش می‌دهد:

شکل ۸. صفحه‌گرد خودکار با دو ثانیه تأخیر بین هر پیشرفت صفحه.

منابع بیشتر