自定义带取景框的camera

前言:公司项目需求,在图像信息采集时只采集肩部以上部位的图片(和我们平时的一寸证件照很像),首先想到的是用第三方的图片选择器,他们都自带裁剪功能,不过每次拍完照后的手动裁剪,结果老大说简化业务人员的操作,不过这也难不倒无所不能的程序猿,没有咱们可以new一个(女朋友)。言归正传,开启我们的自定义带取景框的camera...

转载链接:https://blog.csdn.net/ruancw/article/details/79907677

效果图:

自定义带取景框的camera_第1张图片

技术实现:(Activity中实现)

1.SurfaceView

2.Camera

3.自定义矩形取景框view

SurfaceView

介绍:从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的相关的操作。

自定义view(矩形取景框)

刚才已经介绍了显示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的实现了,让我们继续吧

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

完结!!!

你可能感兴趣的:(Android开发)