Android CameraX入门、双指/双击缩放、点击对焦、切换比例、二维码识别...
一、简介
最近CameraX发布了第一个beta版本,相较于alpha版本的api疯狂改动慢慢趋于稳定。
本篇文章主要内容包含CameraX的简单拍照保存、图像分析(可用于二维码识别等用途)、缩放、对焦等相关内容
注:
- 当前本文使用的
CameraX版本为1.0.0-beta01。 - 修改相机比例、切换摄像头、二维码识别等以及最新版本使用请点击底部链接查看Demo。
- Camera-View Version 1.0.0-alpha19开始PreviewView被标注了final,无法继承,故触摸事件改为了setOnTouchListener,详细改动可以查看底部demo
二、基础使用
-
gradle依赖
def camerax_version = "1.0.0-beta01" implementation "androidx.camera:camera-camera2:${camerax_version}" implementation "androidx.camera:camera-view:1.0.0-alpha08" implementation "androidx.camera:camera-lifecycle:${camerax_version}" -
xml布局
<androidx.camera.view.PreviewViewandroid: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" /> -
构建图像捕获用例
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) {@Overridepublic void onOrientationChanged(int orientation) {int rotation;// Monitors orientation values to determine the target rotation valueif (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();LogUtils.e("Analysis#rotationDegrees", rotationDegrees);ImageProxy.PlaneProxy[] planes = image.getPlanes();ByteBuffer buffer = planes[0].getBuffer();// 转为byte[]// byte[] b = new byte[buffer.remaining()];// LogUtils.e(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();preview.setSurfaceProvider(mViewFinder.getPreviewSurfaceProvider());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();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() {@Overridepublic void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {String msg = "图片保存成功: " + file.getAbsolutePath();showMsg(msg);LogUtils.d(msg);Uri contentUri = Uri.fromFile(new File(file.getAbsolutePath()));Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, contentUri);sendBroadcast(mediaScanIntent);}@Overridepublic void onError(@NonNull ImageCaptureException exception) {String msg = "图片保存失败: " + exception.getMessage();showMsg(msg);LogUtils.e(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);}@Overridepublic boolean onTouchEvent(MotionEvent event) {// 接管onTouchEventreturn mGestureDetector.onTouchEvent(event);}GestureDetector.OnGestureListener onGestureListener = new GestureDetector.OnGestureListener() {@Overridepublic boolean onDown(MotionEvent e) {LogUtils.i("onDown: 按下");return true;}@Overridepublic void onShowPress(MotionEvent e) {LogUtils.i("onShowPress: 刚碰上还没松开");}@Overridepublic boolean onSingleTapUp(MotionEvent e) {LogUtils.i("onSingleTapUp: 轻轻一碰后马上松开");return true;}@Overridepublic 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;}@Overridepublic void onLongPress(MotionEvent e) {LogUtils.i("onLongPress: 长按屏幕");if (mCustomTouchListener != null) {mCustomTouchListener.longClick(e.getX(), e.getY());}}@Overridepublic 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() {@Overridepublic boolean onSingleTapConfirmed(MotionEvent e) {LogUtils.i("onSingleTapConfirmed: 严格的单击");if (mCustomTouchListener != null) {mCustomTouchListener.click(e.getX(), e.getY());}return true;}@Overridepublic boolean onDoubleTap(MotionEvent e) {LogUtils.i("onDoubleTap: 双击");if (mCustomTouchListener != null) {mCustomTouchListener.doubleClick(e.getX(), e.getY());}return true;}@Overridepublic 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() {@Overridepublic void zoom() {float zoomRatio = zoomState.getValue().getZoomRatio();if (zoomRatio < maxZoomRatio) {mCameraControl.setZoomRatio((float) (zoomRatio + 0.1));}}@Overridepublic void ZoomOut() {float zoomRatio = zoomState.getValue().getZoomRatio();if (zoomRatio > minZoomRatio) {mCameraControl.setZoomRatio((float) (zoomRatio - 0.1));}}@Overridepublic void click(float x, float y) {// 1.0.0-beta08 createMeteringPointFactory()方法已删除,Demo改为使用getMeteringPointFactory()MeteringPointFactory factory = mBinding.viewFinder.createMeteringPointFactory(mCameraSelector);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);}@Overridepublic void doubleClick(float x, float y) {// 双击放大缩小float zoomRatio = zoomState.getValue().getZoomRatio();if (zoomRatio > minZoomRatio) {mCameraControl.setLinearZoom(0f);} else {mCameraControl.setLinearZoom(0.5f);}}@Overridepublic 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;
}
六、Demo
Demo:https://github.com/sdwfqin/AndroidQuick/tree/4.x/app
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
