Last active
May 7, 2019 02:55
-
-
Save ipcjs/68f85d92b28fff72f572138f61c662f8 to your computer and use it in GitHub Desktop.
Animator的pause/resume/seek相关Api向下兼容
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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