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

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

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

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

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

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

مشارکت در چرخه به‌معنای رهگیری، مصرف، و گزارش کردن مصرف دلتاها در سلسله‌مراتب است. 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 و 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، چون به‌عنوان فرزند «ترکیب» پیمایش‌کننده والد عمل می‌کند، و همچنین 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(
            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.

منابع بیشتر