مفاتيح تعديل التمرير

يوفّر المعدّلان 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. عنصر في واجهة المستخدم يرصد الضغط بالإصبع ويعرض القيمة الرقمية لموقع الإصبع