SurfaceView做帧动画

前提条件我就不说了,相信能查找这个主题的都不是一般的Android开发者,我只讲核心的部分和该文章所能解决的问题。

之前搜索过很多文章,大都不理想,无法完美的解决自己的需求,然后自己根据资料整理了一下,可以说非常完美。

解决的问题:

1、帧特别多,如果同时decode做动画可能会出现OOM的情况

2、CPU、内存占用过高

3、可以多次做重复做动画

4、代码精简易懂效率高

 

废话不说,代码如下:

public class BoSurfaceView extends SurfaceView implements SurfaceHolder.Callback {


    private static final String TAG = "draw";
    private AssetManager assetManager;
    private List<String> mPathList;
    private DrawThread mDrawThread;
    /**
     * inBitmap如果图片不是太大的话不建议打开
     */
    private boolean mSupportInBitmap = true;
    private SparseArray<Bitmap> mBitmapCache;
    private long mFrameInterval = 48L;

    private int mTotalCount;
    /**
     * 传入inBitmap时的decode参数
     */
    private BitmapFactory.Options mOptions;
    // 由于属性动画最大会当大到1.4倍,为了View能正常显示,因此绘图时必须相应都缩小1.4倍
    private static final float ANIM_ZOOM = 1.4f;
    private SurfaceHolder mSurfaceHolder;
    private Canvas mCanvas;
    private Matrix mDrawMatrix;
    private State mState;
    private PaintFlagsDrawFilter mDrawPaint;
    private Paint mPaint = new Paint();
    private int mWidth;
    private int mHeight;

    public BoSurfaceView(Context context) {
        this(context, null);
    }

    public BoSurfaceView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BoSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        assetManager = context.getAssets();
        mWidth = context.getResources().getDimensionPixelSize(R.dimen.icon_width);
        mHeight = context.getResources().getDimensionPixelSize(R.dimen.icon_height);
        init();
    }

    private void init() {
        mSurfaceHolder = getHolder();
        mSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
        mSurfaceHolder.addCallback(this);
        setZOrderOnTop(true);
        mBitmapCache = new SparseArray<>();
        mOptions = new BitmapFactory.Options();
        mDrawPaint = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
        mPaint.setFilterBitmap(true);
        configureDrawMatrix();
    }

    public void start(State state) {
        mState = state;
        stop();
        start(mState.toString());
    }

    private void start(String assetsPath) {
        initPathList(FileUtil.getPathList(assetManager, assetsPath));
        startDraw();
    }

    @Override
    public void setVisibility(int visibility) {
        super.setVisibility(visibility);
        if (GONE == visibility || INVISIBLE == visibility) {
            stop();
            clearSurface();
        }
    }

    private void initPathList(List<String> pathList) {
        this.mPathList = pathList;
        if (mPathList == null || mPathList.size() == 0) {
            throw new NullPointerException("pathList is null.");
        }
        mTotalCount = mPathList.size();
        Collections.sort(pathList);
    }

    private void startDraw() {
        decodeBitmap();
        mDrawThread = new DrawThread(mSurfaceHolder);
        mDrawThread.startDraw();
    }

    private void decodeBitmap() {
        if (mSupportInBitmap) {
            mOptions.inMutable = true;
            mOptions.inSampleSize = 1;
        }
        mBitmapCache.clear();
        if (!mSupportInBitmap) {
            for (int i = 0; i < mTotalCount; i++) {
                mBitmapCache.put(i, decodeBitmapReal(mPathList.get(i)));
            }
        }

    }

    private void clearSurface() {
        try {
            mCanvas = mSurfaceHolder.lockCanvas();
            if (mCanvas != null) {
                mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (mCanvas != null) {
                mSurfaceHolder.unlockCanvasAndPost(mCanvas);
            }
        }
    }

    public void release() {
        if (mDrawThread != null) {
            mDrawThread.stopDraw();
            mDrawThread = null;
        }
        if (mOptions.inBitmap != null) {
            mOptions.inBitmap.recycle();
            mOptions.inBitmap = null;
        }
        mBitmapCache.clear();
        mSurfaceHolder.removeCallback(this);
        setVisibility(GONE);
    }

    public void stop() {
        if (mDrawThread != null) {
            mDrawThread.stopDraw();
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        stop();
    }

    private void configureDrawMatrix() {
        mDrawMatrix = new Matrix();
        float scale = 1.0f / ANIM_ZOOM;
        float dx = Math.round((mWidth - mWidth * scale) * 0.5f);
        float dy = Math.round((mHeight - mHeight * scale) * 0.5f);
        RectF srcRect = new RectF(0, 0, 160, 120);
        RectF dstRect = new RectF(0, 0, mWidth, mHeight);
        mDrawMatrix.setRectToRect(srcRect, dstRect, Matrix.ScaleToFit.CENTER);
        mDrawMatrix.postScale(scale, scale);
        mDrawMatrix.postTranslate(dx, dy);
    }

    private Bitmap decodeBitmapReal(String path) {
        Bitmap bitmap = null;
        try {
            bitmap = BitmapFactory.decodeStream(assetManager.open(path), null, mOptions);
            if (mSupportInBitmap) {
                mOptions.inBitmap = bitmap;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }

    private final class DrawThread extends Thread {

        private int position;
        private boolean isDrawing;
        private SurfaceHolder surfaceHolder;

        public DrawThread(SurfaceHolder surfaceHolder) {
            this.surfaceHolder = surfaceHolder;
        }

        @Override
        public void run() {
            while (isDrawing) {
                try {
                    drawBitmap();
                    Thread.sleep(mFrameInterval);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        private void drawBitmap() {
            Console.log.d(TAG, "drawBitmap...");
            if (position >= mTotalCount) {
                if (mState == State.LISTENING
                        || mState == State.THINKING) {
                    position = 0;
                } else {
                    isDrawing = false;
                    return;
                }
            }
            Bitmap currentBitmap;
            if (mSupportInBitmap) {
                currentBitmap = decodeBitmapReal(mPathList.get(position));
            } else {
                currentBitmap = mBitmapCache.get(position);
            }
            if (currentBitmap == null) {
                isDrawing = false;
                return;
            }
            synchronized (surfaceHolder) {
                try {
                    mCanvas = surfaceHolder.lockCanvas();
                    if (mCanvas != null) {
                        mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
                        Console.log.d(TAG, "draw position:" + position);
                        mCanvas.setDrawFilter(mDrawPaint);
                        mCanvas.drawBitmap(currentBitmap, mDrawMatrix, mPaint);
                        position++;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (mCanvas != null) {
                        surfaceHolder.unlockCanvasAndPost(mCanvas);
                    }
                }
            }
        }

        public void startDraw() {
            position = 0;
            isDrawing = true;
            start();
        }

        public void stopDraw() {
            isDrawing = false;
        }
    }
}

调用入口是start方法,传入的是一个路径,这里我封装的是一个枚举类型。

public enum State {
    /**
     * 动画状态说明
     */
    START("start"),
    RESUME("resume"),
    PAUSE("pause"),
    SUCCESS("success"),
    FAILURE("failure"),
    STOP("stop");

    private String text;

    State(String text) {
        this.text = text;
    }

    @Override
    public String toString() {
        return text;
    }
}

记住这里start可以自己根据需求去封装路径,我是从asset获取的,你可以自己封装。

需要了解更多的原理性问题或者做一些对比,可以参看一下博客和Github。

https://blog.csdn.net/binbinqq86/article/details/78127284

https://github.com/MasayukiSuda/FPSAnimator

https://github.com/yuyashuai/SilkyAnimation

这三篇文章写的都不错,但是都不适合我需要的场景,但是单独做个动画非常棒,我需要随时可以切换不同的动画状态,

使用过程非常麻烦,所以自己基于原理做了自己的封装,内存和cpu都经过严格测试非常稳定。

Thanks!

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值