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

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

基础使用

xml布局

1
2
3
4
5
6
7
8
9
<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
<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" />

动态权限代码略

构建图像捕获用例

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) {
@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()将不会获取到下一张图片

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();
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();
});
}

初始化相机

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(@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();
}

拍照保存图片

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() {

@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实现手势事件

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

回调接口

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(@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;
}
};
}

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

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
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) {

}
});
}

闪光灯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2020 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :