معدِّلات التمرير المتداخل

التمرير المتداخل هو نظام تعمل فيه مكوّنات تمرير متعدّدة مضمّنة معًا من خلال الاستجابة لإيماءة تمرير واحدة وتوصيل التغييرات في التمرير (الزيادات والنقصان).

يسمح نظام التمرير المتداخل بالتنسيق بين المكوّنات القابلة للتمرير والمرتبطة بشكل هرمي (غالبًا من خلال مشاركة العنصر الرئيسي نفسه). يربط هذا النظام حاويات التمرير ويسمح بالتفاعل مع التغييرات في التمرير التي يتم نشرها ومشاركتها بينها.

توفّر 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)
                    )
                }
            }
        }
    }
}

عنصران متداخلان في واجهة المستخدم يمكن التمرير فيهما عموديًا، ويستجيبان للإيماءات داخل العنصر الداخلي وخارجه
الشكل 1. عنصران في واجهة المستخدم للتمرير العمودي المتداخل، يستجيبان للإيماءات داخل العنصر الداخلي وخارجه

استخدام المعدِّل nestedScroll

إذا كنت بحاجة إلى إنشاء تمرير منسّق متقدّم بين عناصر متعدّدة، يمنحك nestedScroll المعدِّل مزيدًا من المرونة من خلال تحديد تسلسل هرمي للتمرير المتداخل. كما هو موضّح في القسم السابق، تتوافق بعض المكوّنات مع التمرير المتداخل. ومع ذلك، بالنسبة إلى العناصر المركّبة التي لا يمكن تمريرها تلقائيًا، مثل Box أو Column، لن يتم نشر التغييرات في التمرير على هذه المكوّنات في نظام التمرير المتداخل، ولن تصل التغييرات إلى NestedScrollConnection أو المكوّن الرئيسي. لحلّ هذه المشكلة، يمكنك استخدام nestedScroll لمنح هذه الميزة لمكوّنات أخرى، بما في ذلك المكوّنات المخصّصة.

دورة التمرير المتداخل

دورة التمرير المتداخل هي تدفّق التغييرات في التمرير التي يتم إرسالها لأعلى ولأسفل شجرة التسلسل الهرمي من خلال جميع المكوّنات (أو العُقد) التي تشكّل جزءًا من نظام التمرير المتداخل، مثلاً باستخدام مكوّنات ومعدِّلات قابلة للتمرير أو nestedScroll.

مراحل دورة التمرير المتداخل

عندما يرصد مكوّن قابل للتمرير حدثًا مشغِّلاً (مثلاً، إيماءة)، قبل أن يتم تفعيل إجراء التمرير الفعلي، يتم إرسال التغييرات التي تم إنشاؤها إلى نظام التمرير المتداخل وتمر بثلاث مراحل: ما قبل التمرير واستهلاك العُقدة وما بعد التمرير.

مراحل دورة التمرير المتداخل
الشكل 2. مراحل دورة التمرير المتداخل

في المرحلة الأولى، وهي مرحلة ما قبل التمرير، سيرسل المكوّن الذي تلقّى التغييرات في أحداث المشغِّل هذه الأحداث لأعلى، من خلال شجرة التسلسل الهرمي، إلى العنصر الرئيسي الأعلى. بعد ذلك، ستنتقل أحداث التغيير لأسفل، ما يعني أنّه سيتم نشر التغييرات من العنصر الرئيسي الأعلى إلى العنصر الفرعي الذي بدأ دورة التمرير المتداخل.

مرحلة ما قبل التمرير سريعًا - إرسال
up
الشكل 3. مرحلة ما قبل التمرير: الإرسال لأعلى

يمنح ذلك العناصر الرئيسية للتمرير المتداخل (العناصر المركّبة التي تستخدم nestedScroll أو المعدِّلات القابلة للتمرير) فرصة إجراء إجراء معيّن باستخدام التغيير قبل أن تتمكّن العُقدة نفسها من استخدامه.

مرحلة ما قبل التمرير سريعًا - التمرير سريعًا للأسفل
الشكل 4. مرحلة ما قبل التمرير: الانتقال لأسفل

في مرحلة استهلاك العُقدة، ستستخدم العُقدة نفسها أي تغيير لم يستخدمه العنصر الرئيسي. في هذه المرحلة، يتم إجراء حركة التمرير فعليًا وتكون مرئية.

مرحلة استهلاك العُقدة
الشكل 5. مرحلة استهلاك العُقدة

خلال هذه المرحلة، يمكن للعنصر الفرعي اختيار استخدام كل التمرير المتبقي أو جزء منه. سيتم إرسال أي شيء متبقٍ لأعلى مرة أخرى للانتقال إلى مرحلة ما بعد التمرير.

أخيرًا، في مرحلة ما بعد التمرير، سيتم إرسال أي شيء لم تستخدمه العُقدة نفسها لأعلى مرة أخرى إلى العناصر الرئيسية لاستخدامه.

مرحلة ما بعد الانتقال للأسفل - إرسال
للأعلى
الشكل 6. مرحلة ما بعد التمرير: الإرسال لأعلى

تعمل مرحلة ما بعد التمرير بطريقة مشابهة لمرحلة ما قبل التمرير، حيث يمكن لأي من العناصر الرئيسية اختيار استخدام التغيير أو عدم استخدامه.

مرحلة ما بعد الانتقال للأسفل - الترتيب من الأعلى إلى الأسفل
الشكل 7. مرحلة ما بعد التمرير: الانتقال لأسفل

على غرار التمرير، عند انتهاء إيماءة السحب، يمكن ترجمة نيّة المستخدم إلى سرعة تُستخدَم لرمي الحاوية القابلة للتمرير (التمرير باستخدام حركة). يُعدّ التمرير بالإصبع ثم رفعه بسرعة أيضًا جزءًا من دورة التمرير المتداخل، وتمر السرعات التي يتم إنشاؤها من خلال حدث سحب بمراحل مشابهة: ما قبل التمرير بالإصبع ثم رفعه بسرعة واستهلاك العقدة وما بعد التمرير بالإصبع ثم رفعه بسرعة. يُرجى العِلم أنّ حركة التمرير السريع مرتبطة فقط بإيماءة اللمس ولن يتم تفعيلها من خلال أحداث أخرى، مثل تسهيل الاستخدام أو التمرير على الأجهزة.

المشاركة في دورة التمرير المتداخل

تعني المشاركة في الدورة اعتراض التغييرات واستهلاكها والإبلاغ عن استهلاكها على طول التسلسل الهرمي. توفّر 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 بقاء الصورة في المنتصف عموديًا أثناء تغيير حجمها.

النتيجة

تؤدي المقتطفة السابقة إلى تأثير تغيير حجم الصورة عند التمرير:

الشكل 8. تأثير تغيير حجم الصورة عند التمرير

التوافق بين التمرير المتداخل

عند محاولة تضمين عناصر View قابلة للتمرير في عناصر مركّبة قابلة للتمرير، أو العكس، قد تواجه مشاكل. تحدث المشاكل الأكثر وضوحًا عند تمرير العنصر الفرعي والوصول إلى حدود بدايته أو نهايته وتوقّع أن يتولّى العنصر الرئيسي التمرير. ومع ذلك، قد لا يحدث هذا السلوك المتوقّع أو قد لا يعمل على النحو المتوقّع.

تنتج هذه المشكلة عن التوقّعات المضمّنة في العناصر المركّبة القابلة للتمرير. تتّبع العناصر المركّبة القابلة للتمرير قاعدة "التمرير المتداخل تلقائيًا"، ما يعني أنّ أي حاوية قابلة للتمرير يجب أن تشارك في سلسلة التمرير المتداخل، كعنصر رئيسي من خلال NestedScrollConnection، وكعنصر فرعي من خلال NestedScrollDispatcher. بعد ذلك، سيؤدي العنصر الفرعي إلى تمرير متداخل للعنصر الرئيسي عندما يكون العنصر الفرعي عند الحد. على سبيل المثال، تسمح هذه القاعدة لـ Pager وLazyRow في Compose بالعمل معًا بشكل جيد. ومع ذلك، عند إجراء تمرير قابل للتشغيل التفاعلي باستخدام 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 فرعي قابل للتمرير. بعد ذلك، سيتمكّن العنصر الرئيسي في 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

عنصر View غير متعاون هو عنصر لا ينفّذ واجهات NestedScrolling الضرورية على جانب View. يُرجى العِلم أنّ هذا يعني أنّ التوافق بين التمرير المتداخل مع عناصر Views هذه لا يعمل تلقائيًا. إنّ Views غير المتعاونة هي RecyclerView وViewPager2.

مراجع إضافية