目录
- 早期的转场
- Material Design 转场动画
- Material Motion 动画
- MaterialContainerTransform
- Shared axis
- Fade Through
- Fade
- 总结
早期的转场
最初,两个Activity之间的切换的过度动画,都是用overridePendingTransition。它只支持平移、缩放、透明度、旋转四种动画效果。
比如我们写个平移跳转动画,实现是这样的。首先,我们在资源文件anim下新建两个动画资源文件 enter_anim.xml 和 quit_anim.xml,分别表示进入和退出的动画。
enter_anim.xml
<set xmlns:android="http://schemas.android.com/apk/res/android" | |
android:duration=""> | |
<translate | |
android:fromXDelta="%p" | |
android:toXDelta="" /> | |
</set> |
quit_anim.xml
<set xmlns:android="http://schemas.android.com/apk/res/android" | |
android:duration=""> | |
<translate | |
android:fromXDelta="" | |
android:toXDelta="-%p" /> | |
</set> |
然后在界面跳转的时候,调用overridePendingTransition就行啦
startActivity(Intent(this, TestActivity::class.java)) | |
overridePendingTransition(R.anim.enter_anim, R.anim.quit_anim) |
Material Design 转场动画
Android 5.0之后使用,具有三种转场动画效果:
- Explode:爆炸式,将视图移入场景中心或从中移出
- Fade:淡入淡出式,通过更改透明度来添加和移出视图
- Slide:滑动式,从场景的一个边缘移入或移出视图
Materia转场有两种启用方式,一种是在theme中设置 windowActivityTransitions
<style name="Theme.AndroidApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> | |
<item name="android:windowActivityTransitions">true</item> | |
</style> |
一种是通过代码开启
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
下面,通过一个简单的示例,来瞧瞧具体是怎么去使用的
在MainActivity中设置退出动画,然后通过点击事件去进行跳转到TestActivity
class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding | |
override fun onCreate(savedInstanceState: Bundle?) { | |
window.apply { | |
requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) | |
exitTransition = Fade() //退出动画 | |
} | |
super.onCreate(savedInstanceState) | |
binding = ActivityMainBinding.inflate(layoutInflater) | |
setContentView(binding.root) | |
binding.go.setOnClickListener { | |
materialGo() | |
} | |
} | |
private fun materialGo() { | |
val intent = Intent(this, TestActivity::class.java) | |
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle()) | |
} | |
} |
在TestActivity中设置进入动画,这样淡入淡出式就完成了
class TestActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { | |
window.run { | |
requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) | |
enterTransition = Fade() //进入动画 | |
} | |
super.onCreate(savedInstanceState) | |
val binding = ActivityTestBinding.inflate(layoutInflater) | |
setContentView(binding.root) | |
} | |
} |
共享元素
如下所示,共享元素的效果很有趣,看着就像是图片从一个页面放大到另一个页面
共享元素过渡方式有四种
- changeBounds:为目标视图布局边界的变化添加动画效果
- changeClipBounds:为目标视图裁剪边界的变化添加动画效果
- changeTransform:为目标视图缩放和旋转方面的变化添加动画效果
- changeImageTransform:为目标图片尺寸和缩放方面的变化添加动画效果
首先需要设置transitionName,告诉系统,哪个View需要做动画,然后进行Activity的跳转
binding.image.setOnClickListener { | |
binding.image.transitionName = "shared_elements" | |
val options = | |
ActivityOptions.makeSceneTransitionAnimation(this, binding.image, "shared_elements") | |
val intent = Intent(this, TestActivity::class.java) | |
startActivity(intent, options.toBundle()) | |
} |
在目标Activity中给View设置transitionName,也可添加一系列的过渡效果
class TestActivity : AppCompatActivity() { | |
override fun onCreate(savedInstanceState: Bundle?) { val transitionSet = TransitionSet().apply { | |
addTransition(ChangeBounds()) | |
addTransition(ChangeClipBounds()) | |
addTransition(ChangeTransform()) | |
addTransition(ChangeImageTransform()) | |
} | |
with(window) { | |
requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) | |
sharedElementEnterTransition = transitionSet | |
sharedElementExitTransition = transitionSet | |
} | |
super.onCreate(savedInstanceState) | |
val binding = ActivityTestBinding.inflate(layoutInflater) | |
setContentView(binding.root) | |
binding.picture.transitionName = "shared_elements" | |
} | |
} |
如果要使用多个共享元素,可使用Pair,例如
val options = ActivityOptionsCompat.makeSceneTransitionAnimation( | |
this, Pair(binding.imageView, "imageView"), | |
Pair(binding.signature, "signature") | |
) |
Material Motion 动画
它提供了四种模式,可以根据需求灵活选用,分别是:
MaterialContainerTransform
用于包含容器的界面元素之间的过渡,通过将一个UI元素无缝转换为另一个UI元素,在两个不同的界面元素之间创造可视化的连接,跟之前共享元素动画最大的不同点在于它可以是一个 ViewGroup,也可以是一个 View。
下面,我们通过一个简单的例子来感受一下效果,item是个LinearLayout,用于点击跳转。
class MainActivity : AppCompatActivity() { | |
private lateinit var binding: ActivityMainBinding | |
override fun onCreate(savedInstanceState: Bundle?) { //.启用转场动画 | |
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) | |
setExitSharedElementCallback(MaterialContainerTransformSharedElementCallback()) | |
super.onCreate(savedInstanceState) | |
binding = ActivityMainBinding.inflate(layoutInflater) | |
setContentView(binding.root) | |
initView() | |
} | |
private fun initView() { //.设置transitionName | |
binding.item.transitionName = "share" | |
binding.item.setOnClickListener { | |
//.进行页面跳转 | |
startActivity( | |
Intent(this, TestActivity::class.java), | |
ActivityOptions.makeSceneTransitionAnimation(this, it, "share").toBundle() | |
) | |
} | |
} | |
} |
在目标Activity中,只显示了几行文本,整体用LinearLayout做容器,id为display。
这里为了更加清楚的看到动画的转换过程,我将动画的执行时间duration设置为2秒,实际开发中,不能搞这么久哦。
class TestActivity : AppCompatActivity() { | |
override fun onCreate(savedInstanceState: Bundle?) { //.启用转场动画 | |
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) | |
setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback()) | |
super.onCreate(savedInstanceState) | |
val binding = ActivityTestBinding.inflate(layoutInflater) | |
setContentView(binding.root) | |
//.设置transitionName | |
binding.display.transitionName = "share" | |
//.设置具体的动画 | |
window.sharedElementEnterTransition = MaterialContainerTransform().apply { | |
addTarget(binding.display) | |
scrimColor = Color.TRANSPARENT | |
setAllContainerColors(Color.WHITE) | |
duration =L | |
} | |
window.sharedElementExitTransition = MaterialContainerTransform().apply { | |
addTarget(binding.display) | |
scrimColor = Color.TRANSPARENT | |
setAllContainerColors(Color.WHITE) | |
duration =L | |
} | |
} | |
} |
MaterialContainerTransform有两个属性需要注意下:
- scrimColor:用于控制在动画容器后面绘制的半透明阴影的颜色。默认情况下,该元素会设为 32% 黑色。这里将其设为透明,这意味着不会绘制任何纱罩。
- setAllContainerColors:在两个视图之间添加动画时,它会在画布上绘制 3 个容器: 后台容器 ,起始视图的容器和结束视图的容器。对于这 3 个容器,我们都可以为其填充颜色,并将其默认设为透明。如果您的起始视图或结束视图本身没有绘制背景,导致在动画播放期间其他元素显示在其下层,那么设置这些背景填充颜色可能会很有用。我们可以使用 setAllContainerColors 来统一颜色,确保我们不会遇到任何视觉问题。
Shared axis
用于具有空间或导航关系的界面元素之间的过渡,让元素在转换时共用 x 轴、y 轴或 z 轴,用以强调元素间的关系。
在MainActivity中设置退出动画
//启用转场动画 | |
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) | |
window.exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true).apply { | |
//指定转换视图 | |
addTarget(R.id.list) | |
//转换不包含导航和状态栏 | |
excludeTarget(android.R.id.statusBarBackground, true) | |
excludeTarget(android.R.id.navigationBarBackground, true) | |
} |
进行跳转
startActivity( | |
Intent(this, TestActivity::class.java), | |
ActivityOptions.makeSceneTransitionAnimation(this).toBundle() | |
) |
在TestActivity中设置进入动画
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) | |
window.enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true).apply { | |
addTarget(R.id.display) | |
excludeTarget(android.R.id.statusBarBackground, true) | |
excludeTarget(android.R.id.navigationBarBackground, true) | |
} |
然后,我们来看一下转场效果
Fade Through
用于彼此之间没有密切关系的界面元素之间的过渡,使用依序淡出和淡入的效果,并会对转入的元素进行缩放,用法跟MaterialSharedAxis差不多。
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) | |
window.enterTransition = MaterialFadeThrough().apply { | |
addTarget(R.id.display) | |
excludeTarget(android.R.id.statusBarBackground, true) | |
excludeTarget(android.R.id.navigationBarBackground, true) | |
} |
Fade
用于进入或退出屏幕画面范围的界面元素。似乎效果上Fade 和上面的 Fade Through 差不多,其实确实都是透明度+缩放动画,但是官方建议,如果发生在同一个界面,例如在屏幕中心淡出的对话框。
这里为了更清楚的看清转场过程,将动画的执行时间duration设置为1000,实际开发中应设置小点,或者不用去设置
val materialFade = MaterialFade().apply { | |
duration =L | |
} | |
TransitionManager.beginDelayedTransition(binding.list, materialFade) | |
binding.detail.visibility = View.VISIBLE |
总结
在Android 转场动画的发展中,早期的转场支持平移、缩放、透明度、旋转四种基础动画效果,随后,出现了Material Design 转场动画,给我们带来了共享元素动画效果,最后Material Motion 动画封装了四种动画,使得转场效果的实现更加容易。我觉得,页面之间的转场效果,可以赋予应用活力,丰富用户的使用体验,升华应用交互的灵魂,常言道,好看的皮囊千篇一律,有趣的灵魂万里挑一。