欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

帧动画实现播放效果

程序员文章站 2022-03-25 23:38:08
...

帧动画实现播放效果

帧动画如何实现,网上一搜一堆,不再赘述。

这里记录下实现的一个需求。

播放卫星云图,所谓云图,就是很多张图片连起来。所以播放的效果就是帧动画的效果。

那么首先想到的实现方式就是帧动画-----AnimationDrawable。

也确实是这样,用帧动画实现此效果很简单,就是常规的实现方式。

主要实现如下:

private fun loadFrameImages() {
        for (pos in mImageList.indices) {
            loadImage(mImageList[pos], pos)
        }
    }

    private fun loadImage(url: String?, pos: Int) {
        runSafety {
            url?.let {
                val simpleTarget = object: SimpleTarget<Drawable>() {
                    override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
                        mHashMap[pos] = resource
                        addFrameToPlay()
                    }
                }

                Glide.with(this).load(it).into(simpleTarget)
            }
        }
    }

    private fun addFrameToPlay() {
        if (mHashMap.size > 0 && mHashMap.size == mImageList.size) {
            tvFrameEnd?.text = "" + mHashMap.size
            for (index in mImageList.indices) {
                mAnimationDrawable?.addFrame(mHashMap[index], 500)
            }
            ivCloud?.setImageDrawable(mAnimationDrawable)
            isPlaying = true
            mAnimationDrawable?.startAuto()
            ivPlay?.setBackgroundResource(R.mipmap.icon_cloud_pause)
            tvFrameStart?.text = "" + getCurrentIndex()
        }
    }

AnimationDrawable其实就是个Drawable,本身并没有监听。而我们需要监听动画播放开始,结束。所以自定义实现。

class CustomAnimationDrawable: AnimationDrawable() {

    private var mHandler = Handler()
    private val mAnimationRunnable = Runnable { dealWithCallback() }
    private var mMaxDuration: Long = 0L
    private var mOnFrameAnimationListener: OnFrameAnimationListener? = null

    private fun dealWithCallback() {
        //获取最后一帧,和当前帧做比较,如果相等,就结束动画,调用动画结束回调
        Trace.e("dealWithCallback", "当前帧是: $current")
        if (getFrame(numberOfFrames - 1) != current) {
            mOnFrameAnimationListener?.onFramePlaying(current)
            initHandlerFrame(current) //如果不是最后一帧,重新启动handler
        } else {
            onFinish() //如果是最后一帧,触发结束回调
        }
    }

    private fun initHandler(index: Int) {
        Trace.e("initHandler", "重新播放: $index")
        val delayMills = if (mMaxDuration == 0L) getMaxDurationIndex(index) else mMaxDuration
        mHandler?.postDelayed(mAnimationRunnable, delayMills)
    }

    private fun initHandlerFrame(frame: Drawable?) {
        val delayMills = if (mMaxDuration == 0L) getMaxDuration(frame) else mMaxDuration
        mHandler?.postDelayed(mAnimationRunnable, delayMills)
    }

    private fun onFinish() {
        mOnFrameAnimationListener?.onFrameEnd()
        mHandler.removeCallbacks(mAnimationRunnable)
    }


    /**
     * 说明:重写开始方法监听动画
     * 作者:
     * 添加时间:2020/7/9 16:28
     * 修改人:
     * 修改时间:2020/7/9 16:28
     */
    override fun start() {
        super.start()
    }

    override fun scheduleSelf(what: Runnable, `when`: Long) {
        super.scheduleSelf(what, `when`)
    }

    override fun stop() {
        super.stop()
    }

    fun startAuto() {
        initHandlerFrame(current)
        mOnFrameAnimationListener?.onFrameStart();//触发动画开始回调
        start()
    }

    fun startManual(currentIndex: Int) {
        initHandler(currentIndex)
        mOnFrameAnimationListener?.onFrameStart();//触发动画开始回调
        start()
    }

    fun stopManual(currentIndex: Int) {
        mHandler?.removeCallbacks(mAnimationRunnable)
        stop()
        mOnFrameAnimationListener?.onTempStop(current)
    }

    /**
     * 获取持续时间最长的帧的持续时间
     *
     * @return 时间  如果这一帧大于1秒,则返回 1 秒,否则返回这一帧的持续时间
     */
    private fun getMaxDuration(frame: Drawable?): Long {
        for (i in 0 until this.numberOfFrames) {
            if (mMaxDuration < getDuration(i)) {
                mMaxDuration = getDuration(i).toLong()
            }
        }
        return if (mMaxDuration > 1000) 1000 else mMaxDuration
    }

    private fun getMaxDurationIndex(index: Int): Long {
        for (i in 0 until index) {
            if (mMaxDuration < getDuration(i)) {
                mMaxDuration = getDuration(i).toLong()
            }
        }
        return if (mMaxDuration > 1000) 1000 else mMaxDuration
    }

    private fun getTotalDuration(): Long {
        var iDuration = 0
        for (i in 0 until this.numberOfFrames) {
            iDuration += getDuration(i)
        }
        return iDuration.toLong()
    }


    /**
     * 设置动画监听器
     *
     * @param onFrameAnimationListener 监听器
     */
    fun setOnFrameAnimationListener(onFrameAnimationListener: OnFrameAnimationListener?) {
        mOnFrameAnimationListener = onFrameAnimationListener
    }


    /**
     * 动画监听器
     */
    interface OnFrameAnimationListener {
        /**
         * 动画开始
         */
        fun onFrameStart()

        fun onFramePlaying(currentFrame: Drawable)

        fun onTempStop(currentFrame: Drawable?) //播放到某一帧,暂停

        /**
         * 动画结束
         */
        fun onFrameEnd()
    }

}

这样可以实现自动播放。

但是,无法实现暂停-播放效果。因为start()方法每次都是从第一帧开始播放的。

这种情况下就无法满足需求了。

那就换一种方式:

public class SurfaceViewAnimation extends TextureView implements TextureView.SurfaceTextureListener, Runnable {
    private String TAG = "SurfaceViewAnimation2";

    private boolean mIsThreadRunning = true; // 线程运行开关
    public boolean mIsDestroy = false;// 是否已经销毁

    private HashMap<Integer, Bitmap> mBitmapMaps = new HashMap<>();

    private int totalCount;//资源总数
    private Canvas mCanvas;
    private Bitmap mBitmap;// 显示的图片

    private int mCurrentIndext;// 当前动画播放的位置
    private int mGapTime = 50;// 每帧动画持续存在的时间

    private OnFrameFinishedListener mOnFrameFinishedListener;// 动画监听事件
    private Thread thread;

    Rect mSrcRect, mDestRect;

    public SurfaceViewAnimation(Context context) {
        this(context, null);
        initView();
    }

    public SurfaceViewAnimation(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView();
    }

    public SurfaceViewAnimation(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        initView();

    }

    private void initView() {
        try {
            initVariables();
            setOpaque(false);//设置背景透明,记住这里是[是否不透明]

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {

    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        try {
            destroy();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {

    }


    public void drawIndex(int pos) {
        mCurrentIndext = pos;
        drawView();
    }

    public void drawPreIndex(int pos) {
        if (pos < totalCount + 1) {
            mCurrentIndext = pos - 1;
            drawView();
        }
    }

    public void drawAfterIndex() {
        drawView();
    }


    /**
     * 绘制
     */
    private void drawView() {
        // 无资源文件退出
        if (mBitmapMaps == null) {
            mIsThreadRunning = false;
            return;
        }
        if (mOnFrameFinishedListener != null) {
            mOnFrameFinishedListener.onFramePlaying(mCurrentIndext);
        }
        Log.e(TAG, "drawView: mCurrentIndext=" + mCurrentIndext);
        Log.e(TAG, "drawView: Thread id = " + Thread.currentThread().getId());

        //防止是获取不到Canvas
        // 锁定画布
        mCanvas = lockCanvas();
        if (mCanvas == null) {
            return;
        }
        try {
            if (mCanvas != null && mBitmapMaps != null) {
                synchronized (mBitmapMaps) {
                    if (mBitmapMaps != null && mBitmapMaps.size() > 0 && mCurrentIndext > -1 && mCurrentIndext < mBitmapMaps.size()) {
                        mBitmap = mBitmapMaps.get(mCurrentIndext);
                    }
                }

                if (mBitmap == null || mBitmap.isRecycled()) {
                    return;
                }

                if (!mBitmap.isRecycled()) {
                    mBitmap.setHasAlpha(true);
                }

                Paint paint = new Paint();
                paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
                mCanvas.drawPaint(paint);
                paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
                paint.setAntiAlias(true);
                paint.setStyle(Paint.Style.STROKE);

                mSrcRect = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
                mDestRect = new Rect(0, 0, getWidth(), getHeight());
                mCanvas.drawBitmap(mBitmap, mSrcRect, mDestRect, paint);
            }

        } catch (Exception e) {
            Log.d(TAG, "drawView: e =" + e.toString());
            e.printStackTrace();
        } finally {
            if (mCurrentIndext == totalCount) {
                mIsThreadRunning = false;
                if (thread != null) {
                    thread.interrupt();
                    thread = null;
                }
                if (mOnFrameFinishedListener != null) {
                    mOnFrameFinishedListener.onFrameEnd();
                }
            } else {
                mCurrentIndext++;

                if (mCurrentIndext > totalCount) {
                    mCurrentIndext = 0;
                }
            }

            if (mCanvas != null) {
                // 将画布解锁并显示在屏幕上
                unlockCanvasAndPost(mCanvas);
            }
        }
    }

    @Override
    public void run() {
        if (mOnFrameFinishedListener != null) {
            mOnFrameFinishedListener.onFrameStart();
        }
        Log.e(TAG, "run: mIsThreadRunning=" + mIsThreadRunning);
        // 每隔150ms刷新屏幕
        while (mIsThreadRunning) {
            drawView();
            try {
                Thread.sleep(mGapTime);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        if (mOnFrameFinishedListener != null) {
            mOnFrameFinishedListener.onFrameStop();
        }
    }

    /**
     * 开始动画
     */
    public void start() {
        if (mCurrentIndext == totalCount) {
            mIsDestroy = false;
        }
        if (!mIsDestroy) {
            mCurrentIndext = 0;
            mIsThreadRunning = true;
            thread = new Thread(this);
            thread.start();
        } else {
            // 如果SurfaceHolder已经销毁抛出该异常
            try {
                throw new Exception("IllegalArgumentException:Are you sure the SurfaceHolder is not destroyed");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 防止内存泄漏
     */
    private void destroy() {
        //当surfaceView销毁时, 停止线程的运行. 避免surfaceView销毁了线程还在运行而报错.
        mIsThreadRunning = false;
        try {
            Thread.sleep(mGapTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        mIsDestroy = true;

        if (thread != null) {
            thread.interrupt();
            thread = null;
        }

        if (mBitmap != null && !mBitmap.isRecycled()) {
            mBitmap.recycle();
            mBitmap = null;
        }
    }

    void destroyListener() {
        if (mOnFrameFinishedListener != null) {
            mOnFrameFinishedListener = null;
        }
    }

    public void setBitmapArrays(HashMap<Integer, Bitmap> bitmapArrays) {
        synchronized (mBitmapMaps) {
            mBitmapMaps = bitmapArrays;
            totalCount = mBitmapMaps.size();
        }
    }

    /**
     * 设置每帧时间
     */
    public void setGapTime(int gapTime) {
        this.mGapTime = gapTime;
    }

    /**
     * 结束动画
     */
    public void stop() {
        mIsThreadRunning = false;
    }

    /**
     * 继续动画
     */
    public void reStart() {
        try {
            Log.e("reStart", "当前位置: " + mCurrentIndext);
            mIsThreadRunning = true;
            thread = new Thread(this);
            thread.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void pause() {
        mIsThreadRunning = false;
    }

    /**
     * 设置动画监听器
     */
    public void setOnFrameFinisedListener(OnFrameFinishedListener onFrameFinishedListener) {
        this.mOnFrameFinishedListener = onFrameFinishedListener;
    }

    /**
     * 动画监听器
     *
     * @author qike
     */
    public interface OnFrameFinishedListener {

        /**
         * 动画开始
         */
        void onFrameStart();

        void onFramePlaying(int framePos);

        /**
         * 动画结束
         */
        void onFrameStop();//停止(可能未播放完)

        void onFrameEnd();//播放完毕
    }

    /**
     * 当用户点击返回按钮时,停止线程,反转内存溢出
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // 当按返回键时,将线程停止,避免surfaceView销毁了,而线程还在运行而报错
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            mIsThreadRunning = false;
        }

        return super.onKeyDown(keyCode, event);
    }

    private SizeCalculator mSizeCalculator = null;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mSizeCalculator != null) {
            ViewSize size =
                    mSizeCalculator.measure(widthMeasureSpec, heightMeasureSpec);
            setMeasuredDimension(size.width, size.height);
        }
    }

    class ViewSize {
        int width = 0;
        int height = 0;
    }

    class SizeCalculator {
        private int mVideoWidth = 0;
        private int mVideoHeight = 0;

        void setVideoSize(int width, int height) {
            mVideoWidth = width;
            mVideoHeight = height;
        }

        ViewSize measure(int widthMeasureSpec, int heightMeasureSpec) {
            int width = View.getDefaultSize(mVideoWidth, widthMeasureSpec);
            int height = View.getDefaultSize(mVideoHeight, heightMeasureSpec);
            if (mVideoWidth > 0 && mVideoHeight > 0) {
                int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
                int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
                int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
                int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
                if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
                    // the size is fixed
                    width = widthSpecSize;
                    height = heightSpecSize;

                    // for compatibility, we adjust size based on aspect ratio
                    if (mVideoWidth * height < width * mVideoHeight) {
                        width = height * mVideoWidth / mVideoHeight;
                    } else if (mVideoWidth * height > width * mVideoHeight) {
                        height = width * mVideoHeight / mVideoWidth;
                    }
                } else if (widthSpecMode == MeasureSpec.EXACTLY) {
                    // only the width is fixed, adjust the height to match aspect ratio if possible
                    width = widthSpecSize;
                    height = width * mVideoHeight / mVideoWidth;
                    if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
                        // couldn't match aspect ratio within the constraints
                        height = heightSpecSize;
                    }
                } else if (heightSpecMode == MeasureSpec.EXACTLY) {
                    // only the height is fixed, adjust the width to match aspect ratio if possible
                    height = heightSpecSize;
                    width = height * mVideoWidth / mVideoHeight;
                    if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
                        // couldn't match aspect ratio within the constraints
                        width = widthSpecSize;
                    }
                } else {
                    // neither the width nor the height are fixed, try to use actual video size
                    width = mVideoWidth;
                    height = mVideoHeight;
                    if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
                        // too tall, decrease both width and height
                        height = heightSpecSize;
                        width = height * mVideoWidth / mVideoHeight;
                    }
                    if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
                        // too wide, decrease both width and height
                        width = widthSpecSize;
                        height = width * mVideoHeight / mVideoWidth;
                    }
                }
            }
            ViewSize size = new ViewSize();
            size.width = width;
            size.height = height;
            return size;
        }
    }


    private void initVariables() {
        mSizeCalculator = new SizeCalculator();
        mSizeCalculator.setVideoSize(0, 0);
    }

}

这样,可以基本实现需求。