欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

自定义带取景框的camera

程序员文章站 2022-05-30 22:08:36
...

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

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

效果图:

自定义带取景框的camera

技术实现:(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<Camera.Size> 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<Camera.Size> 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<Camera.Size> 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

完结!!!

相关标签: camera Android