Skip to content

Instantly share code, notes, and snippets.

@ipcjs
Last active May 7, 2019 02:55
Show Gist options
  • Select an option

  • Save ipcjs/68f85d92b28fff72f572138f61c662f8 to your computer and use it in GitHub Desktop.

Select an option

Save ipcjs/68f85d92b28fff72f572138f61c662f8 to your computer and use it in GitHub Desktop.
Animator的pause/resume/seek相关Api向下兼容
package com.soling_ss.motorcycle.widget.anim
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.annotation.TargetApi
import android.os.Build
import com.soling_ss.motorcycle.util.ReadWriteProperty
import com.soling_ss.motorcycle.util.SystemUtil.api23
import com.soling_ss.motorcycle.util.SystemUtil.api26
import java.util.*
fun createAnimator(animator: ValueAnimator): IAnimator {
return if (newApi()) AnimatorV19(animator) else ValueAnimatorV11(animator)
}
fun createAnimatorSet(vararg animators: ValueAnimator, animatorSetInit: (AnimatorSet) -> Unit = {}): IAnimator {
return if (newApi()) {
AnimatorSetV19(*animators, animatorSetInit = animatorSetInit)
} else {
ValueAnimatorSetV11(*animators, animatorSetInit = animatorSetInit)
}
}
fun createAnimatorSet(animators: List<ValueAnimator>, animatorSetInit: (AnimatorSet) -> Unit = {}): IAnimator {
return createAnimatorSet(*animators.toTypedArray(), animatorSetInit = animatorSetInit)
}
/** @return 恒返回false; Api19上的seek相关Api依然有bug, 不使用, 详见: [AnimatorV19.resume] */
private inline fun newApi(): Boolean {
// return api19()
return false
}
interface IAnimator {
/**
* 获取内部的Animator,
* 注意: 该方法不保证返回的每次都是同一个对象... [ValueAnimatorSetV11] 每次start()时都会创建新的innerAnimator对象...
* */
fun getInnerAnimator(): Animator
fun addPauseListener(listener: AnimatorPauseListener)
fun removePauseListener(listener: AnimatorPauseListener)
fun addListener(listener: Animator.AnimatorListener)
fun removeListener(listener: Animator.AnimatorListener)
fun resume()
fun pause()
fun isPaused(): Boolean
fun isRunning(): Boolean
fun isStarted(): Boolean
fun start()
fun end()
fun cancel()
/**
* 一般来说可以认为: `fraction = currentPlayTime / duration`
*
* see:
* - [ValueAnimator.getCurrentPlayTime],
* - [ValueAnimator.setCurrentFraction]
* - [ValueAnimator.setCurrentFraction]
* */
fun getCurrentPlayTime(): Long
/** see: [getCurrentPlayTime]*/
fun setCurrentPlayTime(playTime: Long)
var duration: Long
interface AnimatorPauseListener {
fun onAnimationPause(animation: Animator)
fun onAnimationResume(animation: Animator)
}
interface AnimatorListener : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator)
override fun onAnimationEnd(animation: Animator)
override fun onAnimationCancel(animation: Animator)
override fun onAnimationStart(animation: Animator)
}
}
open abstract class AbsAnimator<T : Animator>(var animator: T) : IAnimator {
private val currentPlayTimeProperty: ReadWriteProperty<Long>? by lazy {
if (animator is ValueAnimator) {
object : ReadWriteProperty<Long> {
override fun get(): Long = (animator as ValueAnimator).currentPlayTime
override fun set(value: Long) {
(animator as ValueAnimator).currentPlayTime = value
}
}
} else if (api26() && animator is AnimatorSet) {
object : ReadWriteProperty<Long> {
@TargetApi(Build.VERSION_CODES.O)
override fun get(): Long = (animator as AnimatorSet).currentPlayTime
@TargetApi(Build.VERSION_CODES.O)
override fun set(value: Long) {
(animator as AnimatorSet).currentPlayTime = value
}
}
} else {
null
}
}
private var seekPlayTime = 0L
override fun getInnerAnimator(): Animator = animator
override fun addListener(listener: Animator.AnimatorListener) {
animator.addListener(listener)
}
override fun removeListener(listener: Animator.AnimatorListener) {
animator.removeListener(listener)
}
override fun isStarted(): Boolean = animator.isStarted()
override fun isRunning(): Boolean = animator.isRunning()
override fun start() {
animator.start()
// 实测Api16/19/21上, 在start()之前执行seek, start()之后会回归0点, 故需要手动保存/设置seekPlayTime
if (!api23() && currentPlayTimeProperty != null && seekPlayTime > 0) {
setCurrentPlayTime(seekPlayTime)
seekPlayTime = 0L
}
}
override fun cancel() = animator.cancel()
override fun end() = animator.end()
override fun setCurrentPlayTime(playTime: Long) {
currentPlayTimeProperty?.set(playTime) ?: throw UnsupportedOperationException()
if (!isStarted()) {
seekPlayTime = playTime
}
}
override fun getCurrentPlayTime(): Long {
return currentPlayTimeProperty?.get() ?: throw UnsupportedOperationException()
}
override var duration: Long
get() = animator.duration
set(value) {
animator.duration = value
}
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private open class AnimatorV19(animator: Animator) : AbsAnimator<Animator>(animator) {
data class AnimatorPauseListenerV19(private val listener: IAnimator.AnimatorPauseListener) : Animator.AnimatorPauseListener {
override fun onAnimationPause(animation: Animator) = listener.onAnimationPause(animation)
override fun onAnimationResume(animation: Animator) = listener.onAnimationResume(animation)
}
override fun addPauseListener(listener: IAnimator.AnimatorPauseListener) =
animator.addPauseListener(AnimatorPauseListenerV19(listener))
override fun removePauseListener(listener: IAnimator.AnimatorPauseListener) =
animator.removePauseListener(AnimatorPauseListenerV19(listener))
/**
* ## seek相关的bug
* pause状态时, 执行[setCurrentPlayTime], 再执行[resume], 恢复的位置会错乱
*
* 原因: 执行[pause]时会保存暂停时间, 执行[resume]时会依据当前时间和暂停时间的差值, 对mStartTime进行偏移;
* 同时, 当在播放状态执行[setCurrentPlayTime]时, 会直接依据fraction重设mStartTime;
* [resume]时的偏移量是依据老的mStartTime计算的, 应用到重设后的mStartTime上便导致了混乱...
* */
override fun resume() = animator.resume()
override fun pause() = animator.pause()
override fun isPaused(): Boolean = animator.isPaused()
}
/** 为了一致性, 创建的参数和[ValueAnimatorSetV11]相同 */
@TargetApi(Build.VERSION_CODES.KITKAT)
private class AnimatorSetV19(vararg animators: Animator, animatorSetInit: (AnimatorSet) -> Unit = {}) : AnimatorV19(AnimatorSet()) {
init {
val animatorSet = animator as AnimatorSet
animatorSetInit(animatorSet)
animatorSet.playSequentially(*animators)
}
}
private abstract class AnimatorV11<T : Animator>(animator: T) : AbsAnimator<T>(animator) {
protected val animatorListener = object : IAnimator.AnimatorListener {
private var cancelled = false
override fun onAnimationRepeat(animation: Animator) {
listeners.forEach({ it.onAnimationRepeat(animator) })
}
override fun onAnimationEnd(animation: Animator) {
if (cancelled && isPaused()) {
// pass
} else {
listeners.forEach({ it.onAnimationEnd(animator) })
}
}
override fun onAnimationCancel(animation: Animator) {
cancelled = true
if (isPaused()) {
pauseListeners.forEach { it.onAnimationPause(animator) }
} else {
listeners.forEach({ it.onAnimationCancel(animator) })
}
}
override fun onAnimationStart(animation: Animator) {
cancelled = false
if (isPaused()) {
pauseListeners.forEach({ it.onAnimationResume(animator) })
} else {
listeners.forEach({ it.onAnimationStart(animator) })
}
}
}
init {
animator.addListener(animatorListener)
}
protected var paused = false
protected var playTime = 0L
protected val pauseListeners: MutableList<IAnimator.AnimatorPauseListener> = ArrayList()
protected val listeners: MutableList<Animator.AnimatorListener> = ArrayList()
override fun start() {
if (paused) {
paused = false
playTime = 0L
}
super.start()
}
override fun cancel() {
if (paused) {
// 若当前是暂停状态, 此时animator并不在运行中, 不会触发回调, 需要手动触发
assert(!animator.isStarted() && !animator.isRunning())
paused = false
playTime = 0L
animatorListener.onAnimationCancel(animator)
animatorListener.onAnimationEnd(animator)
}
super.cancel()
}
override fun end() {
if (paused) {
// 同 [cancel()]
assert(!animator.isStarted() && !animator.isRunning())
paused = false
playTime = 0L
animatorListener.onAnimationEnd(animator)
}
super.end()
}
override fun isPaused(): Boolean = paused
override fun isStarted(): Boolean {
return isPaused() || super.isStarted()
}
override fun isRunning(): Boolean {
return isPaused() || super.isRunning()
}
override fun addPauseListener(listener: IAnimator.AnimatorPauseListener) {
pauseListeners.add(listener)
}
override fun removePauseListener(listener: IAnimator.AnimatorPauseListener) {
pauseListeners.remove(listener)
}
override fun addListener(listener: Animator.AnimatorListener) {
listeners.add(listener)
}
override fun removeListener(listener: Animator.AnimatorListener) {
listeners.remove(listener)
}
}
private class ValueAnimatorV11(animator: ValueAnimator) : AnimatorV11<ValueAnimator>(animator) {
override fun pause() {
if (!isStarted()) return // 开始之后才能暂停...
if (!paused) {
paused = true // 进入暂停状态
playTime = animator.currentPlayTime
animator.cancel()
}
}
override fun resume() {
if (paused) {
// 在Api19上一定要先start()再seek, 否则会从头开始
// 在Api26上, 顺序随便...
animator.start()
animator.currentPlayTime = playTime
paused = false // 退出暂停状态
}
}
override fun setCurrentPlayTime(playTime: Long) {
super.setCurrentPlayTime(playTime)
if (isPaused()) {
// 暂停状态时, 需要更新保存的playTime
this.playTime = playTime
}
}
}
/** 内部的animatorSet存在反复创建的情况, 故需要[animatorSetInit]来对animatorSet进行初始化 */
private class ValueAnimatorSetV11(private vararg val animators: ValueAnimator, private val animatorSetInit: (AnimatorSet) -> Unit = {}) : AnimatorV11<AnimatorSet>(AnimatorSet()) {
private var pauseIndex = 0
init {
animatorSetInit(animator)
animator.playSequentially(*animators)
}
private fun recreateAnimator(vararg animators: ValueAnimator) {
// 清除老的
val oldAnimator = animator
oldAnimator.removeListener(animatorListener)
// 设置新的
animator = AnimatorSet()
animator.addListener(animatorListener)
animatorSetInit(animator)
animator.playSequentially(*animators)
}
override fun start() {
recreateAnimator(*animators)
super.start()
}
/** 参见: [ValueAnimatorV11.pause] */
override fun pause() {
if (!isStarted()) return
if (!paused) {
paused = true
for ((index, child) in animator.childAnimations.withIndex()) {
if (child.isStarted()) {
playTime = (child as ValueAnimator).currentPlayTime
pauseIndex = index
animator.cancel()
break
}
}
}
return
}
/** 参见: [ValueAnimatorV11.resume] */
override fun resume() {
if (paused) {
val oldChildren = animator.childAnimations as List<ValueAnimator>
val children = oldChildren.subList(pauseIndex, oldChildren.size)
recreateAnimator(*children.toTypedArray())
animator.start()
if (children.size > 0) {
children[0].currentPlayTime = playTime
}
paused = false
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment