为了Native开发中嵌入cocos2d和unity3d的游戏元素,需要在某些场景下播放一组游戏特效,例如被攻击, 侦查时的全屏闪烁Flash效果和几十帧的动画轮播。
考虑到游戏特效的复杂度,若使用常规的ImageView, 由于需要在主线程中绘制动画,对性能的损耗较大,在配置较差的机器上会卡顿和影响用户操作,体验不佳。
所以想到了用SurfaceView来绘制。关于SurfaceView的特性,简单归纳如下:
- SurfaceView和普通View的区别在于它拥有一个可绘制Surface,这使它不需要绘制在宿主窗口上。
- SurfaceView允许额外的线程(一个或者多个)来绘制图形到它的Surface上,这使它可以自定义绘制的帧数并不会阻塞主线程和影响用户的输入。
- SurfaceView是Z轴排序(XYZ坐标轴的Z轴),这意味在它可以显示在宿主窗口之下(默认)或者之上。
- SurfaceView内部维护双缓冲,显示效果比View流畅,但相比View更耗内存。
SurfaceView和普通View对比的优缺点:
优点
- 不用在主线程中绘制,不会卡顿UI
- 绘制速度快,支持多线程绘制
- 可以自由控制绘制在Activity图层上方或者下方
缺点
- 内部维护双缓冲,比普通的View更耗内存
以下是一些使用备忘点:
1. 清除画布
// 清空画布
Canvas canvas = mHolder.lockCanvas();
if (canvas != null) {
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
mHolder.unlockCanvasAndPost(canvas);
}
2. 多次绘制时清除上次绘制残影
Canvas canvas = mHolder.lockCanvas();
if (canvas != null) {
Paint paint = new Paint();
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawPaint(paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
onDraw(canvas, paint);
mHolder.unlockCanvasAndPost(canvas);
}
sleep(mFrameTime);
3. 设置背景透明
一般只有当SurfaceView的图层需要显示在窗口顶层时才会需要设置为窗口透明,这种情况可以通过以下方式设置:
mHolder = getHolder();
// 设置SurfaceView显示在窗口顶层
setZOrderOnTop(true);
// 设置SurfaceView透明
mHolder.setFormat(PixelFormat.TRANSLUCENT);
4. 多个线程同时绘制时需要对Canvas添加同步锁
Canvas canvas = mHolder.lockCanvas();
if (canvas != null) {
synchronized (canvas){
Paint paint = new Paint();
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawPaint(paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
onDraw(canvas, paint);
mHolder.unlockCanvasAndPost(canvas);
}
}
5. 绘制完成后必须调用mHolder.unlockCanvasAndPost(canvas)
// 解锁画布并提交绘制
mHolder.unlockCanvasAndPost(canvas);
6. 生命周期
- SurfaceView可见时,调用surfaceCreated和surfaceChanged
- SurfaceView不可见时,调用surfaceDestroyed
- 被新的Activity覆盖、回到后台时,调用surfaceDestroyed
- 锁屏时,不会调用surfaceDestroyed
- 若键盘弹出影响到SurfaceView的区域,调用surfaceChanged
文章的主要目的是分享和备忘,若有不足之处,还请帮忙指出!欢迎各位给我留言!