项目地址
这一篇我们主要是介绍下CameraX
的使用
官方地址
官方Demo
这里我们最好是通过官方地址来了解下Camera
同时Google
也为我们写了一个CameraX
的Demo供我们参考,下面我们就简单介绍下CameraX
的使用
def camerax_core_version = "1.0.0-beta03"
def camerax_version = "1.0.0-alpha10"
api "androidx.camera:camera-core:${camerax_core_version}"
api "androidx.camera:camera-camera2:${camerax_core_version}"
// If you want to use the CameraX View class
api "androidx.camera:camera-view:${camerax_version}"
// If you want to use the CameraX Extensions library
api "androidx.camera:camera-extensions:${camerax_version}"
// If you want to use the CameraX Lifecycle library
api "androidx.camera:camera-lifecycle:${camerax_version}"
同时需要JDK1.8
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
<uses-permission android:name="android.permission.CAMERA" />
这里我们还需要动态申请拍照权限
private static final String[] PERMISSIONS = new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO};
//onCreate方法中调用
ActivityCompat.requestPermissions(this, PERMISSIONS, PERMISSION_CODE);
//权限回调
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_CODE) {
deniedPermission.clear();
for (int i = 0; i < permissions.length; i++) {
String permission = permissions[i];
int result = grantResults[i];
if (result != PackageManager.PERMISSION_GRANTED) {
deniedPermission.add(permission);
}
}
if (deniedPermission.isEmpty()) {
bindCameraX();
} else {
new AlertDialog.Builder(this)
.setMessage(getString(R.string.capture_permission_message))
.setNegativeButton(getString(R.string.capture_permission_no), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
CaptureActivity.this.finish();
}
})
.setPositiveButton(getString(R.string.capture_permission_ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String[] denied = new String[deniedPermission.size()];
ActivityCompat.requestPermissions(CaptureActivity.this, deniedPermission.toArray(denied), PERMISSION_CODE);
}
}).create().show();
}
}
}
private void bindCameraX() {
executor = ContextCompat.getMainExecutor(this);
cameraProviderFuture = ProcessCameraProvider.getInstance(this);
cameraProviderFuture.addListener(() -> {
try {
cameraProvider = cameraProviderFuture.get();
bindPreview();
} catch (ExecutionException | InterruptedException e) {
// No errors need to be handled for this Future.
// This should never be reached.
}
}, executor);
}
PreView
@SuppressLint("RestrictedApi")
private void bindPreview() {
cameraProvider.unbindAll();
cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build();
//查询一下当前要使用的设备摄像头(比如后置摄像头)是否存在
boolean hasAvailableCameraId = false;
hasAvailableCameraId = CameraX.hasCamera(cameraSelector);
if (!hasAvailableCameraId) {
showErrorToast("无可用的设备cameraId!,请检查设备的相机是否被占用");
finish();
return;
}
//
//查询一下是否存在可用的cameraId.形式如:后置:"0",前置:"1"
String cameraIdForLensFacing = null;
try {
cameraIdForLensFacing = CameraX.getCameraFactory().cameraIdForLensFacing(CameraSelector.LENS_FACING_BACK);
} catch (CameraInfoUnavailableException e) {
e.printStackTrace();
}
if (TextUtils.isEmpty(cameraIdForLensFacing)) {
showErrorToast("无可用的设备cameraId!,请检查设备的相机是否被占用");
finish();
return;
}
preview = new Preview.Builder()
.setCameraSelector(cameraSelector) //前后摄像头
.setTargetAspectRatio(AspectRatio.RATIO_16_9) //宽高比
.setTargetRotation(rotation) //旋转角度
//.setTargetResolution(resolution) //分辨率
.build();
imageCapture = new ImageCapture.Builder()
.setCameraSelector(cameraSelector)
.setTargetAspectRatio(AspectRatio.RATIO_16_9)
.setTargetRotation(rotation)
//.setTargetResolution(resolution)
.build();
videoCapture = new VideoCaptureConfig.Builder()
.setCameraSelector(cameraSelector)
.setTargetAspectRatio(AspectRatio.RATIO_16_9)
.setTargetRotation(rotation)
//.setTargetResolution(resolution)
//视频帧率
.setVideoFrameRate(25)
//bit率
.setBitRate(10440).build();
//Caused by: java.lang.IllegalArgumentException: No supported surface combination is found for camera device - Id : 0. May be attempting to bind too many use cases.
//cameraSelector与videoCapture不能同时绑定
// Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, imageCapture, videoCapture, preview);
if (takingPicture) {
camera = cameraProvider.bindToLifecycle(this, cameraSelector, imageCapture, preview);
} else {
camera = cameraProvider.bindToLifecycle(this, cameraSelector, videoCapture, preview);
}
CameraInfo cameraInfo = camera.getCameraInfo();
preview.setSurfaceProvider(mBinding.previewView.createSurfaceProvider(cameraInfo));
}
注意:这里有的手机会报错,至少我的小米手机是这样的
Caused by: java.lang.IllegalArgumentException: No supported surface combination is found for camera device - Id : 0. May be attempting to bind too many use cases.
这个暂时还没解决,先记录下来
ImageCapture.OutputFileOptions outputFileOptions =
new ImageCapture.OutputFileOptions.Builder(file).build();
imageCapture.takePicture(outputFileOptions,executor, new ImageCapture.OnImageSavedCallback() {
@Override
public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
onFileSaved(file);
}
@Override
public void onError(@NonNull ImageCaptureException exception) {
showErrorToast(Objects.requireNonNull(exception.getMessage()));
}
});
videoCapture.startRecording(file, executor, new VideoCapture.OnVideoSavedCallback() {
@Override
public void onVideoSaved(@NonNull File file) {
onFileSaved(file);
}
@Override
public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) {
showErrorToast(message);
}
});
private void onFileSaved(File file) {
outputFilePath = file.getAbsolutePath();
String mimeType = takingPicture ? "image/jpeg" : "video/mp4";
MediaScannerConnection.scanFile(this, new String[]{outputFilePath}, new String[]{mimeType}, null);
PreviewActivity.startActivityForResult(this, outputFilePath, !takingPicture, "完成");
}
我们拍完照或者录制完视频我们需要预览下,图片我们使用PhotoView+Glide
实现,视频我们使用Exoplayer
实现
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:orientation="vertical">
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
app:buffered_color="@color/color_theme"
app:resize_mode="fixed_width"
app:show_buffering="when_playing"
app:surface_type="texture_view"
app:use_controller="false">com.google.android.exoplayer2.ui.PlayerView>
<com.github.chrisbanes.photoview.PhotoView
android:id="@+id/photo_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginTop="90dp"
android:layout_marginBottom="@dimen/dp_60"
android:scaleType="fitCenter"
android:visibility="gone">com.github.chrisbanes.photoview.PhotoView>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/action_close"
android:layout_width="20dp"
android:layout_height="@dimen/dimen_20"
android:layout_marginLeft="@dimen/dp_16"
android:layout_marginTop="@dimen/dp_16"
app:srcCompat="@drawable/icon_close"
app:tint="@color/color_white">androidx.appcompat.widget.AppCompatImageView>
<com.google.android.material.button.MaterialButton
android:id="@+id/action_ok"
android:layout_width="@dimen/dp_60"
android:layout_height="@dimen/dp_30"
android:layout_gravity="right|top"
android:layout_marginTop="@dimen/dp_16"
android:layout_marginRight="@dimen/dp_16"
android:gravity="center"
android:text="@string/preview_ok"
app:backgroundTint="@color/color_theme"
app:cornerRadius="@dimen/dp_5">com.google.android.material.button.MaterialButton>
FrameLayout>
layout>
点击左上角退出,点击完成回调出去.如果是视频的话中间有个播放按钮,点击使用Exoplayer
播放
private void previewImage(String previewUrl) {
mPreviewBinding.photoView.setVisibility(View.VISIBLE);
Glide.with(this).load(previewUrl).into(mPreviewBinding.photoView);
}
private void previewVideo(String previewUrl) {
mPreviewBinding.playerView.setVisibility(View.VISIBLE);
player = ExoPlayerFactory.newSimpleInstance(this, new DefaultRenderersFactory(this), new DefaultTrackSelector(), new DefaultLoadControl());
Uri uri = null;
File file = new File(previewUrl);
if (file.exists()) {
DataSpec dataSpec = new DataSpec(Uri.fromFile(file));
FileDataSource fileDataSource = new FileDataSource();
try {
fileDataSource.open(dataSpec);
uri = fileDataSource.getUri();
} catch (FileDataSource.FileDataSourceException e) {
e.printStackTrace();
}
} else {
uri = Uri.parse(previewUrl);
}
ProgressiveMediaSource.Factory factory = new ProgressiveMediaSource.Factory(new DefaultDataSourceFactory(this, Util.getUserAgent(this, getPackageName())));
ProgressiveMediaSource mediaSource = factory.createMediaSource(uri);
player.prepare(mediaSource);
player.setPlayWhenReady(true);
mPreviewBinding.playerView.setPlayer(player);
}
主要就是这两个方法,具体可以去项目中看PreviewActivity
的源码,这样的话我们CameraX
相关的使用就介绍完毕了,我们下一篇将自定义一个录制的View
,完成拍照和录制视频