في Jetpack Compose، يُعدّ scrollable2D وdraggable2D من المعدِّلات المنخفضة المستوى المصمَّمة للتعامل مع إدخال المؤشر في بُعدَين. في حين أنّ معدِّلات الأبعاد الأحادية العادية scrollable وdraggable تقتصر على اتجاه واحد، فإنّ معدِّلات الأبعاد الثنائية تتبع الحركة على محورَي X وY في الوقت نفسه.
على سبيل المثال، يتم استخدام المُعدِّل الحالي scrollable للتمرير والتحريك السريع في اتجاه واحد، بينما يتم استخدام scrollable2d للتمرير والتحريك السريع في اتجاهين. يتيح لك ذلك إنشاء تخطيطات أكثر تعقيدًا تتحرك في جميع
الاتجاهات، مثل جداول البيانات أو عارض الصور. يتيح المُعدِّل scrollable2d أيضًا
التمرير المُدمَج في السيناريوهات الثنائية الأبعاد.
اختَر scrollable2D أو draggable2D
يعتمد اختيار واجهة برمجة التطبيقات المناسبة على عناصر في واجهة المستخدم التي تريد نقلها والسلوك المادي المفضّل لهذه العناصر.
Modifier.scrollable2D: استخدِم هذا المُعدِّل على حاوية لنقل المحتوى داخلها. على سبيل المثال، يمكنك استخدامها مع الخرائط أو جداول البيانات أو عارض الصور، حيث يحتاج محتوى الحاوية إلى التمرير في الاتجاهين الأفقي والعمودي. وتتضمّن هذه الميزة دعمًا مدمجًا للتمرير السريع، ما يتيح استمرار حركة المحتوى بعد التمرير، وتنسيقها مع مكوّنات التمرير الأخرى على الصفحة.
Modifier.draggable2D: استخدِم هذا المُعدِّل لنقل مكوّن معيّن. وهو معدِّل خفيف، لذا تتوقف الحركة عندما يتوقف إصبع المستخدم بالضبط. ولا يتضمّن ميزة "الإلقاء".
إذا أردت جعل مكوّن قابلاً للسحب، ولكنّك لست بحاجة إلى دعم التمرير السريع أو التمرير المُدمَج، استخدِم draggable2D.
تنفيذ المُعدِّلات الثنائية الأبعاد
تقدّم الأقسام التالية أمثلة توضّح كيفية استخدام المعدِّلات الثنائية الأبعاد.
تنفيذ Modifier.scrollable2D
استخدِم هذا المُعدِّل للحاويات التي يحتاج فيها المستخدم إلى نقل المحتوى في جميع الاتجاهات.
تسجيل بيانات الحركة الثنائية الأبعاد
يوضّح هذا المثال كيفية تسجيل بيانات الحركة الأولية الثنائية الأبعاد وعرض الإزاحة على المحورين س وص:
@Composable private fun Scrollable2DSample() { // 1. Manually track the total distance the user has moved in both X and Y directions var offset by remember { mutableStateOf(Offset.Zero) } Box( modifier = Modifier .fillMaxSize() // ... contentAlignment = Alignment.Center ) { Box( modifier = Modifier .size(200.dp) // 2. Attach the 2D scroll logic to capture XY movement deltas .scrollable2D( state = rememberScrollable2DState { delta -> // 3. Update the cumulative offset state with the new movement delta offset += delta // Return the delta to indicate the entire movement was handled by this box delta } ) // ... contentAlignment = Alignment.Center ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { // 4. Display the current X and Y values from the offset state in real-time Text( text = "X: ${offset.x.roundToInt()}", // ... ) Spacer(modifier = Modifier.height(8.dp)) Text( text = "Y: ${offset.y.roundToInt()}", // ... ) } } } }
ينفّذ المقتطف السابق ما يلي:
- يستخدم
offsetكحالة تحتفظ بإجمالي المسافة التي انتقل إليها المستخدم للأسفل. - داخل
rememberScrollable2DState، يتم تحديد دالة lambda للتعامل مع كل دلتا، يتم إنشاؤها بواسطة إصبع المستخدم. يعدّل الرمزoffset.value += deltaالحالة اليدوية بالموضع الجديد. - تعرض مكوّنات
Textقيم X وY الحالية لحالةoffset، والتي يتم تعديلها في الوقت الفعلي أثناء سحب المستخدم.
تحريك إطار عرض كبير
يوضّح هذا المثال كيفية استخدام بيانات ثنائية الأبعاد قابلة للتمرير وتطبيق
translationX وtranslationY على محتوى أكبر من الحاوية
الرئيسية:
@Composable private fun Panning2DImage() { // Manually track the total distance the user has moved in both X and Y directions val offset = remember { mutableStateOf(Offset.Zero) } // Define how gestures are captured. The lambda is called for every finger movement val scrollState = rememberScrollable2DState { delta -> offset.value += delta delta } // The Viewport (Container): A fixed-size box that acts as a window into the larger content Box( modifier = Modifier .size(600.dp, 400.dp) // The visible area dimensions // ... // Hide any parts of the large content that sit outside this container's boundaries .clipToBounds() // Apply the 2D scroll modifier to intercept touch and fling gestures in all directions .scrollable2D(state = scrollState), contentAlignment = Alignment.Center, ) { // The Content: An image given a much larger size than the container viewport Image( painter = painterResource(R.drawable.cheese_5), contentDescription = null, modifier = Modifier .requiredSize(1200.dp, 800.dp) // Manual Scroll Effect: Since scrollable2D doesn't move content automatically, // we use graphicsLayer to shift the drawing position based on the tracked offset. .graphicsLayer { translationX = offset.value.x translationY = offset.value.y }, contentScale = ContentScale.FillBounds ) } }
Modifier.scrollable2DModifier.scrollable2D هو إطار عرض نصي ثنائي الاتجاه.يتضمّن المقتطف السابق ما يلي:
- تم ضبط الحاوية على حجم ثابت (
600x400dp)، بينما تم منح المحتوى حجمًا أكبر بكثير (1200x800dp) لتجنُّب تغيير حجمه إلى حجم الحاوية الرئيسية. - يضمن المُعدِّل
clipToBounds()في الحاوية إخفاء أي جزء من المحتوى الكبير الذي يقع خارج المربّع600x400عن العرض. - على عكس المكوّنات العالية المستوى مثل
LazyColumn، لا ينقلscrollable2Dالمحتوى تلقائيًا. بدلاً من ذلك، يجب تطبيقoffsetالذي يتم تتبُّعه على المحتوى، إما باستخدام تحويلاتgraphicsLayerأو إزاحات التنسيق. - داخل مجموعة
graphicsLayer، تعملtranslationX = offset.value.xوtranslationY = offset.value.yعلى تغيير موضع رسم الصورة أو النص بناءً على حركة إصبعك، مما يؤدي إلى إنشاء التأثير المرئي للتمرير.
تنفيذ الانتقال المتداخل باستخدام scrollable2D
يوضّح هذا المثال كيفية دمج مكوّن ثنائي الاتجاه في مكوّن رئيسي عادي أحادي الاتجاه، مثل خلاصة أخبار عمودية.
يُرجى مراعاة النقاط التالية عند تنفيذ ميزة "التمرير المُدمَج":
- يجب أن تعرض دالة lambda لـ
rememberScrollable2DStateفقط الفرق المستهلك، للسماح للقائمة الرئيسية بالاستحواذ بشكل طبيعي عندما تصل القائمة الفرعية إلى الحد الأقصى. - عندما ينفّذ المستخدم حركة سريعة قُطرية، تتم مشاركة السرعة الثنائية الأبعاد. إذا وصل العنصر الفرعي إلى حدّ معيّن أثناء عرض الصورة المتحركة، يتم نقل الزخم المتبقي إلى العنصر الرئيسي لمواصلة الانتقال بشكل طبيعي.
@Composable private fun NestedScrollable2DSample() { var offset by remember { mutableStateOf(Offset.Zero) } val maxScrollDp = 250.dp val maxScrollPx = with(LocalDensity.current) { maxScrollDp.toPx() } Column( modifier = Modifier .fillMaxSize() .verticalScroll(rememberScrollState()) .background(Color(0xFFF5F5F5)), horizontalAlignment = Alignment.CenterHorizontally ) { Text( "Scroll down to find the 2D Box", modifier = Modifier.padding(top = 100.dp, bottom = 500.dp), style = TextStyle(fontSize = 18.sp, color = Color.Gray) ) // The Child: A 2D scrollable box with nested scroll coordination Box( modifier = Modifier .size(250.dp) .scrollable2D( state = rememberScrollable2DState { delta -> val oldOffset = offset // Calculate new potential offset and clamp it to our boundaries val newX = (oldOffset.x + delta.x).coerceIn(-maxScrollPx, maxScrollPx) val newY = (oldOffset.y + delta.y).coerceIn(-maxScrollPx, maxScrollPx) val newOffset = Offset(newX, newY) // Calculate exactly how much was consumed by the child val consumed = newOffset - oldOffset offset = newOffset // IMPORTANT: Return ONLY the consumed delta. // The remaining (unconsumed) delta propagates to the parent Column. consumed } ) // ... contentAlignment = Alignment.Center ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { val density = LocalDensity.current Text("2D Panning Zone", color = Color.White.copy(alpha = 0.7f), fontSize = 12.sp) Spacer(Modifier.height(8.dp)) Text("X: ${with(density) { offset.x.toDp().value.roundToInt() }}dp", color = Color.White, fontWeight = FontWeight.Bold) Text("Y: ${with(density) { offset.y.toDp().value.roundToInt() }}dp", color = Color.White, fontWeight = FontWeight.Bold) } } Text( "Once the Purple Box hits Y: 250 or -250,\nthis parent list will take over the vertical scroll.", textAlign = TextAlign.Center, modifier = Modifier.padding(top = 40.dp, bottom = 800.dp), style = TextStyle(fontSize = 14.sp, color = Color.Gray) ) } }
في المقتطف السابق:
- يمكن للمكوّن الثنائي الأبعاد استخدام حركة المحور X للتحريك داخليًا مع إرسال حركة المحور Y في الوقت نفسه إلى القائمة الرئيسية بمجرد الوصول إلى الحدود العمودية الخاصة بالمكوّن الفرعي.
- بدلاً من حصر المستخدم داخل السطح الثنائي الأبعاد، يحسب النظام الفرق المستهلك ويمرر الباقي إلى أعلى التسلسل الهرمي. يضمن ذلك أن يتمكن المستخدم من مواصلة التمرير خلال بقية الصفحة دون رفع إصبعه.
تنفيذ Modifier.draggable2D
للحصول على معلومات حول كيفية استخدام Modifier.draggable2D، يُرجى الاطّلاع على السحب في بُعدَين باستخدام Modifier.draggable2D.