Android Jetpack架构组件(四)CameraX基本功能

本文使用的CameraX版本为1.0.0-beta03,包含CameraX的简单拍照保存、图像分析(可用于二维码识别等用途)、缩放、对焦等相关内容

基础使用

xml布局

<androidx.camera.view.PreviewView
    android:id="@+id/view_finder"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    />

权限声明

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FLASHLIGHT" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

动态权限代码略

构建图像捕获用例

private void initImageCapture() {

    // 构建图像捕获用例
    mImageCapture = new ImageCapture.Builder()
            .setFlashMode(ImageCapture.FLASH_MODE_AUTO)
            .setTargetAspectRatio(AspectRatio.RATIO_4_3)
            .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
            .build();

    // 旋转监听
    OrientationEventListener orientationEventListener = new OrientationEventListener((Context) this) {
        @Override
        public void onOrientationChanged(int orientation) {
            int rotation;

            // Monitors orientation values to determine the target rotation value
            if (orientation >= 45 && orientation < 135) {
                rotation = Surface.ROTATION_270;
            } else if (orientation >= 135 && orientation < 225) {
                rotation = Surface.ROTATION_180;
            } else if (orientation >= 225 && orientation < 315) {
                rotation = Surface.ROTATION_90;
            } else {
                rotation = Surface.ROTATION_0;
            }

            mImageCapture.setTargetRotation(rotation);
        }
    };

    orientationEventListener.enable();
}

构建图像分析用例(可用于二维码识别等用途)

注意:Analyzer回调方法中如果不调用image.close()将不会获取到下一张图片

private void initImageAnalysis() {

    mImageAnalysis = new ImageAnalysis.Builder()
            // 分辨率
            .setTargetResolution(new Size(1280, 720))
            // 仅将最新图像传送到分析仪,并在到达图像时将其丢弃。
            .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
            .build();

    mImageAnalysis.setAnalyzer(executor, image -> {
        int rotationDegrees = image.getImageInfo().getRotationDegrees();
        Log.e("yezhou", "Analysis#rotationDegrees: " + rotationDegrees);
        ImageProxy.PlaneProxy[] planes = image.getPlanes();

        ByteBuffer buffer = planes[0].getBuffer();
        // 转为byte[]
        // byte[] b = new byte[buffer.remaining()];
        // Log.e("yezhou", b);
        // TODO: 分析完成后关闭图像参考,否则会阻塞其他图像的产生
        // image.close();
    });
}

初始化相机

private Executor executor;
...
private void initCamera() {

    executor = ContextCompat.getMainExecutor(this);

    cameraProviderFuture = ProcessCameraProvider.getInstance(this);
    cameraProviderFuture.addListener(() -> {
        try {
            ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
            // 绑定预览
            bindPreview(cameraProvider);
        } catch (ExecutionException | InterruptedException e) {
            // No errors need to be handled for this Future.
            // This should never be reached.
        }
    }, executor);

}

绑定预览

private void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {
    Preview preview = new Preview.Builder()
            .build();

    CameraSelector cameraSelector = new CameraSelector.Builder()
            .requireLensFacing(CameraSelector.LENS_FACING_BACK)
            .build();

    // mImageCapture 图像捕获用例
    // mImageAnalysis 图像分析用例
    Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, mImageCapture, mImageAnalysis, preview);

    mCameraInfo = camera.getCameraInfo();
    mCameraControl = camera.getCameraControl();

    preview.setSurfaceProvider(mViewFinder.createSurfaceProvider(mCameraInfo));

    initCameraListener();
}

拍照保存图片

public void saveImage() {
    File file = new File(getExternalMediaDirs()[0], System.currentTimeMillis() + ".jpg");
    ImageCapture.OutputFileOptions outputFileOptions =
            new ImageCapture.OutputFileOptions.Builder(file).build();
    mImageCapture.takePicture(outputFileOptions, executor,
            new ImageCapture.OnImageSavedCallback() {

                @Override
                public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
                    String msg = "图片保存成功: " + file.getAbsolutePath();
                    showMsg(msg);
                    Log.i("yezhou", msg);
                    Uri contentUri = Uri.fromFile(new File(file.getAbsolutePath()));
                    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, contentUri);
                    sendBroadcast(mediaScanIntent);
                }

                @Override
                public void onError(@NonNull ImageCaptureException exception) {
                    String msg = "图片保存失败: " + exception.getMessage();
                    showMsg(msg);
                    Log.e("yezhou", msg);
                }
            }
    );
}

自定义PreviewView实现手势事件

点击、双击、缩放、长按等

回调接口

public interface CustomTouchListener {
    /**
     * 放大
     */
    void zoom();

    /**
     * 缩小
     */
    void ZoomOut();

    /**
     * 点击
     */
    void click(float x, float y);

    /**
     * 双击
     */
    void doubleClick(float x, float y);

    /**
     * 长按
     */
    void longClick(float x, float y);
}

手势监听代码

public class CameraXCustomPreviewView extends PreviewView {
    private GestureDetector mGestureDetector;
    /**
    * 缩放相关
    */
    private float currentDistance = 0;
    private float lastDistance = 0;

    ... 省略部分构造方法

    public CameraXCustomPreviewView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        mGestureDetector = new GestureDetector(context, onGestureListener);
        mGestureDetector.setOnDoubleTapListener(onDoubleTapListener);

        // mScaleGestureDetector = new ScaleGestureDetector(context, onScaleGestureListener);
        // 解决长按屏幕无法拖动,但是会造成无法识别长按事件
        // mGestureDetector.setIsLongpressEnabled(false);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 接管onTouchEvent
        return mGestureDetector.onTouchEvent(event);
    }

    GestureDetector.OnGestureListener onGestureListener = new GestureDetector.OnGestureListener() {
        @Override
        public boolean onDown(MotionEvent e) {
            LogUtils.i("onDown: 按下");
            return true;
        }

        @Override
        public void onShowPress(MotionEvent e) {
            LogUtils.i("onShowPress: 刚碰上还没松开");
        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            LogUtils.i("onSingleTapUp: 轻轻一碰后马上松开");
            return true;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            LogUtils.i("onScroll: 按下后拖动");
            // 大于两个触摸点
            if (e2.getPointerCount() >= 2) {

                //event中封存了所有屏幕被触摸的点的信息,第一个触摸的位置可以通过event.getX(0)/getY(0)得到
                float offSetX = e2.getX(0) - e2.getX(1);
                float offSetY = e2.getY(0) - e2.getY(1);
                //运用三角函数的公式,通过计算X,Y坐标的差值,计算两点间的距离
                currentDistance = (float) Math.sqrt(offSetX * offSetX + offSetY * offSetY);
                if (lastDistance == 0) {//如果是第一次进行判断
                    lastDistance = currentDistance;
                } else {
                    if (currentDistance - lastDistance > 10) {
                        // 放大
                        if (mCustomTouchListener != null) {
                            mCustomTouchListener.zoom();
                        }
                    } else if (lastDistance - currentDistance > 10) {
                        // 缩小
                        if (mCustomTouchListener != null) {
                            mCustomTouchListener.ZoomOut();
                        }
                    }
                }
                //在一次缩放操作完成后,将本次的距离赋值给lastDistance,以便下一次判断
                //但这种方法写在move动作中,意味着手指一直没有抬起,监控两手指之间的变化距离超过10
                //就执行缩放操作,不是在两次点击之间的距离变化来判断缩放操作
                //故这种将本次距离留待下一次判断的方法,不能在两次点击之间使用
                lastDistance = currentDistance;
            }
            return true;
        }

        @Override
        public void onLongPress(MotionEvent e) {
            LogUtils.i("onLongPress: 长按屏幕");
            if (mCustomTouchListener != null) {
                mCustomTouchListener.longClick(e.getX(), e.getY());
            }
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            LogUtils.i("onFling: 滑动后松开");
            currentDistance = 0;
            lastDistance = 0;
            return true;
        }
    };

    GestureDetector.OnDoubleTapListener onDoubleTapListener = new GestureDetector.OnDoubleTapListener() {
        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            LogUtils.i("onSingleTapConfirmed: 严格的单击");
            if (mCustomTouchListener != null) {
                mCustomTouchListener.click(e.getX(), e.getY());
            }
            return true;
        }

        @Override
        public boolean onDoubleTap(MotionEvent e) {
            LogUtils.i("onDoubleTap: 双击");
            if (mCustomTouchListener != null) {
                mCustomTouchListener.doubleClick(e.getX(), e.getY());
            }
            return true;
        }

        @Override
        public boolean onDoubleTapEvent(MotionEvent e) {
            LogUtils.i("onDoubleTapEvent: 表示发生双击行为");
            return true;
        }
    };
}

双指滑动/双击缩放、点击对焦

private void initCameraListener() {
    LiveData<ZoomState> zoomState = mCameraInfo.getZoomState();
    float maxZoomRatio = zoomState.getValue().getMaxZoomRatio();
    float minZoomRatio = zoomState.getValue().getMinZoomRatio();
    LogUtils.e(maxZoomRatio);
    LogUtils.e(minZoomRatio);

    mViewFinder.setCustomTouchListener(new CameraXCustomPreviewView.CustomTouchListener() {
        @Override
        public void zoom() {
            float zoomRatio = zoomState.getValue().getZoomRatio();
            if (zoomRatio < maxZoomRatio) {
                mCameraControl.setZoomRatio((float) (zoomRatio + 0.1));
            }
        }

        @Override
        public void ZoomOut() {
            float zoomRatio = zoomState.getValue().getZoomRatio();
            if (zoomRatio > minZoomRatio) {
                mCameraControl.setZoomRatio((float) (zoomRatio - 0.1));
            }
        }

        @Override
        public void click(float x, float y) {
            // TODO 对焦
            MeteringPointFactory factory = new SurfaceOrientedMeteringPointFactory(1.0f, 1.0f);
            MeteringPoint point = factory.createPoint(x, y);
            FocusMeteringAction action = new FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF)
                    // auto calling cancelFocusAndMetering in 3 seconds
                    .setAutoCancelDuration(3, TimeUnit.SECONDS)
                    .build();

            mFocusView.startFocus(new Point((int) x, (int) y));
            ListenableFuture future = mCameraControl.startFocusAndMetering(action);
            future.addListener(() -> {
                try {
                    FocusMeteringResult result = (FocusMeteringResult) future.get();
                    if (result.isFocusSuccessful()) {
                        mFocusView.onFocusSuccess();
                    } else {
                        mFocusView.onFocusFailed();
                    }
                } catch (Exception e) {
                }
            }, executor);
        }

        @Override
        public void doubleClick(float x, float y) {
            // 双击放大缩小
            float zoomRatio = zoomState.getValue().getZoomRatio();
            if (zoomRatio > minZoomRatio) {
                mCameraControl.setLinearZoom(0f);
            } else {
                mCameraControl.setLinearZoom(0.5f);
            }
        }

        @Override
        public void longClick(float x, float y) {

        }
    });
}

闪光灯

switch (mImageCapture.getFlashMode()) {
    case ImageCapture.FLASH_MODE_AUTO:
        mImageCapture.setFlashMode(ImageCapture.FLASH_MODE_ON);
        mBtnLight.setText("闪光灯:开");
        break;
    case ImageCapture.FLASH_MODE_ON:
        mImageCapture.setFlashMode(ImageCapture.FLASH_MODE_OFF);
        mBtnLight.setText("闪光灯:关");
        break;
    case ImageCapture.FLASH_MODE_OFF:
        mImageCapture.setFlashMode(ImageCapture.FLASH_MODE_AUTO);
        mBtnLight.setText("闪光灯:自动");
        break;
}

版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/25/android-jetpack-architecture-components-basic-features-of-camerax/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
海报
Android Jetpack架构组件(四)CameraX基本功能
本文使用的CameraX版本为1.0.0-beta03,包含CameraX的简单拍照保存、图像分析(可用于二维码识别等用途)、缩放、对焦等相关内容 基础使用 xml布局 <andro……
<<上一篇
下一篇>>
文章目录
关闭
目 录