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

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

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

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

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

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

پیمایش تودرتوی خودکار توسط برخی‌از عناصر و اصلاح‌کننده‌های 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. وقتی کودک در مرز باشد، پیمایش تودرتویی را برای ولی هدایت می‌کند. برای مثال، این قانون به «نوشتن Pager» و «نوشتن 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

این سناریو شامل پیاده‌سازی API تعامل پیمایش تودرتو در سمت Compose است - زمانی که یک عنصر ترکیبی والد دارید که حاوی عنصر ترکیبی فرزند AndroidView است. AndroidView NestedScrollDispatcher را پیاده‌سازی می‌کند، زیرا به‌عنوان فرزند یک والد پیمایش‌کننده «ترکیب» عمل می‌کند، و همچنین 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) {
            // ...
        }
    }
    // ...
}

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

منابع بیشتر