اصلاح‌کننده‌های پیمایش تودرتو

پیمایش تودرتو سیستمی است که در آن چندین مؤلفه پیمایش که درون یکدیگر قرار دارند، با واکنش به یک اشاره پیمایش و انتقال دلتاهای (تغییرات) پیمایش خود، باهم کار می‌کنند.

سیستم پیمایش تودرتو امکان هماهنگی بین مؤلفه‌هایی را فراهم می‌کند که پیمایش‌پذیر هستند و به‌صورت سلسله‌مراتبی پیوند داده شده‌اند (اغلب با هم‌رسانی والد یکسان). این سیستم محفظه‌های پیمایش را پیوند می‌دهد و امکان تعامل با دلتاهای پیمایش را که بین آن‌ها منتشر و هم‌رسانی می‌شود فراهم می‌کند.

‫Compose روش‌های متعددی برای مدیریت پیمایش تودرتو بین composableها ارائه می‌دهد. نمونه‌ای معمول از پیمایش تودرتو، فهرستی درون فهرستی دیگر است و موردی پیچیده‌تر، نوار ابزار جمع‌شدنی است.

پیمایش تودرتوی خودکار

پیمایش تودرتوی ساده به هیچ اقدامی ازسوی شما نیاز ندارد. اشاره‌هایی که کنش پیمایش را آغاز می‌کنند به‌طور خودکار از فرزندان به والدین منتشر می‌شوند، به‌طوری‌که وقتی فرزند دیگر نمی‌تواند پیمایش کند، اشاره توسط عنصر والد آن مدیریت می‌شود.

پیمایش تودرتوی خودکار توسط برخی‌از مؤلفه‌ها و اصلاح‌کننده‌های Compose پشتیبانی و به‌صورت پیش‌فرض ارائه می‌شود: verticalScroll، horizontalScroll، scrollable، Lazy API و 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 با تعریف سلسله‌مراتب پیمایش تودرتو، انعطاف‌پذیری بیشتری به شما می‌دهد. As mentioned in the previous section, some components have built-in nested scroll support. بااین‌حال، برای composableهایی که به‌طور خودکار پیمایش‌پذیر نیستند، مانند Box یا Column، دلتاهای پیمایش در چنین مؤلفه‌هایی در سیستم پیمایش تودرتو منتشر نخواهند شد و دلتاها به NestedScrollConnection یا مؤلفه والد نخواهند رسید. برای حل این مشکل، می‌توانید از nestedScroll برای اعطای چنین پشتیبانی‌ای به مؤلفه‌های دیگر، ازجمله مؤلفه‌های سفارشی، استفاده کنید.

چرخه پیمایش تودرتو

چرخه پیمایش تودرتو جریان دلتاهای پیمایش است که در درخت سلسله‌مراتب ازطریق همه مؤلفه‌ها (یا گره‌ها) که بخشی از سیستم پیمایش تودرتو هستند، برای مثال بااستفاده از مؤلفه‌ها و اصلاح‌کننده‌های پیمایش‌پذیر یا nestedScroll، به بالا و پایین ارسال می‌شوند.

مراحل چرخه پیمایش تودرتو

وقتی رویداد راه‌انداز (برای مثال، اشاره) توسط مؤلفه پیمایشی شناسایی می‌شود، قبل‌از اینکه کنش پیمایش واقعی راه‌اندازی شود، دلتاهای تولیدشده به سیستم پیمایش تودرتو ارسال می‌شوند و سه مرحله را طی می‌کنند: پیش‌پیمایش، مصرف گره، و پس‌پیمایش.

مراحل چرخه پیمایش تودرتو
شکل ۲. مراحل چرخه پیمایش تودرتو.

در فاز اول، یعنی پیش‌از پیمایش، مؤلفه‌ای که رویداد راه‌انداز را دریافت کرده است، تغییرات را ازطریق درخت سلسله‌مراتبی به بالاترین والد ارسال می‌کند. سپس رویدادهای دلتا به پایین حباب می‌شوند، به این معنی که دلتاها از ریشه‌ای‌ترین والد به سمت فرزندی که چرخه پیمایش تودرتو را شروع کرده است، منتشر می‌شوند.

مرحله پیش‌پیمایش - ارسال
بالا
شکل ۳.‏ مرحله پیش‌پیمایش: ارسال به بالا.

این کار به والدین پیمایش تودرتو (ترکیب‌پذیرهایی که از nestedScroll یا اصلاح‌کننده‌های پیمایش‌پذیر استفاده می‌کنند) فرصت می‌دهد تا قبل‌از اینکه خود گره بتواند آن را مصرف کند، کاری با دلتا انجام دهند.

Pre-scroll phase - bubbling
down
شکل ۴. مرحله پیش‌پیمایش - حباب‌سازی.

در مرحله مصرف گره، خود گره از هر دلتایی که توسط والدینش استفاده نشده است استفاده خواهد کرد. این زمانی است که حرکت پیمایش واقعاً انجام شده و قابل‌مشاهده است.

مرحله مصرف
گره
شکل ۵. مرحله مصرف گره.

درطول این مرحله، کودک می‌تواند انتخاب کند که همه یا بخشی از پیمایش باقی‌مانده را مصرف کند. هرچیزی که باقی بماند دوباره به بالا ارسال می‌شود تا مرحله پس‌از پیمایش را طی کند.

درنهایت، در فاز پس‌از پیمایش، هر چیزی که خود گره مصرف نکرده است دوباره برای مصرف به اجدادش ارسال می‌شود.

Post-scroll phase - dispatching
up
شکل ۶.‏ Post-scroll phase - dispatching up.

فاز پس‌از پیمایش به روشی مشابه فاز پیش‌از پیمایش کار می‌کند، که در آن هریک از والدین می‌توانند انتخاب کنند که مصرف کنند یا نه.

Post-scroll phase - bubbling
down
شکل ۷. Post-scroll phase - bubbling down.

به‌طور مشابه با پیمایش، وقتی اشاره کشیدن تمام می‌شود، ممکن است قصد کاربر به سرعتی ترجمه شود که برای پرتاب کردن (پیمایش بااستفاده از پویانمایی) محفظه پیمایش‌پذیر استفاده می‌شود. پرتاب نیز بخشی از چرخه پیمایش تودرتو است و سرعت‌های تولیدشده توسط رویداد کشیدن از مراحل مشابهی عبور می‌کنند: پیش‌پرتاب، مصرف گره، و پس‌پرتاب. توجه داشته باشید که پویانمایی پرتابی فقط با اشاره لمسی مرتبط است و با رویدادهای دیگر، مانند دسترس‌پذیری یا پیمایش سخت‌افزاری، راه‌اندازی نمی‌شود.

شرکت در چرخه پیمایش تودرتو

مشارکت در چرخه به‌معنای رهگیری، مصرف، و گزارش کردن مصرف دلتاها در سلسله‌مراتب است. 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 پیمایشی را در ترکیب‌شدنی‌های پیمایشی تودرتو کنید، یا برعکس، ممکن است با مشکلاتی مواجه شوید. Most noticeable ones would happen when you scroll the child and reach its start or end bounds and expect the parent to take the scrolling over. اما این رفتار موردانتظار ممکن است اتفاق نیفتد یا آن‌طور که انتظار می‌رود کار نکند.

این مشکل ناشی از انتظاراتی است که در ترکیب‌پذیرهای پیمایش‌پذیر ایجاد شده است. ترکیب‌شدنی‌های پیمایش‌پذیر قانون «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 و composable استفاده کنید.

یک مورد استفاده متداول استفاده از 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>

In your Activity or Fragment, you need to set up your child composable and the required 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

این سناریو پیاده‌سازی میان‌کنش‌پذیری پیمایش تودرتو API را در سمت Compose پوشش می‌دهد - زمانی که یک composable والد حاوی یک AndroidView فرزند دارید. AndroidView NestedScrollDispatcher را پیاده‌سازی می‌کند، زیرا به‌عنوان فرزند برای والد پیمایشی Compose عمل می‌کند، و همچنین NestedScrollingParent3 را پیاده‌سازی می‌کند، زیرا به‌عنوان والد برای فرزند پیمایشی View عمل می‌کند. سپس Compose parent می‌تواند دلتاهای پیمایش تودرتو را از فرزند پیمایش‌پذیر تودرتو 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(
            title = {},
            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) {
            // ...
        }
    }
    // ...
}

این مثال نشان می‌دهد که چگونه می‌توانید از API با اصلاح‌کننده 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(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 هستند.

منابع بیشتر