Reputation: 1388
I'm trying to get a callback, using the Jetpack Navigation library, when a new navigation event is completed in order to change the status bar color.
So far I've found
navController.addOnDestinationChangedListener
which notifies me when a new navigation starts but not when it completes, meaning that the enter/exit transitions have completed.
Is there any method to know when all navigation transitions have completed?
Upvotes: 15
Views: 2347
Reputation: 998
The answer is simple as an "abc":
For doing some job after transition ends, add a listener on a Fragment.View
For example, if we need to jump to another Destination
only after current Fragment
is drawn and presented on the screen, just place this hook in onResume callback:
override fun onResume() {
super.onResume()
view?.animation?.setAnimationListener(object: Animation.AnimationListener {
override fun onAnimationRepeat(animation: Animation?) {}
override fun onAnimationStart(animation: Animation?) {}
override fun onAnimationEnd(animation: Animation?) {
// Do the navigation or whatever you want
}
})
}
Upvotes: 0
Reputation: 3982
you can refer this answer to achieve it. briefly, as a work around, you can add some delay to your animation when transaction is started.
as mentioned in the answer; fragment replacement is starting when you click to "navigate to X fragment" button. so the delay is only in fragment transaction animation not in replacement
add delay to animation by adding android:startOffset="300"
in your_anim_name.xml file on your anim folder which is under res folder.
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
<alpha
android:startOffset="300"
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="400"
android:interpolator="@android:interpolator/accelerate_decelerate"
/>
<translate
android:startOffset="300"
android:fromYDelta="100%"
android:toYDelta="0%"
android:duration="400"
android:interpolator="@android:interpolator/accelerate_decelerate"
/>
Upvotes: 0
Reputation: 653
I faced the same problem and finally found a workaround.
The idea is that when the exit animation of the current fragment ends, onDestroyView
of that fragment is called.
So as long as the duration of the enter and exit animations are the same, we can use this method as a callback.
class BaseFragment() {
private var leavingDestination = false
fun navigateNext() {
leavingDestination = true
findNavController().navigate(blaBlaBla)
}
fun onNavigationAnimationEnds() {
TODO()
}
// We just need to add an extra check, to be sure that onNavigationAnimationEnds is not called on configuration change
override fun onDestroyView() {
if(leavingDestination) {
onNavigationAnimationEnds()
leavingDestination = false
}
}
}
I use the described approach to call some code in the back/exiting fragment. If you need a callback in the fragment you navigate to, you can just use shared ViewModel
. Like the ActivityViewModel or NavGraph scoped ViewModel.
Something like this:
class FragmentA() {
private val mainActivityViewModel by activityViewModels<MainActivityViewModel> { viewModelFactory }
...
override fun onDestroyView() {
if(leavingDestination) {
mainActivityViewModel.onNavigationAnimationEnds()
leavingDestination = false
}
}
}
...
class MainActivity() {
private val _navigationAnimationEndsEvent = MutableLiveData(Event())
val navigationAnimationEndsEvent: LiveData<Event>; get() = _navigationAnimationEndsEvent
fun onNavigationAnimationEnds() {
_navigationAnimationEndsEvent.value = Event()
}
}
...
class FragmentB() {
private val mainActivityViewModel by activityViewModels<MainActivityViewModel> { viewModelFactory }
override fun onActivityCreated() {
...
mainActivityViewModel.navigationAnimationEndsEvent.observe(viewLifecycleOwner, Observer {
it.getContentIfNotHandled()?.let {
onNavigationAnimationsEnds()
}
})
}
private fun onNavigationAnimationEnds() {
TODO()
}
}
Event class I used:
open class Event<out T>(private val content: T) {
var hasBeenHandled = false; private set
fun getContentIfNotHandled() = if (hasBeenHandled) null else {
hasBeenHandled = true
content
}
fun peekContent(): T = content
}
P.S.: I tried overriding onCreateAnimation/onCreateAnimator functions and add listeners to them, but they were never called.
Upvotes: -1
Reputation: 21
You can use something like this:
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
navHostFragment?.childFragmentManager?.addOnBackStackChangedListener {
val currentFragment = navHostFragment.childFragmentManager.fragments.firstOrNull()
if (currentFragment is YourFragment) {
// your code here
}
}
Upvotes: 2