حرکات برگشت و انیمیشن‌های پیش‌بینی‌کننده‌ی برگشت را مدیریت کنید

شما می‌توانید کلاس انتزاعی 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)

برای حذف هندلر از dispatcher، تابع myHandler.remove() را فراخوانی کنید:

myHandler.remove()

هندلرها بر اساس اولویت و سپس بر اساس تازگی فراخوانی می‌شوند. همه هندلرهای PRIORITY_OVERLAY قبل از هر هندلر PRIORITY_DEFAULT فراخوانی می‌شوند. در هر گروه اولویت، هندلرها به ترتیب آخرین ورودی، اولین خروجی (LIFO) فراخوانی می‌شوند - هندلری که اخیراً اضافه شده است، ابتدا فراخوانی می‌شود.

رهگیری بازگشت با Jetpack Compose

برای Jetpack Compose، این کتابخانه یک ابزار composable برای مدیریت سلسله مراتب dispatcher ارائه می‌دهد.

کامپوننت NavigationBackHandler یک NavigationEventHandler برای محتوای خود ایجاد می‌کند و آن را به LocalNavigationEventDispatcherOwner پیوند می‌دهد. این کامپوننت از DisposableEffect کامپوننت برای فراخوانی خودکار متد 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 ، مورد استفاده قرار گیرد.

در نوشتن، به ژست بازگشت یا کشیدن انگشت به لبه صفحه دسترسی پیدا کنید

شکل ۱. یک انیمیشن برگشتی پیش‌بینی‌کننده ساخته شده با 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
    }
}