Android 动画总结(1) - 概述
Android 动画总结(2) - 帧动画
Android 动画总结(3) - 补间动画
Android 动画总结(4) - 插值器
Android 动画总结(5) - 属性动画
Android 动画总结(6) - 估值器
Android 动画总结(7) - ViewGroup 子元素间的动画
Android 动画总结(9) - 过渡动画
对于 Activity,在 startActivity 或 finish 后调用
overridePendingTransition(R.anim.activity_in, R.anim.activity_out)
对于 Fragment:
supportFragmentManager.beginTransaction().setCustomAnimations(R.anim.fragment_enter, R.anim.fragment_exit)
ActivityOptions
从 Android 5.0 之后,可以用 ActivityOptions 来实现,ActivityOptionsCompat 是 support v4 的兼容实现,可以支持到 4.1(SDK 16),它有几个 make 开头的方法
- makeCustomAnimation(Context context, int enterResId, int exitResId)
- makeScaleUpAnimation(View source, int startX, int startY, int startWidth, int startHeight)
- makeThumbnailScaleUpAnimation(View source, Bitmap thumbnail, int startX, int startY)
- makeClipRevealAnimation(View source, int startX, int startY, int width, int height)
- makeSceneTransitionAnimation(Activity activity, View sharedElement, String sharedElementName)
- makeSceneTransitionAnimation(Activity activity, Pair
... sharedElements)
makeCustomAnimation
custom.onClick {
val compat = ActivityOptionsCompat.makeCustomAnimation(ctx, R.anim.activity_in, R.anim.activity_out)
start(it!!, compat)
}
private fun start(view: View, compat: ActivityOptionsCompat) {
val intent = Intent(ctx, OptionAfterActivity::class.java)
intent.putExtra("from", (view as Button).text)
// SDK 16 以下会忽略 compat.toBundle()
ActivityCompat.startActivity(ctx, intent, compat.toBundle())
}
最普通的,效果和过去的 overridePendingTransition 一样。
OptionAfterActivity 的布局就只有一个 TextView
class OptionAfterActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_option_after)
// 接收前一个方法传过来的名字显示出来
tv.text = intent.extras["from"].toString()
}
override fun onBackPressed() {
ActivityCompat.finishAfterTransition(this)
}
}
发现 ActivityCompat.finishAfterTransition(this)
并没什么用。看源码
public static void finishAfterTransition(Activity activity) {
if (Build.VERSION.SDK_INT >= 21) {
activity.finishAfterTransition();
} else {
activity.finish();
}
}
好吧,ActivityCompat.startActivity
是 SDK >= 16
就生效,这退出的必须 >= 21
才行。再继续往下走
public void finishAfterTransition() {
if (!mActivityTransitionState.startExitBackTransition(this)) {
finish();
}
}
public boolean startExitBackTransition(final Activity activity) {
if (mEnteringNames == null || mCalledExitCoordinator != null) {
return false;
} else {
if (!mHasExited) {
// 能判断对这里才真正执行页面返回的动画
}
return true;
}
}
而这个 mEnteringNames 的定义是:
The shared elements that the calling Activity has said that they transferred to this
很可惜,此时的 mEnteringNames 是 null,直接返回 false 调用 finish 了。下面会讲到什么是 shared elements
。
makeScaleUpAnimation
参照 Activity 上的某个 View,新 Activity 从指定大小放大到最大显示。
动画速度太快了,也没找到可以控制时间的地方,查了许多资料,包括看源码注释,其实还不是很明白这个 View 到底有没有放大。
使用的一个场景是可能点击一个小 View,然后第二个页面某个位置显示的放大版的,这样看着好像是点击放大到另一个页面似的。
scaleUp.onClick {
val compat = ActivityOptionsCompat.makeScaleUpAnimation(image, image.width / 2, image.height / 2, 0, 0)
start(it!!, compat)
}
看下它的 5 个参数:
- View source - 参照物
- int startX - 相对于 source,新 Activity 开始的位置
- int startY - 同 startX,只不过这是 Y 轴方向上的
- int startWidth - 第二个 Activity 在做放大动画前一开始的初始宽度
- int startHeight - 这当然就是初始高度了
makeThumbnailScaleUpAnimation
和 makeScaleUpAnimation 的区别是,不再是放大页面上的一个 View,而是指定一张图,在转场时,放大这张图片。不过也许是太快了,根本看不见,也不知理解是否正确。
thumbnailScaleUp.onClick {
val compat = ActivityOptionsCompat.makeThumbnailScaleUpAnimation(text, BitmapFactory.decodeResource(resources, R.drawable.timg), 0, 0)
start(it!!, compat)
}
参数:
- View source - 图片放大的参照物
- Bitmap thumbnail - 要放大的图片
- int startX - 相对于参照物 source,这张图片开始放大的 X 轴位置
- int startY - 图片开始放大相对于 source 的 Y 轴位置
makeClipRevealAnimation
说是一个点圆形渐变到全部显示,参数含义和 makeScaleUpAnimation 的一样。也是太快,没看出什么特别的。
clipReveal.onClick {
val compat = ActivityOptionsCompat.makeClipRevealAnimation(text, text.getWidth() / 2, text.getHeight() / 2, 0, 0)
start(it!!, compat)
}
makeSceneTransitionAnimation 单个 View
Scene 就是场景,两个 Activity 中的某些 View 协同完成过渡动画。
在两个 Activity 的布局文件中,要协同做动画的 View 要有一个属性 android:transitionName
并将值设为一样的。
第一个 Activity 的 xml
...
...
要跳转的新 Activity 的 xml
两个要协同的 ImageView 都有一句 android:transitionName="image_name"
。
sceneTransitionSingleView.onClick {
val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this@OptionActivity, image1, "image_name")
start(it!!, compat)
}
参数:
- Activity activity - 当前所在 Activity
- View sharedElement - 要协同过渡的 View,就是
共享元素
- String sharedElementName - 就是 xml 里定义的 transitionName
上面说到 ActivityCompat.finishAfterTransition(this)
没生效是在一处判断 shared elements
为 null 就返回执行普通的 finish 了,现在这里有 shared elements
了,反现返回原来页面确实也有动画效果了。
这种协同过渡用同类型甚至内容都差不多的 View 来做看着效果好,但就算让两个完全不一样的 View 做协同过渡,也是可以的,乳第一个 Activity 的一个 Button,点击就跳转到新 Activity,就让这个 Button 和新 Activity 里的一个 TextView 做过渡,也是可以,效果还好,就是返回时有个突变。
sceneTransitionDifferent.onClick {
val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this@OptionActivity, sceneTransitionDifferent, "transition")
start(it!!, compat)
}
makeSceneTransitionAnimation 多个 View
修改 xml 再给其它 View 也加上 android:transitionName。
...
...
sceneTransitionMultiView.onClick {
val textPair = android.support.v4.util.Pair(text, "text_name")
val imagePair = android.support.v4.util.Pair(image1, "image_name")
val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this@OptionActivity, textPair, imagePair)
start(it!!, compat)
}
有共享元素时的动画效果
使用共享元素时 Activity 的效果
有三种:
- explode - 爆裂,从场景中间移动视图进入或者退出屏幕
- slide - 滑动,视图从场景的一个边缘进入或者退出屏幕
- android:slideEdge 属性控制滑动方向,取值可以是
LEFT, TOP, RIGHT, BOTTOM, START, END
- android:slideEdge 属性控制滑动方向,取值可以是
- fade - 淡入淡出,从场景添加或者移除一个视图时改变他的透明
可以指定 target,只在某个 View 或排除某个 View 上做动画。
主题中可以配置
- android:windowEnterTransition - 当 A start B 时,B 页面进入场景的 transition
- android:windowExitTransition - 当 A start B 时,A 页面退出场景的 transition
- android:windowReturnTransition - 当 B 返回 A 时,B 页面退出场景的 transition
- android:windowReenterTransition - 当 B 返回 A 时,A 页面进入场景的 transition
如果不在主题配置,在 Activity 的代码设置,如 getWindow().setEnterTransition(new Explode());
,那么
- setEnterTransition - B 中设置
- setExitTransition() - A 中设置
- setReturnTransition() - B 中设置
- setReenterTransition() - A 中设置
在 res/transiton
目录创建两个文件,可以定义其时间和插值器
transition_slide.xml
transition_set.xml
然后配置主题
- @transition/transition_set
- @transition/transition_slide
如果不配置主题,也可以在 B 页面(不是跳转前的 A)代码设置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val set = TransitionInflater.from(ctx).inflateTransition(R.transition.transition_set)
val slide = TransitionInflater.from(ctx).inflateTransition(R.transition.transition_slide)
window.enterTransition = set
window.returnTransition = slide
}
也可以不用 xml,全部代码配置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val set = TransitionSet()
set.ordering = TransitionSet.ORDERING_TOGETHER
val explode = Explode()
explode.duration = 2000
explode.interpolator = AccelerateInterpolator()
val fade = Fade()
fade.duration = 2000
fade.interpolator = AccelerateInterpolator()
set.addTransition(explode)
set.addTransition(fade)
window.enterTransition = set
val slide = Slide()
slide.duration = 2000
slide.slideEdge = Gravity.RIGHT
window.returnTransition = slide
}
共享元素间的效果
- changeBounds - 改变目标视图的布局边界
- android:resizeClip
- changeClipBounds - 裁剪目标视图边界
- changeTransform - 改变目标视图的缩放比例和旋转角度
android:reparent 是否追踪父容器的变化
-
android:reparentWithOverlay 默认 true。
When the parent change doesn't use an overlay, it affects the transforms of the child
- changeImageTransform - 改变目标图片的大小和缩放比例
- ChangeScroll
在 res/transition
目录下创建 transition_elements_enter.xml
创建 transition_elements_return_set.xml
配置主题
- @transition/transition_elements_enter
- @transition/transition_elements_return_set
也可以在 Activity B 中代码设置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// val set = TransitionInflater.from(ctx).inflateTransition(R.transition.transition_elements_return_set)
// val enter = TransitionInflater.from(ctx).inflateTransition(R.transition.transition_elements_enter)
val set = TransitionSet()
set.ordering = TransitionSet.ORDERING_TOGETHER
val changeTransform = ChangeTransform()
changeTransform.duration = 2000
val changeClipBounds = ChangeClipBounds()
changeClipBounds.duration = 2000
set.addTransition(changeTransform)
set.addTransition(changeClipBounds)
//
val enter = ChangeBounds()
enter.duration = 2000
enter.resizeClip = true
window.sharedElementEnterTransition = enter
window.sharedElementReturnTransition = set
}
Overlap
To start an enter transition as soon as possible, use the Window.setAllowEnterTransitionOverlap() method on the called activity. This lets you have more dramatic enter transitions.
更鲜活的动画效果?没看出什么来。在 Activity B 中设置
window.allowEnterTransitionOverlap = true
window.allowReturnTransitionOverlap = true
或者配置主题
- true
- true
关于主题
看网上有些文章说必须在主题里设置
或者代码里在 setContentView()
之前 getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
,可我没设置什么问题都没有,都生效了啊。然后去看官方文档 https://developer.android.com/training/transitions/start-activity.html,又出现一个新的配置
:
First, enable window content transitions with the android:windowActivityTransitions attribute when you define a style that inherits from the material theme.
这意思是说用 material theme 时才需要设置这个属性吗?而我 Demo 用的主题是 Theme.AppCompat.Light.DarkActionBar
。不太清楚到底是不是这个原因。