android 动画动态消失,Android属性动画——没有什么动画是一个AnimSet不能解决的...
沒(méi)有什么動(dòng)畫是一個(gè)AnimSet不能解決的,如果有那就再來(lái)一個(gè)AnimSet。項(xiàng)目是Kotlin寫的也不復(fù)雜,不懂Kotlin剛好可以學(xué)學(xué)。
系統(tǒng)動(dòng)畫那些坑
現(xiàn)在應(yīng)該沒(méi)人使用View動(dòng)畫了吧,還再使用怕是學(xué)的假Android了,所以這邊講的是屬性動(dòng)畫。
先說(shuō)說(shuō)ValueAnimator
不提供動(dòng)畫方向判斷方法,這點(diǎn)一直很困惑,查看源碼發(fā)現(xiàn)有一個(gè)很明顯的字段mReversing,跟蹤下方法發(fā)現(xiàn)shouldPlayBackward()方法,興高采烈的去調(diào)用時(shí)才發(fā)現(xiàn)是私有方法😓。無(wú)奈之下只能用反射調(diào)用,結(jié)果在5.0系統(tǒng)突然崩了,去查5.0源碼發(fā)現(xiàn)居然是不同字段mPlayingBackwards,再去查5.1的源碼發(fā)現(xiàn)居然兩個(gè)都存在,就不能專一點(diǎn)嗎,8.0更新又不能獲取了解決方法待定,感覺(jué)不能再玩反射太不靠譜了。
revese() 方向是個(gè)問(wèn)題,字面上理解是反轉(zhuǎn)的意思,不就是到著播放嘛,但是當(dāng)你倒著播放時(shí)再掉reverse()又給正向播放了,然后還不告訴你方向不帶這么玩的啊😢。
播放時(shí)間,還是不給方向判斷的坑😢。
再說(shuō)說(shuō)AnimatorSet
該怎么說(shuō)遠(yuǎn)看像越野車近看才發(fā)現(xiàn)是拖拉機(jī),能存在并被使用簡(jiǎn)直是個(gè)奇跡。
reverse()是隱藏方法也就是說(shuō)不能用了,忍了看在能播放那么多動(dòng)畫的面子上。
播放存在問(wèn)題,當(dāng)一個(gè)動(dòng)畫沒(méi)結(jié)束再次start()會(huì)發(fā)現(xiàn)前面播放過(guò)的動(dòng)畫居然不播放了,這還怎么玩啊。
看似播放方式多樣但并沒(méi)有什么卵用,with,before,after包含了多種播放方式,但是實(shí)際使用時(shí)基本都是一個(gè)動(dòng)畫沒(méi)結(jié)束就開(kāi)始下一個(gè)動(dòng)畫,這中理想的動(dòng)畫播放方式根本用不到。
實(shí)現(xiàn)效果
動(dòng)畫要求:總動(dòng)畫時(shí)間3s,紅塊直接開(kāi)始時(shí)間3s,綠塊1s后開(kāi)始時(shí)間2s,藍(lán)塊2s后開(kāi)始時(shí)間1s,動(dòng)畫執(zhí)行過(guò)程中可以隨時(shí)來(lái)回切換,可以暫停、繼續(xù)、結(jié)束和取消,可以想象下使用系統(tǒng)提供的方式要怎么實(shí)現(xiàn)。
Kapture 2017-07-16 at 13.35.24.gif
ValueAnim
看看怎么填ValueAnimator的坑,獲取播放方向問(wèn)題,通過(guò)反射獲取播放方向,利用Kotlin擴(kuò)展方法的特性,對(duì)ValueAnimator進(jìn)行擴(kuò)展,但是mReversing的值只有再動(dòng)畫播放時(shí)才有效果,動(dòng)畫結(jié)束就被初始化為false了,結(jié)果還得在結(jié)束前把方向保存下來(lái)。Kotlin并不能真正給添加個(gè)參數(shù)到某個(gè)類,只能繼承ValueAnimator進(jìn)行擴(kuò)展了。其次播放控制問(wèn)題,為了保留原來(lái)的方法和避免reverse()存在的問(wèn)題,添加了幾個(gè)方法animStart()正向播放,animReverse()反向播放,animTrigger()切換方向(類似reverse()作用)。代碼很簡(jiǎn)單并注釋了以后就用它來(lái)替代ValueAnimator了,本來(lái)想也改下ObjectAnimator發(fā)現(xiàn)是final無(wú)法繼承,看在沒(méi)什么大問(wèn)題的份上就放過(guò)它了。
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
}
//動(dòng)畫播放時(shí)間
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
// 動(dòng)畫播放時(shí)方向 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
}
}
// 動(dòng)畫播放時(shí)方向 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
這才是本篇的重點(diǎn),首先跟AnimatorSet沒(méi)有半毛關(guān)系,AnimatorSet是個(gè)final類其次再它基礎(chǔ)上修改,還不如重造一個(gè)容易。所以AnimSet當(dāng)然是再擁有優(yōu)良血統(tǒng)的ValueAnim上擴(kuò)展出來(lái)的啦。為了避免AnimatorSet的坑AnimSet設(shè)計(jì)得很簡(jiǎn)單,如果想要AnimatorSet的的before和after的效果也可以很方便的擴(kuò)展,為了偷懶不對(duì)是為了簡(jiǎn)單易懂,就不實(shí)現(xiàn)了畢竟沒(méi)什么用。子動(dòng)畫播放時(shí)間只跟動(dòng)畫集合有關(guān),通俗的講假設(shè)動(dòng)畫集合播放1秒后開(kāi)始播放第一個(gè)動(dòng)畫2秒后開(kāi)始第二個(gè)動(dòng)畫,這樣只要一個(gè)子動(dòng)畫相對(duì)集合的延遲時(shí)間就足夠?qū)崿F(xiàn)復(fù)雜動(dòng)畫了。任何復(fù)雜動(dòng)畫都能簡(jiǎn)單的實(shí)現(xiàn),剩下的就是其它的優(yōu)化了,比如子動(dòng)畫的播放方向,動(dòng)畫集合嵌套問(wèn)題的處理了。代碼重點(diǎn)在于addChildAnim()添加子動(dòng)畫,animChildPlayTime()計(jì)算子動(dòng)畫播放時(shí)間,onAnimationUpdate刷新子動(dòng)畫。
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 = hashSetOf()
init {
interpolator = LinearInterpolator()
}
/**
* 計(jì)算子動(dòng)畫播放時(shí)間
* @param delayed 子動(dòng)畫延遲時(shí)間
* @param duration 子動(dòng)畫時(shí)長(zhǎng)
*
* @return 子動(dòng)畫當(dāng)前播放時(shí)間
*/
fun animChildPlayTime(delayed: Long, duration: Long): Long {
var childPlayTime = animCurrentPlayTime - delayed
when {
childPlayTime < 0 -> {
childPlayTime = 0
}
childPlayTime > duration -> {
childPlayTime = duration
}
}
return childPlayTime
}
/**
* 添加子動(dòng)畫
* @param childAnim 子動(dòng)畫
* @param delayed 子動(dòng)畫延遲時(shí)間
* @param tag 子動(dòng)畫tag標(biāo)簽
*/
fun addChildAnim(childAnim: ValueAnimator, delayed: Long = 0, tag: String = AnimWrapper.EMPTY_TAG): AnimSet {
addChildAnim(AnimWrapper(childAnim, delayed, tag))
return this
}
/**
* 添加子動(dòng)畫
* @param child 子動(dòng)畫包裝類
*
* @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 {
//刷新子動(dòng)畫
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)
}
}
}
/**
* 子動(dòng)畫包裝類
*/
class AnimWrapper(
var anim: ValueAnimator,
var delayed: Long = 0,
var tag: String = AnimWrapper.EMPTY_TAG) {
companion object {
val EMPTY_TAG = ""
}
}
}
使用方法
見(jiàn)證奇跡的時(shí)刻,神獸保佑🙏代碼無(wú)Bug。看看如何實(shí)現(xiàn)上面的動(dòng)畫要求。應(yīng)該沒(méi)什么需要解釋的方案A只用一個(gè)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()
}
總結(jié)
以上是生活随笔為你收集整理的android 动画动态消失,Android属性动画——没有什么动画是一个AnimSet不能解决的...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: c语言的运算符表格,C语言教案(运算符和
- 下一篇: android 按钮吐司,Android