本文是对 Material Motion过渡模式的使用进行总结, 适用于已经明白过渡模式的使用但忘记具体代码实现的开发者. 本文用例基于 androidx
和 kotlin
. 详细完整教程请查阅: material-motion-android.
material-motion-android 是MDC-Android库中的一组过渡模式; 其中主要包含四种过渡模式:
Container Transform
: 包含 container
的UI元素之间的转换 在两个不同的UI元素之间创建可见连接, 使一个UI元素无缝过渡到另一个UI元素.Shared Axis
: 具有空间或导航关系的UI元素之间的过渡; 在x, y或z轴上使用共享变换来加强元素之间的关系.Fade Through
: 在彼此之间没有密切关系的UI元素之间进行过渡; 使用顺序淡入和淡入, 以及传入元素的比例.Fade
: 用于在屏幕范围内进入或退出的UI元素.MDC-Android库在AndroidX Transition库(androidx.transition
)和 Android Transition Framework(android.transition
)的基础上,为这些模式提供了转换类 :
AndroidX (androidx.transition
)
com.google.android.material.transition
包下;Fragments
和 Views
, 不支持 Activityies
和 Windows
;implementation 'com.google.android.material:material:1.2.1'
Framework (android.transition
)
com.google.android.material.transition.platform
包下;Fragments
| Views
| Activityies
| Windows
;本文使用 AndroidX Transition
库; Fragments
由 navigation
库导航.
Container Transform
过渡模式MaterialContainerTransform
;Container
transitionName
标记, 使过渡系统在不同布局获取两个控件;
android:transitionName
;ViewCompat
#setTransitionName
方法;RecyclerView
中的 item
布局中的控件, transitionName
不能相同;RecyclerView
item
跳转 Fragment
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)
Fragment
的 onCreate
中添加 sharedElementEnterTransition (Fragmen.setSharedElementEnterTransition())
,完成这一步便实现了跳转新 Fragment
的过渡sharedElementEnterTransition = MaterialContainerTransform().apply {
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))
}
sharedElementEnterTransition
以后, 返回之前页面时过渡与 sharedElementEnterTransition
过渡相反; 但现在不起作用是因为, 上一个页面列表并未填充到 RecyclerView
中; 解决方法如下:// 在上一个页面的 onViewCreated 方法中添加以下代码
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
// 在点击跳转其他页面之前添加以下代码
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()
}
RecyclerView
中标记 transition group
android:transitionGroup="true"
API 21 及以上直接使用 android:transitionGroup="true"
, 兼容 API 21 以下版本使用 ViewGroupCompat#setTransitionGroup
方法;Container
中
onViewCreated
方法中添加以下代码, (注意: Slide
包为 androidx.transition
, 如使用 android.transition
会崩溃)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_medium).toLong()
addTarget(R.id.email_card_view)
}
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()
}
}
chip
和 recipientCardView
)过渡, endView中包含 container
recipientCardView
之前, 添加以下代码, 其中 addTarget
确保只为传入控件提供过渡效果;val transform = MaterialContainerTransform().apply {
startView = chip
endView = binding.recipientCardView
scrimColor = Color.TRANSPARENT
endElevation = requireContext().resources.getDimension(
R.dimen.email_recipient_card_popup_elevation_compat
)
addTarget(binding.recipientCardView)
}
TransitionManager.beginDelayedTransition(binding.composeConstraintLayout, transform)
chip
之前, 添加以下代码, 其中 addTarget
确保只为传入控件提供过渡效果;val transform = MaterialContainerTransform().apply {
startView = binding.recipientCardView
endView = chip
scrimColor = Color.TRANSPARENT
startElevation = requireContext().resources.getDimension(
R.dimen.email_recipient_card_popup_elevation_compat
)
addTarget(chip)
}
TransitionManager.beginDelayedTransition(binding.composeConstraintLayout, transform)
Shared Axis
过渡模式MaterialSharedAxis
;Container
时使用;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()
}
}
onCreate
方法添加以下代码 (过渡方向与之前页面对应)// 进入时放大过渡
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()
}
MaterialSharedAxis
过渡用于整个屏幕, 在根布局控件添加 transition group
标记android:transitionGroup="true"
API 21 及以上直接使用 android:transitionGroup="true"
, 兼容 API 21 以下版本使用 ViewGroupCompat#setTransitionGroup
方法;Fade Through
过渡模式MaterialFadeThrough
;currentNavigationFragment?.apply {
exitTransition = MaterialFadeThrough().apply {
duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
}
}
onCreate
方法中添加 enterTransition
enterTransition = MaterialFadeThrough().apply {
duration = resources.getInteger(R.integer.reply_motion_duration_large).toLong()
}
navigation
重复启动相同 HomeFragment
, HomeFrament
在 navigation_graph.xlm
中设置为全局操作, 并出栈当前Fragment, 相当于设置了 singleTop=true
,因此不需要处理返回或重新进入过渡; 以下为当前 HomeFragment
在 navigation_graph
中的操作:<action
android:id="@+id/action_global_homeFragment"
app:destination="@+id/homeFragment"
app:launchSingleTop="true"
app:popUpTo="@+id/navigation_graph"
app:popUpToInclusive="true"/>
参考文章: material-motion-android