美文网首页Kotlin专题Android开发经验谈Android知识
Android属性动画——没有什么动画是一个AnimSet不能解

Android属性动画——没有什么动画是一个AnimSet不能解

作者: EitanLiu | 来源:发表于2017-07-16 14:28 被阅读1025次

    没有什么动画是一个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.gif

    ValueAnim

    看看怎么填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的的beforeafter的效果也可以很方便的扩展,为了偷懒不对是为了简单易懂,就不实现了毕竟没什么用。子动画播放时间只跟动画集合有关,通俗的讲假设动画集合播放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 国际许可协议进行许可。转载请保留作者及原文链接

    相关文章

      网友评论

        本文标题:Android属性动画——没有什么动画是一个AnimSet不能解

        本文链接:https://www.haomeiwen.com/subject/ywbzhxtx.html