在今年的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