QuickAnimate DSL

一句话描述:

使用 Kotlin DSL (Domain-Specific Language,领域特定语言)和 ValueAnimator 实现一个动画框架,以 DSL 的方式来方便、快速的实现对 view 的各种属性动画。

通过 Kotlin DSL 的方式来组合针对 view 的多个属性的动画,用于快速、简单的实现复杂的组合动画效果。简单又好用——使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fun animateTest(view: View) {
val controller: Animators.Controller = quickAnimate {
together {
play {
targets = listOf(view)
duration = 300L
interpolator = LinearInterpolator()
translationX(0f, 100f)
}
play {
targets = listOf(view)
duration = 300L
interpolator = LinearInterpolator()
translationY(0f, 100f)
}
}
onStart = {}
onEnd = {}
}
// 开始动画
controller.start()
}

Animators 类: quickAnimate { } 代码块中的 receiver 实现类。通过 封装 Controller 的方式对外提供控制动画的接口。

1
fun quickAnimate(init: Animators.() -> Unit): Animators.Controller

SingleAnimator 类: 具体的 动画动作 封装类 ,也就是 play { } 代码块中的receiver 实现类。负责完成单个属性动画。

1
fun play(anim: SingleAnimator.() -> Unit)

同时也支持多个动画的组合动画,通过 together 包裹的 动画会同时执行。否则,默认所有动画都是 串行执行。

1
fun together(init: Animators.() -> Unit)

最后,提供一个控制类 Animators.Controller ,用于 控制 动画的 开始 、取消,暂停 等操作。

API 接口设计如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
fun quickAnimate(init: Animators.() -> Unit): Animators.Controller

class Animators {

class Controller {
fun start()

fun cancel()

fun pause()

fun resume()

fun isRunning(): Boolean
}

fun play(anim: SingleAnimator.() -> Unit)

fun together(init: Animators.() -> Unit)

class SingleAnimator {

}
}

对于 具体的View 的属性动画,主要利用以下API 来实现:

1
public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> property, float... values)

ObjectAnimator 是 Android 动画框架中的一个类,专门用于执行属性动画。ObjectAnimator 可以对目标对象的某个属性执行动画,例如透明度、旋转角度、位置等。

Property<T, Float> property: 目标对象的属性。Property 是一个属性的抽象表示,这里可以传入以下属性:

1
2
3
4
5
View.ALPHA
View.ROTATION
View.TRANSLATION_X
View.TRANSLATION_Y
......

float... values: 动画的关键帧值序列。可以传入多个值,动画将依次在这些值之间进行插值。

对于单个动画,可以提供以下配置属性,用于配置动画的 view对象、动画时长、插值器、repeatMode 等参数。

1
2
3
4
5
var duration: Long = 0L
var targets: List<View?> = emptyList()
var interpolator: TimeInterpolator? = null
var repeatMode: Int = ValueAnimator.RESTART
var repeatCount: Int = 0

单个动画使用示例:

1
2
3
4
5
6
7
8
play {
targets = listOf(view)
duration = 300L
interpolator = LinearInterpolator()
repeatMode = ValueAnimator.RESTART
repeatCount = 0
translationY(0f, 100f)
}

注意:这里只是完成了动画的配置,并没有真正开始动画,需要通过 Animators.Controller 来统一来控制动画的 开始 、暂停、继续、取消等。

完整源码:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
package com.example.helloworld

import android.animation.*
import android.util.Log
import android.util.Property
import android.view.View
import android.view.animation.LinearInterpolator

fun quickAnimate(init: Animators.() -> Unit): Animators.Controller {
val animators = Animators().apply(init)
val animSet = AnimatorSet()
animSet.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator?) {
animators.onStart?.invoke()
}
override fun onAnimationEnd(animation: Animator?) {
animators.onEnd?.invoke()
}
override fun onAnimationCancel(animation: Animator?) {
}
override fun onAnimationRepeat(animation: Animator?) {
}
})
Log.i("xcc", "quick anim: ${animators.getAnimatorList()}")
animSet.playSequentially(animators.getAnimatorList())
return Animators.Controller(animSet)
}

class Animators {

var onStart: (() -> Unit)? = null

var onEnd: (() -> Unit)? = null

private val anims: MutableList<Animator> = mutableListOf()

fun getAnimatorList() = anims.toList()

fun play(anim: SingleAnimator.() -> Unit) {
val animator = SingleAnimator().apply(anim).createAnimator()
Log.i("xcc", "add anim: $animator")
anims.add(animator)

}

fun together(init: Animators.() -> Unit) {
val animSet = Animators().apply(init).createAnimator()
Log.i("xcc", "add animSet: $animSet")
anims.add(animSet)
}

private fun createAnimator(): Animator {
val animSet = AnimatorSet()
animSet.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator?) {
onStart?.invoke()
}
override fun onAnimationEnd(animation: Animator?) {
onEnd?.invoke()
}
override fun onAnimationCancel(animation: Animator?) {
}
override fun onAnimationRepeat(animation: Animator?) {
}
})
animSet.playTogether(anims.toList())
return animSet
}

class Controller(private val animator: Animator) {
fun start() {
animator.start()
}

fun cancel(){
animator.cancel()
}

fun pause() {
animator.pause()
}

fun resume() {
animator.resume()
}

fun isRunning(): Boolean {
return animator.isRunning
}

}

class SingleAnimator {

var duration: Long = 0L
var targets: List<View?> = emptyList()
var interpolator: TimeInterpolator? = null
var repeatMode: Int = ValueAnimator.RESTART
var repeatCount: Int = 0

private val animList: MutableList<Animator> = mutableListOf()

fun createAnimator(): Animator {
val animSet = AnimatorSet()
animSet.duration = this.duration
animSet.interpolator = this.interpolator
animSet.playSequentially(animList.toList())
return animSet
}

fun translationY(
vararg values: Float,
onUpdate: ((animation: ValueAnimator) -> Unit)? = null
) {
anim(property = View.TRANSLATION_Y, values = values, onUpdate = onUpdate)
}

fun translationX(
vararg values: Float,
onUpdate: ((animation: ValueAnimator) -> Unit)? = null
) {
anim(property = View.TRANSLATION_X, values = values, onUpdate = onUpdate)
}

fun alpha(
vararg values: Float,
onUpdate: ((animation: ValueAnimator) -> Unit)? = null
) {
anim(property = View.ALPHA, values = values, onUpdate = onUpdate)
}

fun rotate(
vararg values: Float,
onUpdate: ((animation: ValueAnimator) -> Unit)? = null
) {
anim(property = View.ROTATION, values = values, onUpdate = onUpdate)
}

// add more view property here...

private fun anim(
property: Property<View, Float>,
vararg values: Float,
onUpdate: ((animation: ValueAnimator) -> Unit)? = null
) {
targets.forEach {
if (it == null) {
return@forEach
}
val anim = ObjectAnimator.ofFloat(it, property, *values)
anim.addUpdateListener { animation ->
onUpdate?.invoke(animation)
}
anim.duration = this.duration
anim.interpolator = this.interpolator
anim.repeatMode = this.repeatMode
anim.repeatCount = this.repeatCount
animList.add(anim)
}
}

}
}

var controller: Animators.Controller? = null

fun animateTest(view: View) {
controller = quickAnimate {
together {
play {
targets = listOf(view)
duration = 300L
interpolator = LinearInterpolator()
translationX(view.translationX, view.translationX + 100f)
}
play {
targets = listOf(view)
duration = 300L
interpolator = LinearInterpolator()
translationY(view.translationY, view.translationY + 100f) {
Log.i("xcc", "on update: ${it.animatedValue}")
}
}
}
play {
targets = listOf(view)
duration = 300L
interpolator = LinearInterpolator()
alpha(0f, 1f)
}
play {
targets = listOf(view)
duration = 300L
interpolator = LinearInterpolator()
rotate(0f, 360f)
}
onStart = {
Log.i("xcc", "onStart")
}
onEnd = {
Log.i("xcc", "onEnd")
}
}
controller?.start()
}

QuickAnimate DSL
https://xcxyh.github.io/2024/07/13/QuickAnimate-DSL/
作者
xcxyh
发布于
2024年7月13日
许可协议