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(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): 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(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(view) override fun onAnimationUpdate(animation: ValueAnimator?) { weakReference.get()?.onAnimatorUpdate(animation) } } // end of AnimatorUpdateListener }