Geri gitme hareketlerini ve tahmin edilen geri gitme animasyonlarını işleme

Platformlar arası gezinme etkinliklerini işlemek için soyut sınıf NavigationEventHandler'i genişletebilirsiniz. Bu sınıf, gezinme hareketinin yaşam döngüsüne karşılık gelen yöntemler sağlar.

val myHandler = object: NavigationEventHandler<NavigationEventInfo>(
    initialInfo = NavigationEventInfo.None,
    isBackEnabled = true
) {
    override fun onBackStarted(event: NavigationEvent) {
        // Prepare for the back event
    }

    override fun onBackProgressed(event: NavigationEvent) {
        // Use event.progress for predictive animations
    }

    // This is the required method for final event handling
    override fun onBackCompleted() {
        // Complete the back event
    }

    override fun onBackCancelled() {
        // Cancel the back event
    }
}

addHandler işlevi, işleyiciyi dağıtıcıya bağlar:

navigationEventDispatcher.addHandler(myHandler)

İşleyiciyi dağıtıcıdan kaldırmak için myHandler.remove() yöntemini çağırın:

myHandler.remove()

İşleyiciler önceliğe ve ardından yeniliğe göre çağrılır. Tüm PRIORITY_OVERLAY işleyiciler, PRIORITY_DEFAULT işleyicilerden önce çağrılır. Her öncelik grubunda işleyiciler, Son Giren İlk Çıkar (LIFO) sırasına göre çağrılır. En son eklenen işleyici ilk olarak çağrılır.

Jetpack Compose ile geri gitme işlemini engelleme

Jetpack Compose için kitaplık, dağıtıcı hiyerarşisini yönetmek üzere bir yardımcı composable sağlar.

NavigationBackHandler composable'ı, içeriği için bir NavigationEventHandler oluşturur ve bunu LocalNavigationEventDispatcherOwner ile bağlar. Oluşturulabilir öğe ekrandan ayrıldığında dağıtıcının dispose() yöntemini otomatik olarak çağırmak için Compose'un DisposableEffect özelliğini kullanarak kaynakları güvenli bir şekilde yönetir.

@Composable
public fun NavigationBackHandler(
    state: NavigationEventState<out NavigationEventInfo>,
    isBackEnabled: Boolean = true,
    onBackCancelled: () -> Unit = {},
    onBackCompleted: () -> Unit,
){

}

Bu işlev, yerelleştirilmiş kullanıcı arayüzü alt ağaçlarında etkinlik işlemeyi hassas bir şekilde kontrol etmenize olanak tanır.

@Composable
fun HandlingBackWithTransitionState(
    onNavigateUp: () -> Unit
) {
    val navigationState = rememberNavigationEventState(
        currentInfo = NavigationEventInfo.None
    )
    val transitionState = navigationState.transitionState
    // React to predictive back transition updates
    when (transitionState) {
        is NavigationEventTransitionState.InProgress -> {
            val progress = transitionState.latestEvent.progress
            // Use progress (0f..1f) to update UI during the gesture
        }
        is NavigationEventTransitionState.Idle -> {
            // Reset any temporary UI state if the gesture is cancelled
        }
    }
    NavigationBackHandler(
        state = navigationState,
        onBackCancelled = {
            // Called if the back gesture is cancelled
        },
        onBackCompleted = {
            // Called when the back gesture fully completes
            onNavigateUp()
        }
    )
}

Bu örnekte, NavigationEventTransitionState kullanarak tahmin edilen geri gitme hareketi güncellemelerinin nasıl gözlemleneceği gösterilmektedir. progress değeri, geri hareketine yanıt olarak kullanıcı arayüzü öğelerini güncellemek için kullanılabilir. Tamamlama ve iptal işlemleri ise NavigationBackHandler aracılığıyla gerçekleştirilir.

Compose'da geri hareketine veya kenardan kaydırma özelliğine erişme

Şekil 1. NavigationEvent ve Compose ile oluşturulmuş, tahmin edilen geri gitme animasyonu.

Kullanıcı geri kaydırırken ekrana animasyon eklemek için (a) NavigationEventTransitionState değerinin InProgress olup olmadığını kontrol etmeniz ve (b) rememberNavigationEventState ile ilerleme durumunu ve kaydırma kenarı durumunu gözlemlemeniz gerekir:

  • progress: Kullanıcının ne kadar kaydırdığını gösteren, 0.0 ile 1.0 arasında bir kayan nokta değeri.
  • swipeEdge: Hareketin nerede başladığını gösteren bir tam sayı sabiti (EDGE_LEFT veya EDGE_RIGHT).

Aşağıdaki snippet, ölçeklendirme ve kaydırma animasyonunun nasıl uygulanacağına dair basitleştirilmiş bir örnektir:

object Routes {
    const val SCREEN_A = "Screen A"
    const val SCREEN_B = "Screen B"
}

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            var state by remember { mutableStateOf(Routes.SCREEN_A) }
            val backEventState = rememberNavigationEventState<NavigationEventInfo>(currentInfo = NavigationEventInfo.None)
            when (state) {
                Routes.SCREEN_A -> {
                    ScreenA(onNavigate = { state = Routes.SCREEN_B })
                }
                else -> {
                    if (backEventState.transitionState is NavigationEventTransitionState.InProgress) {
                        ScreenA(onNavigate = { })
                    }
                    ScreenB(
                        backEventState = backEventState,
                        onBackCompleted = { state = Routes.SCREEN_A }
                    )
                }
            }
        }
    }
}

@Composable
fun ScreenB(
    backEventState: NavigationEventState<NavigationEventInfo>,
    onBackCompleted: () -> Unit = {},
) {
    val transitionState = backEventState.transitionState
    val latestEvent =
        (transitionState as? NavigationEventTransitionState.InProgress)
            ?.latestEvent
    val backProgress = latestEvent?.progress ?: 0f
    val swipeEdge = latestEvent?.swipeEdge ?: NavigationEvent.EDGE_LEFT
    if (transitionState is NavigationEventTransitionState.InProgress) {
        Log.d("BackGesture", "Progress: ${transitionState.latestEvent.progress}")
    } else if (transitionState is NavigationEventTransitionState.Idle) {
        Log.d("BackGesture", "Idle")
    }
    val animatedScale by animateFloatAsState(
        targetValue = 1f - (backProgress * 0.1f),
        label = "ScaleAnimation"
    )
    val windowInfo = LocalWindowInfo.current
    val density = LocalDensity.current
    val maxShift = remember(windowInfo, density) {
        val widthDp = with(density) { windowInfo.containerSize.width.toDp() }
        (widthDp.value / 20f) - 8
    }
    val offsetX = when (swipeEdge) {
        NavigationEvent.EDGE_LEFT -> (backProgress * maxShift).dp
        NavigationEvent.EDGE_RIGHT -> (-backProgress * maxShift).dp
        else -> 0.dp
    }
    NavigationBackHandler(
        state = backEventState,
        onBackCompleted = onBackCompleted,
        isBackEnabled = true
    )
    Box(
        modifier = Modifier
            .offset(x = offsetX)
            .scale(animatedScale)
    ){
        // Rest of UI
    }
}