前段時(shí)間CameraX的Beta版發(fā)布了,這幾天有時(shí)間也來嘗試一下。Beta版本是對外測試版本,意味著它已經(jīng)走出實(shí)驗(yàn)室走向生產(chǎn),API的調(diào)用基本穩(wěn)定不會(huì)大改了,bug也會(huì)更少可以用于生成環(huán)境。
之前使用Camera1和Camera2開發(fā)相機(jī)功能的時(shí)候需要調(diào)用非常復(fù)雜的API,而且由于Android手機(jī)的碎片化嚴(yán)重,不同手機(jī)對相機(jī)功能的支持度也不一樣,因此很多做相機(jī)相關(guān)應(yīng)用的公司都會(huì)封裝自己的相機(jī)庫來簡化相機(jī)的使用步驟和處理兼容性問題。
CameraX其實(shí)就是Google開發(fā)的一個(gè)用來簡化相機(jī)開發(fā)時(shí)候API的調(diào)用和處理各種兼容性問題的庫。最多兼容到Android 5.0,底層調(diào)用的也是Camera2,不過比Camera2用起來更簡單,而且可以綁定生命周期,從而可以自動(dòng)的處理相機(jī)的開啟釋放等工作。
下面開始來嘗試吧
添加依賴
dependencies {
// CameraX 核心庫使用 camera2 實(shí)現(xiàn)
implementation "androidx.camera:camera-camera2:1.0.0-beta03"
// 可以使用CameraView
implementation "androidx.camera:camera-view:1.0.0-alpha10"
// 可以使用供應(yīng)商擴(kuò)展
implementation "androidx.camera:camera-extensions:1.0.0-alpha10"
//camerax的生命周期庫
implementation "androidx.camera:camera-lifecycle:1.0.0-beta03"
}
如果想要使用CameraX拍照非常簡單,只需要配置不同的使用狀態(tài),然后綁定到生命周期中即可。比如預(yù)覽需要設(shè)置預(yù)覽相關(guān)的狀態(tài),拍照需要設(shè)置拍照相關(guān)的狀態(tài),錄制視頻需要設(shè)置錄制相關(guān)的狀態(tài)。
配置狀態(tài)
預(yù)覽配置:Preview用于相機(jī)預(yù)覽的時(shí)候顯示預(yù)覽畫面。
Preview preview = new Preview.Builder()
//設(shè)置寬高比
.setTargetAspectRatio(screenAspectRatio)
//設(shè)置當(dāng)前屏幕的旋轉(zhuǎn)
.setTargetRotation(rotation)
.build();
照相配置:ImageCapture 用于拍照,并將圖片保存
ImageCapture imageCapture = new ImageCapture.Builder()
//優(yōu)化捕獲速度,可能降低圖片質(zhì)量
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
//設(shè)置寬高比
.setTargetAspectRatio(screenAspectRatio)
//設(shè)置初始的旋轉(zhuǎn)角度
.setTargetRotation(rotation)
.build();
錄制視頻設(shè)置:VideoCapture 用來錄制視頻和保存視頻,寬高比和分辨率設(shè)置一個(gè)就可以了,不要同時(shí)設(shè)置否則報(bào)錯(cuò)。根據(jù)實(shí)際的需求來設(shè)置,如果對寬高比要求高就設(shè)置寬高比,反之就設(shè)置分辨率
VideoCapture videoCapture = new VideoCaptureConfig.Builder()
//設(shè)置當(dāng)前旋轉(zhuǎn)
.setTargetRotation(rotation)
//設(shè)置寬高比
.setTargetAspectRatio(screenAspectRatio)
//分辨率
//.setTargetResolution(resolution)
//視頻幀率 越高視頻體積越大
.setVideoFrameRate(25)
//bit率 越大視頻體積越大
.setBitRate(3 * 1024 * 1024)
.build();
綁定到生命周期:ProcessCameraProvider 是一個(gè)單例類,可以把相機(jī)的生命周期綁定到任何LifecycleOwner類中。AppCompatActivity和Fragment都是LifecycleOwner
//Future表示一個(gè)異步的任務(wù),ListenableFuture可以監(jiān)聽這個(gè)任務(wù),當(dāng)任務(wù)完成的時(shí)候執(zhí)行回調(diào)
ListenableFutureProcessCameraProvider> cameraProviderFuture =
ProcessCameraProvider.getInstance(this);
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
//重新綁定之前必須先取消綁定
cameraProvider.unbindAll();
Camera camera = cameraProvider.bindToLifecycle(CameraActivity.this,
cameraSelector,preview,imageCapture,videoCapture);
OK預(yù)覽,照相,錄視頻的配置和綁定到生命周期的工作就完成了
預(yù)覽的時(shí)候需要顯示到一個(gè)View控件上吧,CameraX中提供了一個(gè)PreviewView用來顯示預(yù)覽畫面。其內(nèi)部封裝了TextureView和SurfaceView,可以根據(jù)不同的模式來選擇其內(nèi)部使用TextureView還是SurfaceView來顯示。
xml中添加PreviewView,并在代碼中將其附加到前面創(chuàng)建出來的Preview這個(gè)實(shí)例上
androidx.camera.view.PreviewView
android:id="@+id/view_finder"
android:layout_width="match_parent"
android:layout_height="match_parent" />
preview.setSurfaceProvider(mPreviewView.createSurfaceProvider(camera .getCameraInfo()));
這樣當(dāng)我們進(jìn)入該頁面的時(shí)候就可以看到相機(jī)的預(yù)覽效果呢,接下來就是執(zhí)行拍照和錄制的功能了
執(zhí)行拍照錄像
拍照:
//創(chuàng)建圖片保存的文件地址
File file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath(),
System.currentTimeMillis() + ".jpeg");
ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(file).build();
mImageCapture.takePicture(outputFileOptions,mExecutorService , new ImageCapture.OnImageSavedCallback() {
@Override
public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
Uri savedUri = outputFileResults.getSavedUri();
if(savedUri == null){
savedUri = Uri.fromFile(file);
}
outputFilePath = file.getAbsolutePath();
onFileSaved(savedUri);
}
@Override
public void onError(@NonNull ImageCaptureException exception) {
Log.e(TAG, "Photo capture failed: "+exception.getMessage(), exception);
}
});
//將前面保存的文件添加到媒體中
private void onFileSaved(Uri savedUri) {
if (Build.VERSION.SDK_INT Build.VERSION_CODES.N) {
sendBroadcast(new Intent(android.hardware.Camera.ACTION_NEW_PICTURE, savedUri));
}
String mimeTypeFromExtension = MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap
.getFileExtensionFromUrl(savedUri.getPath()));
MediaScannerConnection.scanFile(getApplicationContext(),
new String[]{new File(savedUri.getPath()).getAbsolutePath()},
new String[]{mimeTypeFromExtension}, new MediaScannerConnection.OnScanCompletedListener() {
@Override
public void onScanCompleted(String path, Uri uri) {
Log.d(TAG, "Image capture scanned into media store: $uri"+uri);
}
});
PreviewActivity.start(this, outputFilePath, !takingPicture);
}
- 調(diào)用ImageCapture的takePicture方法來拍照
- 傳入一個(gè)文件地址用來保存拍好的照片
- onImageSaved方法是照片已經(jīng)拍好并存好之后的回調(diào)
- onFileSaved方法中將前面保存的文件添加到媒體中,最后跳轉(zhuǎn)到預(yù)覽界面。
錄視頻:
//創(chuàng)建視頻保存的文件地址
File file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath(),
System.currentTimeMillis() + ".mp4");
mVideoCapture.startRecording(file, Executors.newSingleThreadExecutor(), new VideoCapture.OnVideoSavedCallback() {
@Override
public void onVideoSaved(@NonNull File file) {
outputFilePath = file.getAbsolutePath();
onFileSaved(Uri.fromFile(file));
}
@Override
public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) {
Log.i(TAG,message);
}
});
videoCapture.stopRecording();
- 使用VideoCapture的startRecording方法來錄視頻
- 傳入一個(gè)File文件用來保存視頻,
- 錄制完成之后回調(diào)onVideoSaved方法,并返回該文件的實(shí)例。
- 調(diào)用onFileSaved方法將前面保存的文件添加到媒體中,最后跳轉(zhuǎn)到預(yù)覽界面。
- 到達(dá)錄制時(shí)間的時(shí)候,需要調(diào)用videoCapture.stopRecording();方法來停止錄像。
到這里使用CameraX拍照和錄制視頻的功能都能完成了,是不是非常簡單。下面來點(diǎn)題外的,自定義一個(gè)View,實(shí)現(xiàn)點(diǎn)擊拍照,長按錄像的效果。效果如下:
代碼:
public class RecordView extends View implements View.OnLongClickListener, View.OnClickListener {
private static final int PROGRESS_INTERVAL = 100;
private int mBgColor;
private int mStrokeColor;
private int mStrokeWidth;
private int mDuration;
private int mWidth;
private int mHeight;
private int mRadius;
private int mProgressValue;
private boolean isRecording;
private RectF mArcRectF;
private Paint mBgPaint, mProgressPaint;
private OnRecordListener mOnRecordListener;
private long mStartRecordTime;
public void setOnRecordListener(OnRecordListener onRecordListener) {
mOnRecordListener = onRecordListener;
}
public RecordView(Context context) {
this(context, null);
}
public RecordView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RecordView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RecordView);
mBgColor = typedArray.getColor(R.styleable.RecordView_bg_color, Color.WHITE);
mStrokeColor = typedArray.getColor(R.styleable.RecordView_stroke_color, Color.RED);
mStrokeWidth = typedArray.getDimensionPixelOffset(R.styleable.RecordView_stroke_width, SizeUtils.dp2px(5));
mDuration = typedArray.getInteger(R.styleable.RecordView_duration, 10);
mRadius = typedArray.getDimensionPixelOffset(R.styleable.RecordView_radius, SizeUtils.dp2px(40));
typedArray.recycle();
mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBgPaint.setStyle(Paint.Style.FILL);
mBgPaint.setColor(mBgColor);
mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mProgressPaint.setStyle(Paint.Style.STROKE);
mProgressPaint.setColor(mStrokeColor);
mProgressPaint.setStrokeWidth(mStrokeWidth);
setEvent();
}
private void setEvent() {
Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
mProgressValue++;
postInvalidate();
if (mProgressValue mDuration*10) {
sendEmptyMessageDelayed(0, PROGRESS_INTERVAL);
} else {
finishRecord();
}
}
};
setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
mStartRecordTime = System.currentTimeMillis();
handler.sendEmptyMessage(0);
}else if(event.getAction() == MotionEvent.ACTION_UP){
long duration = System.currentTimeMillis() - mStartRecordTime;
//是否大于系統(tǒng)設(shè)定的最小長按時(shí)間
if(duration > ViewConfiguration.getLongPressTimeout()){
finishRecord();
}
handler.removeCallbacksAndMessages(null);
isRecording = false;
mStartRecordTime = 0;
mProgressValue = 0;
postInvalidate();
}
return false;
}
});
setOnClickListener(this);
setOnLongClickListener(this);
}
private void finishRecord() {
if(mOnRecordListener!=null){
mOnRecordListener.onFinish();
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = w;
mArcRectF = new RectF(mStrokeWidth / 2f, mStrokeWidth / 2f,
mWidth - mStrokeWidth / 2f, mHeight - mStrokeWidth / 2f);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(mWidth / 2f, mHeight / 2f, mRadius, mBgPaint);
if (isRecording) {
canvas.drawCircle(mWidth / 2f, mHeight / 2f, mRadius/10f*11, mBgPaint);
float sweepAngle = 360f * mProgressValue / (mDuration*10);
Log.i("sweepAngle",sweepAngle+"");
canvas.drawArc(mArcRectF, -90, sweepAngle, false, mProgressPaint);
}
}
@Override
public boolean onLongClick(View v) {
isRecording = true;
if(mOnRecordListener!=null){
mOnRecordListener.onRecordVideo();
}
return true;
}
@Override
public void onClick(View v) {
if(mOnRecordListener!=null){
mOnRecordListener.onTackPicture();
}
}
public interface OnRecordListener {
void onTackPicture();
void onRecordVideo();
void onFinish();
}
}
實(shí)現(xiàn)起來也非常簡單,首先繪制一個(gè)圓,監(jiān)聽該View的點(diǎn)擊和長按事件,長按的時(shí)候在根據(jù)總錄制時(shí)長和當(dāng)前錄制時(shí)間算出需要繪制的角度,就可以在圓上面繪制進(jìn)度了。
最后通過接口將點(diǎn)擊 長按和錄制完成的事件返回,跟前面的拍照,錄制,錄制完成的代碼結(jié)合起來就完成上面的效果了。
CameraView
如果覺得前面的初始化還不夠簡單,那么可以使用CameraX提供的CameraView了,這里面將PreviewView,Preview,ImageCapture,VideoCapture等都封裝起來了,而且還能實(shí)現(xiàn)縮放,裁剪,旋轉(zhuǎn)等功能,使用起來更加簡單。
首先xml文件中添加CameraView
androidx.camera.view.CameraView
android:id="@+id/view_finder"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
然后在Activity中實(shí)例化CameraView,直接綁定當(dāng)前生命周期就可以了。
mBtnCameraSwitch = findViewById(R.id.camera_switch_button);
mCameraView.bindToLifecycle(this);
只需兩句話就完成了前面的初始工作。然后就可以愉快的拍照和錄制視頻了。
拍照和錄制的代碼跟前面一樣只不過全都是通過CamerView對象來調(diào)用 mCameraView.takePicture
, mCameraView.startRecording
,調(diào)用之前需要通過 mCameraView.setCaptureMode(CameraView.CaptureMode.IMAGE)
來切換當(dāng)前的模式是拍照還是錄像。
將前面的自定義的RecordView加入布局文件中,跟CameraView的拍照、錄像代碼一結(jié)合,很快就能實(shí)現(xiàn)跟前面一樣的效果了。
圖片分析
CameraX還提供了圖像分析功能,它提供了可供 CPU 訪問以執(zhí)行圖像處理、計(jì)算機(jī)視覺或機(jī)器學(xué)習(xí)推斷的圖像,可以無縫的訪問緩沖區(qū),一般用不到但功能很強(qiáng)大。創(chuàng)建一個(gè)圖片分析器然后綁定聲明周期即可。
mImageAnalysis = new ImageAnalysis.Builder()
.setTargetAspectRatio(screenAspectRatio)
.setTargetRotation(rotation)
.build();
mImageAnalysis.setAnalyzer(mExecutorService, new ImageAnalysis.Analyzer() {
@Override
public void analyze(@NonNull ImageProxy image) {
}
});
cameraProvider.bindToLifecycle(CameraActivity.this,
cameraSelector,mPreview,mImageCapture,mVideoCapture,mImageAnalysis);
供應(yīng)商擴(kuò)展
供應(yīng)商擴(kuò)展程序:CameraX提供了外部擴(kuò)展的API,可以直接對接手機(jī)產(chǎn)商,如果該手機(jī)廠商實(shí)現(xiàn)了CameraX的擴(kuò)展程序,就可以使用VamerX的擴(kuò)展API直接調(diào)用這些效果比如:美顏、DHR、夜間、自動(dòng)等模式。
因?yàn)椴皇撬械氖謾C(jī)廠商都支持?jǐn)U展程序,所以在使用擴(kuò)展的時(shí)候需要判斷一下該手機(jī)是否支持,支持才添加。
給預(yù)覽界面設(shè)置外部擴(kuò)展,需要 Preview.Builder
和 CameraSelector cameraSelector)
兩個(gè)參數(shù)
private void setPreviewExtender(Preview.Builder builder, CameraSelector cameraSelector) {
AutoPreviewExtender extender = AutoPreviewExtender.create(builder);
if(extender.isExtensionAvailable(cameraSelector)){
extender.enableExtension(cameraSelector);
}
BokehPreviewExtender bokehPreviewExtender = BokehPreviewExtender.create(builder);
if(bokehPreviewExtender.isExtensionAvailable(cameraSelector)){
bokehPreviewExtender.enableExtension(cameraSelector);
}
HdrPreviewExtender hdrPreviewExtender = HdrPreviewExtender.create(builder);
if(hdrPreviewExtender.isExtensionAvailable(cameraSelector)){
hdrPreviewExtender.enableExtension(cameraSelector);
}
BeautyPreviewExtender beautyPreviewExtender = BeautyPreviewExtender.create(builder);
if(beautyPreviewExtender.isExtensionAvailable(cameraSelector)){
beautyPreviewExtender.enableExtension(cameraSelector);
}
NightPreviewExtender nightPreviewExtender = NightPreviewExtender.create(builder);
if(nightPreviewExtender.isExtensionAvailable(cameraSelector)){
nightPreviewExtender.enableExtension(cameraSelector);
}
}
給拍攝的圖片設(shè)置外部擴(kuò)展,,需要 ImageCapture.Builder
和 CameraSelector cameraSelector)
兩個(gè)參數(shù)
private void setImageCaptureExtender(ImageCapture.Builder builder, CameraSelector cameraSelector) {
AutoImageCaptureExtender autoImageCaptureExtender = AutoImageCaptureExtender.create(builder);
if (autoImageCaptureExtender.isExtensionAvailable(cameraSelector)) {
autoImageCaptureExtender.enableExtension(cameraSelector);
}
BokehImageCaptureExtender bokehImageCaptureExtender = BokehImageCaptureExtender.create(builder);
if(bokehImageCaptureExtender.isExtensionAvailable(cameraSelector)){
bokehImageCaptureExtender.enableExtension(cameraSelector);
}
HdrImageCaptureExtender hdrImageCaptureExtender = HdrImageCaptureExtender.create(builder);
if(hdrImageCaptureExtender.isExtensionAvailable(cameraSelector)){
hdrImageCaptureExtender.enableExtension(cameraSelector);
}
BeautyImageCaptureExtender beautyImageCaptureExtender = BeautyImageCaptureExtender.create(builder);
if(beautyImageCaptureExtender.isExtensionAvailable(cameraSelector)){
beautyImageCaptureExtender.enableExtension(cameraSelector);
}
NightImageCaptureExtender nightImageCaptureExtender = NightImageCaptureExtender.create(builder);
if(nightImageCaptureExtender.isExtensionAvailable(cameraSelector)){
nightImageCaptureExtender.enableExtension(cameraSelector);
}
}
demo地址: 地址鏈接
總結(jié)
到此這篇關(guān)于JetPack之使用CameraX完成拍照和拍視頻的文章就介紹到這了,更多相關(guān)JetPack使用CameraX拍照和拍視頻內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
您可能感興趣的文章:- 詳解Android JetPack之LiveData的工作原理
- Android Jetpack- Paging的使用詳解
- Android Jetpack架構(gòu)組件Lifecycle詳解
- Android Jetpack架構(gòu)組件 ViewModel詳解
- Android Jetpack中Room的使用