FragmentTransaction animation z-index trick

If you've ever worked with Fragments you will quickly learn they are can be tricky to animate. The basics can get you pretty far, but to set your app apart you'll need a little more than a simple cross fade or side-to-side slide. I won't be getting into shared element transitions, rather focusing on Fragments as unit for a screen in your app. I also acknowledge an entirely View based approach would be easier in some ways. How ever you found yourself trying to animate Fragments, here is a little tip to fix a z-indexing issue I recently encountered.

I was asked to implement a screen that slides up from the bottom, overlaying on top of the currently displayed one - simple, right? I created four animation XML resources with various <translate toYDelta=...> and rigged it up with setCustomAnimations(...) and this is what I got.

 
 

You can see here it's doing the right thing on the way out, but the way in was just wrong. This is in part due to support Fragment, where you are stuck with the old animation system. This means you have very little control over how things move in Z space and when the FragmentManager makes a bunch of assumptions around that detail, it can be problematic. I applied this fix in a BaseFragment which all of my other Fragment subclass.

override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? {
    if (nextAnim == R.anim.slide_enter) {
        val nextAnimation = AnimationUtils.loadAnimation(context, nextAnim)
        nextAnimation.setAnimationListener(object : Animation.AnimationListener {
            private var startZ = 0f
            override fun onAnimationStart(animation: Animation) {
                view?.apply {
                    startZ = ViewCompat.getTranslationZ(this)
                    ViewCompat.setTranslationZ(this, 1f)
                }
            }

            override fun onAnimationEnd(animation: Animation) {
                // Short delay required to prevent flicker since other Fragment wasn't removed just yet.
                view?.apply {
                    this.postDelayed({ ViewCompat.setTranslationZ(this, startZ) }, 100)
                }
            }

            override fun onAnimationRepeat(animation: Animation) {}
        })
        return nextAnimation
    } else {
        return null
    }
}

In a nutshell it raises the z-index of the Fragment's View only when it's performing an animation that requires the incoming screen to be on top during the animation. For example, the exit animation is perfect as is and we don't want to start swapping z-index there. Here is the final result, animating as expected.

 
 

Have you ever had weird Fragment animation issues?