Kullanıcıların bileşenleri ekranda fiziksel olarak sürüklemesine olanak tanımak, modern Android uygulamalarının temel özelliklerinden biridir. Jetpack Compose, özel bir kaydırma çubuğu, kaydırarak kapatma bileşeni veya etkileşimli bir tuval oluştururken sürükleme etkinliklerini kolayca yönetmek için kapsamlı bir API seti sunar.
Compose sürükleme API'leri, ham dokunma etkinliklerinin karmaşıklığını ortadan kaldırır.
Dokunma koordinatlarını manuel olarak hesaplamak ve hareket yaşam döngülerini pointerInput ile yönetmek yerine, doğrudan Compose'un durum yönetimi ve animasyon sistemlerine bağlanan yerleşik değiştiricileri kullanabilirsiniz.
Bu sayfada, her bir sürükleme API'sinin nasıl ve ne zaman kullanılacağı açıklanmaktadır.
Doğru sürükleme API'sini seçme
Sürüklenebilir içerik uygulamak için kullanabileceğiniz çeşitli API'ler vardır.
- Aşağıdaki durumlarda standart Material bileşenlerini (ör.
SwipeToDismissBoxveya gezinme çekmeceleri) seçin: Varsayılan Material 3 kaydırma veya sürükleme davranışına (ör. e-posta silme, görevi arşivleme ya da yan menü / alt sayfaları açma) ihtiyacınız varsa. Özel durum veya düzen yönetimi gerektirmeden yerleşik davranışlar sağlarlar. - Ne zaman
Modifier.draggableseçeneğini belirleyin: Yalnızca ham sürükleme deltalarına (ör. özel bir kaydırma çubuğunu sürükleme) ihtiyacınız varsa. Daha fazla bilgi için Sürüklenir öğelerle tek eksen boyunca sürükleme başlıklı makaleyi inceleyin.- Şu durumlarda
Modifier.draggable2Dseçin: Bir bileşenin hem X hem de Y eksenleri boyunca aynı anda sürekli olarak hareket etmesi gerektiğinde (örneğin, bir harita üzerinde kaydırma yaparken veya kullanıcının ekranda herhangi bir yere sürükleyebileceği kayan bir işlem düğmesi oluştururken). Daha fazla bilgi içinModifier.draggable2Dile iki boyutta sürükleme başlıklı makaleyi inceleyin.
- Şu durumlarda
- Şu durumlarda
Modifier.anchoredDraggableseçin: İki veya daha fazla ayrı konum arasında geçiş yapması gereken özel etkileşimli bileşenler (ör. alt sayfa, gezinme çekmecesi veya kaydırarak gösterme işlemi) oluşturuyorsanız. Daha fazla bilgi içinanchoredDraggableile bileşenleri bağlantı noktaları arasında sürükleme başlıklı makaleyi inceleyin.
draggable ile tek eksen boyunca sürükleyin.
draggable değiştiricisi, tek bir yönde sürükleme hareketleri için üst düzey giriş noktasıdır ve sürükleme mesafesini piksel cinsinden bildirir.
Bu değiştiricinin, yalnızca hareketi algılaması açısından scrollable ile benzer olduğunu belirtmek isteriz. Durumu korumanız ve ekranda temsil etmeniz gerekir. Örneğin, öğeyi offset değiştiricisiyle taşıyarak bunu yapabilirsiniz:
@Composable private fun DraggableText() { var offsetX by remember { mutableFloatStateOf(0f) } Text( modifier = Modifier .offset { IntOffset(offsetX.roundToInt(), 0) } .draggable( orientation = Orientation.Horizontal, state = rememberDraggableState { delta -> offsetX += delta } ), text = "Drag me!" ) }
Sürükleme hareketinin tamamını kontrol etmeniz gerekiyorsa pointerInput değiştiricisi aracılığıyla sürükleme hareketi algılayıcıyı kullanabilirsiniz.
@Composable private fun DraggableTextLowLevel() { Box(modifier = Modifier.fillMaxSize()) { var offsetX by remember { mutableFloatStateOf(0f) } var offsetY by remember { mutableFloatStateOf(0f) } Box( Modifier .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) } .background(Color.Blue) .size(50.dp) .pointerInput(Unit) { detectDragGestures { change, dragAmount -> change.consume() offsetX += dragAmount.x offsetY += dragAmount.y } } ) } }
anchoredDraggable ile bileşenleri tutturma noktaları arasında sürükleyin.
anchoredDraggable değiştiricisi, bir bileşeni, sabit nokta adı verilen bir dizi önceden tanımlanmış konum arasında sürüklemenize olanak tanır. Kullanıcı sürükleme hareketini bıraktığında bileşen en yakın sabitleyiciye yerleşir. anchoredDraggable, özel alt sayfalar, karmaşık kaydırma çubukları veya çekmece panelleri gibi bileşenler oluşturmak için kullanışlıdır.
API yüzeyi
AnchoredDraggable API'si aşağıdaki ana bölümlerden oluşur:
- Sabitler (
DraggableAnchors): Özel durumlarınız (ör.Start, Center, Endiçin bir enum sınıfı) ile bunlara karşılık gelen piksel uzaklık değerleri arasındaki eşlemeyi tanımlayan bir oluşturucudur. - State (
AnchoredDraggableState<T>): Bileşenin mevcut konumunu (ofset), yerleştiği sabitleme noktasını ve sürükleme ilerleme durumunu yöneten bir durum bilgisi depolayıcıdır. Yapılandırma değişikliklerinde durumunu korumak içinrememberSaveableveAnchoredDraggableState.Saver<T>kullanılarak oluşturulmalıdır. - Hareket değiştirici (
Modifier.anchoredDraggable):Orientationboyunca sürükleme hareketlerini algılayan, durumu sürükleme deltasıyla güncelleyen ve bileşeni serbest bırakıldığında en yakın sabitleme noktasına animasyonla taşıyacak olan fırlatma hareketini işleyen bir değiştiricidir.
Örnek: Özel bir SwipeToDismiss bileşeni oluşturma
Bu örnekte, kullanıcının sola kaydırarak kapatabileceği veya sağa sürükleyerek aşırı kaydırma esneme efektini görebileceği özel bir SwipeToDismiss bileşeninin (beyaz banner) nasıl oluşturulacağı gösterilmektedir.
Sabit değerleri tanımlayın. Sürüklenebilir içeriğin yerleşebileceği ayrı konumları temsil eden bir enum tanımlayın:
private enum class AnchoredDraggableSwipeDismissValue { DismissedStart, Resting }
Örnek oluşturun ve kaydedin
AnchoredDraggableState. Etkinliğin yeniden oluşturulması ve yapılandırma değişiklikleri (ör. ekran döndürme veya tema değişiklikleri) sırasında bileşenin mevcut konumunu korumak içinAnchoredDraggableState.Saver()ilerememberSaveablekullanın:val state = rememberSaveable(saver = AnchoredDraggableState.Saver()) { AnchoredDraggableState(initialValue = AnchoredDraggableSwipeDismissValue.Resting) }
layout,offsetvedraggabledeğiştiricilerini uygulayın. Bileşeni sürüklenebilir hale getirmek ve sabitleme noktalarının boyutuna uyum sağlamasını sağlamak için görsel olarak hareket etmesi gereken içeriğe aşağıdaki değiştiricileri uygulayın:Modifier.layout: Sabitleme konumları genellikle bir bileşenin boyutuna bağlıdır ve bu boyut ilk kompozisyon sırasında bilinmez. Bileşeni ölçmek içinlayoutdeğiştiricisini kullanın,DraggableAnchorsharitasını genişliğine göre dinamik olarak hesaplayın vestate.updateAnchors()kullanarak durumu güncelleyin.
Modifier.anchoredDraggable: Duruma bağlanır, belirtilen yönde sürükleme deltalarını gönderir ve bileşeni serbest bırakıldığında en yakın sabitleme noktasına animasyonla taşımak için hızlıca kaydırma hareketini işler. Bu değiştiriciyi, sürükleme hareketlerine yanıt vermesi gereken bileşene ekleyin vestateile sürüklemeorientation(ve isteğe bağlı olarakoverscrollEffect) değerlerini iletin.Modifier.offset: Ofset lambda içindekistate.requireOffset()'ten ofseti okur. Ofsetin bu lambda içinde okunması, değerin yalnızca yerleşim aşamasında okunmasını sağlayarak sürükleme sırasında gereksiz yeniden oluşturma işlemlerini önler. Bu değiştiriciyi, görsel olarak hareket etmesi gereken içeriğe uygulayın.
Box( modifier = Modifier .fillMaxWidth() // Read state.requireOffset() inside the offset lambda to defer reading to the placement phase .offset { IntOffset(state.requireOffset().roundToInt(), 0) } .layout { measurable, constraints -> val placeable = measurable.measure(constraints) val anchors = DraggableAnchors { AnchoredDraggableSwipeDismissValue.DismissedStart at -placeable.width.toFloat() AnchoredDraggableSwipeDismissValue.Resting at 0f } state.updateAnchors(anchors) layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) } } // Pass overscrollEffect to anchoredDraggable so deltas beyond the bounds (min and max anchor) are dispatched to overscrollEffect, and render the visual effect with Modifier.overscroll. .overscroll(overscrollEffect) .anchoredDraggable( state, Orientation.Horizontal, overscrollEffect = overscrollEffect ) ) { dismissContent() }
[İsteğe bağlı] Kaydırma sonunu entegre edin.
overscrollEffectAPI, kaydırılan bir kapsayıcının kenarlarına kaydırma veya hızlı kaydırma ile ulaşıldığında gösterilen bir görsel efekt uygular. Kullanıcı, tanımlanan sabit sınırların ötesine sürüklediğinde kaydırma sonu efektiyle (genellikle esneme veya parlama) görsel geri bildirim sağlayabilirsiniz.- Gönderme:
anchoredDraggabledeğiştiricisine biroverscrollEffectörneği iletin. Bu, hareketin sabit sınırların içinde veya dışında olmasına bağlı olarak sürükleme deltalarınıAnchoredDraggabledurumuna veya kaydırma sonu efektine gönderir. - Oluşturma: Aynı
OverscrollEffectörneğine sahipModifier.overscroll'ı, efekti görsel olarak oluşturması gereken bileşene uygulayın.
- Gönderme:
Aşağıdaki snippet'te standart bir esnetme efektinin nasıl ekleneceği gösterilmektedir:
val overscrollEffect = rememberOverscrollEffect() Box( modifier.fillMaxWidth() ) { Box(Modifier.fillMaxWidth()) { background() } Box( modifier = Modifier .fillMaxWidth() // Read state.requireOffset() inside the offset lambda to defer reading to the placement phase .offset { IntOffset(state.requireOffset().roundToInt(), 0) } .layout { measurable, constraints -> val placeable = measurable.measure(constraints) val anchors = DraggableAnchors { AnchoredDraggableSwipeDismissValue.DismissedStart at -placeable.width.toFloat() AnchoredDraggableSwipeDismissValue.Resting at 0f } state.updateAnchors(anchors) layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) } } // Pass overscrollEffect to anchoredDraggable so deltas beyond the bounds (min and max anchor) are dispatched to overscrollEffect, and render the visual effect with Modifier.overscroll. .overscroll(overscrollEffect) .anchoredDraggable( state, Orientation.Horizontal, overscrollEffect = overscrollEffect ) ) { dismissContent() } }
Sonuç
Önceki kod snippet'i, kullanıcının kapatmak için sola kaydırabileceği (veya aşırı kaydırma esnetme efektini görmek için sağa sürükleyebileceği) "Kapatmak için beni sürükleyin!" metnini içeren beyaz bir banner oluşturur:
Örnek: Özel, hareketle çalışan gezinme çekmecesi oluşturma
Bu örnekte, özel ve hareketle çalışan bir gezinme çekmecesinin nasıl oluşturulacağı gösterilmektedir. Kullanıcı yatay olarak sürükledikçe ön plan ekranı (ScreenContents) sağa kayar, 1.0f olan ölçeği 0.8f değerine düşer ve köşeleri yuvarlanarak alttaki gezinme çekmecesini gösterir.
Sabitleme durumlarını ve konumlarını tanımlayın. Bu örnekte, bağlantı noktaları çekmecenin açık ve kapalı durumlarıdır.
OpenveCloseddurumları için bir enum tanımlayın veDraggableAnchorskullanarak bunları piksel ofsetleriyle eşleyin:enum class DrawerState { Open, Closed } val anchors = DraggableAnchors { DrawerState.Open at drawerWidth DrawerState.Closed at 0f } val state = remember { AnchoredDraggableState( initialValue = DrawerState.Closed, anchors = anchors, ) }
Bir öğe tıklandığında animasyon oynat
anchoredDraggableStateHomeScreenDraweröğesiniBoxöğesinin altına,ScreenContentsöğesini ise üstüne yerleştirin. Çekmece sabit kalırken üst katman, çekmecenin üzerinde çevrilir ve ölçeklendirilir.Kullanıcı bir çekmece öğesi seçtiğinde etkin ekran durumunu güncelleyin ve
state.animateTo(DrawerState.Closed)kullanarak çekmeceyi otomatik olarak kapatın:@Composable private fun AnchoredDraggableDrawerOnClickSample( modifier: Modifier = Modifier, state: AnchoredDraggableState<DrawerState>, coroutineScope: CoroutineScope ) { var screenState by remember { mutableStateOf(ScreenState.Home) } Box(modifier = modifier.fillMaxSize()) { HomeScreenDrawer( selectedScreen = screenState, onScreenSelected = { screen -> screenState = screen // Dismiss drawer when a destination is selected coroutineScope.launch { state.animateTo(DrawerState.Closed) } } ) // ScreenContents(...) } }
Birden fazla görsel özelliği değiştirmek için
state.requireOffset()kullanın. İçeriği yalnızca kaydırmak yerine,Modifier.graphicsLayerile birden fazla özelliği değiştirebilirsiniz. Böylece, sürükleme ilerlemesine göre çeviri, ölçek ve köşe yarıçapı aynı anda animasyonlandırılır:- Mevcut sürükleme ofsetini almak için
state.requireOffset()kullanın. - Orantılı ölçeği (
1file0.8farası) ve köşe yarıçapını (0.dpile32.dparası) ofset ve çekmece genişliğine göre hesaplamak içinlerp()(doğrusal enterpolasyon) kullanın. Modifier.graphicsLayer, değişiklikleri çizim aşamasında uygulayarak sürükleme sırasında yeniden oluşturma işlemlerini tetiklemeden yüksek performanslı animasyonlar sağlar.
@Composable private fun AnchoredDraggableDrawerGraphicsLayerSample( state: AnchoredDraggableState<DrawerState>, drawerWidth: Float, selectedScreen: ScreenState, onDrawerClicked: () -> Unit ) { ScreenContents( selectedScreen = selectedScreen, onDrawerClicked = onDrawerClicked, modifier = Modifier .graphicsLayer { this.translationX = state.requireOffset() val scale = lerp(1f, 0.8f, state.requireOffset() / drawerWidth) this.scaleX = scale this.scaleY = scale val cornerRadius = lerp(0f, 32f, state.requireOffset() / drawerWidth) this.shape = RoundedCornerShape(cornerRadius.dp) this.clip = true } .anchoredDraggable(state, Orientation.Horizontal) ) }
- Mevcut sürükleme ofsetini almak için
Programatik animasyonları
animateToile tetikleyin. Çekmeceyi programatik olarak kontrol etmek için (ör. menü simgesine dokunurken veya bir çekmece öğesi seçerken) eş yordam kapsamındastate.animateTo()başlatın:fun toggleDrawerState( state: AnchoredDraggableState<DrawerState>, coroutineScope: CoroutineScope ) { coroutineScope.launch { if (state.currentValue == DrawerState.Open) { state.animateTo(DrawerState.Closed) } else { state.animateTo(DrawerState.Open) } } }
Sonuç
Modifier.draggable2D ile iki boyutta sürükleyin
Kullanıcı arayüzü öğelerini hem X hem de Y ekseninde aynı anda taşımak için draggable2D değiştiricisini kullanın.
Birleştirilebilir bir öğeyi sürükleme
Bu örnekte, draggable2D için en yaygın kullanım alanı gösterilmektedir. Bu kullanım alanı, kullanıcının bir kullanıcı arayüzü öğesini alıp üst kapsayıcı içinde herhangi bir yere yeniden konumlandırmasına olanak tanır.
@Composable private fun DraggableComposableElement() { // 1. Track the position of the floating window var offset by remember { mutableStateOf(Offset.Zero) } Box(modifier = Modifier.fillMaxSize().background(Color(0xFFF5F5F5))) { Box( modifier = Modifier // 2. Apply the offset to the box's position .offset { IntOffset(offset.x.roundToInt(), offset.y.roundToInt()) } // ... // 3. Attach the 2D drag logic .draggable2D( state = rememberDraggable2DState { delta -> // 4. Update the position based on the movement delta offset += delta } ), contentAlignment = Alignment.Center ) { Text("Video Preview", color = Color.White, fontSize = 12.sp) } } }
Yukarıdaki kod snippet'i şunları içerir:
offsetdurumu kullanarak kutunun konumunu izler.- Bileşenin konumunu sürükleme deltalarına göre değiştirmek için
offsetdeğiştiricisini kullanır. - Fling desteği olmadığından kullanıcı parmağını kaldırdığı anda kutu hareket etmeyi durdurur.
Ebeveynin sürükleme alanına göre bir alt composable'ı sürükleme
Bu örnekte, seçici düğmesinin belirli bir yüzeyle sınırlandırıldığı 2D giriş alanı oluşturmak için draggable2D özelliğinin nasıl kullanılacağı gösterilmektedir. Sürüklenerek taşınabilen öğe örneğinde bileşen taşınırken bu uygulamada, "seçici" adlı alt composable öğeyi renk seçicide taşımak için 2D delta değerleri kullanılır:
@Composable private fun ExampleColorSelector( // ... ) { // 1. Maintain the 2D position of the selector in state. var selectorOffset by remember { mutableStateOf(Offset.Zero) } // 2. Track the size of the background container. var containerSize by remember { mutableStateOf(IntSize.Zero) } Box( modifier = Modifier .size(300.dp, 200.dp) // Capture the actual pixel dimensions of the container when it's laid out. .onSizeChanged { containerSize = it } .clip(RoundedCornerShape(12.dp)) .background( brush = remember(hue) { // Create a simple gradient representing Saturation and Value for the given Hue. Brush.linearGradient(listOf(Color.White, Color.hsv(hue, 1f, 1f))) } ) ) { Box( modifier = Modifier .size(24.dp) .graphicsLayer { // Center the selector on the finger by subtracting half its size. translationX = selectorOffset.x - (24.dp.toPx() / 2) translationY = selectorOffset.y - (24.dp.toPx() / 2) } // ... // 3. Configure 2D touch dragging. .draggable2D( state = rememberDraggable2DState { delta -> // 4. Calculate the new position and clamp it to the container bounds val newX = (selectorOffset.x + delta.x) .coerceIn(0f, containerSize.width.toFloat()) val newY = (selectorOffset.y + delta.y) .coerceIn(0f, containerSize.height.toFloat()) selectorOffset = Offset(newX, newY) } ) ) } }
Önceki snippet aşağıdakileri içerir:
- Gradyan kapsayıcının gerçek boyutlarını yakalamak için
onSizeChangeddeğiştiricisini kullanır. Seçici, kenarların tam olarak nerede olduğunu bilir. graphicsLayeriçinde, seçicinin sürüklenirken ortalanmış kalmasını sağlamak içintranslationXvetranslationYdeğerlerini ayarlar.
Sizin için önerilenler
- Not: JavaScript devre dışıyken bağlantı metni gösterilir.
- Hareketleri anlama
- Gelişmiş animasyon örneği: Hareketler {:#gesture-and-animation}
- Değere dayalı animasyonlar