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ırıcı, 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.
pointerInput ile dokunma koordinatlarını manuel olarak hesaplamak ve hareket yaşam döngülerini 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.
- Şu durumlarda standart Material bileşenlerini (ör.
SwipeToDismissBoxveya gezinme çekmeceleri) seçin: Varsayılan Material 3 kaydırma veya sürükleme davranışına ihtiyacınız olduğunda (ör. e-posta silme, görev arşivleme veya yan menü / alt sayfaları açma). Özel durum veya düzen yönetimi gerektirmeden yerleşik davranışlar sunarlar. - Şu durumlarda
Modifier.draggableseçin: Yalnızca ham sürükleme deltalarına ihtiyacınız olduğunda (örneğin, özel bir kaydırma çubuğunu sürükleme). Daha fazla bilgi için Sürüklenebilir ile tek bir 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 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 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 bir eksen boyunca sürükleyin
draggable değiştiricisi, tek bir yöndeki 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 tutmanız ve ekranda göstermeniz gerekir.
Örneğin, öğeyi offset değiştiricisiyle taşıyabilirsiniz:
@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ısını 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 bağlantı noktaları arasında sürükleme
anchoredDraggable değiştiricisi, bir bileşeni bağlantı noktaları adı verilen önceden tanımlanmış bir dizi 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:
- Anchors (
DraggableAnchors): Özel durumlarınız (ör.Start, Center, Endiçin bir enum sınıfı) ile bunlara karşılık gelen piksel ofset 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 sabitleyiciye animasyonla taşımak için kaydırma hareketini işleyen bir değiştirici.
Ö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 esnetme 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 }
AnchoredDraggableStateörneğini oluşturun ve kaydedin. Bileşenin mevcut konumunu etkinlik yeniden oluşturma ve yapılandırma değişiklikleri (ör. ekran döndürme veya tema değişiklikleri) boyunca 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 bağlantı 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: Sabit konumlar genellikle bir bileşenin boyutuna bağlıdır. Bu boyut, ilk oluşturma 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 sabitleyiciye animasyonla taşımak için 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()öğesinden 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'si, kaydırılabilir bir kapsayıcının kenarlarına kaydırma veya hızlıca kaydırma hareketiyle 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 sabitleme sınırlarının içinde veya dışında olmasına bağlı olarak sürükleme deltalarınıAnchoredDraggabledurumuna ya da 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ç
Yukarıdaki kod snippet'i, "Kapatmak için beni sürükleyin!" metnini içeren beyaz bir banner oluşturur. Kullanıcı, bu banner'ı sola kaydırarak kapatabilir (veya sağa sürükleyerek aşırı kaydırma esnetme efektini görebilir):
Ö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.
Sabit durumları ve konumları 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
anchoredDraggableStateanimasyonunu oynatın.HomeScreenDrawerBox'in altına,ScreenContentsise üstüne yerleştirilir. Çekmece sabit kalırken üst katman, çekmecenin üzerinde hareket eder ve ölçeklenir.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. Yalnızca içeriği kaydırmak yerine,Modifier.graphicsLayerile birden fazla özelliği değiştirerek sürükleme ilerlemesine göre çeviri, ölçek ve köşe yarıçapını aynı anda canlandırabilirsiniz:- Mevcut sürükleme ofsetini almak için
state.requireOffset()kullanın. - Ofset ve çekmece genişliğine göre orantılı ölçeği (
1f-0.8f) ve köşe yarıçapını (0.dp-32.dp) hesaplamak içinlerp()(doğrusal interpolasyon) 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) bir eş yordam kapsamındastate.animateTo()işlevini 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ükleme
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 öğeyi sürükleme
Bu örnekte, draggable2D için en yaygın kullanım alanı gösterilmektedir: Kullanıcının bir kullanıcı arayüzü öğesini seçip üst kapsayıcıda herhangi bir yere yeniden konumlandırmasına olanak tanıma.
@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:
offsetdurumunu kullanarak kutunun konumunu izler.- Sürükleme deltalarına göre bileşenin konumunu değiştirmek için
offsetdeğiştiricisini kullanır. - Kaydırma desteği olmadığından, kullanıcı parmağını kaldırdığı anda kutu hareket etmeyi durdurur.
Üst öğenin sürükleme alanına göre 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. Bileşenin kendisini taşıyan sürüklenebilir öğe örneğinin aksine, bu uygulama, alt bileşen "seçici"yi renk seçici üzerinde taşımak için 2D deltaları kullanı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, sürükleme sırasında seçicinin ortada kalmasını sağlamak içintranslationXvetranslationYdeğerlerini ayarlar.
Sizin için önerilenler
- Not: JavaScript devre dışı bırakıldığında bağlantı metni gösterilir.
- Hareketleri anlama
- Gelişmiş animasyon örneği: Hareketler {:#gesture-and-animation}
- Değere dayalı animasyonlar