安卓Android CameraX小试牛刀

903 阅读3分钟

在今年的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 want to use Camera2 extensions
implementation "androidx.camera:camera-camera2:camerax_version" // If you to use the Camera View class implementation "androidx.camera:camera-view:camerax_view_version"
// If you to use Camera Extensions
implementation "androidx.camera:camera-extensions: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/…