التمرير المتداخل هو نظام تعمل فيه مكوّنات تمرير متعدّدة مضمّنة في بعضها البعض معًا من خلال الاستجابة لإيماءة تمرير واحدة وإبلاغ التغييرات في التمرير.
يتيح نظام التمرير المتداخل التنسيق بين المكوّنات القابلة للتمرير والمرتبطة بشكل هرمي (غالبًا من خلال مشاركة الأصل نفسه). يربط هذا النظام الحاويات التي يمكن التمرير فيها ويسمح بالتفاعل مع التغيّرات في التمرير التي يتم نشرها ومشاركتها بين الحاويات.
توفّر 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 قابلة للتمرير في عناصر قابلة للتجميع وقابلة للتمرير، أو العكس، قد تواجه مشاكل. تحدث أبرزها عندما تنتقل إلى بداية أو نهاية العنصر الفرعي وتتوقّع أن يتولّى العنصر الرئيسي عملية التمرير. ومع ذلك، قد لا يحدث هذا السلوك المتوقّع أو قد لا يعمل على النحو المتوقّع.
تنتج هذه المشكلة عن التوقعات المضمّنة في العناصر القابلة للإنشاء والقابلة للتمرير.
تتضمّن العناصر القابلة للإنشاء والقابلة للتمرير قاعدة "nested-scroll-by-default"، ما يعني أنّه يجب أن تشارك أي حاوية قابلة للتمرير في سلسلة التمرير المتداخل، سواء كعنصر رئيسي من خلال
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
والعنصر الفرعي في 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. سيتمكّن العنصر الرئيسي من تلقّي قيم التغيير في موضع التمرير المدمج من العنصر الفرعي القابل للتمرير المدمج 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