التعامل مع إيماءات الرجوع إلى الخلف والصور المتحركة لإيماءة إظهار شاشة الرجوع

يمكنك توسيع نطاق الفئة المجردة 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. ضمن كل مجموعة أولوية، يتم استدعاء المعالِجات بترتيب Last-In, First-Out (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.

الوصول إلى إيماءة الرجوع أو التمرير السريع على الحافة في Compose

الشكل 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
    }
}