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

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

دسترسی به اشاره برگشتن یا تند کشیدن لبه در «نوشتن»

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