• Windows Insets + Fragment Transitions: A tale of Woe
  • Originally by Chris Banes
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: LeeSniper
  • Proofreader: Starrier

A sad story

This article is the second in a small series I’m writing about fragment transition animations. The first post, which can be viewed at the link below, describes how to get the Fragment transition animation to work.

  • Fragment transition animation: Put them to work

Before I go any further, I’ll assume you know what WindowsInsets are and how they are distributed. If you don’t know, I suggest you watch this talk first (yes, it’s my talk 🙋).

  • Become a master of screen adaptation 🔧WindowInsets have long been a headache for developers because they’re really hard to understand… _chris.banes.me

I need to confess. When I was writing the first blog post in this series, I tweaked the video. I actually ran into WindowInsets, which means I actually ended up with the following result:

The transition animation breaks the status bar effect.

Woops, not quite the same as I showed in my first article 🤐. I didn’t want to make the first article too complicated, so I decided to write this article alone. However, you can see that when the transition animation is added, we suddenly lose all status bar effects and the view is pushed below the status bar.

The problem

Both fragments make extensive use of WindowInsets for drawing under the system bar. Fragments A used CoordinatorLayout and AppBarLayout and fragments B using custom WindowInsets to handle A OnApplyWindowInsetsListener (through). No matter how they are implemented, transition animations confuse the two.

So why does this happen? When you use the Fragment transition animation, exiting (Fragment A) and entering (Fragment B) the content view actually goes through the following steps:

  1. The transition animation begins.
  2. Since we used an exit transition animation on Fragment A, View A remains where it was, with the transition animation running on it.
  3. View B is added to the content View and is immediately set to invisible.
  4. Fragment B’s entry animation and “Shared Element entry” transition animation begin.
  5. View B is set to be visible.
  6. When the Fragment A exit animation ends, View A is removed from the container View.

That all sounds good, so why the sudden setback to the WindowInsets? This is because during the transition, both fragment views exist in the container.

But it sounds totally OK, doesn’t it? In my scenario, however, both fragment views want to process and consume WindowInsets, because they both expect to display a unique “main” view on the screen. But only one of the views receives the “WindowInsets” : the first subview. This depends on how the ViewGroup distributes the WindowInsets, that is, by iterating sequentially through its children until one of them consumes the WindowInsets. If the first sub-view (Fragment A) consumes the WindowInsets, any subsequent sub-views (Fragment B) don’t get them, and we end up with this situation.

Let’s go over it again, step by step, but this time with the opportunity to distribute the windowinsets:

  1. The transition animation begins.
  2. Since we used an exit transition animation on Fragment A, View A remains where it was, with the transition animation running on it.
  3. View B is added to the content View and is immediately set to invisible.
  4. Distribute WindowInsets. We wanted View B (Child 1) to get them, but View A (Child 0) again got the WindowInsets.
  5. Fragment B’s entry animation and ‘shared element entry’ transition animation begin.
  6. View B is set to be visible.
  7. When the Fragment A exit animation ends, View A is removed from the container View.

repair

This fix is actually relatively simple: we just need to make sure that both views get to the WindowInsets.

Me the way to achieve this is through the container view (in this case is the activity on the host) add a OnApplyWindowInsetsListener, it will give all the manual distributed WindowInsets view, Until one of the subviews consumes the WindowInsets.

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
}
Copy the code

After we apply this fix, both fragments will receive the WindowInsets, and we’ll get the result actually shown in the first article:


Extra section 💃: Be sure to request it

There’s one other little thing I almost forgot to write. If you’re dealing with WindowInsets inside a fragment, either implicitly (by using AppBarLayout, etc.) or explicitly, you need to make sure that some WindowInsets are requested. RequestApplyInsets () can be easily done:

override fun onViewCreated(view: View, icicle: Bundle) {
	super.onViewCreated(view, savedInstanceState)
	// yadda, yadda
	ViewCompat.requestApplyInsets(view)
}
Copy the code

You must do this because Windows automatically distribute WindowInsets only when the value of the overall system UI visibility changes throughout the view hierarchy. Since sometimes your two fragments may provide exactly the same value, the overall value will not change, so the system will ignore this “change”.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.