项目开发需要自定义View实现一个人脸框,代码实现很平常,一些细节记录一下,方便以后查阅。
代码实现:
FaceView.java
package com.android.example.ui.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import com.android.example.R;
import java.util.List;
public class FaceView extends View {
//人脸识别框数组
private List mRectList;
//画笔
private Paint mPaint;
public FaceView(Context context) {
super(context);
init();
}
/* 这个构造函数不能缺失,否则会编译报错
* View走的构造函数也是这个,第一个构造函数反而没走
*/
public FaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public FaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
//设置抗锯齿
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(3);
mPaint.setColor(Color.RED);
}
public void setFaceRects(List mRectList) {
this.mRectList = mRectList;
//请求更新,View会自动执行onDraw进行绘制
invalidate();
//同样是请求更新,该方法表示在主线程发起绘制请求
//postInvalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mRectList != null) {
for (Rect rect : mRectList) {
canvas.drawRect(rect, mPaint);
}
}
}
}
注意:
这里我把FaceView的layout_width、layout_height都设置成了"match_parent"
这里面有个坑容易踩到
本次开发时,xml中FaceView控件往上一直到第一层父布局宽、高的设置都是:
layout_width="match_parent"
layout_height="wrap_content"
导致View的width、height无法确定,所以onDraw()一直不被执行
后来才发现是这里的问题
将控件FaceView控件宽高设置为固定大小就可以了
也就是说如果自定义View的宽高无法被确定,OnDraw()是不会执行的
这时候就需要排查设置宽高,或者修改布局配置使其值能够被确定。
List rectlist = new ArrayList();
Rect rect1 = new Rect(300, 300, 500, 500);
rectlist.add(rect1);
FaceView faceRectsView = findViewById(R.id.face_rects_view);
faceRectsView.setFaceRects(rectlist);
一个完整的自定义人脸识别框FaceView到此就实现了
顺带手在onDraw里可以做出其他不同的效果
使用canvas.drawLine()函数绘制线段,使用实现一个只显示四个角的人脸识别框
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mRectList != null) {
for (Rect rect : mRectList) {
/*左上角竖线*/
canvas.drawLine(rect.left, rect.top, rect.left, rect.top + 20, mPaint);
/*左上角横线*/
canvas.drawLine(rect.left, rect.top, rect.left + 20, rect.top, mPaint);
/*右上角竖线*/
canvas.drawLine(rect.right, rect.top, rect.right - 20, rect.top, mPaint);
/*右上角横线*/
canvas.drawLine(rect.right, rect.top, rect.right, rect.top + 20, mPaint);
/*左下角竖线*/
canvas.drawLine(rect.left, rect.bottom, rect.left, rect.bottom - 20, mPaint);
/*左下角横线*/
canvas.drawLine(rect.left, rect.bottom, rect.left + 20, rect.bottom, mPaint);
/*右下角竖线*/
canvas.drawLine(rect.right, rect.bottom, rect.right, rect.bottom - 20, mPaint);
/*右下角横线*/
canvas.drawLine(rect.right, rect.bottom, rect.right - 20, rect.bottom, mPaint);
}
}
}
还有其他有趣的效果:圆形、椭圆等,就不一一列举了。
上一节讲述的都是用画布Cavas、画笔Paint自带的绘制线段、矩形等Api实现人脸框
这一节讲怎么使用UI设计的图片绘制人脸框,其实也就是onDraw()中绘制Bitmap
人脸框图片facerect.png:
onDraw()绘制Bitmap代码:
package com.android.example.ui.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import com.android.example.R;
import java.util.List;
public class FaceView extends View {
private List mRectList;
private Paint mPaint;
private Bitmap mFaceRectBitmap;
public FaceView(Context context) {
super(context);
init();
}
public FaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public FaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
//不需要再配置画笔
/*mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(3);
mPaint.setColor(Color.RED);*/
/*图片解析成Bitmap
*Android不允许直接修改res里面的图片,所以要用copy方法*/
mFaceRectBitmap = BitmapFactory.decodeResource(getResources(),
R.mipmap.perception_facerect).copy(Bitmap.Config.ARGB_8888, true);
}
public void setFaceRects(List mRectList) {
this.mRectList = mRectList;
invalidate();
}
//不需要再绘制时调用,销毁资源
public void clear() {
mRectList = null;
mFaceRectBitmap.recycle();
mFaceRectBitmap = null;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mRectList != null && mFaceRectBitmap != null) {
for (Rect rect : mRectList) {
//drawBitmap()有多个重载,接下来会详细讨论
canvas.drawBitmap(mFaceRectBitmap, rect.left, rect.top, mPaint);
}
}
}
}
注: 画笔Paint只需要有一个实例对象就行,不需要再进行配置
上一节代码注释中提到了,canvas.drawBitmap()这个函数有多个重载
下面就来详细研究一下Canvas里关于绘制Bitmap的方法:
- drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
- drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)
- drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
- drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)
方法1和方法2只有dst参数类型 Rect 和 RectF 的区别
这两个方法要实现的功能都是把Bitmap的src区域绘制到画布dst区域内
如果dst无法完全覆盖src,而src和dst比例不对称,就会出现拉伸或缩放
方法3是把(left, top)作为Bitmap的左上角进行绘制,不拉伸也不压缩,如果超出了画布就不绘制,佛系绘制。
方法4是使用矩阵matrix绘制Bitmap,绘制前先设置matrix的属性
该方法主要是对图片进行缩放旋转平移等操作时使用
示例:在坐标点(100,100),顺时针旋转45°绘制图片
关键代码:
//配置矩阵
Matrix matrix = new Matrix();
matrix.postTranslate(100f, 100f);
matrix.preRotate(45f);
//绘制
canvas.drawBitmap(mFaceRectBitmap, matrix, mPaint);
在前文1.2小节里有提到,onDraw()出现不执行问题
实现自定义View时,这也是个常见的问题,一般可以尝试如下几种方法:
- 主动调用invalidate()
- 在构造方法里增加setWillNotDraw(false)方法
- View的宽高是否能被系统确定,如果不能就需要设置确定的宽高,或者修改布局使其能被系统确定
- 在onMeasure()方法中没设置控件的宽和高
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); }
到此自定义人脸识别框FaceView及drawBitmap()相关扩展就讲解完了
canvas有十分丰富的功能,能实现多种多样的自定义View,这就要在实际开发中去具体运用了