Material Motion for Android
Posted xiaoqiang_0719
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Material Motion for Android相关的知识,希望对你有一定的参考价值。
Building Beautiful Transitions with Material Motion for android
文章目录
Container transform(MaterialContainerTransform)
MaterialContainerTransform 是一个共享元素动画, 但和传统的Android共享元素动画不同,它不是围绕单一的共享内容(如图像)设计的。这里的共享元素指的是开始视图或ViewGroup容器将其大小和形状转换为结束视图或ViewGroup容器的大小和形状,可以理解为开始和结束容器视图是容器转换的“共享元素”。并且可以用在 Fragments, Activities 或者 Views.
使用场景
由于gif图太大只上传了一个中间过程的效果,可去下面《参考文献》官方描述中查看gif图
- 从一个卡片到详情页
- 从列表的item到详情页
- 从一个FloatingActionButton到详情页
- 从搜索框扩展到搜索详情
Transition between Fragments
共享元素的MaterialContainerTransform(无需定义退出动画,默认翻转进入动画)
1、Set MaterialCardView transitionName(email_item_layout、fragment_email)
email_item_layout:
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="@dimen/plane_00"
android:clickable="true"
android:focusable="true"
android:transitionName="@{@string/email_card_transition_name(email.id)}"
android:onClick="@{(view) -> listener.onEmailClicked(view, email)}"
android:onLongClick="@{(view) -> listener.onEmailLongPressed(email)}"
app:cardPreventCornerOverlap="false">
fragment_email:
<com.google.android.material.card.MaterialCardView
android:id="@+id/email_card_view"
android:transitionName="@string/email_card_detail_transition_name"
android:layout_width="match_parent"
android:layout_height="wrap_content">
2、HomeFragment
RecycleView点击事件:
override fun onEmailClicked(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()
}
val emailCardDetailTransitionName = getString(R.string.email_card_detail_transition_name)
val extras = FragmentNavigatorExtras(cardView to emailCardDetailTransitionName)
val directions = HomeFragmentDirections.actionHomeFragmentToEmailFragment(email.id)
findNavController().navigate(directions, extras)
}
**********************************
这里注意,理论上后面写好EmailFragment的共享动画之后应该是可以实现共享效果了
但是没有从RecycleView的item位置进行动画,退出之后也没有回到item的位置
还需要在onViewCreated执行如下操作,因为列表绘制完成,我没没机会配置我们的Transition
这相当于是在列表绘制过程中加入Transition动画,如果过使用RecycleView执行动画必须要加这两句话
**********************************
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
}
3、EmailFragment
跳转进入的界面:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedElementEnterTransition = MaterialContainerTransform().apply {
// Scope the transition to a view in the hierarchy so we know it will be added under
// the bottom app bar but over the elevation scale of the exiting HomeFragment.
drawingViewId = R.id.nav_host_fragment
duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
scrimColor = Color.TRANSPARENT
setAllContainerColors(requireContext().themeColor(R.attr.colorSurface))
}
}
explain:onEmailClicked为点击事件,其中exitTransition为HomeFragment的退出动画,reenterTransition为重新进入动画。extras 为跳转EmailFragment的共享元素写法。
EmailFragment中MaterialContainerTransform执行共享元素动画,默认情况不需要设置退出动画,Android过渡系统会在导航返回时自动反转回去
- drawingViewId =R.id.nav_host_fragment,运行的container transform与fragment 容器在同一级别,以确保它被绘制在底部应用程序栏和浮动动作按钮的下方。
- scrimColor 它控制绘制在动画容器后面的半透明阴影的颜色。默认情况下,它被设置为32%的黑色。在这里它被设置为透明,意味着没有阴影被绘制
- setAllContainerColors如果你的开始或结束视图本身没有绘制背景,设置这些背景填充颜色会很有用
从 startView 到 endView 的MaterialContainerTransform
例如从Activity的FloatingActionButton跳转到Fragment:
FloatingActionButton的点击事件:
private fun navigateToCompose() {
这里可以定义一个当前Fragment的退出动画,保持连贯的视觉效果
val directions = ComposeFragmentDirections.actionGlobalComposeFragment(currentEmailId)
findNavController(R.id.nav_host_fragment).navigate(directions)
}
跳转到的Fragment(startView、endView)默认无动画,需自己定义退出动画
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.run {
enterTransition = MaterialContainerTransform().apply {
startView = requireActivity().findViewById(R.id.fab)
endView = emailCardView
duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
scrimColor = Color.TRANSPARENT
containerColor = requireContext().themeColor(R.attr.colorSurface)
startContainerColor = requireContext().themeColor(R.attr.colorSecondary)
endContainerColor = requireContext().themeColor(R.attr.colorSurface)
}
returnTransition = Slide().apply {
duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
addTarget(R.id.email_card_view)
}
}
}
Shared axis (MaterialSharedAxis)
Shared axis 看上去像平移动画,官方展示的三个例子分别是,横向平移、纵向平移和Z轴平移。
使用场景
- 从左到右是一个流程的(注册流程)使用 x-axis
- 从上倒下一步一步走的流程使用 y-axis
- 一个 parent-child navigation transitions 使用 z-axis
代码示例
点击跳转,当前页面先执行exitTransition动画:
private fun navigateToSearch() {
// TODO: Set up MaterialSharedAxis transition as exit and reenter transitions.
currentNavigationFragment?.apply {
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true).apply {
duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
}
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false).apply {
duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
}
}
val directions = SearchFragmentDirections.actionGlobalSearchFragment()
findNavController(R.id.nav_host_fragment).navigate(directions)
}
进入页面执行enterTransition动画:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true).apply {
duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
}
returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false).apply {
duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
}
}
Fade through (MaterialFadeThrough)
Fade Through 本质上是一个透明度+缩放动画,官方的建议是用在两个关联性不强的界面的跳转中。
使用场景
- 点击底部导航栏切换
- 点击刷新列表
- 切换账户的场景
代码示例
private fun navigateToCompose() {
currentNavigationFragment?.apply {
exitTransition = MaterialFadeThrough().apply {
duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
}
reenterTransition = MaterialFadeThrough().apply {
duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
}
}
val directions = ComposeFragmentDirections.actionGlobalComposeFragment(currentEmailId)
findNavController(R.id.nav_host_fragment).navigate(directions)
}
Fade (MaterialFade)
Fade 动画和 Fade Through就动画本质而言,它们的确是一样的透明度+缩放动画,但是官方建议,如果发生在同一个界面,比如弹出Dialog、Menu等这类的弹框可以考虑这种动画。
使用场景
- A dialog
- A menu
- A snackbar
- A FAB
代码示例
跟MaterialFadeThrough使用和效果基本一致
exitTransition = MaterialFade().apply {
duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
}
enterTransition = MaterialFade().apply {
duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
}
官方文档中只提供了上述四种Motion的描述,查看源代码发现还有以下动画效果
MaterialElevationScale
效果上跟MaterialSharedAxis很相似
MaterialElevationScale(false),注意下参数这个boolen值,它代表是视觉上是扩张还是收缩
代码示例
exitTransition是一个渐隐缩小后退的动画
reenterTransition是一个渐隐放大的动画
private fun navigateToCompose() {
currentNavigationFragment?.apply {
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()
}
}
val directions = ComposeFragmentDirections.actionGlobalComposeFragment(currentEmailId)
findNavController(R.id.nav_host_fragment).navigate(directions)
}
参考文献
material-components-android,github官方介绍地址
https://codelabs.developers.google.com/codelabs/material-motion-android
以上是关于Material Motion for Android的主要内容,如果未能解决你的问题,请参考以下文章
Motion Layout OnSwipe 禁止点击 YoutubePlayer (YoutubePlayer API)