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

می‌توانید کلاس انتزاعی 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 composable یک NavigationEventHandler برای محتوای خود ایجاد می‌کند و آن را به LocalNavigationEventDispatcherOwner پیوند می‌دهد. این ویژگی از DisposableEffect Compose برای فراخوانی خودکار روش dispose() توزیع‌کننده هنگام خروج composable از صفحه استفاده می‌کند و منابع را به‌طور ایمن مدیریت می‌کند.

@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 مدیریت می‌شود.

دسترسی به اشاره برگشت یا لبه تند کشیدن در Compose

شکل ۱. پویانمایی اشاره برگشت پیش‌گویانه ساخته‌شده با NavigationEvent و Compose.

برای پویانمایی کردن صفحه هنگام تند کشیدن کاربر به عقب، باید (الف) بررسی کنید که آیا NavigationEventTransitionState InProgress است یا نه، و (ب) پیشرفت و وضعیت لبه تند کشیدن را با rememberNavigationEventState مشاهده کنید:

  • ‫progress: یک Float از 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) {
        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
    }
}