安卓中使用相機從來就不是一件容易的事。
Camera1要自己管理Camera相機實例,要處理SufraceView相關的一堆東西,還有預覽尺寸跟畫面尺寸的選擇,頁面生命周期切換等等問題。。。
后來推出了Camera2,從官方Demo
就上千行代碼來看,Camera2并不解決用起來復雜的問題,它提供了更多的調用接口,可定制性更好,結果就是對普通開發(fā)者來說更難用了。。。
終于Google也意識到這個問題,推出了最終版CameraX. CameraX實際上還是用的Camera2,但它對調用API進行了很好的封裝,使用起來非常方便。官方教程也很詳細,如下:
https://codelabs.developers.google.com/codelabs/camerax-getting-started/#0
官方用的Kotlin代碼,我轉成了Java,其實用起來差不多。
注意:CameraX跟Camera2一樣最低支持API21,也就是5.0及以上。
開發(fā)環(huán)境用Android Studio3.3及以上,依賴庫都用androidx的
1 導入依賴
在app的build.gradle中加入
implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
// Use the most recent version of CameraX, currently that is alpha04
def camerax_version = "1.0.0-alpha04"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
2 布局文件和權限
放一個TextureView就行了
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextureView
android:id="@+id/view_finder"
android:layout_width="640px"
android:layout_height="640px"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
權在AndroidManifest.xml中加入相機權限
<uses-permission android:name="android.permission.CAMERA" />
并加入動態(tài)申請權限代碼,這里省略掉(你要在App安裝后手動打開相機權限)。
3 啟動相機
給TextureView設置布局變化的監(jiān)聽,用updateTransform()更新相機預覽,然后startCamera()啟動相機
TextureView viewFinder = findViewById(R.id.view_finder);
viewFinder.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
updateTransform();
}
});
viewFinder.post(new Runnable() {
@Override
public void run() {
startCamera();
}
});
更新相機預覽:主要是給TextureView設置一個旋轉的矩陣變化,防止預覽方向不對
private void updateTransform() {
Matrix matrix = new Matrix();
// Compute the center of the view finder
float centerX = viewFinder.getWidth() / 2f;
float centerY = viewFinder.getHeight() / 2f;
float[] rotations = {0,90,180,270};
// Correct preview output to account for display rotation
float rotationDegrees = rotations[viewFinder.getDisplay().getRotation()];
matrix.postRotate(-rotationDegrees, centerX, centerY);
// Finally, apply transformations to our TextureView
viewFinder.setTransform(matrix);
}
啟動相機:創(chuàng)建PreviewConfig和Preview這兩個對象,可以設置預覽圖像的尺寸和比例,在OnPreviewOutputUpdateListener回調中用setSurfaceTexture方法,將相機圖像輸出到TextureView。最后用CameraX.bindToLifecycle方法將相機與當前頁面的生命周期綁定。
private void startCamera() {
// 1. preview
PreviewConfig previewConfig = new PreviewConfig.Builder()
.setTargetAspectRatio(new Rational(1, 1))
.setTargetResolution(new Size(640,640))
.build();
Preview preview = new Preview(previewConfig);
preview.setOnPreviewOutputUpdateListener(new Preview.OnPreviewOutputUpdateListener() {
@Override
public void onUpdated(Preview.PreviewOutput output) {
ViewGroup parent = (ViewGroup) viewFinder.getParent();
parent.removeView(viewFinder);
parent.addView(viewFinder, 0);
viewFinder.setSurfaceTexture(output.getSurfaceTexture());
updateTransform();
}
});
CameraX.bindToLifecycle(this, preview);
這樣就實現(xiàn)了基本的相機預覽功能。這幾個方法都很簡單明了,對外只依賴一個TextureView。生命周期自動綁定,這意味著代碼可以寫在一塊,在一處調用。不像以前這里插一段代碼,那里插一段代碼。
還有最大的好處,就是可擴展性。相機預覽使用了PreviewConfig和Preview兩個對象,加入新的相機功能同樣是加兩個對象XXXConfig和XXX,其他地方都不同改!
加入拍照功能就加入ImageCaptureConfig和ImageCapture,加入圖像分析功能就加入ImageAnalysisConfig和ImageAnalysis,非常方便統(tǒng)一。
4 拍照
創(chuàng)建ImageCaptureConfig和ImageCapture這兩個對象,用imageCapture.takePicture方法傳入相片保存地址就行了。當然在生命周期綁定中也加上imageCapture。
ImageCaptureConfig可以定制相片尺寸和長寬比例,這里的尺寸和比例跟相機預覽的尺寸比例無關,我測試傳入任何比例都能得到圖片。
// 2. capture
ImageCaptureConfig imageCaptureConfig = new ImageCaptureConfig.Builder()
.setTargetAspectRatio(new Rational(1,1))
.setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
.build();
final ImageCapture imageCapture = new ImageCapture(imageCaptureConfig);
viewFinder.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
File photo = new File(getExternalCacheDir() + "/" + System.currentTimeMillis() + ".jpg");
imageCapture.takePicture(photo, new ImageCapture.OnImageSavedListener() {
@Override
public void onImageSaved(@NonNull File file) {
showToast("saved " + file.getAbsolutePath());
}
@Override
public void onError(@NonNull ImageCapture.UseCaseError useCaseError, @NonNull String message, @Nullable Throwable cause) {
showToast("error " + message);
cause.printStackTrace();
}
});
return true;
}
});
CameraX.bindToLifecycle(this, preview, imageCapture);
5 圖片分析
圖片分析名字很高大上,實際上就是圖像數據回調,實時獲取相機的圖像數據,可以自己處理這些圖像。
創(chuàng)建ImageAnalysisConfig和ImageAnalysis這兩個對象,創(chuàng)建一個HandlerThread用于在子線程中處理數據,創(chuàng)建一個ImageAnalysis.Analyzer接口實現(xiàn)類,在analyze(ImageProxy imageProxy, int rotationDegrees)回調方法中就能拿到圖像數據了。當然ImageAnalysis對象也要綁定生命周期。
我這里分析圖像數據用了之前寫的一個工具YUVDetectView,來分析圖像屬于哪種YUV420格式。
// 3. analyze
HandlerThread handlerThread = new HandlerThread("Analyze-thread");
handlerThread.start();
ImageAnalysisConfig imageAnalysisConfig = new ImageAnalysisConfig.Builder()
.setCallbackHandler(new Handler(handlerThread.getLooper()))
.setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
.setTargetAspectRatio(new Rational(2, 3))
// .setTargetResolution(new Size(600, 600))
.build();
ImageAnalysis imageAnalysis = new ImageAnalysis(imageAnalysisConfig);
imageAnalysis.setAnalyzer(new MyAnalyzer());
CameraX.bindToLifecycle(this, preview, imageCapture, imageAnalysis);
private class MyAnalyzer implements ImageAnalysis.Analyzer {
@Override
public void analyze(ImageProxy imageProxy, int rotationDegrees) {
final Image image = imageProxy.getImage();
if(image != null) {
Log.d("chao", image.getWidth() + "," + image.getHeight());
imageView.input(image);
}
}
}
Github地址
https://github.com/rome753/android-YuvTools
(CameraX代碼在camerax包下面)