自定义控件时经常用到Canvas,画新的东西之前需要先清除画布内容,人脸识别项目中需要准确画出当前人脸位置,清空上一帧位置。
关于清除画布内容网上有两种非常流行的方法:
方法一:
mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
方法二:
Paint paint = new Paint();
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mCanvas.drawPaint(paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
遗憾的是这两种方法在我自定义的SurfaceView中都不起作用,尝试结果如出一辙,左右晃一下 画面如下:
我的解决方案是在每次设置Path画线前,清空一下Path,问题迎刃而解。
清除Path中的内容有两个方法:
reset( )不保留内部数据结构,但会保留FillType.
rewind( )会保留内部的数据结构,但不保留FillType
关于Path其他更多方法解释详见这篇博客。
自定义SurfaceView代码如下:
/**
* Title:
* Description:
* Company: 北京****科技有限公司,010-62538800,support@interjoy.com.cn
*
* @author Created by ylwang on 2018/1/30
*/
public class CustomSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private SurfaceHolder mHolder;
private Canvas mCanvas;//绘图的画布
private volatile boolean mIsDrawing;//控制绘画线程的标志位
private Paint mPaint;
private Path mPath;
public CustomSurfaceView(Context context) {
super(context);
initView();
}
public CustomSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public CustomSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private Gson gson;
private List<FaceBean.FacesEntity> listFaces;
public void setFaceInfo(String faceInfo) {
FaceBean bean = gson.fromJson(faceInfo, FaceBean.class);
if (null != bean && null != bean.getFaces()) {
listFaces = bean.getFaces();
}
}
private void initView() {
mPaint = new Paint();
mPaint.setColor(Color.GREEN);//画笔颜色
mPaint.setAntiAlias(true);//抗锯齿
mPaint.setStrokeWidth(6);//画笔宽度
mPaint.setStyle(Paint.Style.STROKE);//空心
mPath = new Path();
mHolder = getHolder();//获取SurfaceHolder对象
//TODO:保证该SurfaceView在最上层,避免两个SurfaceView叠加,遮挡问题
mHolder.setFormat(PixelFormat.TRANSPARENT);
setZOrderOnTop(true);
mHolder.addCallback(this);//注册SurfaceHolder的回调方法
setFocusable(true);// 能否用键盘获得焦点
setFocusableInTouchMode(true);//能否通过触摸获得焦点
this.setKeepScreenOn(true);//屏幕常亮
gson = new Gson();
listFaces = new ArrayList<>();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mIsDrawing = true;
new Thread(this).start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mIsDrawing = false;
}
public static final int TIME_IN_FRAME = 20;
@Override
public void run() {
long start;
while (mIsDrawing) {
start = SystemClock.uptimeMillis();//开始绘制
synchronized (this) {
setPathToCanvas();
}
//保证稳定帧率刷新界面
while (SystemClock.uptimeMillis() - start <= TIME_IN_FRAME) {
Thread.yield();//线程让出
}
}
}
private void setPathToCanvas() {
mPath.rewind();//TODO:必须重置,否则不能清屏
//mPath.reset();
// mPath = new Path();
for (FaceBean.FacesEntity face : listFaces) {
int[] ps = getPositions(face.getFace_rectangle());
mPath.moveTo(ps[0], ps[1]);
for (int i = 2; i < ps.length - 1; i += 2) {
mPath.lineTo(ps[i], ps[i + 1]);
}
mPath.close();//第一个点连接到最后一个点,形成一个闭合区域
//mPath.lineTo(ps[0], ps[1]);
drawSth();
}
}
/**
* 获取每个人脸的四点位置坐标
*
* @param fre
* @return
*/
private int[] getPositions(FaceBean.FacesEntity.Face_rectangleEntity fre) {
int[] faces = new int[8];
faces[0] = fre.getLeft_top().getX();
faces[1] = fre.getLeft_top().getY();
faces[2] = fre.getRight_top().getX();
faces[3] = fre.getRight_top().getY();
faces[4] = fre.getRight_bottom().getX();
faces[5] = fre.getRight_bottom().getY();
faces[6] = fre.getLeft_bottom().getX();
faces[7] = fre.getLeft_bottom().getY();
for (int i = 0; i < faces.length; i++) {
faces[i] *= 2;
if (i % 2 == 0) {
faces[i] = (640 * 2) - faces[i];
}
}
return faces;
}
private void drawSth() {
try {
mCanvas = mHolder.lockCanvas(); //拿到当前画布 然后锁定
mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
mCanvas.drawPath(mPath, mPaint);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (mCanvas != null) {
mHolder.unlockCanvasAndPost(mCanvas);//保证每次都将绘图的内容提交
}
}
}
}
说明一下,多个SurfaceView叠加可能会遇到的问题,
请参考这篇博客。
mHolder = getHolder();//获取SurfaceHolder对象
//TODO:保证该SurfaceView在最上层,避免两个SurfaceView叠加,遮挡问题
mHolder.setFormat(PixelFormat.TRANSPARENT);
setZOrderOnTop(true);