Обработка жестов «назад» и анимаций «предсказательного возврата»

Вы можете расширить абстрактный класс NavigationEventHandler для обработки событий навигации на разных платформах. Этот класс предоставляет методы, соответствующие жизненному циклу навигационного жеста.

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 связывает обработчик с диспетчером:

navigationEventDispatcher.addHandler(myHandler)

Вызовите метод myHandler.remove() , чтобы удалить обработчик из диспетчера:

myHandler.remove()

Обработчики вызываются в зависимости от приоритета, а затем в зависимости от давности вызова. Все обработчики PRIORITY_OVERLAY вызываются раньше любых обработчиков PRIORITY_DEFAULT . В каждой группе приоритетов обработчики вызываются в порядке «последний вошел — первый вышел» (LIFO) — сначала вызывается обработчик, добавленный последним.

Перехватите обратно с помощью Jetpack Compose

В библиотеке Jetpack Compose предоставляется вспомогательный модуль для управления иерархией диспетчеров.

Компонент NavigationBackHandler создает обработчик NavigationEventHandler для своего содержимого и связывает его с LocalNavigationEventDispatcherOwner . Он использует DisposableEffect из Compose для автоматического вызова метода dispose() диспетчера, когда компонент покидает экран, обеспечивая безопасное управление ресурсами.

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

}

Эта функция позволяет точно управлять обработкой событий в локализованных поддеревьях пользовательского интерфейса.

@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()
        }
    )
}

В этом примере показано, как отслеживать обновления, вызванные предиктивным жестом «назад», с помощью NavigationEventTransitionState . Значение progress можно использовать для обновления элементов пользовательского интерфейса в ответ на жест «назад», при этом обработка завершения и отмены осуществляется через NavigationBackHandler .

Воспользуйтесь жестом «назад» или свайпом вдоль края экрана в меню «Создать».

Рисунок 1. Анимация возврата назад с функцией прогнозирования, созданная с помощью NavigationEvent и Compose.

Чтобы анимировать экран во время возврата пользователя с помощью свайпа назад, вам потребуется (а) проверить, находится ли NavigationEventTransitionState InProgress , и (б) отслеживать прогресс и состояние края свайпа с помощью rememberNavigationEventState :

  • progress : Число с плавающей запятой от 0.0 до 1.0 указывающее, насколько далеко пользователь провел пальцем по экрану.
  • swipeEdge : Целочисленная константа ( EDGE_LEFT или EDGE_RIGHT ), указывающая точку начала жеста.

Приведённый ниже фрагмент кода представляет собой упрощённый пример реализации анимации масштабирования и сдвига:

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) {
        EDGE_LEFT -> (backProgress * maxShift).dp
        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
    }
}