جهاز نداء في "إنشاء"

للتنقل عبر المحتوى بطريقة لليسار واليمين أو لأعلى ولأسفل، يمكنك استخدام الـ HorizontalPager أو VerticalPager للمواد القابلة للإنشاء على التوالي. هذه العناصر القابلة للإنشاء لها دوال مشابهة ViewPager في العرض . يشغل HorizontalPager عرض الشاشة بالكامل تلقائيًا، تشغل VerticalPager الارتفاع الكامل، بينما تتنقل أجهزة النداء صفحة واحدة فقط في الوقت. جميع هذه الإعدادات الافتراضية قابلة للتهيئة.

HorizontalPager

لإنشاء جهاز نداء يتم تمريره أفقيًا يسارًا ويمينًا، استخدم HorizontalPager:

الشكل 1. عرض توضيحي عن 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:

الشكل 2. عرض توضيحي عن 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 لملاحظة التغييرات التي تطرأ على هذه المتغيرات والتفاعل معها. فعلى سبيل المثال، لإرسال حدث بالإحصاءات عن كل تغيير في الصفحة، يمكنك إجراء ما يلي:

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

جهاز نداء يعرض مؤشر دائرة أسفل المحتوى
الشكل 3. جهاز نداء يعرض مؤشر دائرة أسفل المحتوى

تطبيق تأثيرات التمرير على العناصر على المحتوى

من بين حالات الاستخدام الشائعة استخدام موضع التمرير لتطبيق تأثيرات على جهاز النداء عناصر. لمعرفة مدى بُعد صفحة عن الصفحة المحددة حاليًا، يمكنك استخدام PagerState.currentPageOffsetFraction يمكنك بعد ذلك تطبيق تأثيرات التحويل على المحتوى استنادًا إلى المسافة من الصفحة المحددة.

الشكل 4. تطبيق عمليات التحويل على محتوى جهاز النداء

فعلى سبيل المثال، لضبط تعتيم العناصر بناءً على مدى بُعدها عن المنتصف، غيِّر 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),
        beyondBoundsPageCount = 10,
        flingBehavior = fling
    ) {
        PagerSampleItem(page = it)
    }
}