عوامل تعديل الانتقال
توفّر المُعدِّلات
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)) } } }
يتيح لك الرمز 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)) } } }
مُعدِّل قابل للتمرير
يختلف المُعدِّل
scrollable
عن مُعدِّلات الانتقال إلى الأعلى أو الأسفل في أنّ scrollable
يرصد
إيماءات الانتقال إلى الأعلى أو الأسفل ويُسجّل الاختلافات، ولكنّه لا يغيّر محتوياته
تلقائيًا. بدلاً من ذلك، يتم تفويض هذا الإجراء إلى المستخدم من خلال
ScrollableState
، وهو مطلوب لكي يعمل هذا المُعدِّل بشكل صحيح.
عند إنشاء ScrollableState
، يجب تقديم دالة consumeScrollDelta
ستتمّ دعوتها في كلّ خطوة لفّ (من خلال إدخال إيماءة أو
لفّ سلس أو رمي) مع إضافة القيمة delta بالبكسل. يجب أن تعرِض هذه الدالة مقدار المسافة التي تم الانتقال إليها باستخدام شريط التمرير، لضمان انتشار الحدث بشكلٍ سليم في الحالات التي تتوفّر فيها عناصر متداخلة تحتوي على المُعدِّل scrollable
.
يرصد المقتطف التالي الإيماءات ويعرض قيمة رقمية لoffset، ولكنّه لا يغيّر موضع أي عناصر:
@Composable private fun ScrollableSample() { // actual composable state var offset by remember { mutableStateOf(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()) } }
التمرير المتداخل
الانتقال المتداخل هو نظام يعمل فيه معًا مكوّنات متعددة للانتقال في الصفحة مضمّنة داخل بعضها البعض من خلال الاستجابة لإيماءة واحدة للانتقال في الصفحة وإرسال قيم التغييرات في الانتقال في الصفحة.
يتيح نظام الانتقال المتداخل التنسيق بين المكوّنات التي يمكن التمرير فيها والتي تكون مرتبطة بشكل هرمي (غالبًا من خلال مشاركة العنصر الرئيسي نفسه). يربط هذا النظام الحاويات التي يتم التمرير فيها ويسمح بالتفاعل مع التغييرات التي يتم نشرها ومشاركتها بين الحاويات.
توفّر أداة الإنشاء طرقًا متعدّدة للتعامل مع الانتقال المتداخل بين العناصر القابلة للتجميع. ومن الأمثلة الشائعة على الانتقال المتداخل قائمة داخل قائمة أخرى، والمثال الأكثر تعقيدًا هو شريط أدوات قابل للطي.
الانتقال التلقائي للأعلى أو للأسفل في القوائم المُدمجة
لا يتطلب الانتقال البسيط للأعلى أو للأسفل في المحتوى المُدمَج أي إجراء من جانبك. إنّ الإيماءات التي تبدأ بإجراء الانتقال للأعلى أو للأسفل يتم نشرها من العناصر الثانوية إلى العناصر الرئيسية تلقائيًا، بحيث عندما يتعذّر على العنصر الثانوي الانتقال للأعلى أو للأسفل، يعالج العنصر الرئيسي الإيماءة.
تتوفّر ميزة الانتقال المتداخل التلقائي بشكل تلقائي في بعض مكونات verticalScroll
وhorizontalScroll
وscrollable
وLazy
واجهات برمجة التطبيقات وTextField
. وهذا يعني أنّه عندما ينتقل المستخدم إلى ملف شخصي
داخلي لعنصر مكوّن مُدمَج، تُرسِل المُعدِّلات السابقة قيم اختلافات التمرير
إلى العناصر الرئيسية التي تتيح التمرير المُدمَج.
يعرض المثال التالي عناصر تم تطبيق المُعدِّل
verticalScroll
عليها داخل حاوية تم تطبيق المُعدِّل verticalScroll
عليها أيضًا.
@Composable private fun AutomaticNestedScroll() { val gradient = Brush.verticalGradient(0f to Color.Gray, 1000f to Color.White) Box( modifier = Modifier .background(Color.LightGray) .verticalScroll(rememberScrollState()) .padding(32.dp) ) { Column { repeat(6) { Box( modifier = Modifier .height(128.dp) .verticalScroll(rememberScrollState()) ) { Text( "Scroll here", modifier = Modifier .border(12.dp, Color.DarkGray) .background(brush = gradient) .padding(24.dp) .height(150.dp) ) } } } } }
استخدام مفتاح التعديل nestedScroll
إذا كنت بحاجة إلى إنشاء عملية تمرير مُنسّقة ومتقدّمة بين عناصر متعدّدة، يمنحك المُعدِّل
nestedScroll
مزيدًا من المرونة من خلال تحديد تسلسل هرمي مُدمَج للتمرير. كما ذكرنا في القسم السابق، تتضمّن بعض المكوّنات ميزة مدمجة للانتقال المُدمَج. ومع ذلك، بالنسبة إلى العناصر القابلة للتجميع التي لا يمكن التمرير فيها تلقائيًا، مثل
Box
أو Column
، لن تنتشر قيم التغيير في التمرير على هذه المكوّنات في
نظام التمرير المُدمَج ولن تصل قيم التغيير إلىNestedScrollConnection
أو
المكوّن الرئيسي. لحلّ هذه المشكلة، يمكنك استخدام nestedScroll
لمنح هذا
الدعم للمكونات الأخرى، بما في ذلك المكونات المخصّصة.
دورة التمرير المُدمَجة
دورة التنقّل المتداخل هي تدفق تغييرات التنقّل التي يتم إرسالها للأعلى وللأسفل في شجرة التسلسل الهرمي من خلال جميع المكوّنات (أو العقد) التي تشكّل جزءًا من نظام التنقّل المتداخل، على سبيل المثال باستخدام المكوّنات والمُعدِّلات القابلة للتنقّل، أو
nestedScroll
.
مراحل دورة الانتقال المُدمَج
عندما يرصد عنصر قابل للانتقال على الصفحة حدثًا مشغّلاً (مثلاً، إيماءة)، يتم إرسال الاختلافات المُنشأة إلى نظام الانتقال على الصفحة المُدمَج قبل بدء عملية الانتقال على الصفحة، كما تمرّ هذه الاختلافات بثلاث مراحل: الفترة قبل الانتقال على الصفحة، واستهلاك العقدة، والفترة بعد الانتقال على الصفحة.
في المرحلة الأولى، وهي مرحلة ما قبل الانتقال للأعلى أو للأسفل، سيرسل المكوّن الذي تلقّى حدث التفعيل التغييرات إلى أعلى، من خلال شجرة التسلسل الهرمي، إلى العنصر родительский الأكثر أهمية. ستتم بعد ذلك ترقية أحداث البيانات المتغيرة، ما يعني أنّه سيتم نشر البيانات المتغيرة من العنصر الرئيسي الأقرب إلى الجذر وصولاً إلى العنصر الفرعي الذي بدأ دورة الانتقال المُدمَج.
يمنح ذلك عناصر العرض المدرَجة في العنصر الرئيسي (العناصر القابلة للتجميع التي تستخدم nestedScroll
أو
المُعدِّلات القابلة للتقديم/الترجيع) فرصة لإجراء إجراء ما بشأن القيمة الإضافية قبل أن يتمكّن
العنصر نفسه من استخدامها.
في مرحلة استهلاك العقدة، ستستخدم العقدة نفسها أيّ تغيير لم يتم استخدامه من قِبل العقد الرئيسية. هذا هو الوقت الذي تنتهي فيه حركة التمرير وتصبح مرئية.
خلال هذه المرحلة، يمكن للطفل اختيار استخدام كلّ المحتوى المعروض أو جزء منه في الملف الشخصي. سيتم إرسال أيّ محتوى متبقٍّ إلى أعلى الصفحة لاجتياز مرحلة ما بعد الانتقال إلى الأسفل.
أخيرًا، في مرحلة ما بعد الانتقال إلى الأسفل، سيتم إرسال أيّ محتوى لم تستهلكه العقدة نفسها إلى أسلافها مرة أخرى للاستهلاك.
تعمل مرحلة ما بعد الانتقال للأعلى أو للأسفل بطريقة مشابهة لمرحلة ما قبل الانتقال للأعلى أو للأسفل، حيث يمكن لأي والدٍين اختيار الاستهلاك أو عدم الاستهلاك.
على غرار الانتقال للأعلى أو للأسفل، عند انتهاء إيماءة السحب، قد يتم ترجمة نية المستخدم إلى سرعة تُستخدَم لرمي (الانتقال للأعلى أو للأسفل باستخدام صورة متحركة) الحاوية القابلة للانتقال. ويشكّل الرمي السريع أيضًا جزءًا من دورة الانتقال المُدمَجة، ويمُرّ معدل السرعة الذي ينشئه حدث السحب بفترات مماثلة: مرحلة ما قبل الرمي السريع، ومرحلة استخدام العقدة، ومرحلة ما بعد الرمي السريع. يُرجى العلم أنّ التحريك المفاجئ للعناصر مرتبط فقط بإيماءة اللمس ولن يتم تشغيله من خلال أحداث أخرى، مثل a11y أو التمرير باستخدام الأجهزة.
المشاركة في دورة الانتقال المُدمَج
تعني المشاركة في الدورة اعتراض استهلاك القيم الجديدة واستخدامها وإعداد تقارير عنها على مستوى التسلسل الهرمي. يوفّر Compose مجموعة من الأدوات لتأثير في آلية عمل نظام التمرير المُدمَج وكيفية التفاعل معه مباشرةً، مثلاً عندما تحتاج إلى إجراء عملية ما باستخدام قيم اختلافات التمرير قبل أن يبدأ المكوّن القابل للتقديم أو الإيقاف في التمرير.
إذا كانت دورة الانتقال المُدمَجة للأسفل أو للأعلى هي نظام يعمل على سلسلة من العقد، فإنّ المُعدِّل
nestedScroll
هو طريقة للاعتراض على هذه التغييرات وإدراجها،
والتأثير في البيانات (الاختلافات في الانتقال للأسفل أو للأعلى) التي يتم نشرها في السلسلة. يمكن وضع
هذا المُعدِّل في أيّ مكان في التسلسل الهرمي، ويتواصل مع
مثيلات مُعدِّل الانتقال المُدمَج للأعلى في الشجرة حتى يتمكّن من مشاركة المعلومات من خلال
هذه القناة. عنصرَا هذا المُعدِّل هما NestedScrollConnection
وNestedScrollDispatcher
.
يقدّم NestedScrollConnection
طريقة للردّ على مراحل دورة التمرير المتداخل والتأثير
في نظام التمرير المتداخل. يتألّف من أربع طرق للرجوع، يمثّل كلّ منها إحدى مراحل الاستهلاك: ما قبل/ما بعد التمرير السريع وما قبل/ما بعد الرمي:
val nestedScrollConnection = object : NestedScrollConnection { override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { println("Received onPreScroll callback.") return Offset.Zero } override fun onPostScroll( consumed: Offset, available: Offset, source: NestedScrollSource ): Offset { println("Received onPostScroll callback.") return Offset.Zero } }
يقدّم كلّ ردّ تلقائي أيضًا معلومات عن القيمة الإضافية التي يتم نشرها:
available
القيمة الإضافية لهذه المرحلة المحدّدة، وconsumed
القيمة الإضافية المستخدَمة في
المراحل السابقة. إذا أردت في أي وقت إيقاف نشر القيم المنسَّقة صعودًا في السلسلة الهرمية، يمكنك استخدام ربط التمرير المُدمَج لإجراء ذلك:
val disabledNestedScrollConnection = remember { object : NestedScrollConnection { override fun onPostScroll( consumed: Offset, available: Offset, source: NestedScrollSource ): Offset { return if (source == NestedScrollSource.SideEffect) { available } else { Offset.Zero } } } }
تقدّم جميع عمليات معاودة الاتصال معلومات عن نوع
NestedScrollSource
الاستجابة.
NestedScrollDispatcher
تُستخدَم لبدء دورة التمرير المُدمَجة. يؤدي استخدام أداة توزيع وتشغيل أساليبها
إلى بدء الدورة. تحتوي الحاويات القابلة للانتقال إلى أعلى أو أسفل على أداة توجيه مضمّنة تُرسِل
الاختلافات التي تم رصدها أثناء الإيماءات إلى النظام. لهذا السبب، تتضمن معظم حالات الاستخدام
لتخصيص الانتقال المتداخل للأسفل أو للأعلى استخدام NestedScrollConnection
بدلاً
من أداة الإرسال، للتفاعل مع الاختلافات الحالية بدلاً من إرسال اختلافات جديدة.
يُرجى الاطّلاع على
NestedScrollDispatcherSample
لمزيد من حالات الاستخدام.
تغيير حجم صورة أثناء الانتقال إلى الأسفل أو للأعلى
أثناء تنقّل المستخدم، يمكنك إنشاء تأثير بصري ديناميكي يتغيّر فيه حجم الصورة استنادًا إلى موضع الانتقال.
تغيير حجم صورة استنادًا إلى موضع الانتقال
يوضّح هذا المقتطف تغيير حجم صورة ضمن LazyColumn
استنادًا إلى
موضع التمرير العمودي. تتقلّص الصورة عندما ينتقل المستخدم للأسفل، وتكبر
عندما ينتقل للأعلى، وتبقى ضمن حدود الحد الأدنى والحد الأقصى للحجم المحدّدَين:
@Composable fun ImageResizeOnScrollExample( modifier: Modifier = Modifier, maxImageSize: Dp = 300.dp, minImageSize: Dp = 100.dp ) { var currentImageSize by remember { mutableStateOf(maxImageSize) } var imageScale by remember { mutableFloatStateOf(1f) } val nestedScrollConnection = remember { object : NestedScrollConnection { override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { // Calculate the change in image size based on scroll delta val delta = available.y val newImageSize = currentImageSize + delta.dp val previousImageSize = currentImageSize // Constrain the image size within the allowed bounds currentImageSize = newImageSize.coerceIn(minImageSize, maxImageSize) val consumed = currentImageSize - previousImageSize // Calculate the scale for the image imageScale = currentImageSize / maxImageSize // Return the consumed scroll amount return Offset(0f, consumed.value) } } } Box(Modifier.nestedScroll(nestedScrollConnection)) { LazyColumn( Modifier .fillMaxWidth() .padding(15.dp) .offset { IntOffset(0, currentImageSize.roundToPx()) } ) { // Placeholder list items items(100, key = { it }) { Text( text = "Item: $it", style = MaterialTheme.typography.bodyLarge ) } } Image( painter = ColorPainter(Color.Red), contentDescription = "Red color image", Modifier .size(maxImageSize) .align(Alignment.TopCenter) .graphicsLayer { scaleX = imageScale scaleY = imageScale // Center the image vertically as it scales translationY = -(maxImageSize.toPx() - currentImageSize.toPx()) / 2f } ) } }
النقاط الرئيسية حول الرمز
- يستخدم هذا الرمز
NestedScrollConnection
لمنع أحداث التمرير. - تحسب دالة
onPreScroll
التغيُّر في حجم الصورة استنادًا إلى قيمة delta لقيمة التمرير. - تخزِّن متغيّر الحالة
currentImageSize
الحجم الحالي للصورة، وهو محدود بينminImageSize
وmaxImageSize. imageScale
، ويتم الحصول عليه منcurrentImageSize
. - يتم احتساب
LazyColumn
استنادًا إلىcurrentImageSize
. - يستخدم
Image
معدِّلgraphicsLayer
لتطبيق المقاييس التي تم احتسابها. - يضمن الرمز
translationY
ضمن الرمزgraphicsLayer
بقاء الصورة في وسط الشاشة عموديًا أثناء تغيير حجمها.
النتيجة
يؤدي المقتطف السابق إلى تأثير تكبير الصورة عند الانتقال للأعلى أو للأسفل:
إمكانية التشغيل التفاعلي لميزة "التمرير المتداخل"
عند محاولة دمج عناصر View
قابلة للتقديم أو الإيقاف في عناصر قابلة للتقديم أو الإيقاف، أو
العكس، قد تواجه مشاكل. تحدث معظم الأخطاء الملحوظة عند التمرير للأسفل أو للأعلى في العنصر الفرعي للوصول إلى حدود البداية أو النهاية، وتوقّع أن يكمل الوالد التمرير. ومع ذلك، قد لا يحدث هذا السلوك المتوقّع
أو قد لا يعمل على النحو المتوقّع.
تحدث هذه المشكلة نتيجةً للتوقعات المتأصّلة في العناصر القابلة للتركيب والتصفّح.
تحتوي العناصر القابلة للتمرير على قاعدة "التمرير المتداخل تلقائيًا"، ما يعني أنّه
يجب أن تشارك أي حاوية قابلة للتمرير في سلسلة التمرير المتداخل، سواءً كأحد
العناصر الرئيسية من خلال
NestedScrollConnection
،
أو كأحد العناصر الثانوية من خلال
NestedScrollDispatcher
.
سيؤدي العنصر الفرعي بعد ذلك إلى تحريك عنصر التنقل المُدمَج للعنصر الرئيسي عندما يكون العنصر الفرعي عند
الحدّ. على سبيل المثال، تسمح هذه القاعدة لتطبيقَي Compose Pager
وCompose LazyRow
بالعمل معًا بشكل جيد. ومع ذلك، عند استخدام ميزة التمرير للتوافق مع ViewPager2
أو RecyclerView
، لا يمكن التمرير المستمر من العنصر الفرعي إلى العنصر الرئيسي لأنّ هذين العنصرين لا ينفِّذان NestedScrollingParent3
.
لتفعيل واجهة برمجة التطبيقات لإمكانية التشغيل التفاعلي للانتقال المتداخل بين عناصر View
التي يمكن التمرير فيها ومواد تركيب يمكن التمرير فيها، والتي تكون متداخلة في كلا الاتجاهين، يمكنك استخدام واجهة برمجة التطبيقات لإمكانية التشغيل التفاعلي للانتقال المتداخل للتخفيف من هذه المشاكل في السيناريوهات التالية:
حساب أحد الوالدَين المتعاونَين View
الذي يتضمّن حساب طفل ComposeView
العنصر الرئيسي المتعاون View
هو عنصر ينفِّذ
NestedScrollingParent3
، وبالتالي يمكنه تلقّي بيانات اختلافات التمرير من عنصر تركيبي مكوّن
تابع متعاون. سيعمل ComposeView
كعنصر فرعي في هذه الحالة وسيكون عليه
تنفيذ (بشكل غير مباشر)
NestedScrollingChild3
.
ومن الأمثلة على التطبيقات التي تتعاون مع الوالدَين
androidx.coordinatorlayout.widget.CoordinatorLayout
.
إذا كنت بحاجة إلى إمكانية التشغيل التفاعلي للانتقال المتداخل بين View
حاويات والد
قابلة للانتقال وعناصر تركيب فرعية قابلة للانتقال متداخلة، يمكنك استخدام
rememberNestedScrollInteropConnection()
.
يسمح rememberNestedScrollInteropConnection()
ويتذكر
NestedScrollConnection
الذي يتيح إمكانية التشغيل التفاعلي للانتقال المتداخل بين View
الرئيسي الذي
ينفّذ
NestedScrollingParent3
وعنصر الإنشاء الفرعي. ويجب استخدامه مع أحد المُعدِّلات التالية:
nestedScroll
. بما أنّ ميزة "الانتقال المتداخل" مفعَّلة تلقائيًا على جانب "الإنشاء"،
يمكنك استخدام هذا الربط لتفعيل كلّ من ميزة "الانتقال المتداخل" على جانب View
وإضافة
منطق التجميع الضروري بين Views
والعناصر القابلة للتجميع.
ومن حالات الاستخدام الشائعة استخدام CoordinatorLayout
وCollapsingToolbarLayout
و
عنصر مركب ثانوي، كما هو موضّح في هذا المثال:
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.appbar.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="100dp" android:fitsSystemWindows="true"> <com.google.android.material.appbar.CollapsingToolbarLayout android:id="@+id/collapsing_toolbar_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <!--...--> </com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.AppBarLayout> <androidx.compose.ui.platform.ComposeView android:id="@+id/compose_view" app:layout_behavior="@string/appbar_scrolling_view_behavior" android:layout_width="match_parent" android:layout_height="match_parent"/> </androidx.coordinatorlayout.widget.CoordinatorLayout>
في النشاط أو المقتطف، عليك إعداد العنصر القابل للتجميع الفرعي وNestedScrollConnection
:
open class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<ComposeView>(R.id.compose_view).apply { setContent { val nestedScrollInterop = rememberNestedScrollInteropConnection() // Add the nested scroll connection to your top level @Composable element // using the nestedScroll modifier. LazyColumn(modifier = Modifier.nestedScroll(nestedScrollInterop)) { items(20) { item -> Box( modifier = Modifier .padding(16.dp) .height(56.dp) .fillMaxWidth() .background(Color.Gray), contentAlignment = Alignment.Center ) { Text(item.toString()) } } } } } } }
عنصر مكوّن رئيسي يحتوي على عنصر فرعي AndroidView
يتناول هذا السيناريو تنفيذ واجهة برمجة التطبيقات لإمكانية التشغيل التفاعلي للانتقال المتداخل على جانب
الإنشاء، عندما يكون لديك عنصر إنشاء رئيسي يحتوي على عنصر إنشاء فرعي
AndroidView
. ينفِّذ AndroidView
NestedScrollDispatcher
،
لأنّه يعمل كعنصر فرعي لعنصر رئيسي قابل للتقديم أو الإيقاف في ميزة "الإنشاء"، بالإضافة إلى
NestedScrollingParent3
، لأنّه يعمل كعنصر رئيسي لعنصر فرعي قابل للتقديم أو الإيقاف في View
. سيتمكّن العنصر الرئيسي لإنشاء المحتوى
بعد ذلك من تلقّي اختلافات التمرير المُدمجة من عنصر فرعي قابل للتمرير مُدمج
View
.
يوضّح المثال التالي كيفية تحقيق إمكانية التشغيل التفاعلي للانتقال المتداخل في هذا السيناريو، بالإضافة إلى شريط أدوات قابل للطي في ميزة "الإنشاء":
@Composable
private fun NestedScrollInteropComposeParentWithAndroidChildExample() {
val toolbarHeightPx = with(LocalDensity.current) { ToolbarHeight.roundToPx().toFloat() }
val toolbarOffsetHeightPx = remember { mutableStateOf(0f) }
// Sets up the nested scroll connection between the Box composable parent
// and the child AndroidView containing the RecyclerView
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
// Updates the toolbar offset based on the scroll to enable
// collapsible behaviour
val delta = available.y
val newOffset = toolbarOffsetHeightPx.value + delta
toolbarOffsetHeightPx.value = newOffset.coerceIn(-toolbarHeightPx, 0f)
return Offset.Zero
}
}
}
Box(
Modifier
.fillMaxSize()
.nestedScroll(nestedScrollConnection)
) {
TopAppBar(
modifier = Modifier
.height(ToolbarHeight)
.offset { IntOffset(x = 0, y = toolbarOffsetHeightPx.value.roundToInt()) }
)
AndroidView(
{ context ->
LayoutInflater.from(context)
.inflate(R.layout.view_in_compose_nested_scroll_interop, null).apply {
with(findViewById<RecyclerView>(R.id.main_list)) {
layoutManager = LinearLayoutManager(context, VERTICAL, false)
adapter = NestedScrollInteropAdapter()
}
}.also {
// Nested scrolling interop is enabled when
// nested scroll is enabled for the root View
ViewCompat.setNestedScrollingEnabled(it, true)
}
},
// ...
)
}
}
private class NestedScrollInteropAdapter :
Adapter<NestedScrollInteropAdapter.NestedScrollInteropViewHolder>() {
val items = (1..10).map { it.toString() }
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): NestedScrollInteropViewHolder {
return NestedScrollInteropViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
)
}
override fun onBindViewHolder(holder: NestedScrollInteropViewHolder, position: Int) {
// ...
}
class NestedScrollInteropViewHolder(view: View) : ViewHolder(view) {
fun bind(item: String) {
// ...
}
}
// ...
}
يوضّح هذا المثال كيفية استخدام واجهة برمجة التطبيقات مع مُعدِّل scrollable
:
@Composable
fun ViewInComposeNestedScrollInteropExample() {
Box(
Modifier
.fillMaxSize()
.scrollable(rememberScrollableState {
// View component deltas should be reflected in Compose
// components that participate in nested scrolling
it
}, Orientation.Vertical)
) {
AndroidView(
{ context ->
LayoutInflater.from(context)
.inflate(android.R.layout.list_item, null)
.apply {
// Nested scrolling interop is enabled when
// nested scroll is enabled for the root View
ViewCompat.setNestedScrollingEnabled(this, true)
}
}
)
}
}
أخيرًا، يوضّح هذا المثال كيفية استخدام واجهة برمجة التطبيقات لإمكانية التشغيل التفاعلي لميزة التمرير المُدمَج مع رمز العنصر
BottomSheetDialogFragment
لتحقيق سلوك السحب والإغلاق بنجاح:
class BottomSheetFragment : BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val rootView: View = inflater.inflate(R.layout.fragment_bottom_sheet, container, false)
rootView.findViewById<ComposeView>(R.id.compose_view).apply {
setContent {
val nestedScrollInterop = rememberNestedScrollInteropConnection()
LazyColumn(
Modifier
.nestedScroll(nestedScrollInterop)
.fillMaxSize()
) {
item {
Text(text = "Bottom sheet title")
}
items(10) {
Text(
text = "List item number $it",
modifier = Modifier.fillMaxWidth()
)
}
}
}
return rootView
}
}
}
يُرجى العِلم أنّه عند استخدام العنصر
rememberNestedScrollInteropConnection()
، سيتم تثبيت العنصر
NestedScrollConnection
في العنصر الذي يتم إرفاقه به. تتحمّل NestedScrollConnection
مسؤولية
نقل القيم المتغيرة من مستوى "الإنشاء" إلى مستوى View
. يتيح ذلك
للعنصر المشاركة في التمرير المُدمَج، ولكنّه لا يتيح
التمرير للعناصر تلقائيًا. بالنسبة إلى العناصر التي لا يمكن التمرير فيها
تلقائيًا، مثل Box
أو Column
، لن يتم
نشر قيم التغيير في التمرير على هذه المكوّنات في نظام التمرير المتداخل ولن تصل قيم التغيير إلى
NestedScrollConnection
المقدَّمة من rememberNestedScrollInteropConnection()
،
وبالتالي لن تصل قيم التغيير هذه إلى المكوّن الرئيسي View
. لحلّ هذه المشكلة،
تأكَّد أيضًا من ضبط المُعدِّلات القابلة للتنقّل على هذه الأنواع من العناصر المُركّبة المُدمجة. يمكنك الرجوع إلى القسم السابق حول التمرير المُدمَج للحصول على معلومات
أكثر تفصيلاً.
حساب أحد الوالدَين غير المتعاونَين View
الذي يتضمّن حساب طفل ComposeView
طريقة العرض غير المتعاونة هي طريقة عرض لا تنفِّذ واجهات
NestedScrolling
اللازمة من جهة View
. يُرجى العِلم أنّه لا يمكن استخدام ميزة التنقل المُدمَج مع هذه Views
مباشرةً. Views
غير المتعاونة هي RecyclerView
وViewPager2
.
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عندما تكون لغة JavaScript غير مفعّلة.
- التعرّف على الإيماءات
- نقل
CoordinatorLayout
إلى ميزة "إنشاء" - استخدام "طرق العرض" في ميزة "الإنشاء"