参考:
联合动画 AnimatorSet
ValueAnimator和ObjectAnimator都只能单单实现一个动画,如果要多个效果,如:边放大,边改变颜色,边移动,或者顺序播放等,这个时候,就需要用到AnimatorSet,组合型动画了;
View动画对应的为AnimationSet
AnimatorSet
- playSequentially 多个动画顺序播放;
- playTogether 多个动画同时播放;
val animator1 = ObjectAnimator.ofFloat(text1, "translationY", 0f, 300f, 0f).setDuration(1000)
val animator2 = ObjectAnimator.ofFloat(text2, "rotation", 0f, -180f, +180f, 0f).setDuration(1000)
btn_seque.setOnClickListener {
AnimatorSet().apply {
duration = 2000
playSequentially(animator1, animator2)
}.start()
}
btn_together.setOnClickListener {
AnimatorSet().apply {
duration = 2000
playTogether(animator1, animator2)
}.start()
}
playSequentially 与 playTogether说明,源博客的例子举得很好
- playTogether和playSequentially在激活动画后,控件的动画情况与它们无关,他们只负责定时激活控件动画。
- playSequentially只有上一个控件做完动画以后,才会激活下一个控件的动画,如果上一控件的动画是无限循环,那下一个控件就别再指望能做动画了。
自由设置动画顺序 - AnimatorSet.Builder
对于3个动画A,B,C要实现A播放后,同时播放B与C,这个时候,就需要用到AnimatorSet.Builder了;
播放1后,再2,3同时播放:
// AnimatorSet.Builder
btn_builder.setOnClickListener {
AnimatorSet().apply {
val builder = play(animator2).with(animator3)
builder.after(animator1)
}.start()
}
AnimatorSet.Builder
通过animatorSet.play(animator2)生成Builder对象,这是生成AnimatorSet.Builder对象的唯一途径;
一些控制动画播放顺序的方法如下:
//和前面动画一起执行
public Builder with(Animator anim)
//执行前面的动画后才执行该动画
public Builder before(Animator anim)
//执行先执行这个动画再执行前面动画
public Builder after(Animator anim)
//延迟n毫秒之后执行动画
public Builder after(long delay)
play(Animator anim)表示当前在播放哪个动画,另外的with(Animator anim)、before(Animator anim)、after(Animator anim)都是以play中的当前所播放的动画为基准的;
AnimatorSet.Builder 监听器
AnimatorSet().apply {
val builder = play(animator2).with(animator3)
builder.after(animator1)
addListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator?) {
}
override fun onAnimationEnd(animation: Animator?) {
}
override fun onAnimationCancel(animation: Animator?) {
}
override fun onAnimationStart(animation: Animator?) {
}
})
}.start()
因为ValueAnimator和AnimatorSet都派生自Animator类,而AnimatorListener是Animator类中的函数;
AnimatorSet的监听说明:
- AnimatorSet的监听函数也只是用来监听AnimatorSet的状态的,与其中的动画无关;
- AnimatorSet中没有设置循环的函数,所以AnimatorSet监听器中永远无法运行到onAnimationRepeat()中!
分享的时候,现场写代码;
动画逐个设置与AnimatorSet设置的区别
AnimatorSet设置的几个方法:
//设置单次动画时长
public AnimatorSet setDuration(long duration);
//设置加速器
public void setInterpolator(TimeInterpolator interpolator)
//设置ObjectAnimator动画目标控件
public void setTarget(Object target)
对应的单个ObjectAnimator也可以设置这些参数,AnimatorSet中设置与在单个ObjectAnimator中的区别如下:
在AnimatorSet中设置以后,会覆盖单个ObjectAnimator中的设置;即如果AnimatorSet中没有设置,那么就以ObjectAnimator中的设置为准。如果AnimatorSet中设置以后,ObjectAnimator中的设置就会无效。
setTarget的覆盖
// AnimatorSet.Builder会覆盖具体的ObjectAnimator设置
btn_builder_target.setOnClickListener {
AnimatorSet().apply {
duration = 5000 // 覆蓋
playTogether(animator1,animator2,animator3)
setTarget(text1) // 這個setTarget必須放在這裡,放在playTogether之前無效
}.start()
}
注意:setTarget位置,放在了最后,这样才能确保将动画的目标统一设置为当前控件;
setStartDelay
设置延迟开始动画组;setStartDelay函数不会覆盖单个动画的延时,而且仅针对性的延长AnimatorSet的激活时间,单个动画的所设置的setStartDelay仍对单个动画起作用。
btn_delay.setOnClickListener {
animator1.startDelay = 2000
AnimatorSet().apply {
duration = 2000
startDelay = 2000
play(animator1).with(animator2)
}.start()
}
整体延迟2s后,再开始animator2
,animtor1
通过xml实现ValueAnimator、ObjectAnimator,AnimatorSet
在xml中对应animator总共有三个标签:
- <animator />:对应ValueAnimator
- <objectAnimator />:对应ObjectAnimator
- <set />:对应AnimatorSet
View动画标签相对多几个;对比Vew动画,animator相关的xml放在 animator
文件夹下:
Animator
对应ValueAnimator
<animator
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode=["repeat" | "reverse"]
android:valueType=["intType" | "floatType"]
android:interpolator=["@android:interpolator/XXX"]/>
字段解释,参考原博客
http://blog.csdn.net/harvic880925/article/details/50763286
Animator xml 动画实现过程:
- 建立Animator动画文件;
- 通过AnimatorInflater装载,并强制类型转换;
- 绑定到对象并播放动画;
// 动画xml,类似 ValueAnimator.ofInt(0,300)
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0"
android:valueTo="300"
android:duration="1000"
android:valueType="intType"
android:interpolator="@android:anim/bounce_interpolator">
</animator>
// 装载动画
val animator1 = AnimatorInflater.loadAnimator(baseContext, R.animator.property_animator)
as ValueAnimator // 强转一下
// 播放
btn_start.setOnClickListener {
animator1.apply {
addUpdateListener { it ->
val offset = it.animatedValue as Int //
text.layout( offset,offset,text.width+offset,text.height + offset)
}
}.start()
}
xml中根属性是<animator/>所以它对应的是ValueAnimator,所以在加载后,将其强转为valueAnimator;然后对其添加控件监听。
objectAnimator
<objectAnimator
android:propertyName="string"
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode=["repeat" | "reverse"]
android:valueType=["intType" | "floatType"]
android:interpolator=["@android:interpolator/XXX"]/>
字段的解释原博客很详细;
使用方法与ValueAnimator类似,需要一个target
// 装置ObjectAnimator动画
val animator2 = AnimatorInflater.loadAnimator(baseContext, R.animator.property_object_animator) as ObjectAnimator
btn_start_object.setOnClickListener {
animator2.target = text
animator2.start()
}
set
这个是AnimatorSet所对应的标签。它只有一个属性:
<set android:ordering=["together" | "sequentially"]>
// 装载set
val set = AnimatorInflater.loadAnimator(baseContext, R.animator.property_set_animator) as AnimatorSet
btn_set.setOnClickListener {
set.setTarget(text)
set.start()
}
总结
对于属性动画用代码来创建,比xml装载更便捷,直观;
例子:旋转菜单:
fun toggle() {
// 1.分别获取各个按钮的位置 5个按钮,4个角度平分90度
val degree = 90 * 1.0f / 4
val totalSet = AnimatorSet()
(0..4).forEach { it ->
// 获取x , y 坐标
val radians = Math.toRadians(degree * it.toDouble())
val delay = it * 50.toLong()
val duration = 1000L
val target = find<View>(baseContext.resources.getIdentifier("btn_${it}", "id", packageName))
val x = -radius!! * Math.sin(radians).toFloat()
val y = -radius!! * Math.cos(radians).toFloat()
lateinit var obj1: ObjectAnimator
lateinit var obj2: ObjectAnimator
lateinit var obj3: ObjectAnimator
lateinit var obj4: ObjectAnimator
lateinit var obj5: ObjectAnimator
lateinit var obj6: ObjectAnimator
if (!isOpen) {
target.visibility = View.VISIBLE
obj1 = ObjectAnimator.ofFloat(target, "translationX", 0f, x)
obj2 = ObjectAnimator.ofFloat(target, "translationY", 0f, y)
obj3 = ObjectAnimator.ofFloat(target, "scaleX", 0.1f, 1.0f)
obj4 = ObjectAnimator.ofFloat(target, "scaleY", 0.1f, 1.0f)
obj5 = ObjectAnimator.ofFloat(target, "alpha", 0.5f, 1.0f)
obj6 = ObjectAnimator.ofFloat(target, "rotation", 0f, 720f)
obj6.addUpdateListener {
if (it.animatedFraction >= 1.0f) isOpen = true
}
} else {
obj1 = ObjectAnimator.ofFloat(target, "translationX", x, 0f)
obj2 = ObjectAnimator.ofFloat(target, "translationY", y, 0f)
obj3 = ObjectAnimator.ofFloat(target, "scaleX", 1.0f, 0.1f)
obj4 = ObjectAnimator.ofFloat(target, "scaleY", 1.0f, 0.1f)
obj5 = ObjectAnimator.ofFloat(target, "alpha", 1.0f, 0.5f)
obj6 = ObjectAnimator.ofFloat(target, "rotation", 0f, 720f)
obj6.addUpdateListener {
if (it.animatedFraction >= 1.0f) {
target.visibility = View.GONE
isOpen = false
}
}
}
totalSet.playTogether(AnimatorSet().apply {
setDuration(duration)
startDelay = delay
playTogether(obj1, obj2, obj3, obj4, obj5, obj6)
})
totalSet.start()
}
}