在今年的Google I/O大会上,Google新推出了CameraX支持包,按照官方的说法, 这个包的作用是:
help you make camera app development easier
安卓中使用相机从来就不是一件容易的事。
Camera1要自己管理Camera相机实例,要处理SufraceView相关的一堆东西,还有预览尺寸跟画面尺寸的选择,页面生命周期切换等等问题,后来推出了Camera2,从官方demo 就上千行代码来看,Camera2并不解决用起来复杂的问题,它提供了更多的调用接口,可定制性更好,结果就是对普通开发者来说更难用了。。。
终于Google也意识到这个问题,推出了最终版CameraX. CameraX实际上还是用的Camera2,但它对调用API进行了很好的封装,使用起来非常方便。官方教程也很详细,如下: codelabs.developers.google.com/codelabs/ca…
注意:CameraX跟Camera2一样最低支持API21,也就是5.0及以上。 开发环境用Android Studio3.3及以上,依赖库都用androidx的
创建app 创建app,声明相机权限
添加相关依赖// CameraX core library
def camerax_version = "1.0.0-alpha05"
// CameraX view library
def camerax_view_version = "1.0.0-alpha02"
// CameraX extensions library
def camerax_ext_version = "1.0.0-alpha02"
implementation "androidx.camera:camera-core:camerax_version"
// If you to use the Camera View class
implementation "androidx.camera:camera-view:
camerax_ext_version"
布局文件:
<androidx.camera.view.CameraView
android:id="@+id/view_camera"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="@+id/btn_take_pic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginLeft="16dp"
android:layout_marginBottom="16dp"
android:text="拍照" />
<Button
android:id="@+id/btn_start_recording"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginLeft="16dp"
android:layout_marginBottom="16dp"
android:layout_toRightOf="@+id/btn_take_pic"
android:text="@string/start_recording" />
<Button
android:id="@+id/btn_switch_camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginLeft="16dp"
android:layout_marginBottom="16dp"
android:layout_toRightOf="@+id/btn_start_recording"
android:text="切换摄像头" />
<ImageView
android:id="@+id/iv_show_pic"
android:layout_width="100dp"
android:layout_height="150dp"
android:layout_alignParentRight="true"
android:layout_marginTop="16dp"
android:layout_marginRight="16dp" />
<VideoView
android:id="@+id/vv_show_recording"
android:layout_width="100dp"
android:layout_height="150dp"
android:visibility="gone" />
切记在启动该Activity的时候要动态获取到相机权限 读写权限,麦克风权限
我demo 中引入的是:
implementation 'com.blankj:utilcodex:1.26.0'
动态获取取权限: public void getPermission() { PermissionUtils.permission(PermissionConstants.STORAGE, PermissionConstants.CAMERA, PermissionConstants.MICROPHONE) .rationale(new PermissionUtils.OnRationaleListener() { @Override public void rationale(final ShouldRequest shouldRequest) { shouldRequest.again(true); } }) .callback(new PermissionUtils.FullCallback() { @Override public void onGranted(List permissionsGranted) { startActivity(new Intent(WelcomeActivity.this, MainActivity.class)); }
@Override
public void onDenied(List<String> permissionsDeniedForever,
List<String> permissionsDenied) {
if (!permissionsDeniedForever.isEmpty()) {
PermissionUtils.launchAppDetailsSettings();
}
}
}).request();
} onGranted回调是获取到了所有权限,获取到所有权限后就可以进入到相机界面,这时候进入相机界面相机后置摄像头就默认打开了,也就是进入了Preview状态
相机界面首先: CameraView 的 bindToLifeCycle 方法将这个 View 与当前组件的生命周期绑定:
mCameraView.bindToLifecycle(this); 拍照:
if (mCameraView == null) { return; } mCameraView.setCaptureMode(CameraView.CaptureMode.IMAGE); mCameraView.takePicture(initTakePicPath(), new ImageCapture.OnImageSavedListener() { @Override public void onImageSaved(@NonNull File file) { LogUtils.d("MainActivity takePicture onImageSaved file : " + file); if (mShowPicView == null) { return; } try { Glide.with(MainActivity.this) .load(file) .into(mShowPicView); } catch (Exception e) {
}
}
@Override
public void onError(@NonNull ImageCapture.ImageCaptureError imageCaptureError, @NonNull String message,
@Nullable Throwable cause) {
LogUtils.d("MainActivity takePicture onError imageCaptureError : " + imageCaptureError + " message : " + message +
" Throwable : " + cause);
}
}); 录像:
if (mCameraView == null) { return; } if (mCameraView.isRecording()) { mCameraView.stopRecording(); mStartRecordingView.setText(ResourceUtil.getString(R.string.start_recording)); } else { mStartRecordingView.setText(ResourceUtil.getString(R.string.stop_recording)); mCameraView.setCaptureMode(CameraView.CaptureMode.VIDEO); mCameraView.startRecording(initStartRecordingPath(), new VideoCapture.OnVideoSavedListener() { @Override public void onVideoSaved(@NonNull File file) { LogUtils.d("MainActivity startRecording onVideoSaved file : " + file); if (mShowVideoView == null) { return; } mShowVideoView.post(new Runnable() { @Override public void run() { mShowVideoView.setVisibility(View.VISIBLE); mShowVideoView.setVideoPath(file.getAbsolutePath()); mShowVideoView.start(); mShowVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mediaPlayer) { mShowVideoView.start(); } }); } }); }
@Override
public void onError(@NonNull VideoCapture.VideoCaptureError videoCaptureError, @NonNull String message, @Nullable Throwable cause) {
LogUtils.d("MainActivity startRecording onError imageCaptureError : " + videoCaptureError + " message : " + message +
" Throwable : " + cause);
}
});
} 注意:1:录像回来是子线程 如果想使用VideoView播放该视频 需要切换到UI线程 2.录像钱要切换:CaptureMode 否则会报错:
throw new IllegalStateException("Can not record video under IMAGE capture mode."); 看下源码就知道:
public void startRecording(File file, final OnVideoSavedListener listener) { if (mVideoCapture == null) { return; }
if (getCaptureMode() == CaptureMode.IMAGE) {
throw new IllegalStateException("Can not record video under IMAGE capture mode.");
}
if (listener == null) {
throw new IllegalArgumentException("OnVideoSavedListener should not be empty");
}
mVideoIsRecording.set(true);
mVideoCapture.startRecording(
file,
new VideoCapture.OnVideoSavedListener() {
@Override
public void onVideoSaved(@NonNull File savedFile) {
mVideoIsRecording.set(false);
listener.onVideoSaved(savedFile);
}
@Override
public void onError(
@NonNull VideoCapture.VideoCaptureError videoCaptureError,
@NonNull String message,
@Nullable Throwable cause) {
mVideoIsRecording.set(false);
Log.e(TAG, message, cause);
listener.onError(videoCaptureError, message, cause);
}
});
} 切换摄像头:
if (mCameraView == null) { return; } CameraX.LensFacing lensFacing = mCameraView.getCameraLensFacing(); if (lensFacing == null) { return; } if (lensFacing == CameraX.LensFacing.FRONT) { mCameraView.setCameraLensFacing(CameraX.LensFacing.BACK); } else { mCameraView.setCameraLensFacing(CameraX.LensFacing.FRONT); }
Demo已经上传到Github 上了:github.com/HuaDanJson/…