没有什么动画是一个AnimSet不能解决的,如果有那就再来一个AnimSet。项目是Kotlin写的也不复杂,不懂Kotlin刚好可以学学。
吹牛.jpg系统动画那些坑
现在应该没人使用View动画了吧,还再使用怕是学的假Android了,所以这边讲的是属性动画。
先说说ValueAnimator
- 不提供动画方向判断方法,这点一直很困惑,查看源码发现有一个很明显的字段
mReversing
,跟踪下方法发现shouldPlayBackward()
方法,兴高采烈的去调用时才发现是私有方法😓。无奈之下只能用反射调用,结果在5.0系统突然崩了,去查5.0源码发现居然是不同字段mPlayingBackwards
,再去查5.1的源码发现居然两个都存在,就不能专一点吗,8.0更新又不能获取了解决方法待定,感觉不能再玩反射太不靠谱了。 -
revese()
方向是个问题,字面上理解是反转的意思,不就是到着播放嘛,但是当你倒着播放时再掉reverse()又给正向播放了,然后还不告诉你方向不带这么玩的啊😢。 - 播放时间,还是不给方向判断的坑😢。
再说说AnimatorSet
该怎么说远看像越野车近看才发现是拖拉机,能存在并被使用简直是个奇迹。
-
reverse()
是隐藏方法也就是说不能用了,忍了看在能播放那么多动画的面子上。 - 播放存在问题,当一个动画没结束再次start()会发现前面播放过的动画居然不播放了,这还怎么玩啊。
- 看似播放方式多样但并没有什么卵用,
with
,before
,after
包含了多种播放方式,但是实际使用时基本都是一个动画没结束就开始下一个动画,这中理想的动画播放方式根本用不到。
实现效果
动画要求:总动画时间3s,红块直接开始时间3s,绿块1s后开始时间2s,蓝块2s后开始时间1s,动画执行过程中可以随时来回切换,可以暂停、继续、结束和取消,可以想象下使用系统提供的方式要怎么实现。
Kapture 2017-07-16 at 13.35.24.gifValueAnim
看看怎么填ValueAnimator的坑,获取播放方向问题,通过反射获取播放方向,利用Kotlin扩展方法的特性,对ValueAnimator进行扩展,但是mReversing
的值只有再动画播放时才有效果,动画结束就被初始化为false了,结果还得在结束前把方向保存下来。Kotlin并不能真正给添加个参数到某个类,只能继承ValueAnimator进行扩展了。其次播放控制问题,为了保留原来的方法和避免reverse()
存在的问题,添加了几个方法animStart()
正向播放,animReverse()
反向播放,animTrigger()
切换方向(类似reverse()作用)。代码很简单并注释了以后就用它来替代ValueAnimator了,本来想也改下ObjectAnimator
发现是final无法继承,看在没什么大问题的份上就放过它了。
package cn.wittyneko.anim
import android.animation.*
/**
* Created by wittyneko on 2017/7/7.
*/
open class ValueAnim : ValueAnimator(), AnimListener {
companion object {
internal val argbEvaluator = ArgbEvaluator()
fun ofInt(vararg values: Int): ValueAnim {
val anim = ValueAnim()
anim.setIntValues(*values)
return anim
}
fun ofArgb(values: IntArray): ValueAnim {
val anim = ValueAnim()
anim.setIntValues(*values)
anim.setEvaluator(argbEvaluator)
return anim
}
fun ofFloat(vararg values: Float): ValueAnim {
val anim = ValueAnim()
anim.setFloatValues(*values)
return anim
}
fun ofPropertyValuesHolder(vararg values: PropertyValuesHolder): ValueAnim {
val anim = ValueAnim()
anim.setValues(*values)
return anim
}
fun ofObject(evaluator: TypeEvaluator<*>, vararg values: Any): ValueAnim {
val anim = ValueAnim()
anim.setObjectValues(*values)
anim.setEvaluator(evaluator)
return anim
}
}
private var _isAnimReverse: Boolean = true
var listener: AnimListener? = null
var isAnimEnd: Boolean = false
protected set
var isAnimCancel: Boolean = false
protected set
//是否反向
var isAnimReverse: Boolean
get() {
if (isRunning) {
return isReversing
} else {
return _isAnimReverse
}
}
internal set(value) {
_isAnimReverse = value
}
//动画播放时间
val animCurrentPlayTime: Long
get() {
if (isRunning && isAnimReverse) {
return duration - currentPlayTime
} else {
return currentPlayTime
}
}
init {
addListener(this)
addUpdateListener(this)
}
/**
* 正向播放
*/
open fun animStart() {
when {
isRunning && isAnimReverse -> {
reverse()
}
!isRunning -> {
start()
}
}
}
/**
* 反向播放
*/
open fun animReverse() {
when {
isRunning && !isAnimReverse -> {
reverse()
}
!isRunning -> {
reverse()
}
}
}
/**
* 切换播放方向
*/
open fun animTrigger() {
if (isAnimReverse) {
animStart()
} else {
animReverse()
}
}
override fun start() {
isAnimCancel = false
isAnimEnd = false
super.start()
}
override fun reverse() {
isAnimCancel = false
isAnimEnd = false
super.reverse()
}
override fun end() {
isAnimCancel = false
isAnimEnd = true
super.end()
}
override fun cancel() {
isAnimCancel = true
isAnimEnd = false
super.cancel()
}
override fun onAnimationUpdate(animation: ValueAnimator?) {
listener?.onAnimationUpdate(animation)
}
override fun onAnimationStart(animation: Animator?) {
listener?.onAnimationStart(animation)
}
override fun onAnimationEnd(animation: Animator?) {
if ((isStarted || isRunning) && animation is ValueAnimator) {
_isAnimReverse = animation.isReversing
}
listener?.onAnimationEnd(animation)
}
override fun onAnimationCancel(animation: Animator?) {
listener?.onAnimationCancel(animation)
}
override fun onAnimationRepeat(animation: Animator?) {
listener?.onAnimationRepeat(animation)
}
}
interface AnimListener : ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener
// 动画播放时方向 api22+
val ValueAnimator.isReversing: Boolean
get() {
try {
var rfield = ValueAnimator::class.java.getDeclaredField("mReversing")
rfield.isAccessible = true
return rfield.get(this) as? Boolean ?: false
} catch (e: Throwable) {
return isPlayingBackwards
}
}
// 动画播放时方向 api21-
val ValueAnimator.isPlayingBackwards: Boolean
get() {
try {
var rfield = ValueAnimator::class.java.getDeclaredField("mPlayingBackwards")
rfield.isAccessible = true
return rfield.get(this) as? Boolean ?: false
} catch (e: Throwable) {
return false
}
}
AnimSet
这才是本篇的重点,首先跟AnimatorSet
没有半毛关系,AnimatorSet是个final类其次再它基础上修改,还不如重造一个容易。所以AnimSet
当然是再拥有优良血统的ValueAnim
上扩展出来的啦。为了避免AnimatorSet的坑AnimSet设计得很简单,如果想要AnimatorSet的的before
和after
的效果也可以很方便的扩展,为了偷懒不对是为了简单易懂,就不实现了毕竟没什么用。子动画播放时间只跟动画集合有关,通俗的讲假设动画集合播放1秒后开始播放第一个动画2秒后开始第二个动画,这样只要一个子动画相对集合的延迟时间就足够实现复杂动画了。任何复杂动画都能简单的实现,剩下的就是其它的优化了,比如子动画的播放方向,动画集合嵌套问题的处理了。代码重点在于addChildAnim()
添加子动画,animChildPlayTime()
计算子动画播放时间,onAnimationUpdate
刷新子动画。
package cn.wittyneko.anim
import android.animation.*
import android.view.animation.LinearInterpolator
/**
* Created by wittyneko on 2017/7/6.
*/
open class AnimSet : ValueAnim() {
companion object {
fun ofDef(): AnimSet {
return ofFloat(0f, 1f)
}
fun ofInt(vararg values: Int): AnimSet {
val anim = AnimSet()
anim.setIntValues(*values)
return anim
}
fun ofArgb(values: IntArray): AnimSet {
val anim = AnimSet()
anim.setIntValues(*values)
anim.setEvaluator(argbEvaluator)
return anim
}
fun ofFloat(vararg values: Float): AnimSet {
val anim = AnimSet()
anim.setFloatValues(*values)
return anim
}
fun ofPropertyValuesHolder(vararg values: PropertyValuesHolder): AnimSet {
val anim = AnimSet()
anim.setValues(*values)
return anim
}
fun ofObject(evaluator: TypeEvaluator<*>, vararg values: Any): AnimSet {
val anim = AnimSet()
anim.setObjectValues(*values)
anim.setEvaluator(evaluator)
return anim
}
}
var childAnimSet: HashSet<AnimWrapper> = hashSetOf()
init {
interpolator = LinearInterpolator()
}
/**
* 计算子动画播放时间
* @param delayed 子动画延迟时间
* @param duration 子动画时长
*
* @return 子动画当前播放时间
*/
fun animChildPlayTime(delayed: Long, duration: Long): Long {
var childPlayTime = animCurrentPlayTime - delayed
when {
childPlayTime < 0 -> {
childPlayTime = 0
}
childPlayTime > duration -> {
childPlayTime = duration
}
}
return childPlayTime
}
/**
* 添加子动画
* @param childAnim 子动画
* @param delayed 子动画延迟时间
* @param tag 子动画tag标签
*/
fun addChildAnim(childAnim: ValueAnimator, delayed: Long = 0, tag: String = AnimWrapper.EMPTY_TAG): AnimSet {
addChildAnim(AnimWrapper(childAnim, delayed, tag))
return this
}
/**
* 添加子动画
* @param child 子动画包装类
*
* @throws e duration grate than parent
*/
fun addChildAnim(child: AnimWrapper): AnimSet {
if (child.delayed + child.anim.duration > this.duration)
throw Exception("duration greater than parent")
childAnimSet.add(child)
return this
}
override fun onAnimationUpdate(animation: ValueAnimator?) {
super.onAnimationUpdate(animation)
childAnimSet.forEach {
//刷新子动画
val anim = it.anim
anim.currentPlayTime = animChildPlayTime(it.delayed, anim.duration)
if(anim is ValueAnim) {
anim.isAnimReverse = isAnimReverse
}
}
}
override fun onAnimationStart(animation: Animator?) {
super.onAnimationStart(animation)
childAnimSet.forEach {
val anim = it.anim
anim.listeners?.forEach {
it.onAnimationStart(anim)
}
}
}
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
childAnimSet.forEach {
val anim = it.anim
if (isAnimEnd) {
if (isAnimReverse)
anim.currentPlayTime = 0
else
anim.currentPlayTime = anim.duration
}
anim.listeners?.forEach {
it.onAnimationEnd(anim)
}
}
}
override fun onAnimationCancel(animation: Animator?) {
super.onAnimationCancel(animation)
childAnimSet.forEach {
val anim = it.anim
anim.listeners?.forEach {
it.onAnimationCancel(anim)
}
}
}
override fun onAnimationRepeat(animation: Animator?) {
super.onAnimationRepeat(animation)
childAnimSet.forEach {
val anim = it.anim
anim.listeners?.forEach {
it.onAnimationRepeat(anim)
}
}
}
/**
* 子动画包装类
*/
class AnimWrapper(
var anim: ValueAnimator,
var delayed: Long = 0,
var tag: String = AnimWrapper.EMPTY_TAG) {
companion object {
val EMPTY_TAG = ""
}
}
}
使用方法
见证奇迹的时刻,神兽保佑🙏代码无Bug。看看如何实现上面的动画要求。应该没什么需要解释的方案A只用一个AnimSet,方案B采用AnimSet嵌套AnimSet。
val msec = 1000L
val animTime = ValueAnim.ofFloat(0f, 1f)
animTime.interpolator = LinearInterpolator()
animTime.duration = msec * 3
animTime.addUpdateListener {
time.text = "time: ${animTime.animCurrentPlayTime}"
}
val objAnimRed = ObjectAnimator.ofFloat(red, "translationX", 0f, 300f)
objAnimRed.interpolator = LinearInterpolator()
objAnimRed.duration = msec * 3
val objAnimGreen = ObjectAnimator.ofFloat(green, "translationX", 0f, 300f)
objAnimGreen.interpolator = LinearInterpolator()
objAnimGreen.duration = msec * 2
val objAnimBlue = ObjectAnimator.ofFloat(blue, "translationX", 0f, 300f)
objAnimBlue.interpolator = LinearInterpolator()
objAnimBlue.duration = msec * 1
animSet = AnimSet.ofDef()
animSet.duration = msec * 3;
//Plan A
// animSet.addChildAnim(animTime)
// .addChildAnim(objAnimRed)
// .addChildAnim(objAnimGreen, msec * 1)
// .addChildAnim(objAnimBlue, msec * 2)
//Plan B
val childSet = AnimSet.ofDef()
childSet.duration = msec * 2
childSet.addChildAnim(objAnimGreen)
.addChildAnim(objAnimBlue, msec * 1)
animSet.addChildAnim(animTime)
.addChildAnim(objAnimRed)
.addChildAnim(childSet, msec * 1)
trigger.onClick {
animSet.animTrigger()
}
start.onClick {
animSet.animStart()
}
reverse.onClick {
animSet.animReverse()
}
pause.onClick {
animSet.pause()
}
resume.onClick {
animSet.resume()
}
end.onClick {
animSet.end()
}
cancel.onClick {
animSet.cancel()
}
还是贴下链接吧 https://github.com/wittyneko/libs-android/tree/master/base/src/main/kotlin/cn/wittyneko/anim
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。转载请保留作者及原文链接
网友评论