前言:公司项目需求,在图像信息采集时只采集肩部以上部位的图片(和我们平时的一寸证件照很像),首先想到的是用第三方的图片选择器,他们都自带裁剪功能,不过每次拍完照后的手动裁剪,结果老大说简化业务人员的操作,不过这也难不倒无所不能的程序猿,没有咱们可以new一个(女朋友)。言归正传,开启我们的自定义带取景框的camera...
转载链接:https://blog.csdn.net/ruancw/article/details/79907677
效果图:
1.SurfaceView
2.Camera
3.自定义矩形取景框view
介绍:从API中可以看出SurfaceView属于View的子类,它的功能很强大,它支持OpenGL ES库,2D和3D的效果,可以制作游戏、视频等,这里我们用surfaceview和camera实现相机的拍照取景功能。首先,让我们创建了SurfaceView的类实现SurfaceHolder的CallBack接口,重写CallBack的3个方法用于监听SurfaceView的创建、改变、销毁状态。
//SurfaceHolder的callback接口
public interface Callback {
//对surfaceView的创建状态的监听
void surfaceCreated(SurfaceHolder var1);
//对SurfaceView的状态改变进行监听
void surfaceChanged(SurfaceHolder var1, int var2, int var3, int var4);
//对SurfaceView的销毁进行监听
void surfaceDestroyed(SurfaceHolder var1);
}
1.创建SurfaceView
这里我们使用Api自带的SurfaceView进行相机的预览(当然你也可以自定义surfaceView),在Activity的onCreate方法中进行初始化SurfaceView。
mCameraSurfaceView = (SurfaceView) findViewById(R.id.cameraSurfaceView);
2.使用SurfaceView
//根据layoutParams设置surfaceView的大小
mCameraSurfaceView.setLayoutParams(new FrameLayout.LayoutParams((int) (height * (h / w)), height));
3.获取SrfaceView的SurfaceHoler
mHolder = mCameraSurfaceView.getHolder();
//添加监听
mHolder.addCallback(this);
//设置类型
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
然后就是在三个监听方法中进行camera的相关的操作。
刚才已经介绍了显示camera预览的surfaceView,那么surfaceView上的取景框该如何实现呢?canvas and paint,没错,今天我们就用paint和canvas画出效果图的矩形取景框,接下来我们来自定义view。
模块实现:
a.顶部文字
b.阴影区域
c.矩形取景框
d.四角红色短线
1.自定义View,继承自imageView
构造方法:
public OverLayerTopView(Context context) {
this(context,null,0);
}
public OverLayerTopView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public OverLayerTopView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
2.初始化view,实现四个模块
(1)顶部文字实现
a.初始化画笔
//顶部文字提示信息
wordPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//抗锯齿
wordPaint.setColor(Color.WHITE);//字体颜色
wordPaint.setTextAlign(Paint.Align.CENTER);//居中显示
wordPaint.setStrokeWidth(3f);//画笔的宽度
wordPaint.setTextSize(45);//字体大小
b.onDraw方法中绘画
注:必须在super.onDraw之前调用
/**
* 画文字提示
* @param canvas 画布
*/
private void drawTipText(Canvas canvas) {
canvas.drawText(TIPS, mCenterRect.centerX(), mCenterRect.top-50, wordPaint);
}
(2)矩形取景框实现
a.初始化画笔
//中间矩形取景框的边界
mRectBorderPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
mRectBorderPaint.setColor(Color.RED);
mRectBorderPaint.setStyle(Paint.Style.STROKE);
mRectBorderPaint.setStrokeWidth(5f);
mRectBorderPaint.setAlpha(0);//透明度
注:setAlpha(int value),value的值越小,透明度越高
b.onDraw方法中绘画
注:必须在super.onDraw之前调用
//判断取景框矩形是否为空
if (mCenterRect==null) return;
//绘制中间矩形取景框
canvas.drawRect(mCenterRect,mRectBorderPaint);
(3)阴影区域实现
a.初始化画笔
//阴影区域的画笔
mShadePaint=new Paint(Paint.ANTI_ALIAS_FLAG);
mShadePaint.setColor(Color.GRAY);
mShadePaint.setStyle(Paint.Style.FILL);
mShadePaint.setAlpha(100);
b.onDraw方法中绘画
注:必须在super.onDraw之前调用
canvas.drawRect(0,0,screenWidth,mCenterRect.top-2,mShadePaint);//顶部
canvas.drawRect(0,mCenterRect.bottom+2,screenWidth,screenHeight,mShadePaint);//左侧
canvas.drawRect(0,mCenterRect.top-2,mCenterRect.left-2,mCenterRect.bottom+2,mShadePaint);//下部
canvas.drawRect(mCenterRect.right+2,mCenterRect.top-2,screenWidth,mCenterRect.bottom+2,mShadePaint);//右侧
(4)四角红色短线实现
a.初始化画笔
//矩形四角的短线
mLinePaint=new Paint();
mLinePaint.setColor(Color.RED);
mLinePaint.setAlpha(150);
b.onDraw方法中绘画
注:必须在super.onDraw之前调用
//左下
canvas.drawRect(mCenterRect.left-2,mCenterRect.bottom,mCenterRect.left+50,mCenterRect.bottom+2,mLinePaint);//底部
canvas.drawRect(mCenterRect.left-2,mCenterRect.bottom-50,mCenterRect.left,mCenterRect.bottom,mLinePaint);//左侧
//左上
canvas.drawRect(mCenterRect.left-2,mCenterRect.top-2,mCenterRect.left+50,mCenterRect.top,mLinePaint);//顶部
canvas.drawRect(mCenterRect.left-2,mCenterRect.top,mCenterRect.left,mCenterRect.top+50,mLinePaint);//左侧
//右上
canvas.drawRect(mCenterRect.right-50,mCenterRect.top-2,mCenterRect.right+2,mCenterRect.top,mLinePaint);//顶部
canvas.drawRect(mCenterRect.right,mCenterRect.top,mCenterRect.right+2,mCenterRect.top+50,mLinePaint);//右侧
//右下
canvas.drawRect(mCenterRect.right-50,mCenterRect.bottom,mCenterRect.right+2,mCenterRect.bottom+2,mLinePaint);//右侧
canvas.drawRect(mCenterRect.right,mCenterRect.bottom-50,mCenterRect.right+2,mCenterRect.bottom,mLinePaint);//底部
3.设置矩形框的大小
/**
* 设置取景框的矩形区域大小
* @param mCenterRect 取景框矩形
*/
public void setCenterRect(Rect mCenterRect){
this.mCenterRect=mCenterRect;
//postInvalidate();
}
4.OverLayerTopView的使用(Activity中)
// 设置取景框的margin; 距 左 、上 、右、下的 距离 单位是dp
mCenterRect = DisplayUtils.createCenterRect(this, new Rect(120, 180, 120, 300));
mOverLayerView.setCenterRect(mCenterRect);
这样我们就实现了取景框的绘制,并能设置你想要的取景框的大小。
!!!重点就是下面的camera的实现了,让我们继续吧
介绍:android framework包括对设备上可用的各种相机及相机功能的支持,在应用中实现拍照和录制视频,不过,API 21(Android5.0)中将原来的camera API弃用转而推荐使用新增的camera 2 API,这是google对camera架构的一个大动作,因为API换了新架构,让开发者用起来有些难度,本文不对camera 2进行研究。这里使用的还是之前的camera API进行相机拍照实现。接下来我们将在surfaceView的接口方法中对camera进行初始化、设置参数以及资源释放等。
1.camera的初始化(surfaceCreated的方法中)
@Override
public void surfaceCreated(SurfaceHolder holder) {
openCamera();
}
openCamera方法
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
private void openCamera() {
if (!isFrontCamera) {//是否是后置摄像头
//打开相机
mCamera = Camera.open();
try {
//摄像头画面显示在Surface上
mCamera.setPreviewDisplay(mHolder);
} catch (IOException e) {
e.printStackTrace();
}
} else {//前置摄像头的操作
//获取摄像头信息
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
//遍历所有摄像头信息,查找前置摄像头
for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
Camera.getCameraInfo(i, cameraInfo);
{
//判断是否是前置摄像头
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
//打开前置摄像头
mCamera = Camera.open(i);
isFrontCamera = true;
}
}
}
}
}
2.camera参数设置(surfaceChanged方法中)
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
initCamera();
}
initCamera方法
/**
* 照相机参数设置
*/
public void initCamera() {
if (mCamera != null && !isPreview) {
//获取相机参数
Camera.Parameters parameters = mCamera.getParameters();
// 设置闪光灯为自动 前置摄像头时 不能设置
if (!isFrontCamera) {
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
}
//设置相机预览及图片参数
setCameraParams(mPoint.x, mPoint.y);
//开启预览
mCamera.startPreview();
isPreview = true;
}
}
setCameraparams方法
/**
* 设置预览图片和裁剪图片的大小
* @param width 屏幕宽度
* @param height 屏幕高度
*/
private void setCameraParams(int width, int height) {
Log.i(TAG, "setCameraParams width=" + width + " height=" + height);
Camera.Parameters parameters = mCamera.getParameters();
// 获取摄像头支持的PictureSize列表
List pictureSizeList = parameters.getSupportedPictureSizes();
for (Camera.Size size : pictureSizeList) {
Log.i(TAG, "pictureSizeList size.width=" + size.width + " size.height=" + size.height);
}
//从列表中选取合适的分辨率
Camera.Size picSize = getPreviewSize(pictureSizeList, ((float) height / width));
Log.i(TAG, "picSize.width=" + picSize.width + " picSize.height=" + picSize.height);
// 根据选出的PictureSize重新设置SurfaceView大小
float w = picSize.width;
float h = picSize.height;
parameters.setPictureSize(picSize.width, picSize.height);
//根据layoutParams设置surfaceView的大小
mCameraSurfaceView.setLayoutParams(new FrameLayout.LayoutParams((int) (height * (h / w)), height));
// 获取摄像头支持的PreviewSize列表
List previewSizeList = parameters.getSupportedPreviewSizes();
//获取预览图片的大小
Camera.Size preSize = getPreviewSize(previewSizeList, ((float) height) / width);
if (null != preSize) {
Log.i(TAG, "preSize.width=" + preSize.width + " preSize.height=" + preSize.height);
parameters.setPreviewSize(preSize.width, preSize.height);
}
// 设置照片质量
parameters.setJpegQuality(100);
if (parameters.getSupportedFocusModes().contains(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
parameters.setFocusMode(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);// 连续对焦模式
}
//自动对焦
mCamera.cancelAutoFocus();
// 设置PreviewDisplay的方向,效果就是将捕获的画面旋转多少度显示
mCamera.setDisplayOrientation(90);
//设置参数(不设置不会有效果)
mCamera.setParameters(parameters);
}
getPreviewSize方法:
/**
* 从列表中选取合适的分辨率
* 默认w:h = 4:3
*/
private Camera.Size getPreviewSize(List pictureSizeList, float screenRatio) {
Camera.Size result = null;
for (Camera.Size size : pictureSizeList) {
float currentRatio = ((float) size.width) / size.height;
if (currentRatio - screenRatio == 0) {
result = size;
break;
}
}
if (null == result) {
for (Camera.Size size : pictureSizeList) {
float curRatio = ((float) size.width) / size.height;
if (curRatio == 4f / 3) {// 默认w:h = 4:3
result = size;
break;
}
}
}
return result;
}
3.释放camera(surfaceDestoryed方法中)
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// 当holder被回收时 释放硬件
releaseCamera();
}
releaseCamera方法:
/**
* 释放camera资源
*/
private void releaseCamera() {
if (mCamera != null) {
if (isPreview) {
//停止camera的预览
mCamera.stopPreview();
}
//释放相机资源
mCamera.release();
//相机设置为null
mCamera = null;
}
isPreview = false;
}
重点来啦,让我们来拍照吧
4.camera拍照
(1)设置camera的回调接口:Camera.pictureCallBack
/**
* 相机拍照的返回接口
*/
private Camera.PictureCallback jpeg = new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
isTake = false;
if (data == null) return;
// 获取拍照回调的图片数据。
Bitmap bitmap = BitmapFactory
.decodeByteArray(data, 0, data.length, opt);
Bitmap bm;
//获取相机的方向(横向或纵向)
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
//矩阵转换
Matrix matrix = new Matrix();
matrix.setRotate(90, 0.1f, 0.1f);
bm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
bitmap.getHeight(), matrix, false);
if (isFrontCamera) {
//前置摄像头旋转图片270度。
matrix.setRotate(270);
bm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
}
} else {
bm = bitmap;
}
//判断矩形取景框是否为空
if (mCenterRect != null) {
//获取取景框大小的bitmap
bitmap = BitmapUtils.getRectBitmap(mCenterRect, bm, mPoint);
}
//图片缩放到341x481大小
Bitmap scaleBitmap=Bitmap.createScaledBitmap(bitmap,341,481,false);
//将以矩形取景框大小的图片保存到sd卡
if (SdcardUtils.existSdcard()) {
SdcardUtils.saveBitmap2SD(scaleBitmap, filesDir, imageName);
//SdcardUtils.saveBitmap2SD(bitmap, filesDir, imageName);
} else ToastUtil.showT(CameraActivity.this, "未检测到SD卡");
//释放图片资源,防止OOM
BitmapUtils.recycleBitmap(bm);
//显示预览图
ivPreview.setImageBitmap(bitmap);
//释放相机资源
if (mCamera != null) {
mCamera.stopPreview();
mCamera.startPreview();
isPreview = true;
}
//拍照成功返回
setResult(-1);
finish();
}
};
(2)点击拍照
注:camera调用takePicture方法前要设置相机参数,不然拍照次数多了会出现闪退
// 设置相机参数
setCameraParams(mPoint.x,mPoint.y);
//拍照
mCamera.takePicture(null, null, jpeg);
jpeg就是我们刚才设置的接口回调名称
注:文中之前设置的自动对焦在华为等部分机型上会出现闪退,因为文中是在预览之前就让其对焦了,源码中已经更改了自动对焦的位置在预览显示之后。
源码地址:https://github.com/ruancw/CustomCamerDemo
完结!!!