沒有什么動畫是一個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)。
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的的before
和after
的效果也可以很方便的擴(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)載請保留作者及原文鏈接