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

公司做视频直播的礼物动效。
前期调研的过程中发现很多竞品竟然都是利用帧动画做的。
利用帧动画当然不能直接加载多张图片,要知道最大的礼物有一百多张图片,有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的少,毕竟有很多礼物,需要的图片太多,能小则小。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值