Android 自定义SurfaceView实现拍照功能

2019-07-12  本文已影响0人  NanoBull

前言

项目集成第三方H5页面需要调用本地相机进行拍照并回传给H5,本来H5可以直接调用相机但是进入的是一个相册列表然后再进拍照,业务要求直接打开相机拍照并且必须是刚拍的照片,所以最后是通过JSBridge调用原生相机。哪位大牛知道H5怎么直接进入相机的还请指教,感激不尽。

正文

由于拍证件需要有一个简单的取景框,我用自定义view来实现,详情请移步Android 自定义View实现简单的相机预览取景框。下面说下拍照部分主要实现。

实现相机预览界面

自定义PreviewSurfaceView继承SurfaceView实现SurfaceHolder.Callback,SurfaceView用于实时预览界面,实现构造方法添加SurfaceHolder.Callback

    public PreviewSurfaceView(Context context) {
        this(context, null);
    }

    public PreviewSurfaceView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PreviewSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        init();
    }

    private void init() {
        SurfaceHolder holder = getHolder();
        holder.addCallback(this);
//        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

重写三个方法surfaceCreated、surfaceChanged、surfaceDestroyed用于在界面发生改变时对Camera的处理。Camera的预览方向需要旋转90°才和实际看到的一致,保存照片时也要先旋转90°。

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mHolder = holder;
        // camera动态请求权限,需要在相应的activity中处理请求权限的回调然后调用initCamera方法
        int checkSelfPermission = ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA);
        if (checkSelfPermission == PackageManager.PERMISSION_GRANTED) {
            initCamera();
        } else {
            ActivityCompat.requestPermissions((Activity) mContext, new String[]{Manifest.permission.CAMERA}, REQUEST_CODE_CAMERA);
        }
    }

    public void initCamera() {
        if (mCamera == null) {
            mCamera = Camera.open();
        }
        try {
            mCamera.setPreviewDisplay(mHolder);
            setCameraDisplayOrientation((Activity) mContext, 0, mCamera);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * google官方建议的处理camera预览方向
     * @param activity
     * @param cameraId
     * @param camera
     */
    public static void setCameraDisplayOrientation(Activity activity, int cameraId, Camera camera) {
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, info);
        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }

        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;  // compensate the mirror
        } else {  // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        camera.setDisplayOrientation(result);
    }


    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        setCameraParams();
        mCamera.startPreview();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        releaseCamera();
    }

    private void releaseCamera() {
        mCamera.stopPreview();
        mCamera.release();
        mCamera = null;
        mHolder = null;
    }

    /**
     * 设置Camera参数
     */
    private void setCameraParams() {
        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 = getProperSize(pictureSizeList, ((float) mScreenHeight / mScreenWidth));
        if (null == picSize) {
            Log.i(TAG, "null == picSize");
            picSize = parameters.getPictureSize();
        }
        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);
        this.setLayoutParams(new ConstraintLayout.LayoutParams((int) (mScreenHeight * (h / w)), mScreenHeight));

        // 获取摄像头支持的PreviewSize列表
        List<Camera.Size> previewSizeList = parameters.getSupportedPreviewSizes();

        for (Camera.Size size : previewSizeList) {
            Log.i(TAG, "previewSizeList size.width=" + size.width + "  size.height=" + size.height);
        }
        Camera.Size preSize = getProperSize(previewSizeList, ((float) mScreenHeight) / mScreenWidth);
        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();//自动对焦。
        mCamera.setDisplayOrientation(90);// 设置PreviewDisplay的方向,效果就是将捕获的画面旋转多少度显示
        mCamera.setParameters(parameters);
    }
    /**
     * 从列表中选取合适的分辨率
     * 默认w:h = 4:3
     * <p>注意:这里的w对应屏幕的height
     * h对应屏幕的width<p/>
     */
    private Camera.Size getProperSize(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;
    }

Camera在surfaceCreated中初始化,在surfaceDestroyed中记得释放资源,注意动态申请相机权限,manifest中也要添加权限

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

初始化操作完成后可以在xml中使用了,此时可以看到预览画面了

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.customcamera.PreviewSurfaceView
        android:id="@+id/previewSurfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    
    <!--取景框-->
    <com.example.customcamera.BackgroundView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:rectLeft="30dp"
        app:rectTop="100dp"
        app:rectRight="30dp"
        app:rectBottom="300dp"
        app:rectCornerRadius="10dp"
        app:rectOutColor="#99ff0000" />

    <Button
        android:id="@+id/take_photo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="100dp"
        android:text="拍照"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</android.support.constraint.ConstraintLayout>
接下来调用拍照功能将照片保存到本地
    /**
     * 调用camera拍照
     * @param callBack
     */
    public void takePhotos(final CommonCallBack callBack) {
        mCallBack = callBack;
        mCamera.takePicture(null, null, (data, camera) -> {
            if (data != null) {
                savePhotos(data);
            }
        });
    }

    /**
     * 保存图片到私有目录
     * @param data
     */
    @SuppressLint("WrongThread")
    public void savePhotos(byte[] data) {
        ByteArrayInputStream bais = new ByteArrayInputStream(data);// camera返回的字节数组直接转bitmap不行,是YuvImage格式的;曲线救国:先转流再转bitmap
        Bitmap bitmap = BitmapFactory.decodeStream(bais);
        Matrix matrix = new Matrix();
        matrix.postRotate(90);// 图片旋转90度;camera生成的图片和预览方向都是以手机屏幕右上角为原点向下为X轴正方向,向左为Y轴正方向的坐标系,所以预览方向和生成图片后都需要旋转90度
        bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        ByteArrayOutputStream baos = BitmapUtils.compressImage2Stream(bitmap);
        FileOutputStream fos = null;
        try {
            File privateImgFile = FileUtils.getPrivateImgFile(mContext, true);
            File file = File.createTempFile("img_001", ".jpeg", privateImgFile);
            fos = new FileOutputStream(file);
            fos.write(baos.toByteArray());
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
                try {
                    if (fos != null) fos.close();
                    if (bais != null) bais.close();
                    if (baos != null) baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
        mCallBack.accept(null);
    }

保存照片时涉及到转换和压缩操作比较耗时,后续考虑进一步优化。至此,一个带取景框的自定义相机功能就实现了。

上一篇 下一篇

猜你喜欢

热点阅读