一、简介
最近CameraX发布了第一个beta版本,相较于alpha版本的api疯狂改动慢慢趋于稳定。
本篇文章主要内容包含CameraX的简单拍照保存、图像分析(可用于二维码识别等用途)、缩放、对焦等相关内容
注:
- 当前本文使用的CameraX版本为1.0.0-beta01。
- 修改相机比例、切换摄像头、二维码识别等以及最新版本使用请点击底部链接查看Demo。
二、基础使用
- gradle依赖 - 1 
 2
 3
 4- 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布局 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10- <!-- ... --> 
 <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" />
 <!-- ... -->
- 构建图像捕获用例 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32- 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) {
 
 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()将不会获取到下一张图片- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22- 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();
 });
 }
- 初始化相机 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19- 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);
 }
- 绑定预览 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19- private void bindPreview( 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();
 }
- 拍照保存图片 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26- 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() {
 
 public void onImageSaved( 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);
 }
 
 public void onError( ImageCaptureException exception) {
 String msg = "图片保存失败: " + exception.getMessage();
 showMsg(msg);
 LogUtils.e(msg);
 }
 }
 );
 }
三、自定义PreviewView实现手势事件(点击、双击、缩放…)
- 回掉接口 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26- 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);
 }
- 手势监听代码 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123- public class CameraXCustomPreviewView extends PreviewView { 
 private GestureDetector mGestureDetector;
 /**
 * 缩放相关
 */
 private float currentDistance = 0;
 private float lastDistance = 0;
 ... 省略部分构造方法
 public CameraXCustomPreviewView( Context context, 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);
 }
 
 public boolean onTouchEvent(MotionEvent event) {
 // 接管onTouchEvent
 return mGestureDetector.onTouchEvent(event);
 }
 GestureDetector.OnGestureListener onGestureListener = new GestureDetector.OnGestureListener() {
 
 public boolean onDown(MotionEvent e) {
 LogUtils.i("onDown: 按下");
 return true;
 }
 
 public void onShowPress(MotionEvent e) {
 LogUtils.i("onShowPress: 刚碰上还没松开");
 }
 
 public boolean onSingleTapUp(MotionEvent e) {
 LogUtils.i("onSingleTapUp: 轻轻一碰后马上松开");
 return true;
 }
 
 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;
 }
 
 public void onLongPress(MotionEvent e) {
 LogUtils.i("onLongPress: 长按屏幕");
 if (mCustomTouchListener != null) {
 mCustomTouchListener.longClick(e.getX(), e.getY());
 }
 }
 
 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() {
 
 public boolean onSingleTapConfirmed(MotionEvent e) {
 LogUtils.i("onSingleTapConfirmed: 严格的单击");
 if (mCustomTouchListener != null) {
 mCustomTouchListener.click(e.getX(), e.getY());
 }
 return true;
 }
 
 public boolean onDoubleTap(MotionEvent e) {
 LogUtils.i("onDoubleTap: 双击");
 if (mCustomTouchListener != null) {
 mCustomTouchListener.doubleClick(e.getX(), e.getY());
 }
 return true;
 }
 
 public boolean onDoubleTapEvent(MotionEvent e) {
 LogUtils.i("onDoubleTapEvent: 表示发生双击行为");
 return true;
 }
 };
 }
四、双指滑动/双击缩放、点击对焦
| 1 | private void initCameraListener() { | 
五、闪光灯
| 1 | switch (mImageCapture.getFlashMode()) { |