
有一种使您的应用程序美观和生动的良好实践,如今,有很多工具和方法可以实现这一目标。 其中之一是共享元素转换。
在本文中,我将介绍一些错误,这些错误花费了我很多时间。 如果您决定在应用程序中使用Fragments实现这种转换,我将展示如何避免它们。
开始吧
在制作动画之前,我已经阅读了数十篇文章,但其中大多数都是关于“活动过渡”的。 但是,我遇到了有关Fragments的非常好的文章,并且我想对如何创建Shared Element Transition进行一些回顾。
以下是创建动画的主要步骤:
- 启用
setReorderingAllowed(true)
。 它允许对生命周期方法进行重新排序,动画将正确显示。 - 调用
addSharedElement()
并添加将在屏幕之间共享的视图 - 在转换中的每个视图上添加唯一的
android:transitionName
transitionName - 在目标片段中添加
sharedElementEnterTransition/sharedElementReturnTransition
。 可选:为了获得更好的效果,我们还可以设置enterTransition/exitTransition
。 - 添加
postponeEnterTransition/startPostponedEnterTransition
定义加载数据和准备绘制UI的时刻
看起来足以制作动画并使您的设计师和用户满意。 但是总会有一些意外。 让我们看看如果采取上面列出的步骤将会得到什么:
那不是我们所期望的。 让我们弄清楚。
错误#1。 静态transitionNames(浪费了半天)
如前所述,我们的视图应具有唯一的过渡名称-否则过渡框架将无法识别哪个View参与了过渡,而没有过渡时它将成为过渡视图。 那么哪里有问题呢?
事情是RecyclerView。 就是这样
如果有RecyclerView,并且动画视图是RecyclerView项的一部分,则应动态构建和设置
transitionName
(忘记XML)。 此外,即使第二个片段没有RecyclerView,也应在两个片段中都执行此操作。
因此解决方法是:
val transitionNameImage = context.getString(R.string.transition_image, title)
您可能已经注意到,我将“ title”作为获取唯一名称的参数。 最好使用域模型而不是例如屏幕上的项目位置,因为不需要将这些名称作为参数传递给Bundle并从第二个Fragment解析它们。
错误2。 不考虑父片段(浪费了1.5天)
我知道,您可能会问:“为什么来?” 但是,当我阅读有关共享动画的文章时,没有人考虑过复杂片段层次结构中的示例。 这就是为什么您有时可能不注意它的原因。
因此,我的第一个片段是该片段的容器的子片段,也难怪
postponeEnterTransition()/startPostponedEnterTransition()
无效。
在这里 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) parentFragment?.also { parentFragment -> NewsTransitioner.setupFirstFragment(parentFragment) parentFragment.postponeEnterTransition() }
您需要在此处从父片段调用这些方法。
错误3。 低估滑行(浪费了2天)
“好吧,我一直在学习如何进行共享转换,何时调用有关生命周期和加载的必需方法。 这次它将成功!”
好吧,我错了。 这也许是我遇到的最棘手的错误。 让我们来看看到目前为止的情况:
您可能会注意到输入转换时出现了一个奇怪的故障。 当它开始时,图像已经改变了矩阵,然后仅移动到最终位置。
我不想在这里描述整个调查。 长话短说,我很幸运偶然发现
一篇不错的文章 。
我在哪里找到解决方案。 这是:
“我们有这个小故障,因为Glide尝试优化图像加载。 默认情况下,Glide会调整图像大小和修剪以匹配目标视图。”
为了解决这个问题,我没有开玩笑地将这样的一行代码添加到初始化Glide的链中:
Glide .with(target) .load(url) .apply( RequestOptions().dontTransform()
因此,如果图像涉及共享转换,则应禁用它们的任何Glide转换。
错误#4。 错误地管理postPostponeTransition()
老实说,这不是一个错误,但我仍然认为应该提一下。
在管理
postPostponeTransition()
和
startPostponedEnterTransition()
方法时,您应该选择合适的时机。 现在是已经绘制UI的时候。
在调用方法之前,我们应该知道两点:
- 一方面,当加载具有过渡的图像并准备就绪时
- 另一方面,对视图层次结构进行了测量和布局
对于图像,通常我们使用Glide,它具有出色的监听器:
RequestListener<Drawable> { override fun onLoadFailed( e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean ): Boolean { startPostponedEnterTransition() return false } override fun onResourceReady( resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean ): Boolean { startPostponedEnterTransition() return false } }
请注意,在两种情况下,我们都调用
startPostponedEnterTransition()
:成功和错误。 这很重要,因为如果不这样做,您的应用程序将冻结。
如果过渡时没有任何图像,则可以在根布局上使用
doOnPreDraw()
扩展方法,并在该位置执行
startPostponedEnterTransition()
。
奖励 :说到RecyclerView,仅添加图像的侦听器是不够的。 我们应该保留从其开始过渡的RecyclerView的项目位置。 当用户返回上一个屏幕时,我们应该将图像加载位置与侦听器上的保留位置进行比较,并仅在它们匹配时才开始过渡。
放在一起
在本文中,我展示了在实现带有片段的共享过渡以及处理片段的方法时可能遇到的一些陷阱。
简而言之,它们是:
- 记住片段层次结构(不要忘记父片段)
- 如果使用RecyclerView,请始终动态构建转换名称(源+目标)
- 禁用任何Glide转换
- 关于您的逻辑,请正确调用
postPostponeTransition()
和startPostponedEnterTransition()
。
感谢您的阅读,下次见!
PS:如果您想知道如何对图像角进行动画处理,请
在此处的代码中添加到其余的共享动画过渡中。
例子 fragment.sharedElementEnterTransition = TransitionSet().apply { addTransition(ChangeImageTransform()) addTransition(ChangeBounds()) addTransition(ChangeTransform()) addTransition(ChangeOutlineRadiusTransition(animationRadiusData.startRadius, animationRadiusData.endRadius) ) }