SurfaceView简单使用--可做帧动画

来源:互联网 发布:苹果教育软件 编辑:程序博客网 时间:2024/06/01 22:09

公司做视频直播的礼物动效。
前期调研的过程中发现很多竞品竟然都是利用帧动画做的。
利用帧动画当然不能直接加载多张图片,要知道最大的礼物有一百多张图片,有OOM的风险。
所以利用SurfaceView实现了帧动画。这样可以控制内存一直处于非常底的范围内抖动。所占的CPU也比较小。

另外一种实现方案就是利用webp,直接播放webp.
webp相较与SurfaceView的帧动画优势就是内存占用更小,但是CPU占比会稍微大一点。
下面就是利用SurfaceView实现的帧动画:

public class SpecialGiftSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {    private static final String TAG = "Surface";    private static final long INTERVAL_TIME = 66;//最大间隔时间,每帧时间为最大时间减去加载图片消耗的时间。    private SurfaceHolder mHolder;    private boolean isDrawing = false;    private boolean isSurfaceCreated = false;    private List<String> mFilePathListRGB = new ArrayList<>();    private List<String> mFilePathListAlpha = new ArrayList<>();    private HandlerThread handlerThread = new HandlerThread("surfaceview");    private RectF mRectF;    private OnFrameAnimationListener mListener;    private Handler mWorkHandler;    public SpecialGiftSurfaceView(Context context) {        super(context);        init();    }    public SpecialGiftSurfaceView(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    private void init() {        mHolder = getHolder();        mHolder.addCallback(this);        //设置SurfaceView透明        setZOrderOnTop(true);        mHolder.setFormat(PixelFormat.TRANSLUCENT);        handlerThread.start();    }    /**     * 开始帧动画     * @param pathListRGB  彩色图片路径     * @param pathListAlpha 透明图片路径     */    public void startAnimation(List<String> pathListRGB, List<String> pathListAlpha) {        long delay = 0;        if (pathListRGB == null || pathListRGB.size() == 0) {            return;        }        setVisibility(VISIBLE);        mFilePathListRGB.clear();        mFilePathListRGB.addAll(pathListRGB);        mFilePathListAlpha.clear();        if (pathListAlpha != null && mFilePathListAlpha.size() > 0) {            mFilePathListAlpha.addAll(pathListAlpha);        }        mWorkHandler = new Handler(handlerThread.getLooper());        if (!isSurfaceCreated) {            Log.d(TAG, "SurfaceView is not created.wait 1000");            delay = 1000;        }        setLayerType(LAYER_TYPE_HARDWARE, null);        mWorkHandler.postDelayed(this, delay);    }    public void startAnimation(List<String> pathListRGB) {        startAnimation(pathListRGB, null);    }    /**     * 停止动画     */    public void stopAnimation() {        mFilePathListRGB.clear();        mFilePathListAlpha.clear();        setVisibility(INVISIBLE);        setLayerType(LAYER_TYPE_NONE, null);        isDrawing = false;        if (mWorkHandler != null) {            mWorkHandler.removeCallbacks(this);        }    }    public void setListener(OnFrameAnimationListener listener) {        mListener = listener;    }    @Override    public void surfaceCreated(SurfaceHolder holder) {        isSurfaceCreated = true;        isDrawing = true;        Log.d(TAG, "surfaceCreated");    }    @Override    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {    }    @Override    public void surfaceDestroyed(SurfaceHolder holder) {        stopAnimation();    }    @Override    public boolean onKeyDown(int keyCode, KeyEvent event) {        if(keyCode == event.KEYCODE_BACK) {            stopAnimation();        }        return super.onKeyDown(keyCode, event);    }    @Override    public void run() {        SpecialGiftSurfaceView.this.post(new Runnable() {            @Override            public void run() {                notifyStart();            }        });        for (int i = 0; i < mFilePathListRGB.size() ; i++) {            if (isDrawing) {                try {                    long temp = System.currentTimeMillis();                    if (mFilePathListAlpha != null && mFilePathListAlpha.size() > 0) {                        draw(mFilePathListRGB.get(i), mFilePathListAlpha.get(i));                    } else {                        draw(mFilePathListRGB.get(i));                    }                    //间隔幅度越小,CPU占比越大。所以应该合理设置。                    long ll = System.currentTimeMillis() - temp;                    Log.d(TAG, "id :" + i + "   temp :" + ll);                    Thread.sleep(Math.max(0, (INTERVAL_TIME- ll)));                } catch (Exception e) {                    e.printStackTrace();                }            } else {                break;            }        }        SpecialGiftSurfaceView.this.post(new Runnable() {            @Override            public void run() {                stopAnimation();                notifyFinished();            }        });    }    private void draw(String path) {        Canvas canvas = mHolder.lockCanvas();        if (canvas != null) {            Bitmap diskBitmap = getDiskBitmap(path);            if (diskBitmap != null) {                canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);                mRectF = new RectF(SpecialGiftSurfaceView.this.getLeft(),                        SpecialGiftSurfaceView.this.getTop(),                        SpecialGiftSurfaceView.this.getWidth(),                        SpecialGiftSurfaceView.this.getHeight());                canvas.drawBitmap(diskBitmap, null, mRectF, null);            }            mHolder.unlockCanvasAndPost(canvas);        }    }    private void draw(String pathRGB, String pathAlpha) {        Canvas canvas = mHolder.lockCanvas();        if (canvas != null) {            int saveCount = canvas.getSaveCount();            Paint l = new Paint();            l.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));            Paint m = new Paint();            m.setColorFilter(new ColorMatrixColorFilter(new ColorMatrix(new float[]{                    0.0f, 0.0f, 0.0f, 0.0f, 0.0f,                    0.0f, 0.0f, 0.0f, 0.0f, 0.0f,                    0.0f, 0.0f, 0.0f, 0.0f, 0.0f,                    1.0f, 0.0f, 0.0f, 0.0f, 0.0f})));            Bitmap decodeFile = getDiskBitmap(pathRGB);            Bitmap decodeFile2 = getDiskBitmap(pathAlpha);            if (!(decodeFile == null || decodeFile2 == null)) {                Bitmap r = Bitmap.createBitmap(decodeFile.getWidth(), decodeFile.getHeight(), Bitmap.Config.ARGB_8888);                Canvas c = new Canvas(r);                RectF rectF = new RectF(SpecialGiftSurfaceView.this.getLeft(),                        SpecialGiftSurfaceView.this.getTop(),                        SpecialGiftSurfaceView.this.getWidth(),                        SpecialGiftSurfaceView.this.getHeight());                c.drawBitmap(decodeFile2, 0.0f, 0.0f, m);                c.drawBitmap(decodeFile, 0.0f, 0.0f, l);                canvas.drawBitmap(r, null, rectF, null);                canvas.restoreToCount(saveCount);            }            mHolder.unlockCanvasAndPost(canvas);        }    }    private Bitmap getDiskBitmap(String pathString) {        Bitmap bitmap = null;        try {            File file = new File(pathString);            if (file.exists()) {                bitmap = BitmapFactory.decodeFile(pathString);            }        } catch (Exception e) {            e.printStackTrace();        }        return bitmap;    }    private void notifyStart() {        if (mListener != null) {            mListener.onFrameAnimationStart();        }    }    private void notifyFinished() {        if (mListener != null) {            mListener.onFrameAnimationFinished();        }    }    public interface OnFrameAnimationListener {        void onFrameAnimationStart();        void onFrameAnimationFinished();    }

其对外的接口有两个:

startAnimation(List pathListRGB)
startAnimation(List pathListRGB, List pathListAlpha)

这里提供两个重载的方法是有不同的含义的。
前提:
动画一定要是透明的,因为在播放礼物的同时,也一定要能看到主播的直播画面。
而且我们都知道PNG是支持透明的,而JPG是不支持透明的。

一个参数的方法,需要出入一组PNG图片的地址。
两个参数的方法,需要传入两组JPG图片的地址,第一组是正常的图片,带白色底。第二组是其蒙版,利用第二组将最后绘画出来的图片变为透明。
(这个方法是拆分猎豹的APK发现的,这样做的好处就是,JPG占的内存回避PNG的少,毕竟有很多礼物,需要的图片太多,能小则小。)