Android-自定义Camera实现拍照功能2

新一
2022-09-23 / 0 评论 / 68 阅读 / 正在检测是否收录...

上次封装的那个我觉得不够完美。再来个2吧

效果图

l6yl9o2h.png

自定义View

package com.element.wisdomclassbrand.ui.view;

import android.content.Context;
import android.hardware.Camera;
import android.graphics.Point;
import android.os.Build;
import android.util.AttributeSet;
import android.view.Surface;
import android.view.ViewTreeObserver;
import android.view.WindowManager;

import com.element.common.util.SystemPropertiesUtil;
import com.element.facerecognition.camera.CameraHelper;
import com.element.facerecognition.camera.CameraListener;
import com.element.wisdomclassbrand.R;
import com.element.wisdomclassbrand.app.SessionContext;
import com.element.wisdomclassbrand.databinding.WidgetCameraPhotographBinding;
import com.element.wisdomclassbrand.ui.base.BaseViewBindingConstraintLayout;
import com.eric.jinglecat.app.AppContext;

import org.jetbrains.annotations.Nullable;

public class CameraPhotographWidget extends BaseViewBindingConstraintLayout<WidgetCameraPhotographBinding> implements ViewTreeObserver.OnGlobalLayoutListener{

    private CameraHelper cameraHelper;
    private WindowManager windowManager;

    public CameraPhotographWidget(@Nullable Context context) {
        super(context);
    }

    public CameraPhotographWidget(@Nullable Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public CameraPhotographWidget(@Nullable Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected int initLayoutId() {
        return R.layout.widget_camera_photograph;
    }

    @Override
    protected void initParams() {
        super.initParams();
        windowManager = (WindowManager) AppContext.mMainContext.getSystemService(Context.WINDOW_SERVICE);
    }

    @Override
    protected void initListeners() {
        super.initListeners();
        getBinding().cameraPreview.getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    @Override
    public void onGlobalLayout() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            getBinding().cameraPreview.getViewTreeObserver().removeOnGlobalLayoutListener(this);
        } else {
            getBinding().cameraPreview.getViewTreeObserver().removeGlobalOnLayoutListener(this);
        }
        initCamera();
    }

    private void initCamera() {
        int cameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
        if (SessionContext.getDeviceModelType() != SystemPropertiesUtil.UNKNOWN_TYPE) {
            cameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
        }
        cameraHelper = new CameraHelper.Builder()
                .previewViewSize(new Point(getBinding().cameraPreview.getMeasuredWidth(), getBinding().cameraPreview.getMeasuredHeight()))
                .pictureSizes(new Point(getBinding().cameraPreview.getMeasuredWidth(), getBinding().cameraPreview.getMeasuredHeight()))
                .rotation(windowManager.getDefaultDisplay().getRefreshRate() == 0 ? (int) windowManager.getDefaultDisplay().getRefreshRate() : Surface.ROTATION_0)
                .specificCameraId(cameraId)
                .isMirror(false)
                .previewOn(getBinding().cameraPreview)
                .build();

        cameraHelper.init();
        cameraHelper.start();
    }

    public void takePicture(Camera.ShutterCallback mShutterCallback, Camera.PictureCallback rawPictureCallback, Camera.PictureCallback jpegPictureCallback) {
        if (cameraHelper != null) {
            cameraHelper.takePicture(mShutterCallback, rawPictureCallback, jpegPictureCallback);
        }
    }

}

视图

l8dw6cpy.png

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

    <TextureView
        android:id="@+id/camera_preview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@null"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

CameraHelper,该类是核心。

package com.element.facerecognition.camera;

import android.graphics.ImageFormat;
import android.graphics.Point;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.os.AsyncTask;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;

import com.eric.jinglecat.util.LogUtil;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;


public class CameraHelper implements Camera.PreviewCallback {
    private static final String TAG = CameraHelper.class.getName();
    private static final int NO_CAMERA_ID = -1;

    private Camera mCamera;
    private int mCameraId;
    private Point previewViewSize;
    private Point pictureSizes;
    private View previewDisplayView;
    private Camera.Size previewSize;
    private Camera.Size imageSize;
    private Point specificPreviewSize;
    private int displayOrientation = 0;
    private int additionalRotation;
    private int rotation;
    private boolean isMirror = false;

    private Integer specificCameraId;
    private CameraListener cameraListener;

    private CameraHelper(Builder builder) {
        previewDisplayView = builder.previewDisplayView;
        specificCameraId = builder.specificCameraId;
        cameraListener = builder.cameraListener;
        rotation = builder.rotation;
        additionalRotation = builder.additionalRotation;
        previewViewSize = builder.previewViewSize;
        pictureSizes = builder.pictureSizes;
        specificPreviewSize = builder.previewSize;
        if (builder.previewDisplayView instanceof TextureView) {
            isMirror = builder.isMirror;
        } else if (isMirror) {
            throw new RuntimeException("mirror is effective only when the preview is on a textureView");
        }
    }

    public void init() {
        if (previewDisplayView instanceof TextureView) {
            ((TextureView) this.previewDisplayView).setSurfaceTextureListener(textureListener);
        } else if (previewDisplayView instanceof SurfaceView) {
            ((SurfaceView) previewDisplayView).getHolder().addCallback(surfaceCallback);
        }

        if (isMirror) {
            previewDisplayView.setScaleX(-1);
        }
    }

    private int findCameraIdByFacing(int cameraFacing) {
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        int cameraCount = Camera.getNumberOfCameras();

        for (int cameraId = 0; cameraId < cameraCount; cameraId++) {
            try {
                Camera.getCameraInfo(cameraId, cameraInfo);
                if (cameraInfo.facing == cameraFacing) {
                    return cameraId;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return NO_CAMERA_ID;
    }

    /**
     * 获取当前使用的摄像头ID
     * @return 当前摄像头ID
     */
    public int getUseCameraId() {
        return mCameraId;
    }

    /**
     * 是否后缀摄像头
     * @return 是否后缀
     */
    public boolean isBackCamera() {
        return getUseCameraId() == Camera.CameraInfo.CAMERA_FACING_BACK;
    }

    private int findCameraId() {
        if (mCamera != null || Camera.getNumberOfCameras() == 0) {
            return NO_CAMERA_ID;
        }

        int ultimateCameraId = findCameraIdByFacing(mCameraId);
        if (ultimateCameraId != NO_CAMERA_ID) {
            return ultimateCameraId;
        }

        if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {
            ultimateCameraId = findCameraIdByFacing(Camera.CameraInfo.CAMERA_FACING_FRONT);
        } else if (mCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            ultimateCameraId = findCameraIdByFacing(Camera.CameraInfo.CAMERA_FACING_BACK);
        }

        return ultimateCameraId;
    }

    public void start() {
        synchronized (this) {
            //若指定了相机ID,则打开指定的相机
            if (specificCameraId != null) {
                mCameraId = specificCameraId;
            } else {//未指定则默认打开后置
                mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
            }

            //查找相机ID
            mCameraId = findCameraId();

            //没有相机
            if (mCameraId == -1) {
                if (cameraListener != null) {
                    cameraListener.onCameraError(new Exception("camera not found"));
                }
                return;
            }
            new CameraOpenTask(this).execute(mCameraId);
        }
    }

    private static final class CameraOpenTask extends AsyncTask<Integer, Void, Camera> {

        private final WeakReference<CameraHelper> cameraHelperWeakReference;

        public CameraOpenTask(CameraHelper cameraHelper) {
            cameraHelperWeakReference = new WeakReference<>(cameraHelper);
        }

        @Override
        protected Camera doInBackground(Integer... params) {
            return cameraHelperWeakReference.get().safeOpenCamera(params[0]);
        }

        @Override
        protected void onPostExecute(Camera camera) {
            CameraHelper mHelper = cameraHelperWeakReference.get();

            //验证视图对象是否消失,如果消失则释放Camera
            if (mHelper == null || mHelper.previewDisplayView == null || camera == null) {
                mHelper.release();
                return;
            }

            mHelper.setCameraParameters(camera);
        }
    }

    private Camera safeOpenCamera(int cameraId) {
        synchronized (this) {
            Camera camera = null;
            try {
                //打开前先判断Camera对象是否为空,如果不为空则先释放Camera,然后再打开
                releaseCamera();
                //打开前先判断视图是否消失,未消失则打开Camera
                if (previewDisplayView != null) {
                    camera = Camera.open(cameraId);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
                camera = null;
            }
            return camera;
        }
    }

    private void setCameraParameters(Camera camera) {
        synchronized (this) {
            try {
                mCamera = camera;

                displayOrientation = getCameraOri(rotation);
                mCamera.setDisplayOrientation(displayOrientation);

                Camera.Parameters parameters = mCamera.getParameters();
                parameters.setPreviewFormat(ImageFormat.NV21);

                //预览大小设置
                previewSize = parameters.getPreviewSize();
                LogUtil.d(TAG, "before preview width:" + previewSize.width + " height:" + previewSize.height);

                List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
                if (supportedPreviewSizes != null && supportedPreviewSizes.size() > 0) {
                    previewSize = getBestSupportedSize(supportedPreviewSizes, previewViewSize);
                }
                LogUtil.d(TAG, "after preview width:" + previewSize.width + " height:" + previewSize.height);
                parameters.setPreviewSize(previewSize.width, previewSize.height); // 预览大小

                // 照片大小赋值
                imageSize  = parameters.getPictureSize();
                LogUtil.d(TAG, "getPictureSize width:" + imageSize.width + " height:" + imageSize.height);
                List<Camera.Size> supportedPictureSizes = parameters.getSupportedPictureSizes();
                if (supportedPictureSizes != null && supportedPictureSizes.size() > 0) {
                    imageSize = getBestSupportedSize(supportedPictureSizes, pictureSizes);
                }
                LogUtil.d(TAG, "previewSize width:" + imageSize.width + " height:" + imageSize.height);
                parameters.setPictureSize(imageSize.width, imageSize.height); // 照片大小

                //对焦模式设置
                List<String> supportedFocusModes = parameters.getSupportedFocusModes();
                if (supportedFocusModes != null && supportedFocusModes.size() > 0) {
                    if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
                        parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
                    } else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
                        parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
                    } else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
                        parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
                    }
                }
                mCamera.setParameters(parameters);

                if (previewDisplayView instanceof TextureView) {
                    mCamera.setPreviewTexture(((TextureView) previewDisplayView).getSurfaceTexture());
                } else {
                    mCamera.setPreviewDisplay(((SurfaceView) previewDisplayView).getHolder());
                }

                mCamera.setPreviewCallback(this);
                mCamera.startPreview();

                if (cameraListener != null) {
                    cameraListener.onCameraOpened(mCamera, mCameraId, displayOrientation, isMirror);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
                if (cameraListener != null) {
                    cameraListener.onCameraError(ex);
                }
            }
        }
    }

    private int getCameraOri(int rotation) {
        int degrees = rotation * 90;
        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;
            default:
                break;
        }
        int result;
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(mCameraId, info);
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;
        } else {
            result = (info.orientation - degrees + 360) % 360;
        }
        return result;
    }

    public void releaseCamera() {
        try {
            if (mCamera == null) {
                return;
            }

            mCamera.setPreviewCallback(null);
            mCamera.setPreviewDisplay(null);
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void stop() {
        synchronized (this) {
            releaseCamera();
            if (cameraListener != null) {
                cameraListener.onCameraClosed();
            }
        }
    }

    public boolean isStopped() {
        return mCamera == null;
    }

    public void release() {
        stop();
        previewDisplayView = null;
        specificCameraId = null;
        cameraListener = null;
        previewViewSize = null;
        pictureSizes = null;
        specificPreviewSize = null;
        previewSize = null;
    }

    private Camera.Size getBestSupportedSize(List<Camera.Size> sizes, Point previewViewSize) {
        if (sizes == null || sizes.size() == 0) {
            return mCamera.getParameters().getPreviewSize();
        }
        Camera.Size[] tempSizes = sizes.toArray(new Camera.Size[0]);
        Arrays.sort(tempSizes, new Comparator<Camera.Size>() {
            @Override
            public int compare(Camera.Size o1, Camera.Size o2) {
                if (o1.width > o2.width) {
                    return -1;
                } else if (o1.width == o2.width) {
                    if (o1.height > o2.height) {
                        return -1;
                    } else if (o1.height < o2.height) {
                        return 1;
                    } else {
                        return 0;
                    }
                } else {
                    return 1;
                }
            }
        });
        sizes = Arrays.asList(tempSizes);

        Camera.Size bestSize = sizes.get(0);
        float previewViewRatio;
        if (previewViewSize != null) {
            previewViewRatio = (float) previewViewSize.x / (float) previewViewSize.y;
        } else {
            previewViewRatio = (float) bestSize.width / (float) bestSize.height;
        }

        if (previewViewRatio > 1) {
            previewViewRatio = 1 / previewViewRatio;
        }
        boolean isNormalRotate = (additionalRotation % 180 == 0);

        for (Camera.Size s : sizes) {
            LogUtil.d(TAG, "camera size width:" + s.width + " height:" + s.height);

            if (specificPreviewSize != null && specificPreviewSize.x == s.width && specificPreviewSize.y == s.height) {
                return s;
            }
            if (isNormalRotate) {
                if (Math.abs((s.height / (float) s.width) - previewViewRatio) < Math.abs(bestSize.height / (float) bestSize.width - previewViewRatio)) {
                    bestSize = s;
                }
            } else {
                if (Math.abs((s.width / (float) s.height) - previewViewRatio) < Math.abs(bestSize.width / (float) bestSize.height - previewViewRatio)) {
                    bestSize = s;
                }
            }
        }
        return bestSize;
    }

    public List<Camera.Size> getSupportedPreviewSizes() {
        if (mCamera == null) {
            return null;
        }
        return mCamera.getParameters().getSupportedPreviewSizes();
    }

    public List<Camera.Size> getSupportedPictureSizes() {
        if (mCamera == null) {
            return null;
        }
        return mCamera.getParameters().getSupportedPictureSizes();
    }

    @Override
    public void onPreviewFrame(byte[] nv21, Camera camera) {
        if (cameraListener != null) {
            cameraListener.onPreview(nv21, camera);
        }
    }

    private final TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
            if (mCamera != null) {
                try {
                    mCamera.setPreviewTexture(surfaceTexture);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
            Log.i(TAG, "onSurfaceTextureSizeChanged: " + width + "  " + height);
        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
            stop();
            return false;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {

        }
    };

    private final SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            if (mCamera != null) {
                try {
                    mCamera.setPreviewDisplay(holder);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

        }

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

    public void changeDisplayOrientation(int rotation) {
        if (mCamera != null) {
            this.rotation = rotation;
            displayOrientation = getCameraOri(rotation);
            mCamera.setDisplayOrientation(displayOrientation);
            if (cameraListener != null) {
                cameraListener.onCameraConfigurationChanged(mCameraId, displayOrientation);
            }
        }
    }

    public static final class Builder {

        /**
         * 预览显示的view,目前仅支持surfaceView和textureView
         */
        private View previewDisplayView;

        /**
         * 是否镜像显示,只支持textureView
         */
        private boolean isMirror;
        /**
         * 指定的相机ID
         */
        private Integer specificCameraId;
        /**
         * 事件回调
         */
        private CameraListener cameraListener;
        /**
         * 屏幕的长宽,在选择最佳相机比例时用到
         */
        private Point previewViewSize;
        /**
         * 图片大小
         */
        private Point pictureSizes;
        /**
         * 传入getWindowManager().getDefaultDisplay().getRotation()的值即可
         */
        private int rotation;
        /**
         * 指定的预览宽高,若系统支持则会以这个预览宽高进行预览
         */
        private Point previewSize;

        /**
         * 额外的旋转角度(用于适配一些定制设备)
         */
        private int additionalRotation;

        public Builder() {

        }


        public Builder previewOn(View val) {
            if (val instanceof SurfaceView || val instanceof TextureView) {
                previewDisplayView = val;
                return this;
            } else {
                throw new RuntimeException("you must preview on a textureView or a surfaceView");
            }
        }


        public Builder isMirror(boolean val) {
            isMirror = val;
            return this;
        }

        public Builder previewSize(Point val) {
            previewSize = val;
            return this;
        }

        public Builder previewViewSize(Point val) {
            LogUtil.d(TAG, "preview view width:" + val.x + " height:" + val.y);
            previewViewSize = val;
            return this;
        }

        public Builder pictureSizes(Point val) {
            LogUtil.d(TAG, "pictureSizes width:" + val.x + " height:" + val.y);
            pictureSizes = val;
            return this;
        }

        public Builder rotation(int val) {
            LogUtil.d(TAG, "rotation:" + val);
            rotation = val;
            return this;
        }

        public Builder additionalRotation(int val) {
            additionalRotation = val;
            return this;
        }

        public Builder specificCameraId(Integer val) {
            specificCameraId = val;
            return this;
        }

        public Builder cameraListener(CameraListener val) {
            cameraListener = val;
            return this;
        }

        public CameraHelper build() {
            if (previewViewSize == null) {
                Log.e(TAG, "previewViewSize is null, now use default previewSize");
            }
            if (cameraListener == null) {
                Log.e(TAG, "cameraListener is null, callback will not be called");
            }
            if (previewDisplayView == null) {
                throw new RuntimeException("you must preview on a textureView or a surfaceView");
            }
            return new CameraHelper(this);
        }
    }

    public void takePicture(Camera.ShutterCallback mShutterCallback, Camera.PictureCallback rawPictureCallback, Camera.PictureCallback jpegPictureCallback) {
        if (mCamera != null)
            mCamera.takePicture(mShutterCallback, rawPictureCallback, jpegPictureCallback);
    }
}

Actlvity调用

l8dw8tz4.png

    /**
     * 拍照
     */
    private void photographButton() {
        getChildBinding().svCamera.takePicture(mShutterCallback, rawPictureCallback, jpegPictureCallback);
    }

    /**
     * 拍照快门的回调
     */
    private final Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() {
        @Override
        public void onShutter() {

        }
    };

    /**
     * 拍照完成之后返回原始数据的回调
     */
    private final Camera.PictureCallback rawPictureCallback = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {

        }
    };

    /**
     * 拍照完成之后返回压缩数据的回调
     */
    private final Camera.PictureCallback jpegPictureCallback = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            CommonToast.INSTANCE.showToast(ReturnSchoolOrEliminateLeaveActivity.this, getResources().getString(R.string.photograph_button_succeed_text));
            queueEvent(new Runnable() {
                @Override
                public void run() {
                    saveFile(data);
                }
            },0L);
        }
    };

到此结束,下次再见。

本文共 332 个字数,平均阅读时长 ≈ 1分钟
1

打赏

:D 获取中...

更多精彩文章,按Ctrl+D收藏本站!

评论 (0)

取消