Android屬性動畫——沒有什么動畫是一個AnimSet不能解決的

沒有什么動畫是一個AnimSet不能解決的,如果有那就再來一個AnimSet。項目是Kotlin寫的也不復(fù)雜,不懂Kotlin剛好可以學(xué)學(xué)。

系統(tǒng)動畫那些坑

現(xiàn)在應(yīng)該沒人使用View動畫了吧,還再使用怕是學(xué)的假Android了,所以這邊講的是屬性動畫。

先說說ValueAnimator

  • 不提供動畫方向判斷方法,這點一直很困惑,查看源碼發(fā)現(xiàn)有一個很明顯的字段mReversing,跟蹤下方法發(fā)現(xiàn)shouldPlayBackward()方法,興高采烈的去調(diào)用時才發(fā)現(xiàn)是私有方法??。無奈之下只能用反射調(diào)用,結(jié)果在5.0系統(tǒng)突然崩了,去查5.0源碼發(fā)現(xiàn)居然是不同字段mPlayingBackwards,再去查5.1的源碼發(fā)現(xiàn)居然兩個都存在,就不能專一點嗎,8.0更新又不能獲取了解決方法待定,感覺不能再玩反射太不靠譜了。
  • revese() 方向是個問題,字面上理解是反轉(zhuǎn)的意思,不就是到著播放嘛,但是當(dāng)你倒著播放時再掉reverse()又給正向播放了,然后還不告訴你方向不帶這么玩的啊??。
  • 播放時間,還是不給方向判斷的坑??。

再說說AnimatorSet

該怎么說遠(yuǎn)看像越野車近看才發(fā)現(xiàn)是拖拉機(jī),能存在并被使用簡直是個奇跡。

  • reverse()是隱藏方法也就是說不能用了,忍了看在能播放那么多動畫的面子上。
  • 播放存在問題,當(dāng)一個動畫沒結(jié)束再次start()會發(fā)現(xiàn)前面播放過的動畫居然不播放了,這還怎么玩啊。
  • 看似播放方式多樣但并沒有什么卵用,with,before,after包含了多種播放方式,但是實際使用時基本都是一個動畫沒結(jié)束就開始下一個動畫,這中理想的動畫播放方式根本用不到。

實現(xiàn)效果

動畫要求:總動畫時間3s,紅塊直接開始時間3s,綠塊1s后開始時間2s,藍(lán)塊2s后開始時間1s,動畫執(zhí)行過程中可以隨時來回切換,可以暫停、繼續(xù)、結(jié)束和取消,可以想象下使用系統(tǒng)提供的方式要怎么實現(xiàn)。

Kapture 2017-07-16 at 13.35.24.gif

ValueAnim

看看怎么填ValueAnimator的坑,獲取播放方向問題,通過反射獲取播放方向,利用Kotlin擴(kuò)展方法的特性,對ValueAnimator進(jìn)行擴(kuò)展,但是mReversing的值只有再動畫播放時才有效果,動畫結(jié)束就被初始化為false了,結(jié)果還得在結(jié)束前把方向保存下來。Kotlin并不能真正給添加個參數(shù)到某個類,只能繼承ValueAnimator進(jìn)行擴(kuò)展了。其次播放控制問題,為了保留原來的方法和避免reverse()存在的問題,添加了幾個方法animStart()正向播放,animReverse()反向播放,animTrigger()切換方向(類似reverse()作用)。代碼很簡單并注釋了以后就用它來替代ValueAnimator了,本來想也改下ObjectAnimator發(fā)現(xiàn)是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沒有半毛關(guān)系,AnimatorSet是個final類其次再它基礎(chǔ)上修改,還不如重造一個容易。所以AnimSet當(dāng)然是再擁有優(yōu)良血統(tǒng)的ValueAnim上擴(kuò)展出來的啦。為了避免AnimatorSet的坑AnimSet設(shè)計得很簡單,如果想要AnimatorSet的的beforeafter的效果也可以很方便的擴(kuò)展,為了偷懶不對是為了簡單易懂,就不實現(xiàn)了畢竟沒什么用。子動畫播放時間只跟動畫集合有關(guān),通俗的講假設(shè)動畫集合播放1秒后開始播放第一個動畫2秒后開始第二個動畫,這樣只要一個子動畫相對集合的延遲時間就足夠?qū)崿F(xiàn)復(fù)雜動畫了。任何復(fù)雜動畫都能簡單的實現(xiàn),剩下的就是其它的優(yōu)化了,比如子動畫的播放方向,動畫集合嵌套問題的處理了。代碼重點在于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 子動畫當(dāng)前播放時間
     */
    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標(biāo)簽
     */
    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。看看如何實現(xiàn)上面的動畫要求。應(yīng)該沒什么需要解釋的方案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

本作品采用知識共享署名-非商業(yè)性使用-相同方式共享 4.0 國際許可協(xié)議進(jìn)行許可。轉(zhuǎn)載請保留作者及原文鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內(nèi)容