安卓Android CameraX小试牛刀

在今年的Google I/O大会上,Google新推出了CameraX支持包,按照官方的说法, 这个包的作用是:

help you make camera app development easier

安卓中使用相机从来就不是一件容易的事。

Camera1要自己管理Camera相机实例,要处理SufraceView相关的一堆东西,还有预览尺寸跟画面尺寸的选择,页面生命周期切换等等问题,后来推出了Camera2,从官方demo 就上千行代码来看,Camera2并不解决用起来复杂的问题,它提供了更多的调用接口,可定制性更好,结果就是对普通开发者来说更难用了。。。

终于Google也意识到这个问题,推出了最终版CameraX. CameraX实际上还是用的Camera2,但它对调用API进行了很好的封装,使用起来非常方便。官方教程也很详细,如下:
https://codelabs.developers.google.com/codelabs/camerax-getting-started/#0

注意:CameraX跟Camera2一样最低支持API21,也就是5.0及以上。
开发环境用Android Studio3.3及以上,依赖库都用androidx的

创建app

创建app,声明相机权限

<uses-permission android:name="android.permission.CAMERA" /> 
<uses-permission android:name="android.permission.INTERNET" /> 
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

添加相关依赖

// 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"

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    
    <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" />

</RelativeLayout>

切记在启动该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<String> 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 上了:https://github.com/HuaDanJson/CameraX

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章