در Jetpack Compose، scrollable2D و draggable2D
اصلاحکنندههای سطح پایینی هستند که برای مدیریت ورودی اشارهگر در دو بعد طراحی شدهاند. درحالیکه
اصلاحکنندههای استاندارد یکبعدی scrollable و draggable به
یک جهت محدود هستند، انواع دوبعدی حرکت را در هر دو
محور X و Y بهطور همزمان ردیابی میکنند.
برای مثال، اصلاحکننده scrollable موجود برای پیمایش و پرتاب تکجهته استفاده میشود، درحالیکه scrollable2d برای پیمایش و پرتاب دوبعدی استفاده میشود. این به شما امکان میدهد طرحبندیهای پیچیدهتری ایجاد کنید که در همه
جهتها حرکت میکنند، مانند صفحهگستردهها یا نمایشگرهای تصویر. اصلاحکننده scrollable2d
از پیمایش تودرتو در سناریوهای دوبعدی نیز پشتیبانی میکند.
scrollable2D یا draggable2D را انتخاب کنید
انتخاب کردن میانای برنامهسازی کاربردی مناسب به عناصر میانای کاربری که میخواهید جابهجا کنید و رفتار فیزیکی ترجیحی برای این عناصر بستگی دارد.
Modifier.scrollable2D: از این اصلاحکننده در یک محفظه برای جابهجا کردن محتوای داخل آن استفاده کنید. برای مثال، از آن در نقشهها، صفحهگستردهها، یا نمایشگرهای عکس استفاده کنید، جایی که محتوای محفظه باید در هر دو جهت افقی و عمودی پیمایش شود. این مؤلفه دارای پشتیبانی داخلی از پرتاب است تا محتوا پساز تند کشیدن همچنان حرکت کند و با دیگر مؤلفههای پیمایش در صفحه هماهنگ شود.
Modifier.draggable2D: از این اصلاحکننده برای جابهجا کردن خود مؤلفه استفاده کنید. این یک اصلاحکننده سبک است، بنابراین حرکت دقیقاً زمانی متوقف میشود که انگشت کاربر متوقف شود. این برنامه از پشتیبانی fling برخوردار نیست.
اگر میخواهید مؤلفهای را کشیدنی کنید، اما به پشتیبانی از پیمایش تودرتو یا پرتابی نیاز ندارید، از draggable2D استفاده کنید.
پیادهسازی اصلاحگرهای دوبعدی
بخشهای زیر مثالهایی را برای نشان دادن نحوه استفاده از اصلاحکنندههای دوبعدی ارائه میدهند.
پیادهسازی Modifier.scrollable2D
از این اصلاحکننده برای محفظههایی استفاده کنید که کاربر باید محتوا را در همه جهتها حرکت دهد.
ضبط دادههای حرکت دوبعدی
این مثال نحوه ضبط دادههای حرکت دوبعدی خام و نمایش جابهجایی X و Y را نشان میدهد:
@Composable private fun Scrollable2DSample() { // 1. Manually track the total distance the user has moved in both X and Y directions var offset by remember { mutableStateOf(Offset.Zero) } Box( modifier = Modifier .fillMaxSize() // ... contentAlignment = Alignment.Center ) { Box( modifier = Modifier .size(200.dp) // 2. Attach the 2D scroll logic to capture XY movement deltas .scrollable2D( state = rememberScrollable2DState { delta -> // 3. Update the cumulative offset state with the new movement delta offset += delta // Return the delta to indicate the entire movement was handled by this box delta } ) // ... contentAlignment = Alignment.Center ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { // 4. Display the current X and Y values from the offset state in real-time Text( text = "X: ${offset.x.roundToInt()}", // ... ) Spacer(modifier = Modifier.height(8.dp)) Text( text = "Y: ${offset.y.roundToInt()}", // ... ) } } } }
تکهکد بالا کارهای زیر را انجام میدهد:
- از
offsetبهعنوان وضعیتی استفاده میکند که کل مسافتی را که کاربر پیمایش کرده است نگه میدارد. - درون
rememberScrollable2DState، یک تابع لاندا برای رسیدگی به هر دلتا، که توسط انگشت کاربر ایجاد میشود، تعریف شده است. کدoffset.value += deltaوضعیت دستی را با موقعیت جدید بهروز میکند. - مؤلفههای
Textمقادیر X و Y فعلی آن حالتoffsetرا نمایش میدهند که با کشیدن کاربر بهطور همزمان بهروزرسانی میشوند.
پانورامای ناحیه نمایش بزرگ
این مثال نشان میدهد که چگونه از دادههای پیمایشی دوبعدی ضبطشده استفاده کنید و
translationX و translationY را روی محتوایی که بزرگتر از محفظه والد خود است
اعمال کنید:
@Composable private fun Panning2DImage() { // Manually track the total distance the user has moved in both X and Y directions val offset = remember { mutableStateOf(Offset.Zero) } // Define how gestures are captured. The lambda is called for every finger movement val scrollState = rememberScrollable2DState { delta -> offset.value += delta delta } // The Viewport (Container): A fixed-size box that acts as a window into the larger content Box( modifier = Modifier .size(600.dp, 400.dp) // The visible area dimensions // ... // Hide any parts of the large content that sit outside this container's boundaries .clipToBounds() // Apply the 2D scroll modifier to intercept touch and fling gestures in all directions .scrollable2D(state = scrollState), contentAlignment = Alignment.Center, ) { // The Content: An image given a much larger size than the container viewport Image( painter = painterResource(R.drawable.cheese_5), contentDescription = null, modifier = Modifier .requiredSize(1200.dp, 800.dp) // Manual Scroll Effect: Since scrollable2D doesn't move content automatically, // we use graphicsLayer to shift the drawing position based on the tracked offset. .graphicsLayer { translationX = offset.value.x translationY = offset.value.y }, contentScale = ContentScale.FillBounds ) } }
Modifier.scrollable2D.Modifier.scrollable2D.گزیده قبلی شامل موارد زیر است:
- اندازه ظرف روی اندازه ثابتی (
600x400dp) تنظیم شده است، درحالیکه اندازه محتوا بسیار بزرگتر (1200x800dp) درنظر گرفته شده است تا اندازه آن به اندازه والدش تغییر نکند. - اصلاحکننده
clipToBounds()در محفظه تضمین میکند که هر بخشی از محتوای بزرگ که خارج از چارگوش600x400قرار میگیرد از دید پنهان شود. - برخلاف مؤلفههای سطح بالا مثل
LazyColumn،scrollable2Dمحتوا را بهطور خودکار برای شما جابهجا نمیکند. درعوض، بایدoffsetردیابیشده را بااستفاده از تبدیلهایgraphicsLayerیا جابهجاییهای چیدمان روی محتوایتان اعمال کنید. - درون بلوک
graphicsLayer،translationX = offset.value.xوtranslationY = offset.value.yموقعیت ترسیم تصویر یا نوشتار را براساس حرکت انگشت شما تغییر میدهند و جلوه دیداری پیمایش را ایجاد میکنند.
پیادهسازی پیمایش تودرتو با scrollable2D
این مثال نشان میدهد که چگونه میتوان یک مؤلفه دوجهته را در یک والد استاندارد یکبعدی، مانند فید اخبار عمودی، ادغام کرد.
هنگام پیادهسازی پیمایش تودرتو، نکات زیر را درنظر داشته باشید:
- lambda برای
rememberScrollable2DStateباید فقط تغییر مصرفشده را برگرداند تا وقتی فرزند به محدودیت خود رسید، فهرست اصلی بهطور طبیعی کنترل را بهدست بگیرد. - وقتی کاربر حرکت تند مورب انجام میدهد، سرعت دوبعدی همرسانی میشود. اگر فرزند درطول پویانمایی به مرزی برسد، تکانه باقیمانده به والد منتقل میشود تا پیمایش بهطور طبیعی ادامه یابد.
@Composable private fun NestedScrollable2DSample() { var offset by remember { mutableStateOf(Offset.Zero) } val maxScrollDp = 250.dp val maxScrollPx = with(LocalDensity.current) { maxScrollDp.toPx() } Column( modifier = Modifier .fillMaxSize() .verticalScroll(rememberScrollState()) .background(Color(0xFFF5F5F5)), horizontalAlignment = Alignment.CenterHorizontally ) { Text( "Scroll down to find the 2D Box", modifier = Modifier.padding(top = 100.dp, bottom = 500.dp), style = TextStyle(fontSize = 18.sp, color = Color.Gray) ) // The Child: A 2D scrollable box with nested scroll coordination Box( modifier = Modifier .size(250.dp) .scrollable2D( state = rememberScrollable2DState { delta -> val oldOffset = offset // Calculate new potential offset and clamp it to our boundaries val newX = (oldOffset.x + delta.x).coerceIn(-maxScrollPx, maxScrollPx) val newY = (oldOffset.y + delta.y).coerceIn(-maxScrollPx, maxScrollPx) val newOffset = Offset(newX, newY) // Calculate exactly how much was consumed by the child val consumed = newOffset - oldOffset offset = newOffset // IMPORTANT: Return ONLY the consumed delta. // The remaining (unconsumed) delta propagates to the parent Column. consumed } ) // ... contentAlignment = Alignment.Center ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { val density = LocalDensity.current Text("2D Panning Zone", color = Color.White.copy(alpha = 0.7f), fontSize = 12.sp) Spacer(Modifier.height(8.dp)) Text("X: ${with(density) { offset.x.toDp().value.roundToInt() }}dp", color = Color.White, fontWeight = FontWeight.Bold) Text("Y: ${with(density) { offset.y.toDp().value.roundToInt() }}dp", color = Color.White, fontWeight = FontWeight.Bold) } } Text( "Once the Purple Box hits Y: 250 or -250,\nthis parent list will take over the vertical scroll.", textAlign = TextAlign.Center, modifier = Modifier.padding(top = 40.dp, bottom = 800.dp), style = TextStyle(fontSize = 14.sp, color = Color.Gray) ) } }
در قطعه کد بالا:
- مؤلفه دوبعدی میتواند حرکت محور X را برای حرکت داخلی مصرف کند، درحالیکه بهطور همزمان حرکت محور Y را به فهرست اصلی ارسال میکند، بهمحض اینکه مرزهای عمودی خود فرزند رسیده باشد.
- بهجای اینکه کاربر را در سطح دوبعدی گیر بیندازد، سیستم دلتای مصرفشده را محاسبه میکند و باقیمانده را به سلسلهمراتب بالاتر منتقل میکند. این کار تضمین میکند که کاربر میتواند بدون برداشتن انگشت خود، به پیمایش در بقیه صفحه ادامه دهد.
پیادهسازی Modifier.draggable2D
برای کسب اطلاعات درباره نحوه استفاده از Modifier.draggable2D، به کشیدن در دو
بُعد با Modifier.draggable2D مراجعه کنید.