专栏首页Android 开发者[译] WindowsInsets 和 Fragment 过渡动画

[译] WindowsInsets 和 Fragment 过渡动画

一个悲伤的故事

这篇文章是我写的关于 fragment 过渡动画的小系列中的第二篇。第一篇可以通过下面的链接查看,里面写了如何让 fragment 过渡动画开始工作。


在我开始进一步探讨之前,我会假设你知道什么是 WindowsInsets 以及它们是如何分发的。如果你不知道,我建议你先看这个演讲(是的,这是我的演讲 ?)


我需要坦白。当我在写本系列第一篇博客文章的时候,我对视频做了点手脚。实际上我遇到了 WindowInsets 的问题,也就是说我实际上最终得到的是以下结果:

过渡动画破坏了状态栏的效果。

Woops,跟我在第一篇文章中展示的效果不太一样 ?。我不想让第一篇文章变得太复杂,所以决定单独写这篇文章。无论如何,你可以看到当添加过渡动画之后,我们突然失去了所有状态栏的效果,而且视图被推到状态栏的下面。

问题

这两个 fragment 为了在系统栏下面进行绘制都大量使用了 WindowInsets。Fragment A 使用了 CoordinatorLayoutAppBarLayout,而 Fragment B 使用自定义 WindowInsets 来处理(通过一个 OnApplyWindowInsetsListener)。无论它们是如何实现的,过渡动画都会混淆两者。

那么为什么会这样呢?其实当你在使用 fragment 过渡动画时,退出(Fragment A)和进入(Fragment B)的内容视图实际上经历了以下几个过程:

  1. 过渡动画开始。
  2. 因为我们对 Fragment A 使用了一个退出的过渡动画,所以 View A 还留在原来的位置,过渡动画在上面运行。
  3. View B 被添加到内容视图里面,并且被立即设置成不可见。
  4. Fragment B 的进入动画和“共享元素进入”过渡动画开始执行。
  5. View B 被设置成可见的。
  6. 当 Fragment A 的退出动画结束的时候,View A 从容器视图中移除。

这一切听起来都很好,那为什么会突然影响到 WindowInsets 的效果呢?这是因为在过渡的过程中,两个 fragment 的视图都存在于容器中。

但是这听起来完全 OK 啊,不是吗?然而在我的场景中,这两个 fragment 的视图都想要处理和消费 WindowInsets,因为它们都期望在屏幕上显示唯一的“主”视图。可是只有其中的一个视图会收到 WindowInsets:也就是第一个子 view。这取决于 ViewGroup 是如何分发 WindowInsets 的,也就是通过按顺序遍历它的子节点直到其中的一个消费了 WindowInsets。 如果第一个子 view(就是这里的 Fragment A)消费了 WindowInsets,任何后续的子 view(就是这里的 Fragment B)都不会得到它们,我们最终就会得到这种情况。

让我们再来一步一步检查一遍,只是这一次加上分发 windowinsets 的时机:

  1. 过渡动画开始。
  2. 因为我们对 Fragment A 使用了一个退出的过渡动画,所以 View A 还留在原来的位置,过渡动画在上面运行。
  3. View B 被添加到内容视图里面,并且被立即设置成不可见。
  4. 分发 WindowInsets。我们希望 View B(child 1)拿到它们,但是 View A(child 0)又一次拿到了 WindowInsets。
  5. Fragment B 的进入动画和‘共享元素进入’过渡动画开始执行。
  6. View B 被设置成可见的。
  7. 当 Fragment A 的退出动画结束的时候,View A 从容器视图中移除。

修复

这个修复实际上相对简单:我们只需要确保两个视图都能够拿到 WindowInsets。

我实现这一点的方法是通过在容器视图(在这个例子中就是在宿主 activity)里添加一个 OnApplyWindowInsetsListener,它会手动分发 WindowInsets 给所有的子 view,直到其中一个子 view 消费掉这个 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
}

在我们应用这个修复之后,这两个 fragment 都会收到 WindowInsets,然后我们就会得到第一篇文章中实际显示的结果:


额外部分 ?: 一定要进行请求

还有一件我差点忘了写的小事。如果你要在 fragment 里面处理 WindowInsets,无论是隐式(通过使用 AppBarLayout 等)还是显式,你需要确保请求了一些 WindowInsets。只需要调通过 requestApplyInsets() 就能很容易做到:

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

你必须这样做是因为窗口只有在整个视图层级总体的系统 UI 可见性的值发生改变的时候才会自动分发 WindowInsets。 由于有时你的两个 fragment 可能提供完全相同的值,总体的值不会改变,因此系统将忽略这个“改变”。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 使用视图绑定替代 findViewById

    从 Android Studio 3.6 开始,视图绑定能够通过生成绑定对象来替代 findViewById,从而可以帮您简化代码、移除 bug,并且从 fi...

    Android 开发者
  • [译] 如何用 Android vitals 解决应用程序的质量问题

    对于一个应用开发者来说,没有比开心的用户更好的衡量成功的标准,而且最好是有很多这样的用户。实现这一目标的最佳方式是拥有一个人人都想用的优秀应用,不过我们所说的“...

    Android 开发者
  • 提示Android Q 对非 SDK 接口限制的更新 | Android 中文教学视频

    为了提高 Android 应用的兼容性,降低开发者们的维护成本,我们从 Android 9 Pie 开始引入非 SDK 接口限制。在这个视频中,我们介绍了非 ...

    Android 开发者
  • 如何手动实现一个 UIScrollView

    UIKit 坐标系每一个 View 都定义了他自己的坐标系,如下图所示,x 轴指向右方,y 轴指向下方:

    s_在路上
  • 如何在onCreate中获取View的高度和宽度

    在开发过程中经常需要获取到View的宽和高,可以通过View.getWidth()和View.getHeight()来得到宽高。然而新手们经常在onCreate...

    阳仔
  • 工业互联网可视化系统风格的抉择:线框模式之 3D 数据中心机房的实现

    3D 可视化,就是把复杂抽象的数据信息,以合适的视觉元素及视角去呈现,方便系统的展示、维护和管理。而在可视化系统的搭建选择上,所呈现的风格样式效果多种多样,各自...

    HT for Web
  • Leetcode上的解法看不懂?试着用动画的方式去辅助理解

    https://github.com/MisterBooo/LeetCodeAnimation

    Jerry Wang
  • 【OCP最新题库解析(052)--题47】Which three are true about dynamic

    该系列专题为2018年4月OCP-052考题变革后的最新题库。题库为小麦苗解答,若解答有不对之处,可留言,也可联系小麦苗进行修改。

    小麦苗DBA宝典
  • UIView中frame属性的内部实现

    UIView中用于表征视图在父视图中显示出来的位置和尺寸的属性是frame。 同时系统还提供另外两个属性center和bounds。其中center属性值描述视...

    欧阳大哥2013
  • 实战 Spine 骨骼动画,教你如何换皮游戏动画!

    武汉位游出品的《梦幻厨房》目前已经获得了14位伙伴的支持,5折活动今天下午16:30结束,即将恢复原价,需要的朋友请赶快了!

    张晓衡

扫码关注云+社区

领取腾讯云代金券