معدِّلات التمرير
يوفّر المعدّلان
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
سيتم استدعاؤها في كل خطوة من خطوات التمرير (عن طريق إدخال الإيماءات أو التمرير السلس أو التمرير السريع) مع الفرق بالبكسل. يجب أن تعرض هذه الدالة مقدار مسافة التمرير التي تم استهلاكها، وذلك لضمان نشر الحدث بشكل صحيح في الحالات التي تتضمّن عناصر متداخلة تحتوي على المعدِّل scrollable
.
تكتشف المقتطفة البرمجية التالية الإيماءات وتعرض قيمة رقمية للإزاحة، ولكنها لا تزيل أي عناصر:
@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()) } }
التمرير المتداخل
التمرير المتداخل هو نظام تعمل فيه مكوّنات التمرير المتعدّدة التي تحتوي على بعضها البعض معًا من خلال الاستجابة لإيماءة تمرير واحدة وإرسال التغييرات في التمرير (الفرق بين موضعَي التمرير) إلى بعضها البعض.
يتيح نظام التمرير المتداخل التنسيق بين المكوّنات القابلة للتمرير والمرتبطة بشكل هرمي (في أغلب الأحيان من خلال مشاركة الأصل نفسه). يربط هذا النظام الحاويات التي يمكن التمرير فيها ويسمح بالتفاعل مع التغيّرات في التمرير التي يتم نشرها ومشاركتها بين الحاويات.
توفّر Compose طرقًا متعددة للتعامل مع التمرير المتداخل بين العناصر القابلة للإنشاء. من الأمثلة الشائعة على التمرير المتداخل قائمة داخل قائمة أخرى، ومن الحالات الأكثر تعقيدًا شريط أدوات قابل للطي.
التمرير المتداخل التلقائي
لا يتطلّب التمرير المتداخل البسيط أيّ إجراء من جانبك. يتم تلقائيًا نقل الإيماءات التي تبدأ إجراء التمرير من العناصر الفرعية إلى العناصر الرئيسية، وذلك حتى عندما يتعذّر على العنصر الفرعي التمرير أكثر من ذلك، يتم التعامل مع الإيماءة من خلال العنصر الرئيسي.
تتوفّر ميزة التمرير المتداخل التلقائي بشكلٍ جاهز من خلال بعض مكوّنات ومعدِّلات Compose، مثل:
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
أو
القابلة للتمرير) فرصة تنفيذ إجراء ما باستخدام الفرق قبل أن يتمكّن العنصر نفسه من استخدامه.
في مرحلة استهلاك العقدة، ستستخدم العقدة نفسها أي دلتا لم تستخدمها العقد الأصلية. وهي الفترة التي يتم فيها تنفيذ حركة التمرير وتكون مرئية.
وخلال هذه المرحلة، يمكن للطفل اختيار استهلاك كل أو جزء من المحتوى المتبقي. سيتم إرسال أي بيانات متبقية إلى الأعلى لتمريرها عبر مرحلة ما بعد التمرير.
أخيرًا، في مرحلة ما بعد التمرير، سيتم إرسال أي شيء لم يستهلكه العنصر نفسه إلى العناصر الرئيسية مرة أخرى لاستهلاكه.
تعمل مرحلة ما بعد التمرير للأعلى أو للأسفل بطريقة مشابهة لمرحلة ما قبل التمرير للأعلى أو للأسفل، حيث يمكن لأي من العناصر الرئيسية اختيار استهلاك المحتوى أو عدم استهلاكه.
وعلى غرار التمرير، عندما تنتهي إيماءة السحب، قد تتم ترجمة نية المستخدم إلى سرعة يتم استخدامها للتمرير السريع (التمرير باستخدام حركة) للحاوية القابلة للتمرير. يُعدّ التحريك السريع أيضًا جزءًا من دورة التمرير المتداخل، وتمر السرعات التي يولدها حدث السحب بمراحل مشابهة: ما قبل التحريك السريع، واستهلاك العقدة، وما بعد التحريك السريع. يُرجى العِلم أنّ الرسوم المتحركة الخاصة بالتمرير السريع مرتبطة فقط بإيماءة اللمس ولن يتم تشغيلها بواسطة أحداث أخرى، مثل أحداث تسهيل الاستخدام أو التمرير باستخدام الأجهزة.
المشاركة في دورة التمرير المتداخل
وتعني المشاركة في الدورة اعتراض استهلاك الفروق والإبلاغ عنه على طول التسلسل الهرمي. توفّر 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
التغيير في حجم الصورة استنادًا إلى الفرق في التمرير. - يخزّن متغير الحالة
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
الرئيسية وعناصر Composables الثانوية القابلة للتمرير والمتداخلة، يمكنك استخدام rememberNestedScrollInteropConnection()
.
تتيح السمة rememberNestedScrollInteropConnection()
وتتذكّر
NestedScrollConnection
التي تتيح إمكانية التشغيل التفاعلي للتمرير المتداخل بين العنصر الرئيسي View
الذي
ينفّذ
NestedScrollingParent3
والعنصر الفرعي في Compose. يجب استخدام هذا المعدِّل مع معدِّل
nestedScroll
. بما أنّ ميزة "التمرير المتداخل" مفعَّلة تلقائيًا في Compose، يمكنك استخدام هذا الربط لتفعيل ميزة "التمرير المتداخل" في 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
يغطّي هذا السيناريو تنفيذ واجهة برمجة التطبيقات الخاصة بالتوافق مع التمرير المتداخل على جانب Compose، أي عندما يكون لديك عنصر قابل للإنشاء رئيسي يحتوي على عنصر ثانوي AndroidView
. تنفِّذ السمة AndroidView
الواجهة
NestedScrollDispatcher
،
لأنّها تعمل كعنصر فرعي لعنصر رئيسي قابل للتمرير في Compose، كما تنفِّذ الواجهة
NestedScrollingParent3
،
لأنّها تعمل كعنصر رئيسي لعنصر فرعي قابل للتمرير View
. سيتمكّن العنصر الرئيسي الذي يتضمّن Compose من تلقّي فروق التمرير المدمجة من عنصر فرعي قابل للتمرير ومدمج View
.
يوضّح المثال التالي كيفية تحقيق إمكانية التشغيل التفاعلي للتمرير المتداخل في هذا السيناريو، بالإضافة إلى شريط أدوات قابل للتصغير في Compose:
@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
هي المسؤولة عن نقل التغييرات من مستوى Compose إلى مستوى View
. يتيح ذلك مشاركة العنصر في التنقّل المتداخل، ولكنّه لا يتيح التنقّل في العناصر تلقائيًا. بالنسبة إلى العناصر القابلة للإنشاء غير القابلة للتمرير تلقائيًا، مثل Box
أو Column
، لن تنتقل فروق التمرير في هذه المكوّنات إلى نظام التمرير المتداخل، ولن تصل الفروق إلى NestedScrollConnection
التي توفّرها rememberNestedScrollInteropConnection()
، وبالتالي لن تصل هذه الفروق إلى المكوّن الرئيسي View
. لحلّ هذه المشكلة، تأكَّد من ضبط المعدِّلات القابلة للتمرير على هذه الأنواع من العناصر القابلة للإنشاء المتداخلة. يمكنك الرجوع إلى القسم السابق حول التمرير المتداخل للحصول على معلومات أكثر تفصيلاً.
أحد الوالدَين غير المتعاونَين View
الذي لديه طفل ComposeView
تكون طريقة العرض غير متوافقة إذا لم تنفّذ واجهات NestedScrolling
الضرورية على جهة View
. يُرجى العِلم أنّ هذا يعني أنّ التوافق مع التمرير المتداخل مع هذه Views
لا يعمل بشكل تلقائي. Views
غير المتعاونة هي RecyclerView
وViewPager2
.
مراجع إضافية
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عندما تكون JavaScript غير مفعّلة
- التعرّف على الإيماءات
- نقل البيانات من
CoordinatorLayout
إلى Compose - استخدام طرق العرض في Compose