صفحة مواضع التمرير

معدِّلات التمرير

يوفّر المعدّلان verticalScroll و horizontalScroll أبسط طريقة للسماح للمستخدم بتمرير عنصر عندما تكون حدود محتواه أكبر من قيود الحجم الأقصى. باستخدام المعدِّلَين verticalScroll وhorizontalScroll، لن تحتاج إلى ترجمة المحتوى أو إزاحته.

@Composable
private fun ScrollBoxes() {
    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .verticalScroll(rememberScrollState())
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

قائمة عمودية بسيطة تستجيب لإيماءات التمرير
الشكل 1. قائمة عمودية بسيطة تستجيب لإيماءات التمرير

تتيح لك السمة ScrollState تغيير موضع التمرير أو الحصول على حالته الحالية. لإنشائه باستخدام المَعلمات التلقائية، استخدِم rememberScrollState().

@Composable
private fun ScrollBoxesSmooth() {
    // Smoothly scroll 100px on first composition
    val state = rememberScrollState()
    LaunchedEffect(Unit) { state.animateScrollTo(100) }

    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .padding(horizontal = 8.dp)
            .verticalScroll(state)
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

معدِّل المنطقة القابلة للتمرير

المعدِّل scrollableArea هو وحدة أساسية لإنشاء حاويات مخصّصة قابلة للتمرير. توفّر هذه السمة مستوى تجريد أعلى من المعدِّل scrollable، وتتعامل مع المتطلبات الشائعة، مثل تفسير الفرق بين الإيماءات، واقتصاص المحتوى، وتأثيرات التمرير الزائد.

على الرغم من أنّ scrollableArea تُستخدَم في عمليات التنفيذ المخصّصة، ننصحك عمومًا باستخدام حلول جاهزة، مثل verticalScroll أو horizontalScroll، أو عناصر قابلة للإنشاء، مثل LazyColumn، لقوائم التمرير العادية. تكون هذه المكوّنات ذات المستوى الأعلى أبسط لحالات الاستخدام الشائعة، وهي نفسها مبنية باستخدام scrollableArea.

الفرق بين المعدِّلَين scrollableArea وscrollable

يكمن الاختلاف الرئيسي بين scrollableArea وscrollable في طريقة تفسيرهما لإيماءات التمرير التي يجريها المستخدم:

  • scrollable (الفرق الأولي): يعكس الفرق مباشرةً الحركة الفعلية التي أجراها المستخدم (مثل سحب المؤشر) على الشاشة.
  • scrollableArea (دلتا موجّهة نحو المحتوى): يتم عكس delta دلاليًا لتمثيل التغيير المحدّد في موضع التمرير لجعل المحتوى يبدو وكأنّه يتحرّك مع إيماءة المستخدم، وهو ما يكون عادةً عكس حركة المؤشر.

يمكنك التفكير في الأمر على النحو التالي: يخبرك scrollable بكيفية تحرّك المؤشر، بينما يحوّل scrollableArea حركة المؤشر هذه إلى كيفية تحرّك المحتوى ضمن طريقة العرض العادية القابلة للتمرير. هذا الانعكاس هو السبب في أنّ scrollableArea يبدو أكثر طبيعية عند تنفيذ حاوية عادية قابلة للتمرير.

يلخّص الجدول التالي إشارات التغيير في السيناريوهات الشائعة:

إيماءة المستخدم

تم إرسال تقرير عن السلالة المتحوّرة دلتا إلى dispatchRawDelta من قِبل scrollable

تم إرسال قيمة التغيير إلى dispatchRawDelta من قِبل scrollableArea*

تحرُّك المؤشر للأعلى

سلبية

إيجابي

تحرُّك المؤشر للأسفل

إيجابي

سلبية

تحرُّك المؤشر لليسار

سلبية

إيجابي (سلبي للغات من اليمين إلى اليسار)

تحرُّك المؤشر لليسار

إيجابي

سالب (موجب للغات من اليمين إلى اليسار)

(*) ملاحظة حول scrollableArea علامة دلتا: إنّ علامة دلتا من scrollableArea ليست مجرد انعكاس بسيط. تأخذ هذه الميزة في الاعتبار بذكاء ما يلي:

  1. الاتجاه: عمودي أو أفقي
  2. LayoutDirection: من اليمين إلى اليسار أو من اليسار إلى اليمين (مهم بشكل خاص للتمرير الأفقي).
  3. علامة reverseScrolling: لتحديد ما إذا كان اتجاه التمرير معكوسًا.

بالإضافة إلى عكس الفرق في التمرير، تعمل السمة scrollableArea أيضًا على قص المحتوى ضمن حدود التصميم ومعالجة تأثيرات التمرير الزائد. يستخدم هذا الخيار تلقائيًا التأثير الذي توفّره LocalOverscrollFactory. يمكنك تخصيص هذا السلوك أو إيقافه باستخدام التحميل الزائد scrollableArea الذي يقبل المَعلمة OverscrollEffect.

حالات استخدام المعدِّل scrollableArea

يجب استخدام المعدِّل scrollableArea عندما تحتاج إلى إنشاء مكوّن مخصّص للتمرير لا يمكن توفيره بشكل كافٍ من خلال المعدِّلين horizontalScroll أو verticalScroll أو التصاميم الكسولة. يشمل ذلك غالبًا الحالات التي تتضمّن ما يلي:

  • منطق التنسيق المخصّص: عندما يتغيّر ترتيب العناصر بشكل ديناميكي استنادًا إلى موضع التمرير
  • تأثيرات بصرية فريدة: تطبيق عمليات تحويل أو تغيير حجم أو تأثيرات أخرى على الأطفال أثناء التمرير
  • التحكّم المباشر: الحاجة إلى تحكّم دقيق في آليات التمرير بما يتجاوز ما تعرضه verticalScroll أو التنسيقات الكسولة

إنشاء قوائم مخصّصة على شكل عجلة باستخدام scrollableArea

يوضّح المثال التالي كيفية استخدام scrollableArea لإنشاء قائمة عمودية مخصّصة يتم فيها تصغير حجم العناصر كلما ابتعدت عن المنتصف، ما يؤدي إلى إنشاء تأثير مرئي "شبيه بالعجلة". هذا النوع من عمليات التحويل المستندة إلى التمرير هو حالة استخدام مثالية لـ scrollableArea.

الشكل 2. قائمة عمودية مخصّصة باستخدام scrollableArea

@Composable
private fun ScrollableAreaSample() {
    // ...
    Layout(
        modifier =
            Modifier
                .size(150.dp)
                .scrollableArea(scrollState, Orientation.Vertical)
                .background(Color.LightGray),
        // ...
    ) { measurables, constraints ->
        // ...
        // Update the maximum scroll value to not scroll beyond limits and stop when scroll
        // reaches the end.
        scrollState.maxValue = (totalHeight - viewportHeight).coerceAtLeast(0)

        // Position the children within the layout.
        layout(constraints.maxWidth, viewportHeight) {
            // The current vertical scroll position, in pixels.
            val scrollY = scrollState.value
            val viewportCenterY = scrollY + viewportHeight / 2

            var placeableLayoutPositionY = 0
            placeables.forEach { placeable ->
                // This sample applies a scaling effect to items based on their distance
                // from the center, creating a wheel-like effect.
                // ...
                // Place the item horizontally centered with a layer transformation for
                // scaling to achieve wheel-like effect.
                placeable.placeRelativeWithLayer(
                    x = constraints.maxWidth / 2 - placeable.width / 2,
                    // Offset y by the scroll position to make placeable visible in the viewport.
                    y = placeableLayoutPositionY - scrollY,
                ) {
                    scaleX = scaleFactor
                    scaleY = scaleFactor
                }
                // Move to the next item's vertical position.
                placeableLayoutPositionY += placeable.height
            }
        }
    }
}
// ...

عنصر تعديل قابل للتمرير

المقاطع محتواها ضمن حدودها، وتعرض التمرير الزائد، وتعدّل اتجاه إيماءات التمرير للتحقّق من أنّ المحتوى يتحرّك مع إيماءات المستخدم.

يختلف المعدِّل scrollable عن معدِّلات التمرير في أنّ scrollable يرصد إيماءات التمرير ويلتقط القيم المتغيرة، لكنّه لا يزيح محتواه تلقائيًا. بدلاً من ذلك، يتم تفويض هذا الإذن إلى المستخدم من خلال ScrollableState ، وهو أمر ضروري لكي يعمل معدِّل الوصول هذا بشكل صحيح.

عند إنشاء ScrollableState، يجب توفير دالة consumeScrollDelta سيتم استدعاؤها في كل خطوة من خطوات التمرير (من خلال إدخال الإيماءات أو التمرير السلس أو التمرير السريع) مع الفرق بالبكسل. يجب أن تعرض هذه الدالة مقدار مسافة التمرير التي تم استهلاكها، وذلك لضمان نشر الحدث بشكل صحيح في الحالات التي تتضمّن عناصر متداخلة تحتوي على المعدِّل scrollable.

تعمل المقتطفة البرمجية التالية على رصد الإيماءات وعرض قيمة رقمية للإزاحة، ولكنّها لا تعمل على إزاحة أي عناصر:

@Composable
private fun ScrollableSample() {
    // actual composable state
    var offset by remember { mutableFloatStateOf(0f) }
    Box(
        Modifier
            .size(150.dp)
            .scrollable(
                orientation = Orientation.Vertical,
                // Scrollable state: describes how to consume
                // scrolling delta and update offset
                state = rememberScrollableState { delta ->
                    offset += delta
                    delta
                }
            )
            .background(Color.LightGray),
        contentAlignment = Alignment.Center
    ) {
        Text(offset.toString())
    }
}

عنصر واجهة مستخدم يرصد ضغطة الإصبع ويعرض القيمة الرقمية لموضع الإصبع
الشكل 3. عنصر واجهة مستخدم يرصد ضغطة الإصبع ويعرض القيمة الرقمية لموقع الإصبع.