ফিরে যাওয়া সংক্রান্ত জেসচার ও ফিরে যাওয়া সংক্রান্ত পূর্বাভাসের অ্যানিমেশন ম্যানেজ করা

বিভিন্ন প্ল্যাটফর্ম জুড়ে নেভিগেশন ইভেন্ট ম্যানেজ করতে, আপনি অ্যাবস্ট্রাক্ট ক্লাস 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_DEFAULT হ্যান্ডলারের আগে সব PRIORITY_OVERLAY হ্যান্ডলারকে কল করা হয়। প্রতিটি প্রায়োরিটি গ্রুপের মধ্যে, হ্যান্ডলারদের একটি লাস্ট-ইন, ফার্স্ট-আউট (LIFO) অর্ডারে ইনভোক করা হয় — সবচেয়ে সম্প্রতি যোগ করা হ্যান্ডলারকে প্রথমে কল করা হয়।

Jetpack Compose-এর মাধ্যমে 'ব্যাক' ইন্টারসেপ্ট করা

Jetpack Compose-এর জন্য, লাইব্রেরি ডিসপ্যাচার হায়ারার্কি ম্যানেজ করার জন্য একটি ইউটিলিটি কম্পোজ করার উপযুক্ত।

NavigationBackHandler কম্পোজ করার মতো আইটেমটি এর কন্টেন্টের জন্য একটি NavigationEventHandler তৈরি করে এবং এটিকে LocalNavigationEventDispatcherOwner-এর সাথে লিঙ্ক করে। কম্পোজ করার মতো আইটেম স্ক্রিন থেকে বেরিয়ে গেলে, এটি রিসোর্স নিরাপদে ম্যানেজ করার জন্য কম্পোজের DisposableEffect মাধ্যমে অটোমেটিক ডিসপ্যাচারের dispose() মেথড কল করে।

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

}

এই ফাংশন আপনাকে লোকাল UI সাবট্রি-এর মধ্যে ইভেন্ট হ্যান্ডলিং সঠিকভাবে কন্ট্রোল করতে দেয়।

@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 ব্যবহার করে 'প্রেডিক্টিভ ব্যাক জেসচার' আপডেট পর্যবেক্ষণ করতে হয়। NavigationBackHandler-এর মাধ্যমে সম্পূর্ণতা ও বাতিলকরণ ম্যানেজ করার সময়, ব্যাক জেসচারের প্রতিক্রিয়ায় UI এলিমেন্ট আপডেট করতে progress ভ্যালু ব্যবহার করা যেতে পারে।

কম্পোজ করার সময় 'ফিরে যাওয়া' জেসচার অ্যাক্সেস করা বা স্ক্রিনের প্রান্ত সোয়াইপ করা

ছবি ১. 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
    }
}