安卓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

 

 

 

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