image
近期发布的Material动效系统是MDC-Android库(v1.2.0)的一部分,它将常用的过渡效果归纳为一组简单的模式,提供更流畅更加容易理解的用户体验。Material动效目前包括四种过渡效果:
容器转换(Containertransform)
共享轴(Sharedaxis)
淡入淡出(Fadethrough)
褪色(Fade)
我们已经在Android平台和AndroidX过渡系统实现了以上过渡效果,以便在Activity、Fragment和View之间切换时轻松使用。
本文会介绍上面每种模式,并解释如何将这些模式应用到您的应用中。我将会通过在示例应用Reply(一个简单易用的邮件客户端)中实现对应的效果来说明每个步骤。Reply应用的三个操作流程会使用到这些过渡动效:打开邮件、打开搜索页面、切换信箱。
如果您不满足于上手介绍,更希望深入源码,请参阅Material动效Codelab,按步骤上手实践这项技术,Codelab也提供了在Android上使用这些过渡效果的其他信息。
容器转换:打开邮件容器转换是过渡的主角,容器转换用在将一个元素转换为另一个元素。什么意思呢?例如示例的一个列表展开成为了详情页、FAB变形为工具栏,或chip扩展为了浮动的卡片。在每个场景中都有一个组件变换为另一个组件,并以动画方式切换"内部"内容,同时维护一个共享的"外部"容器。使用容器变换,实现视图间的动画切换,可帮助增强它们之间的联系,并维持一个用户的导航上下文。
在Reply示例中,我们在展示邮件列表的Fragment(HomeFragment)和邮件详情Fragment(EmailFragment)间添加了容器转换。如果您熟悉Android共享元素过渡,它与容器转换的设置非常相似。
首先,确定两个共享元素的视图,并为每一个视图添加过渡名称。第一个是单个邮件列表项的卡片,我们将使用数据绑定,来确保每一个列表项都有唯一的过渡名称。
android:transitionName="{string/email_card_transition_name(email.id)}"第二个是EmailFragment内部的全屏卡片组件,这个组件可以设置一个静态的过渡名称,因为在视图层级中只有这一个试图。注意,两个共享元素不需要使用相同的过渡名称。
这两个视图会被我们的容器转换使用。工作原理是:它们都会被放在一个drawable内部,此drawable的边界会被裁剪到"容器"中,而"容器"会将自己的形状通过动画从一个列表项转换为详情页。在过渡过程中,通过传入页面在传出屏幕上淡入,容器的内容(列表项和详情页)发生了交换。
现在我们已经标记了共享元素的视图,接下来就可以创建目的地Fragment的sharedElementEnterTransition,并将其设置给一个MaterialContainerTransform的实例。默认情况下,从详情页面返回时,这个sharedElementEnterTransition会自动反转并播放。
sharedElementEnterTransition=MaterialContainerTransform().apply{//drawingViewId是视图的id,在其上方,容器变换将在z轴空间进行drawingViewId=R.id.nav_host_fragmentduration=resources.getInteger(R.integer.reply_motion_duration_large).toLong()//由于我们也想将列表页面通过动画转换出视图,所以将scrimColor设置为透明scrimColor=Color.TRANSPARENTsetAllContainerColors(requireContext().themeColor(R.attr.colorSurface))}有关参数的详细信息,请参阅动效文档当一封邮件被点击时,我们所有需要做的就是为Fragment事务提供开始视图和结束视图过渡名称之间的映射。有了这些信息,邮箱详情Fragment共享元素过渡就可以使用我们提供的MaterialContinaerTransform找到并在两个视图之间进行动画切换。
overridefunonEmailClicked(cardView:View,email:Email){exitTransition=MaterialElevationScale(false).apply{duration=resources.getInteger(R.integer.reply_motion_duration_large).toLong()}reenterTransition=MaterialElevationScale(true).apply{duration=resources.getInteger(R.integer.reply_motion_duration_large).toLong()}valemailCardDetailTransitionName=getString(R.string.email_card_detail_transition_name)valextras=FragmentNavigatorExtras(cardViewtoemailCardDetailTransitionName)valdirections=HomeFragmentDirections.actionHomeFragmentToEmailFragment(email.id)findNavController().navigate(directions,extras)}在上面的代码片段中,我们也为传出页邮件列表Fragment设置了exit和reenter的过渡效果。Material组件提供了两个过渡辅助:Hold和MaterialElevationScale,以平滑地为将要被替换的Fragment设置动画。除了褪色(Fade),MaterialElevationScale还会在邮件列表页退出时,对其进行缩放,并在重新进入邮件列表时缩放回来。Hold仅仅是简单地保留邮件列表。如果没有设置退出时的过渡,我们的邮件列表会被立刻删除并从视图中消失。如果我们在这个时候运行代码,从详情页导航返回到邮件列表页,则返回过渡不会执行。这是因为当过渡开始时,邮件列表的适配器还未被填充,过渡系统找不到与过渡名称对应的两个视图。幸运的是,有两个简单方法可供我们使用:postponeEnterTransition和startPostponedEnterTransition。这两个方法允许我们延迟过渡,直到我们知道我们的共享元素已经被布局,并且可以被过渡系统发现。在Reply应用中,我们可以使用以下代码延迟过渡,直到我们确定RecyclerView适配器已被填充,列表项已和过渡名称绑定:
postponeEnterTransition()view.doOnPreDraw{startPostponedEnterTransition()}在您自己的应用中,您可能需要尝试这两种方法,以根据您填充UI的方式和时间,来找到合适的时间开始延迟过渡。如果您发现您的返回动画没有执行,可能是在共享元素就绪之前开始了过渡。
接下来进入我们的搜索页面。
共享轴:打开搜索页面共享轴模式用于有空间和导航关系的UI元素之间的过渡。在Reply应用中,打开搜索页面会将用户带到邮件列表顶部的新页面。为了介绍这个三维模型,我们可以在邮件列表(HomeFragment)和搜索页面(SearchFragment)之间使用共享z轴过渡。
共享轴过度会在操作两个目标的同时创建最终的、编排过的过渡效果。这意味着"成对"的过渡会一起运行去创建连续的定向的动画。对Fragment来说,这成对的过渡包括:FragmentA的exitTransition和FragmentB的enterTransition
FragmentA的reenterTransitionreturnTransition
MaterialSharedAxis是实现了共享轴模式的类,它接收forward属性来控制方向性的概念。在每一个过渡配对中,forward必须被设置为相同的值,以便正确地协调这对动画。
如需了解更多关于共享轴方向性的详细信息,请查阅动效文档。在Reply应用中,这是我们为当前的Fragment(HomeFragment)建立退出和重入过渡的方法。
currentNavigationFragment?.apply{exitTransition=MaterialSharedAxis(MaterialSharedAxis.Z,/*forward=*/true).apply{duration=resources.getInteger(R.integer.reply_motion_duration_large).toLong()}reenterTransition=MaterialSharedAxis(MaterialSharedAxis.Z,/*forward=*/false).apply{duration=resources.getInteger(R.integer.reply_motion_duration_large).toLong()}}在我们目的fragment(SearchFragment)中,我们建立进入和返回的过渡。
enterTransition=MaterialSharedAxis(MaterialSharedAxis.Z,/*forward=*/true).apply{duration=resources.getInteger(R.integer.reply_motion_duration_large).toLong()}returnTransition=MaterialSharedAxis(MaterialSharedAxis.Z,/*forward=*/false).apply{duration=resources.getInteger(R.integer.reply_motion_duration_large).toLong()}注意:当前Fragment的退出过渡和搜索Fragment的进入过度使用相同的forward值-true,当前Fragment的重入过渡和搜索Fragment的返回过渡也是如此。接下来,默认情况下,过度会在场景根层次结构内的所有子视图上运行,这意味着一个共享轴过度会应用于邮件列表上的每一封邮件以及搜索页面的每一个子视图。如果您想要"传播"或者"错开"动画,这是一个非常好的功能,但是由于我们需要对每个Fragment的根作为整体进行动画处理,我们需要在邮件列表的RecyclerView和我们的搜索页面的根viewgroup设置android:transitionGroup="true"。
这样,我们就在进出搜索页面时有了一个漂亮的共享z轴过渡!共享轴是一个非常灵活的过渡,可以应用于许多不同的场景,从页面过渡到智能回复选择,再到进入或者垂直的步骤流程。您已经配置好了设置,还可以尝试使用MaterialSharedAxis的axis参数来了解其他轴动画是什么样子。
淡入淡出:切换邮箱我们要介绍的最后一个模式是淡入淡出模式。淡入淡出可用于在没有强关系的UI元素间过渡。当在两个信箱之间过渡时,我们不希望用户认为他们已经发送的邮件和他们的收件箱在导航上相关。由于每个信箱是一个顶级的目的地,淡入淡出是一个合适的选择。在Reply应用中,我们将用不同的电子邮件列表(带有新参数的HomeFragment)替换电子邮件列表(HomeFragment)。
由于MaterialFadeThrough没有方向性,所以设置起来更加简单。我们只需要为传出Fragment设置一个退出过渡,为传入Fragment设置一个进入过渡。
currentNavigationFragment?.apply{exitTransition=MaterialFadeThrough().apply{duration=resources.getInteger(R.integer.reply_motion_duration_large).toLong()}}enterTransition=MaterialFadeThrough().apply{duration=resources.getInteger(R.integer.reply_motion_duration_large).toLong()}}在上设置的需求同样适用于这里,但是我们已经在共享轴配置的步骤中解决了这个问题。
以上就是淡入淡出过渡!您可以在自己项目有趣的地方来使用淡入淡出模式,比如:底部导航栏的切换、列表项的交换,或替换一个工具栏菜单。
一往无前!本文简要介绍了Android的Material动效系统。通过使用该系统所提供的模式,您可以在自定义动效时,做很多事情,使动效成为品牌体验的一部分。本文我们看到了Fragment的过渡,但动效系统也可用于Activity甚至View间的过渡。查看完整的动效规范文档,获得更多启发,以便思考哪些地方可提高您应用的核心体验,或在一些小的地方增加额外的乐趣。
1
继续学习,请查看以下其他资源:
Material动效开发文档:您可以在MaterialAndroid动效文档找到许多关于在Activity和View之间进行动画的自定义选项和建议。
:一个完整的分步的开发者教程,内容涉及如何在Reply应用中添加Material动效。
AndroidGoogle云盘:您可以在AndroidGoogle云盘应用中看到正在运行的动效系统。点击文件夹、打开搜索、在底部导航间切换,这些都用到了MDC-Android的过渡效果。