合并6.8.0代码
This commit is contained in:
@@ -0,0 +1,329 @@
|
||||
package com.opensource.svgaplayer
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.ValueAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.animation.LinearInterpolator
|
||||
import android.widget.ImageView
|
||||
import com.opensource.svgaplayer.utils.SVGARange
|
||||
import com.opensource.svgaplayer.utils.log.LogUtils
|
||||
import java.lang.ref.WeakReference
|
||||
import java.net.URL
|
||||
|
||||
/**
|
||||
* Created by PonyCui on 2017/3/29.
|
||||
*/
|
||||
open class SVGAImageView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : ImageView(context, attrs, defStyleAttr) {
|
||||
|
||||
private val TAG = "SVGAImageView"
|
||||
|
||||
enum class FillMode {
|
||||
Backward,
|
||||
Forward,
|
||||
Clear,
|
||||
}
|
||||
|
||||
var isAnimating = false
|
||||
private set
|
||||
|
||||
var loops = 0
|
||||
|
||||
@Deprecated(
|
||||
"It is recommended to use clearAfterDetached, or manually call to SVGAVideoEntity#clear." +
|
||||
"If you just consider cleaning up the canvas after playing, you can use FillMode#Clear.",
|
||||
level = DeprecationLevel.WARNING
|
||||
)
|
||||
var clearsAfterStop = false
|
||||
var clearsAfterDetached = false
|
||||
var fillMode: FillMode = FillMode.Forward
|
||||
var callback: SVGACallback? = null
|
||||
|
||||
private var mAnimator: ValueAnimator? = null
|
||||
private var mItemClickAreaListener: SVGAClickAreaListener? = null
|
||||
private var mAntiAlias = true
|
||||
private var mAutoPlay = true
|
||||
private val mAnimatorListener = AnimatorListener(this)
|
||||
private val mAnimatorUpdateListener = AnimatorUpdateListener(this)
|
||||
private var mStartFrame = 0
|
||||
private var mEndFrame = 0
|
||||
|
||||
init {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
this.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
|
||||
}
|
||||
attrs?.let { loadAttrs(it) }
|
||||
}
|
||||
|
||||
private fun loadAttrs(attrs: AttributeSet) {
|
||||
val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.SVGAImageView, 0, 0)
|
||||
loops = typedArray.getInt(R.styleable.SVGAImageView_loopCount, 0)
|
||||
clearsAfterStop = typedArray.getBoolean(R.styleable.SVGAImageView_clearsAfterStop, false)
|
||||
clearsAfterDetached = typedArray.getBoolean(R.styleable.SVGAImageView_clearsAfterDetached, false)
|
||||
mAntiAlias = typedArray.getBoolean(R.styleable.SVGAImageView_antiAlias, true)
|
||||
mAutoPlay = typedArray.getBoolean(R.styleable.SVGAImageView_autoPlay, true)
|
||||
typedArray.getString(R.styleable.SVGAImageView_fillMode)?.let {
|
||||
when (it) {
|
||||
"0" -> {
|
||||
fillMode = FillMode.Backward
|
||||
}
|
||||
"1" -> {
|
||||
fillMode = FillMode.Forward
|
||||
}
|
||||
"2" -> {
|
||||
fillMode = FillMode.Clear
|
||||
}
|
||||
}
|
||||
}
|
||||
typedArray.getString(R.styleable.SVGAImageView_source)?.let {
|
||||
parserSource(it)
|
||||
}
|
||||
typedArray.recycle()
|
||||
}
|
||||
|
||||
private fun parserSource(source: String) {
|
||||
val refImgView = WeakReference<SVGAImageView>(this)
|
||||
val parser = SVGAParser(context)
|
||||
if (source.startsWith("http://") || source.startsWith("https://")) {
|
||||
parser.decodeFromURL(URL(source), createParseCompletion(refImgView))
|
||||
} else {
|
||||
parser.decodeFromAssets(source, createParseCompletion(refImgView))
|
||||
}
|
||||
}
|
||||
|
||||
private fun createParseCompletion(ref: WeakReference<SVGAImageView>): SVGAParser.ParseCompletion {
|
||||
return object : SVGAParser.ParseCompletion {
|
||||
override fun onComplete(videoItem: SVGAVideoEntity) {
|
||||
ref.get()?.startAnimation(videoItem)
|
||||
}
|
||||
|
||||
override fun onError() {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAnimation(videoItem: SVGAVideoEntity) {
|
||||
this@SVGAImageView.post {
|
||||
videoItem.antiAlias = mAntiAlias
|
||||
setVideoItem(videoItem)
|
||||
getSVGADrawable()?.scaleType = scaleType
|
||||
if (mAutoPlay) {
|
||||
startAnimation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startAnimation() {
|
||||
startAnimation(null, false)
|
||||
}
|
||||
|
||||
fun startAnimation(range: SVGARange?, reverse: Boolean = false) {
|
||||
stopAnimation(false)
|
||||
play(range, reverse)
|
||||
}
|
||||
|
||||
private fun play(range: SVGARange?, reverse: Boolean) {
|
||||
LogUtils.info(TAG, "================ start animation ================")
|
||||
val drawable = getSVGADrawable() ?: return
|
||||
setupDrawable()
|
||||
mStartFrame = Math.max(0, range?.location ?: 0)
|
||||
val videoItem = drawable.videoItem
|
||||
mEndFrame = Math.min(videoItem.frames - 1, ((range?.location ?: 0) + (range?.length ?: Int.MAX_VALUE) - 1))
|
||||
val animator = ValueAnimator.ofInt(mStartFrame, mEndFrame)
|
||||
animator.interpolator = LinearInterpolator()
|
||||
animator.duration = ((mEndFrame - mStartFrame + 1) * (1000 / videoItem.FPS) / generateScale()).toLong()
|
||||
animator.repeatCount = if (loops <= 0) 99999 else loops - 1
|
||||
animator.addUpdateListener(mAnimatorUpdateListener)
|
||||
animator.addListener(mAnimatorListener)
|
||||
if (reverse) {
|
||||
animator.reverse()
|
||||
} else {
|
||||
animator.start()
|
||||
}
|
||||
mAnimator = animator
|
||||
}
|
||||
|
||||
private fun setupDrawable() {
|
||||
val drawable = getSVGADrawable() ?: return
|
||||
drawable.cleared = false
|
||||
drawable.scaleType = scaleType
|
||||
}
|
||||
|
||||
private fun getSVGADrawable(): SVGADrawable? {
|
||||
return drawable as? SVGADrawable
|
||||
}
|
||||
|
||||
@Suppress("UNNECESSARY_SAFE_CALL")
|
||||
private fun generateScale(): Double {
|
||||
var scale = 1.0
|
||||
try {
|
||||
val animatorClass = Class.forName("android.animation.ValueAnimator") ?: return scale
|
||||
val getMethod = animatorClass.getDeclaredMethod("getDurationScale") ?: return scale
|
||||
scale = (getMethod.invoke(animatorClass) as Float).toDouble()
|
||||
if (scale == 0.0) {
|
||||
val setMethod = animatorClass.getDeclaredMethod("setDurationScale",Float::class.java) ?: return scale
|
||||
setMethod.isAccessible = true
|
||||
setMethod.invoke(animatorClass,1.0f)
|
||||
scale = 1.0
|
||||
LogUtils.info(TAG,
|
||||
"The animation duration scale has been reset to" +
|
||||
" 1.0x, because you closed it on developer options.")
|
||||
}
|
||||
} catch (ignore: Exception) {
|
||||
ignore.printStackTrace()
|
||||
}
|
||||
return scale
|
||||
}
|
||||
|
||||
private fun onAnimatorUpdate(animator: ValueAnimator?) {
|
||||
val drawable = getSVGADrawable() ?: return
|
||||
drawable.currentFrame = animator?.animatedValue as Int
|
||||
val percentage = (drawable.currentFrame + 1).toDouble() / drawable.videoItem.frames.toDouble()
|
||||
callback?.onStep(drawable.currentFrame, percentage)
|
||||
}
|
||||
|
||||
private fun onAnimationEnd(animation: Animator?) {
|
||||
isAnimating = false
|
||||
stopAnimation()
|
||||
val drawable = getSVGADrawable()
|
||||
if (drawable != null) {
|
||||
when (fillMode) {
|
||||
FillMode.Backward -> {
|
||||
drawable.currentFrame = mStartFrame
|
||||
}
|
||||
FillMode.Forward -> {
|
||||
drawable.currentFrame = mEndFrame
|
||||
}
|
||||
FillMode.Clear -> {
|
||||
drawable.cleared = true
|
||||
}
|
||||
}
|
||||
}
|
||||
callback?.onFinished()
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
getSVGADrawable()?.cleared = true
|
||||
getSVGADrawable()?.clear()
|
||||
// 清除对 drawable 的引用
|
||||
setImageDrawable(null)
|
||||
}
|
||||
|
||||
fun pauseAnimation() {
|
||||
stopAnimation(false)
|
||||
callback?.onPause()
|
||||
}
|
||||
|
||||
fun stopAnimation() {
|
||||
stopAnimation(clear = clearsAfterStop)
|
||||
}
|
||||
|
||||
fun stopAnimation(clear: Boolean) {
|
||||
mAnimator?.cancel()
|
||||
mAnimator?.removeAllListeners()
|
||||
mAnimator?.removeAllUpdateListeners()
|
||||
getSVGADrawable()?.stop()
|
||||
getSVGADrawable()?.cleared = clear
|
||||
}
|
||||
|
||||
fun setVideoItem(videoItem: SVGAVideoEntity?) {
|
||||
setVideoItem(videoItem, SVGADynamicEntity())
|
||||
}
|
||||
|
||||
fun setVideoItem(videoItem: SVGAVideoEntity?, dynamicItem: SVGADynamicEntity?) {
|
||||
if (videoItem == null) {
|
||||
setImageDrawable(null)
|
||||
} else {
|
||||
val drawable = SVGADrawable(videoItem, dynamicItem ?: SVGADynamicEntity())
|
||||
drawable.cleared = true
|
||||
setImageDrawable(drawable)
|
||||
}
|
||||
}
|
||||
|
||||
fun stepToFrame(frame: Int, andPlay: Boolean) {
|
||||
pauseAnimation()
|
||||
val drawable = getSVGADrawable() ?: return
|
||||
drawable.currentFrame = frame
|
||||
if (andPlay) {
|
||||
startAnimation()
|
||||
mAnimator?.let {
|
||||
it.currentPlayTime = (Math.max(0.0f, Math.min(1.0f, (frame.toFloat() / drawable.videoItem.frames.toFloat()))) * it.duration).toLong()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stepToPercentage(percentage: Double, andPlay: Boolean) {
|
||||
val drawable = drawable as? SVGADrawable ?: return
|
||||
var frame = (drawable.videoItem.frames * percentage).toInt()
|
||||
if (frame >= drawable.videoItem.frames && frame > 0) {
|
||||
frame = drawable.videoItem.frames - 1
|
||||
}
|
||||
stepToFrame(frame, andPlay)
|
||||
}
|
||||
|
||||
fun setOnAnimKeyClickListener(clickListener : SVGAClickAreaListener){
|
||||
mItemClickAreaListener = clickListener
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
||||
if (event?.action != MotionEvent.ACTION_DOWN) {
|
||||
return super.onTouchEvent(event)
|
||||
}
|
||||
val drawable = getSVGADrawable() ?: return super.onTouchEvent(event)
|
||||
for ((key, value) in drawable.dynamicItem.mClickMap) {
|
||||
if (event.x >= value[0] && event.x <= value[2] && event.y >= value[1] && event.y <= value[3]) {
|
||||
mItemClickAreaListener?.let {
|
||||
it.onClick(key)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.onTouchEvent(event)
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
stopAnimation(clearsAfterDetached)
|
||||
if (clearsAfterDetached) {
|
||||
clear()
|
||||
}
|
||||
}
|
||||
|
||||
private class AnimatorListener(view: SVGAImageView) : Animator.AnimatorListener {
|
||||
private val weakReference = WeakReference<SVGAImageView>(view)
|
||||
|
||||
override fun onAnimationRepeat(animation: Animator?) {
|
||||
weakReference.get()?.callback?.onRepeat()
|
||||
}
|
||||
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
weakReference.get()?.onAnimationEnd(animation)
|
||||
}
|
||||
|
||||
override fun onAnimationCancel(animation: Animator?) {
|
||||
weakReference.get()?.isAnimating = false
|
||||
}
|
||||
|
||||
override fun onAnimationStart(animation: Animator?) {
|
||||
weakReference.get()?.isAnimating = true
|
||||
}
|
||||
} // end of AnimatorListener
|
||||
|
||||
|
||||
private class AnimatorUpdateListener(view: SVGAImageView) : ValueAnimator.AnimatorUpdateListener {
|
||||
private val weakReference = WeakReference<SVGAImageView>(view)
|
||||
|
||||
override fun onAnimationUpdate(animation: ValueAnimator?) {
|
||||
weakReference.get()?.onAnimatorUpdate(animation)
|
||||
}
|
||||
} // end of AnimatorUpdateListener
|
||||
}
|
||||
Reference in New Issue
Block a user