Android-动画整理(过渡动画)

Transition TransitionManager Scene 之间的关系,梳理 过渡动画 场景动画

概述

首先明白下面两个名词的意思

过渡动画:Android 中 Activity 或者 Fragment 之间切换时候的动画效果。
场景动画:使用过渡为布局变化添加动画效果

为了研究透彻,我们先从 过渡动画 动画说起。

在5.0 以前,我们想要实现 Activity 跳转动画,使用到的就是 overridePendingTransition 方法。

5.0以后,加入了Transition 为我们提供了更多可用的动画效果,例如滑入滑出,还可以通过 ActivityOptionsCompat实现共享元素的效果

追本溯源,先从 overridePendingTransition 的使用 作为切入点,聊聊 过渡动画的效果

overridePendingTransition 的使用

这个方法是我们在 activity 或者 fragment 切换 时候的切场动画

下面的示例 左移退场,右移退场的效果

效果图

代码

  • 入场设置
1
2
3
4
startActivity(intent)
//参数1 进场动画 (B Activity 出现的动画)
//参数2 退场动画 (A Activity 消失的动画)
overridePendingTransition(R.anim.enter_in, R.anim.enter_out)
  • 退场设置
1
2
3
4
finish()
//参数1 进场动画 (A Activity 出现的动画)
//参数2 退场动画 (B Activity 消失的动画)
overridePendingTransition(R.anim.exit_in, R.anim.exit_out)

动画设置

  • enter_in
1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="100%"
android:toXDelta="0"
android:duration="3000">
</translate>
  • enter_out
1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0"
android:toXDelta="-100%"
android:duration="3000">
</translate>
  • exit_in
1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="-100%"
android:toXDelta="0"
android:duration="3000">
</translate>
  • exit_out
1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0"
android:toXDelta="100%"
android:duration="3000">
</translate>

注意事项

  • 需要在 startActivity 或者 finish 之后立马执行

ActivityOptionsCompat && ActivityOptions

说到转场动画不得不说一下 ActivityOptionsCompatActivityOptions

乍一看感觉极其相似,其实何止是像是,方法都差不多。 ActivityOptions 是帮助我们处理 转场动画的,作用和 overridePendingTransition 差不多,但是他能够处理更多的的动画效果,,而ActivityOptionsCompat则是向后兼容的方式访问ActivityOptions中的特性

主要都有下面的方法,下面会对每个方法介绍

方法
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)
makeSceneTransitionAnimation(Activity activity, View sharedElement, String sharedElementName)
makeSceneTransitionAnimation(Activity activity, Pair<View, String>… sharedElements)

makeCustomAnimation

指定在显示活动时运行的自定义动画

这个方法和 overridePendingTransition 相似,都是传入进场和退场的动画

效果图

可以看到 入场和退场的动画都是我们设置的 ,但是点击返回以后退出的动画 走的是系统默认的动画效果

代码

1
2
3
4
5
6
7
8
9
//参数2 enterResId 进场动画 (B Activity 出现的动画)
//参数3 exitResId 退场动画 (A Activity 消失的动画)
val compat = ActivityOptionsCompat.makeCustomAnimation(
this,
R.anim.enter_in,
R.anim.enter_out
)
ActivityCompat.startActivity(this, intent, compat.toBundle())
// startActivity(intent, compat.toBundle()) 等价于上面的效果

如果使用 ActivityOptions 效果同上

1
2
val compat = ActivityOptions.makeCustomAnimation(this, R.anim.enter_in, R.anim.enter_out)
startActivity(intent, compat.toBundle())

注意事项

在第二个界面使用 ActivityCompat.finishAfterTransition(this) 不会有回退的动画效果

makeScaleUpAnimation

新Activity将从屏幕的一小块原始区域缩放到其最终的完整表示。

效果图

代码设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private fun makeScaleUpAnimationTodo(view: ImageView) {
val intent = Intent(this, TransitionFinishAct::class.java)
//参数1 参照物 指定从哪个View的坐标开始放大
//参数2 指定以ViewX坐标为放大中心的X坐标
//参数3 指定以ViewY坐标为放大中心的Y坐标
//参数4 指定放大前新Activity是多宽
//参数5 指定放大前新Activity是多高
val compat = ActivityOptionsCompat.makeScaleUpAnimation(
view,
view.width / 2,
view.height / 2,
0,
0
)
ActivityCompat.startActivity(this, intent, compat.toBundle())
}

makeThumbnailScaleUpAnimation

其中缩略图将从给定位置缩放到正在启动的新Activity窗口。

效果图

代码设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private fun makeThumbnailScaleUpAnimationTodo(view: ImageView) {
val intent = Intent(this, TransitionFinishAct::class.java)
//要放大的图片从哪个View的左上角的坐标作为中心点放大
//指定要放大的图片
//x 坐标
//y 坐标
val compat = ActivityOptionsCompat.makeThumbnailScaleUpAnimation(
view,
BitmapFactory.decodeResource(resources, TypeHelper.getImageResId()),
0,
0
)
ActivityCompat.startActivity(this, intent, compat.toBundle())
}

makeClipRevealAnimation

新的Activity从屏幕的一小块原始区域显示到其最终的完整表示。

效果图

代码设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private fun makeClipRevealAnimationTodo(view: ImageView) {
val intent = Intent(this, TransitionFinishAct::class.java)
//参数1 参照物 指定从哪个View的坐标开始放大
//参数2 指定以ViewX坐标为放大中心的X坐标
//参数3 指定以ViewY坐标为放大中心的Y坐标
//参数4 指定放大前新Activity是多宽
//参数5 指定放大前新Activity是多高
val compat = ActivityOptionsCompat.makeClipRevealAnimation(
view,
view.width / 2,
view.height / 2,
0,
0
)
ActivityCompat.startActivity(this, intent, compat.toBundle())
}

makeSceneTransitionAnimation 单个共享元素

1
makeSceneTransitionAnimation(Activity activity, View sharedElement, String sharedElementName)

单个共享元素,设置了需要共享view 的 transitionName 系统会帮助我们完成切场的动画效果

效果图

代码设置

在第一个界面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private fun makeSceneTransitionAnimationTodo(view: ImageView) {
val intent = Intent(this, TransitionFinishAct::class.java)
//单个 共享元素
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//这里使用代码的方式设置 transitionName
//也可以在xml 中设置属性 5.0以上才有此属性
view.transitionName = "image_1_test"
//参数1 上下文
//参数2 共享的view
//参数3 共享的view的transitionName 这里要和第二个界面的transitionName相同
val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(
this,
view,
"image_1_test"
)
ActivityCompat.startActivity(this, intent, compat.toBundle())
}
}

在第二个界面使用

1
2
3
4
5
6
private fun makeSceneTransitionAnimationTodo() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
image.transitionName = "image_1_test"
ActivityCompat.finishAfterTransition(this)
}
}

当然也可以在xml 中使用 android:transitionName设置view 的 transitionName

makeSceneTransitionAnimation 多个共享元素

1
makeSceneTransitionAnimation(Activity activity, Pair<View, String>… sharedElements)

通过 Pair.create 方法生成一个 共享元素对,实现的效果可上面是的是相同的

代码

在第一个界面

1
2
3
4
5
6
7
8
9
10
11
private fun makeSceneTransitionAnimationMoreTodo(view: ImageView) {
val intent = Intent(this, TransitionFinishAct::class.java)
//单个 共享元素
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val pair = androidx.core.util.Pair.create(view as View, "image_1_test")
//参数1 上下文
//参数2 共享对象 ,可添加多个
val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this, pair)
ActivityCompat.startActivity(this, intent, compat.toBundle())
}
}

在第二个界面使用

1
2
3
4
5
6
private fun makeSceneTransitionAnimationTodo() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
image.transitionName = "image_1_test"
ActivityCompat.finishAfterTransition(this)
}
}

makeSceneTransitionAnimation

1
makeSceneTransitionAnimation(Activity activity)

直接使用 上面的方法,系统会执行默认的 AutoTransition 动画 ,下面会介绍

效果图

代码

在第一个界面

1
2
3
4
5
6
7
8

private fun makeSceneTransitionAnimationActTodo(view: ImageView) {
val intent = Intent(this, TransitionFinishAct::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this)
ActivityCompat.startActivity(this, intent, compat.toBundle())
}
}

在第二个界面使用

1
ActivityCompat.finishAfterTransition(this)

Transition

在上面说的到 AutoTransition 属于 Transition 的一种

首先看一下他的继承关系

方法说明
TransitionSet组合效果
AutoTransition默认过渡动画,Fade out 渐隐, move 位移 和 resize 大小缩放,fade in 渐显 ,按顺序
changeBounds位移和缩放
changeClipBounds裁剪目标视图边界
changeImageTransform改变目标图片的大小和缩放比例
changeTransform改变目标视图的缩放比例和旋转角度
changeScroll改变滑动位置
Explode分解效果
Fadeandroid:fadingMode 淡入淡出 有 fade_in,fade_out ,fade_in_out
Slideandroid:slideEdge 从哪边滑动出,有 left, top, right, bottom, start, end 模式

Slide

滑入滑出

效果图

代码

  • 第一个界面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private fun slideTodo(view: ImageView) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val intent = Intent(this, TransitionFinishAct::class.java)
//系统自带的
// val transition =
// TransitionInflater.from(this).inflateTransition(android.R.transition.slide_left)
//xml
// val transition = TransitionInflater.from(this)
// .inflateTransition(R.transition.transition_slide)

// 代码
val transition = Slide().apply {
duration = 1000
slideEdge = Gravity.LEFT
}


window.exitTransition = transition
val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this)
ActivityCompat.startActivity(this, intent, compat.toBundle())
}
}
  • transition_slide.xml
1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<slide xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:slideEdge="left"
android:interpolator="@android:interpolator/accelerate_decelerate" />
  • 第二个界面
1
2
3
4
5
6
7
8
9

private fun slideTodo() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.enterTransition = Slide().apply {
duration = 1000
slideEdge = Gravity.RIGHT
}
}
}

Fade

淡入淡出

效果图

代码

  • 第一个界面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private fun fadeTodo(view: ImageView) {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val intent = Intent(this, TransitionFinishAct::class.java)
//系统自带的
// val transition =
// TransitionInflater.from(this).inflateTransition(android.R.transition.fade)
//xml
// val transition = TransitionInflater.from(this)
// .inflateTransition(R.transition.transition_fade)

// 代码
val transition = Fade().apply {
duration = 500
}
window.exitTransition = transition
val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this)
ActivityCompat.startActivity(this, intent, compat.toBundle())
}
}
  • transition_fade.xml
1
2
3
4
5

<?xml version="1.0" encoding="utf-8"?>
<fade xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:interpolator="@android:interpolator/accelerate_decelerate" />
  • 第二个界面
1
2
3
4
5
6
private fun fadeTodo() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.enterTransition = Fade().apply {
duration = 500
}
}

Explode

分解效果

效果图

代码

  • 第一个界面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private fun explodeTodo(view: ImageView) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val intent = Intent(this, TransitionFinishAct::class.java)
//系统自带的
// val transition =
// TransitionInflater.from(this).inflateTransition(android.R.transition.explode)
//xml
val transition = TransitionInflater.from(this)
.inflateTransition(R.transition.transition_fade)

// 代码
// val transition = Explode().apply {
// duration = 1000
// }
window.exitTransition = transition
val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this)
ActivityCompat.startActivity(this, intent, compat.toBundle())
}
}
  • transition_fade.xml
1
2
3
4
<?xml version="1.0" encoding="utf-8"?>
<explode xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:interpolator="@android:interpolator/accelerate_decelerate" />
  • 第二个界面
1
2
3
4
5
6
7
private fun explodeTodo() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.enterTransition = Explode().apply {
duration = 500
}
}
}

TransitionSet

组合动画

效果图

代码

  • 第一个界面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private fun transitionSetTodo(view: ImageView) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val intent = Intent(this, TransitionFinishAct::class.java)

val slide = Slide()
val fade = Fade()

// 代码
val transition = TransitionSet().apply {
duration = 500
addTransition(slide)
addTransition(fade)
}
window.exitTransition = transition
val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this)
ActivityCompat.startActivity(this, intent, compat.toBundle())
}
}
  • 第二个界面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private fun transitionSetTodo() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

val slide = Slide()
val fade = Fade()

// 代码
val transition = TransitionSet().apply {
duration = 500
addTransition(slide)
addTransition(fade)
}
window.enterTransition = transition
}
}

还有其他的效果 在下面的Scene 中会详细说明

ChangeBounds

ChangeBounds:检测view的位置边界创建移动和缩放动画 下面使用 Scene 切换演示效果

演示效果

创建两个不同的 scene

先创见两个布局效果

scene_1scene_2
  • scene_1.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/scene_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<ImageView
android:id="@+id/image_2"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="50dp"
android:src="@drawable/test_2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />


<ImageView
android:id="@+id/image_3"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginStart="50dp"
android:layout_marginTop="50dp"
android:src="@drawable/test_3"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image_2" />


<ImageView
android:id="@+id/image_1"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="50dp"
android:layout_marginEnd="50dp"
android:src="@drawable/test_1"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image_2" />


</androidx.constraintlayout.widget.ConstraintLayout>
  • scene_2.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/scene_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<ImageView
android:id="@+id/image_1"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginTop="50dp"
android:src="@drawable/test_1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />


<ImageView
android:id="@+id/image_2"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginStart="50dp"
android:layout_marginTop="50dp"
android:src="@drawable/test_2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image_1" />


<ImageView
android:id="@+id/image_3"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginTop="50dp"
android:layout_marginEnd="50dp"
android:src="@drawable/test_3"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image_1" />


</androidx.constraintlayout.widget.ConstraintLayout>
  • activity 根布局
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">



<FrameLayout
android:id="@+id/scene_parent"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp"/>



<Button
android:id="@+id/btn_switch"
android:text="切换"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>

</LinearLayout>

界面切换的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class SceneChangeBoundsAct : AppCompatActivity() {


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scene)
title = intent.getStringExtra("name")


val scene2 = createScene2()
val scene1 = createScene1()

var isSwitch = false

TransitionManager.go(scene1, getTransition())


btn_switch.setOnClickListener {
val transition = getTransition()
if (isSwitch) {
isSwitch = false
TransitionManager.go(scene1, transition)
} else {
isSwitch = true
TransitionManager.go(scene2, transition)
}
}
}


private fun getTransition(): Transition {
return ChangeBounds()
}

private fun createScene2(): Scene {
//参数1 容器
//参数2 scene
return Scene.getSceneForLayout(scene_parent, R.layout.scene_2, this)
}

private fun createScene1(): Scene {
return Scene.getSceneForLayout(scene_parent, R.layout.scene_1, this)
}
}

ChangeImageTransform

ChangeImageTransform:检测ImageView(这里是专指ImageView)的尺寸,位置以及ScaleType,并创建相应动画
一般时候,我们都是和 ChangeBounds 一起使用,能够做出很漂亮的动画

效果

关键代码

1
2
3
4
5
6
7
8
9
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun getTransition(): Transition {
return TransitionSet().apply {
addTransition(ChangeBounds())
addTransition(ChangeImageTransform())
interpolator = DecelerateInterpolator()
duration = 500
}
}

ChangeTransform

ChangeTransform:检测view的scale和rotation创建缩放和旋转动画

效果图

代码

1
2
3
4
5
6
7
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun getTransition(): Transition {
return ChangeTransform().apply {
interpolator = DecelerateInterpolator()
duration = 500
}
}

ChangeClipBounds

检测view的剪切区域的位置边界,和ChangeBounds类似。不过ChangeBounds针对的是view而ChangeClipBounds针对的是view的剪切区域(setClipBound(Rect rect) 中的rect)。如果没有设置则没有动画效果

效果

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57


class SceneChangeClipBoundsAct : AppCompatActivity() {


@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scene)
title = intent.getStringExtra("name")


val scene2 = createScene2()
val scene1 = createScene1()

var isSwitch = false





TransitionManager.go(scene1, getTransition())

scene1.sceneRoot.findViewById<ImageView>(R.id.image_1).clipBounds = Rect(0, 0, 400, 400)
btn_switch.setOnClickListener {
val transition = getTransition()
if (isSwitch) {
isSwitch = false
TransitionManager.go(scene1, transition)
scene1.sceneRoot.findViewById<ImageView>(R.id.image_1).clipBounds = Rect(0, 0, 400, 400)
} else {
isSwitch = true
TransitionManager.go(scene2, transition)
scene2.sceneRoot.findViewById<ImageView>(R.id.image_1).clipBounds = Rect(400, 400, 800, 800)
}


}
}


@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun getTransition(): Transition {
return ChangeClipBounds()
}

private fun createScene2(): Scene {
//参数1 容器
//参数2 scene

return Scene.getSceneForLayout(scene_parent, R.layout.scene_8, this)
}

private fun createScene1(): Scene {

return Scene.getSceneForLayout(scene_parent, R.layout.scene_7, this)
}

TransitionManager

TransitionManager在场景变换时控制transitions的执行。通过TransitionManager可以添加场景和Transition变换

图片截取自 TransitionManager

Scene && Transition && TransitionManager 的关系

官网的这个图能够很详细的描述其中的关系

通过开始的 场景(Scene) 和一个结束的 场景,然后加上一个需要的动画效果(Transition),通过TransitionManager就可以实现自己需要的场景动画效果

ViewAnimationUtils

这个类只有一个方法,

1
2
3
4
5
6
//view:代表的是你要操作的view
//centerX:圆的x方向的中点
//centerY:圆的y方向的中点
//startRadius:这个圆开始时候的半径
//endRadius:结束时候的半径
public static Animator createCircularReveal(View view, int centerX, int centerY, float startRadius, float endRadius)

由外向内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun btn_2_anim() {
util_image.post {
val cenX: Int = util_image.width / 2
val cenY: Int = util_image.height / 2

val animator = ViewAnimationUtils.createCircularReveal(
util_image,
cenX,
cenY,
util_image.width.toFloat(),
0f
)
animator.duration = 500
animator.interpolator = LinearOutSlowInInterpolator()
animator.start()
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
// 在这里移除侦听器是非常重要的,因为共享元素过渡在退出时再次向后执行。 如果我们不删除侦听器,这个代码将再次被触发。
animation.removeListener(this)
}
})
}
}

由内向外

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun btn_1_anim() {
util_image.post {
val cenX: Int = util_image.width / 2
val cenY: Int = util_image.height / 2

val an =
ViewAnimationUtils.createCircularReveal(
util_image,
cenX,
cenY,
0f,
cenX.toFloat()
)
an.duration = 500
an.start()
an.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
// 在这里移除侦听器是非常重要的,因为共享元素过渡在退出时再次向后执行。 如果我们不删除侦听器,这个代码将再次被触发。
animation.removeListener(this)
}
})
}
}

Github

DEMO
博客

参考

官方文档
Android 过渡动画框架
初识Transition—实现两个场景的变换