I have a confession to make. When I was working on the first blog post in this series I cheated a bit with the videos. I actually hit an issue with window insets which meant that I actually ended up with the following:

Transition breaks status bar handling

Woops, not exactly what I showed in the first post 🤐. I did not want to overcomplicate the first post so decided to write this up separately. Anyway, you can see that we suddenly lost all status bar handling when the transition was added, and the views got pushed up behind the status bar.

The problem

Both of these fragments make heavy use of the window insets to draw behind the system bars. Fragment A uses a CoordinatorLayout and AppBarLayout, whereas Fragment B uses custom window inset handling (via an OnApplyWindowInsetsListener). Regardless of the implementation, the transition messes with both.

So why is this happening? Well when you’re using fragment transitions, what actually happens to the exiting (Fragment A) and entering (Fragment B) content views is the following:

Transition is commit() ed. Since we’re using an exit transition on Fragment A, View A stays in place and the transition is run on it. View B is added to the container view and immediately set to be invisible. Fragment B’s enter and ‘shared element enter’ transitions are started. View B is set to be visible. When Fragment A’s exit transition has finished, View A is removed from the container view.

That all sounds fine, so why does it suddenly affect window insets handling? It’s all due the fact that during the transition, both fragments’ views are present in the container.

That sounds perfectly OK though right? Well in my scenario both of the fragments’ views want to handle and consume the window insets, since they both expect to the only “main” view on screen. Only one of the views will receive the window insets though: the first child. This is due to how ViewGroup dispatches window insets, which is by iterating through it’s children in order until one of them consumes the insets. If the first child (fragment A here) consumes the insets, any subsequent children (fragment B here) won’t get them, and we end up in this situation.

Lets step through again, but this time add in the time when window insets are dispatched:

Transaction is commit() ed. Since we’re using an exit transition, View A stays in place and the transition is run on it. View B is added to the container view and immediately set to be invisible. Window insets are dispatched. We want View B (child 1) to get them, but View A (child 0) gets them again. Fragment B’s enter and ‘shared element enter’ transitions are started. View B is set to be visible. When Fragment A’s exit transition has finished, View A is removed.

The fix

The fix is actually relatively simple: we just need to make sure that both views

receive the window insets.

The way I’ve done this is by adding an OnApplyWindowInsetsListener to the container view (in the host activity in this case) which manually dispatches any insets to all of it’s children, not just until one consumes the insets.

fragment_container.setOnApplyWindowInsetsListener { view, insets ->

var consumed = false



(view as ViewGroup).forEach { child ->

// Dispatch the insets to the child

val childResult = child.dispatchApplyWindowInsets(insets)

// If the child consumed the insets, record it

if (childResult.isConsumed) {

consumed = true

}

}



// If any of the children consumed the insets, return

// an appropriate value

if (consumed) insets.consumeSystemWindowInsets() else insets

}

After we apply that, both fragments receive the window insets and we get the result I actually shown in the first post: