پیمایش تودرتو سیستمی است که در آن چندین عنصر پیمایش که در داخل یکدیگر قرار دارند با واکنش به یک اشاره پیمایش و انتقال تغییرات پیمایش خود با یکدیگر کار میکنند.
سیستم پیمایش تودرتو امکان هماهنگی بین مؤلفههایی را فراهم میکند که پیمایشپذیر هستند و بهصورت سلسلهمراتبی پیوند داده شدهاند (اغلب با همرسانی والد یکسان). این سیستم محفظههای پیمایش را پیوند میدهد و امکان تعامل با دلتاهای پیمایش را که بین آنها منتشر و همرسانی میشود فراهم میکند.
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 یا
اصلاحکنندههای پیمایشپذیر استفاده میکنند) فرصت میدهد تا قبلاز اینکه خود گره بتواند آن را مصرف کند، کاری با دلتا انجام دهند.
در مرحله مصرف گره، خود گره از هر دلتایی که توسط والدینش استفاده نشده است استفاده خواهد کرد. این زمانی است که حرکت پیمایش واقعاً انجام میشود و قابلمشاهده است.
در این مرحله، کودک میتواند انتخاب کند که تمام یا بخشی از پیمایش باقیمانده را مصرف کند. هرچیزی که باقی بماند دوباره به بالا ارسال میشود تا مرحله پساز پیمایش را طی کند.
درنهایت، در فاز پساز پیمایش، هر چیزی که خود گره مصرف نکرده است دوباره برای مصرف به اجدادش ارسال میشود.
فاز پساز پیمایش به روشی مشابه فاز پیشاز پیمایش کار میکند، که در آن هریک از والدین میتوانند انتخاب کنند که مصرف کنند یا نه.
بهطور مشابه با پیمایش، وقتی اشاره کشیدن تمام میشود، ممکن است قصد کاربر به سرعتی ترجمه شود که برای پرتاب کردن (پیمایش بااستفاده از پویانمایی) محفظه پیمایشپذیر استفاده میشود. پرتاب نیز بخشی از چرخه پیمایش تودرتو است و سرعتهای تولیدشده توسط رویداد کشیدن مراحل مشابهی را طی میکنند: پیشپرتاب، مصرف گره، و پسپرتاب. توجه داشته باشید که پویانمایی پرتابی فقط با اشاره لمسی مرتبط است و با رویدادهای دیگر، مانند دسترسپذیری یا پیمایش سختافزاری، راهاندازی نمیشود.
شرکت در چرخه پیمایش تودرتو
مشارکت در چرخه بهمعنای رهگیری، مصرف، و گزارش کردن مصرف دلتاها در سلسلهمراتب است. 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.
منابع بیشتر
توصیهشده برای شما
- توجه: وقتی جاوا اسکریپت خاموش باشد، نوشتار پیوند نمایش داده میشود
- آشنایی با اشارهها
- انتقال
CoordinatorLayoutبه «نگارش» - استفاده از «نماها» در «نوشتن»