一 .功能介紹(UI布局)
打開camera app
首先會進入拍照模式的預覽界面:
總共就四個控制元件
1.整體的預覽控制元件TextureView
2.左下角的縮略圖ImageView
3.中間的拍照按鈕ImageButton
4.切換攝像頭的ImageButton
主要是兩個Fragment 通過ViewPage 左右滑動切換拍照模式跟錄像模式具體實作在后面,在我的Demo中向左滑動切換到錄像模式
進入錄像的預覽模式:
總共就五個控制元件
1.整體的預覽控制元件TextureView
2.左下角的縮略圖ImageView
3.中間的拍照按鈕ImageButton
4.切換攝像頭的ImageButton
5.頂部的計時器Chronometer
二 .UI的代碼部分
1.首先是 activity_main.xml中
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager.widget.ViewPager
android:layout_gravity="center"
android:id="@+id/change_page"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!--標題 <androidx.viewpager.widget.PagerTitleStrip
android:layout_gravity="top"
android:id="@+id/page_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>-->
</androidx.viewpager.widget.ViewPager>
</RelativeLayout>
主界面主要是注冊了一個ViewPager 注釋的部分是之前準備的標題欄,是想通過標題提示是錄像界面還是拍照界面,但是自己沒有找到把標題欄背景變成透明的方法于是就拋棄了,我方在這里如果后續找到的話及時更新.
2.其次是 Mainactivity.java中
package com.example.camera;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.viewpager.widget.ViewPager;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private ViewPager change_page; //拍照錄像切換
private List<Fragment> layoutList; //布局集合(拍照錄像切換)
//private List<String> titleList; //標題集合(拍照錄像切換)
private MyPagerAdapter myPagerAdapter;
//初始化視圖界面(拍照/攝像)
private void initView(){
change_page = findViewById(R.id.change_page);
layoutList = new ArrayList<>();
layoutList.add(new TakePictureFragment());
layoutList.add(new RecorderVideoFragment());
// titleList = new ArrayList<>();
// titleList.add("拍照");
// titleList.add("錄像");
//設定配接器
myPagerAdapter = new MyPagerAdapter(getSupportFragmentManager(), layoutList);
change_page.setAdapter(myPagerAdapter);
}
//活動的創建
@Override
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_main);
initView();
//隱藏通知欄狀態欄
if (Build.VERSION.SDK_INT >= 21) {
View decorView=getWindow().getDecorView();//獲取當前界面的decorView
int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|View.SYSTEM_UI_FLAG_FULLSCREEN//隱藏狀態欄
|View.SYSTEM_UI_FLAG_LAYOUT_STABLE//保持整個View的穩定,使其不會隨著SystemUI的變化而變化;
|View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION//讓導航欄懸浮在Activity上
// |View.SYSTEM_UI_FLAG_HIDE_NAVIGATION//隱藏導航欄
|View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;//沉浸模式且狀態欄和導航欄出現片刻后會自動隱藏
decorView.setSystemUiVisibility(option);
getWindow().setStatusBarColor(Color.TRANSPARENT);//設定透明顏色
getWindow().setNavigationBarColor(Color.TRANSPARENT);
}
ActionBar actionBar=getSupportActionBar();
actionBar.hide();
}
protected void onDestroy() {
super.onDestroy();
TakePictureFragment.closeCamera();
}
}
主活動中:
1.做了初始化視圖界面添加兩個碎片TakePictureFragment和RecorderVideoFragment
2.因為ViewPage要用到PagerAdapter 將默認的界面設定成了拍照預覽界面
3.onCreate中做了個沉浸式通知欄(隱藏狀態欄和通知欄)
4.onDestroy中呼叫了TakePictureFrament中的closeCamera
5.注釋掉的部分同上是標題欄
3.MyPagerAdapter.java中
package com.example.camera;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import java.util.List;
public class MyPagerAdapter extends FragmentPagerAdapter {
private List<Fragment> layoutList;
// private List<String> titleList;
public MyPagerAdapter(FragmentManager manager, List<Fragment> layoutList ){
super(manager);
this.layoutList = layoutList;
// this.titleList = titleList;
}
@Override
public int getCount() {
// 頁面數
return layoutList.size();
}
@NonNull
@Override
public Fragment getItem(int position) {
return layoutList.get(position);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
}
}
這個類繼承FragmentPageAdapter跟ViewPage聯合使用,注:ViewPage跟activity也可以不一定是需要Fragment主要的功能就是通過左右滑動來切換fragment
3.fragment_take_picture.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextureView
android:id="@+id/textureView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<ImageButton
android:background="@drawable/shape_white_ring"
android:id="@+id/takePicture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/shape_take_photo"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="50dp"
/>
<ImageView
android:id="@+id/image_show"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:layout_marginLeft="50dp"
android:layout_marginBottom="50dp" />
<ImageButton
android:id="@+id/change"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_baseline_flip_camera_android_24"
android:layout_alignParentBottom="true"
android:layout_marginRight="50dp"
android:layout_marginBottom="50dp"
android:layout_alignParentRight="true"
/>
</RelativeLayout>
4 TakePpictureFragment.java中
package com.example.camera;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class TakePictureFragment extends Fragment implements View.OnClickListener {
private static final String TAG = "TakePictureFragment";
private static final SparseIntArray ORIENTATION = new SparseIntArray();
static {
//手機ROTATION逆時針旋轉
ORIENTATION.append(Surface.ROTATION_0, 90);
ORIENTATION.append(Surface.ROTATION_90, 0);
ORIENTATION.append(Surface.ROTATION_180, 270);
ORIENTATION.append(Surface.ROTATION_270, 180);
}
private TextureView textureView; //預覽框控制元件
private ImageButton takePicture; //拍照按鈕
private ImageButton change; //前后攝像頭切換按鈕
private ImageView mImageView; // 縮略圖顯示
private String mCameraId; // 攝像頭Id
private Size mPreviewSize; //獲取解析度
private ImageReader mImageReader; //圖片閱讀器
private static CameraDevice mCameraDevice; //攝像頭設備
private static CameraCaptureSession mCaptureSession; //獲取會話
private CaptureRequest mPreviewRequest; //獲取預覽請求
private CaptureRequest.Builder mPreviewRequestBuilder; //獲取到預覽請求的Builder通過它創建預覽請求
private Surface mPreviewSurface; //預覽顯示圖
//權限申請
private String[] permissions = {Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.RECORD_AUDIO};
private List<String> permissionList = new ArrayList();
private ArrayList<String> imageList = new ArrayList<>(); //圖片集合
protected boolean isCreated=false; //Fragment是否創建成功
private boolean isVisible; //Fragment是否可見
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreateView: success");
View view = inflater.inflate(R.layout.fragment_take_picture, container, false);
//注冊監聽控制元件
initView(view);
textureView.setSurfaceTextureListener(textureListener); //surfaceView回呼里面配置相機打開相機
takePicture.setOnClickListener(this); //拍照監聽
mImageView.setOnClickListener(this); //縮略圖監聽
change.setOnClickListener(this); //攝像頭切換監聽
getPermission(); //申請權限
//顯示最后一張圖
isCreated = true; //Fragment View 創建成功
return view; //顯示當前View
}
// 第一步:獲取權限
/**
* 獲取拍照和讀寫權限
*/
private void getPermission() {
Log.d(TAG, "getPermission: success");
//版本判斷 當手機系統大于23時,才有必要去判斷權限是否獲取
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//權限是否已經 授權 GRANTED-授權 DINIED-拒絕
for (String permission : permissions) {
//檢查權限是否全部授予
if (ContextCompat.checkSelfPermission(getContext(), permission) != PackageManager.PERMISSION_GRANTED) {
//如果沒有就添加到權限集合
permissionList.add(permission);
}
}
//是慷訓傳ture
if (!permissionList.isEmpty()) {
requestPermissions(permissionList.toArray(new String[permissionList.size()]), 1);
} else {
//表示全都授權了
textureView.setSurfaceTextureListener(textureListener);
//顯示最后一張圖片 最新
setLastImagePath();
}
}
}
//權限回呼
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] mPermissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
Log.d(TAG, "onRequestPermissionsResult: success");
if (requestCode == 1) {
//權限請求失敗
if (grantResults.length > 0) {
//存放沒授權的權限
List<String> deniedPermissions = new ArrayList<>();
for (int i = 0; i < grantResults.length; i++) {
int grantResult = grantResults[i];
String permission = permissions[grantResult];
if (grantResult != PackageManager.PERMISSION_GRANTED) {
deniedPermissions.add(permission);
}
}
if (deniedPermissions.isEmpty()) {
//說明都授權了 打開相機
openCamera();
//顯示最新的一張圖片或者視頻的第一幀
setLastImagePath();
} else {
// 繼續申請權限
getPermission();
}
}
}
}
//判斷Fragment是否可見
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
//判斷是否是第一次創建
if (!isCreated) {
return;
}
//如果可見
if (isVisibleToUser) {
isVisible = true;
//顯示第一張照片
setLastImagePath();
//如果textureView 可用
if (textureView.isAvailable()) {
//打開相機
openCamera();
} else {
//先配置相機再打開相機
textureView.setSurfaceTextureListener(textureListener);
}
} else {
//當切換成錄像時,將當前fragment 置為不可見
Log.d(TAG, TAG + " releaseCamera");
isVisible = false;
closeCamera();
}
}
@Override
public void onResume() {
super.onResume();
//如果fragment 可見
if (isVisible) {
//textureView 可用
if (textureView.isAvailable()) {
//打開相機
openCamera();
} else {
//設定相機引數
textureView.setSurfaceTextureListener(textureListener);
}
}
}
//注冊視圖上監聽控制元件ID
private void initView(View view) {
Log.d(TAG, "initView: success");
//預覽框控制元件
textureView = view.findViewById(R.id.textureView);
//拍照控制元件
takePicture = view.findViewById(R.id.takePicture);
//縮略圖控制元件
mImageView = view.findViewById(R.id.image_show);
//前后攝像頭切換控制元件
change = view.findViewById(R.id.change);
}
@Override
public void onClick(View view) {
Log.d(TAG, "onClick: success");
switch (view.getId()) {
case R.id.takePicture:
//拍照
takePhoto();
break;
case R.id.change:
//切換攝像頭
changeCamera();
break;
case R.id.image_show:
//打開相冊
openAlbum();
break;
}
}
//第二步 寫回呼函式
// 1. SurfaceView狀態回呼
TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
@Override
//textureView.isAvailable()為ture
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
Log.d(TAG, "onSurfaceTextureAvailable: success");
setupCamera();
// configureTransform(width, height); //旋轉
openCamera(); //打開相機
}
@Override
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
// configureTransform(width, height);
}
@Override
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
return false;
}
@Override
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surfaceTexture) {
}
};
// 2. 攝像頭狀態回呼
private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
//打開成功獲取到camera設備
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
Log.d(TAG, "onOpened: success");
mCameraDevice = cameraDevice;
//開啟預覽
startPreview();
}
//打開失敗
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
Toast.makeText(getContext(), "攝像頭設備連接失敗", Toast.LENGTH_SHORT).show();
}
//打開錯誤
@Override
public void one rror(@NonNull CameraDevice cameraDevice, int i) {
Toast.makeText(getContext(), "攝像頭設備連接出錯", Toast.LENGTH_SHORT).show();
}
};
// 3.實作PreviewCallback 拍照時呼叫
private CameraCaptureSession.CaptureCallback mPreviewCaptureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
// 一旦捕獲完成
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
super.onCaptureCompleted(session, request, result);
}
};
//第三步 設定(配置)相機
//設定攝像機 id 引數
private void setupCamera() {
Log.d(TAG, "setupCamera: success");
//獲取攝像頭的管理者CameraManager
CameraManager manager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
try {
//遍歷所有攝像頭
for (String cameraId : manager.getCameraIdList()) {
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); //獲取攝像機的特征
//默認打開后置 - 忽略前置 LENS(鏡頭)
if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT)
{
continue;
}
//獲取StreamConfigurationMap,他是管理攝像頭支持的所有輸出格式
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
mPreviewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class),
720, 960); //獲取最佳的預覽大小
//進入回呼設定相機然后打開
textureView.setSurfaceTextureListener(textureListener);
mCameraId = cameraId;
break;
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
//旋轉螢屏
private void configureTransform(int viewWidth, int viewHeight) {
Log.d(TAG, "configureTransform: success");
if (textureView == null || mPreviewSize == null) {
return;
}
int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
Log.i("TAGggg", "rotation: "+rotation);
Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max((float) viewHeight / mPreviewSize.getHeight(),
(float) viewWidth / mPreviewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
} else if (rotation == Surface.ROTATION_180) {
Log.i("TAGggg", "rotation --- : "+rotation);
matrix.postRotate(180, centerX, centerY);
}
textureView.setTransform(matrix);
}
//選擇sizeMap中大于并且接近width和height的size
private Size getOptimalSize(Size[] sizeMap, int width, int height) {
Log.d(TAG, "getOptimalSize: success");
List<Size> sizeList = new ArrayList<>();
for (Size option : sizeMap) {
//當width > height
if (width > height) {
//選取官渡大于surface的寬度并且選取的高度大于surface的高度
if (option.getWidth() > width && option.getHeight() > height) {
//符合的添加到sizeList
sizeList.add(option);
}
} else {
//如果選擇寬度大于surface的高度并且選擇的高度大于surface的寬度
if (option.getWidth() > height && option.getHeight() > width) {
//符合的添加到sizeList
sizeList.add(option);
}
}
}
if (sizeList.size() > 0) {
return Collections.min(sizeList, new Comparator<Size>() {
@Override
public int compare(Size size, Size t1) {
return Long.signum(size.getWidth() * size.getHeight() - t1.getWidth() * t1.getHeight());
}
});
}
return sizeMap[0];
}
//創建預覽請求的Builder (TEMPLATE_PREVIEW表示預覽請求)
private void setPreviewRequestBuilder() {
Log.d(TAG, "setPreviewRequestBuilder: success");
try {
//通過cameraDevice獲取到預覽請求的Builder
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
} catch (CameraAccessException e) {
e.printStackTrace();
}
//設定預覽的顯示圖
mPreviewRequestBuilder.addTarget(mPreviewSurface);
MeteringRectangle[] meteringRectangles = mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AE_REGIONS);
if (meteringRectangles != null && meteringRectangles.length > 0) {
Log.d(TAG,"PreviewRequestBuilder: AF_REGIONS");
}
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_MODE_AUTO);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
}
// 重復預覽
private void repeatPreview() {
Log.d(TAG, "repeatPreview: success");
mPreviewRequestBuilder.setTag(TAG);
//通過預覽請求的builder的.build獲取到預覽請求
mPreviewRequest = mPreviewRequestBuilder.build();
//設定反復捕獲會話的請求,這樣預覽界面就會一直有資料顯示
try {
//第一個引數就是預覽求情,第二個引數是PreviewCallback,第三個是處理的執行緒
mCaptureSession.setRepeatingRequest(mPreviewRequest, mPreviewCaptureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
//第四步 打開相機
private void openCamera() {
Log.d(TAG, "openCamera: success");
//獲取攝像頭的管理者 CameraManager
CameraManager manager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
//檢查權限
try {
if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA) !=
PackageManager.PERMISSION_GRANTED) {
return;
} else {
//通過manager.openCamera(id,cameraStateCallback,處理的執行緒)
manager.openCamera(mCameraId, stateCallback, null);
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
// 關閉相機
public static void closeCamera() {
Log.d(TAG, "closeCamera: success");
if (mCaptureSession != null) {
mCaptureSession.close();
mCaptureSession = null;
}
if (mCameraDevice != null) {
mCameraDevice.close();
mCameraDevice = null;
}
}
//第五步 開啟相機預覽
private void startPreview() {
Log.d(TAG, "startPreview: success");
//設定圖片閱讀器
setupImageReader();
SurfaceTexture mSurfaceTexture = textureView.getSurfaceTexture();
//設定TextureView的緩沖區大小
mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
//獲取Surface顯示預覽資料
mPreviewSurface = new Surface(mSurfaceTexture);
try {
//創建預覽請求的Builder
setPreviewRequestBuilder();
//通過CameraDevice創建相機捕捉會話,第一個引數是捕獲資料的輸出Surface串列,第二個引數是CameraCaptureSession的狀態回呼介面,
// 當他創建好后會回呼onCconfigured方法
//第三個引數用來確定Callback在那個執行緒執行,null表示在當前執行緒執行
mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface, mImageReader.getSurface()),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
mCaptureSession = cameraCaptureSession;
repeatPreview();
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
//第七步 設定圖片閱讀
private void setupImageReader() {
Log.d(TAG, "setupImageReader: success");
//前三個引數分別是需要的尺寸和格式,最后一個引數代表每次最多獲取幾幀資料
mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.JPEG, 1);
//監聽ImageReader的事件,當有影像流資料可用時會回呼onImageAvailable
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader imageReader) {
Toast.makeText(getContext(), "圖片已保存", Toast.LENGTH_SHORT).show();
//獲得mage
Image image = imageReader.acquireNextImage();
//開啟執行緒一部保存圖片
ImageSaver imageSaver = new ImageSaver(getContext(), image);
new Thread(imageSaver).start();
}
}, null);
}
// 第八步 拍照
private void takePhoto() {
Log.d(TAG, "takePhoto: success");
try {
//首先創建拍照的請求 CaptureRequest
final CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest
(CameraDevice.TEMPLATE_STILL_CAPTURE);
//獲取螢屏方向
int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
//獲取到當前預覽視窗的圖
mCaptureBuilder.addTarget(mImageReader.getSurface());
//設定拍照方向
if (mCameraId.equals("1")) {
rotation = 2;
}
//設定圖片的方向 因為默認的是橫屏 我們使用手機一般是豎屏所以需要處理
mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));
//停止預覽
mCaptureSession.stopRepeating();
//開始拍照,然后回呼上面的介面重啟預覽,因為mCaptureBuilder設定ImageReader作為target,
// 所以會自動回呼ImageReader的onImageAvailable()方法保存圖片
CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
super.onCaptureCompleted(session, request, result);
repeatPreview();
}
};
mCaptureSession.capture(mCaptureBuilder.build(), captureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
// 第九步 創建子執行緒保存圖片
public class ImageSaver implements Runnable {
private Image mImage;//圖片
private Context mContext;
public ImageSaver(Context context, Image image) {
Log.d(TAG, "ImageSaver: success");
mImage = image;
mContext = context;
}
@Override
public void run() {
//將照片轉位元組
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String path = Environment.getExternalStorageDirectory() +
"/DCIM/camera/myPicture" + System.currentTimeMillis() + ".jpg";
File imageFile = new File(path);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(imageFile);
fos.write(data, 0, data.length);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
broadcast();
Message message = new Message();
message.what = 0;
Bundle mBundle = new Bundle();
mBundle.putString("myPath",path);
message.setData(mBundle);
handler.sendMessage(message);
mImage.close(); // 必須關閉 不然拍第二章會報錯
}
}
// 異步訊息處理
private Handler handler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(@NonNull Message message) {
super.handleMessage(message);
switch (message.what) {
case 0:
Bundle bundle = message.getData();
//通過指定的鍵值對獲取到剛剛發送過來的地址
String myPath = bundle.getString("myPath");
imageList.add(myPath);
//imageList.add(bundle.getString(myPath)); // 這樣不行必須分開寫 不然 arrList沒有資料
setLastImagePath();
break;
default:
throw new IllegalStateException("Unexpected value: " + message.what);
}
}
};
// 廣播通知相冊更新
public void broadcast() {
Log.d(TAG, "broadcast: success");
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/";
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri uri = Uri.fromFile(new File(path));
intent.setData(uri);
mContext.sendBroadcast(intent);
}
}
// 改變前后攝像頭
private void changeCamera() {
Log.d(TAG, "changeCamera: success");
if (mCameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_BACK))) {
Toast.makeText(getContext(), "前置轉后置", Toast.LENGTH_SHORT).show();
mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_FRONT);
} else {
Toast.makeText(getContext(), "后置轉前置", Toast.LENGTH_SHORT).show();
mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_BACK);
}
mCameraDevice.close();
openCamera();
}
//找到最后一張的路徑
private void setLastImagePath() {
Log.d(TAG, "setLastImagePath: success");
imageList = GetImageFilePath.getFilePath();
String string = imageList.get(imageList.size() - 1);
if(string.contains(".jpg")){
setImageBitmap(string);
}else {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(string);
Bitmap bitmap = retriever.getFrameAtTime(1);
mImageView.setImageBitmap(bitmap);
}
}
// 設定縮略圖
private void setImageBitmap(String path){
Log.d(TAG, "setImageBitmap: success");
Bitmap bitmap = (Bitmap) BitmapFactory.decodeFile(path);
mImageView.setImageBitmap(bitmap);
}
//打開相冊
private void openAlbum() {
Log.d(TAG, "openAlbum: success");
Intent intent = new Intent();
// 在ImageShowActivity中直接從相冊中遍歷 不需要傳遞過去
//intent.putStringArrayListExtra("myList", imageList);
intent.setClass(getContext(), ImageShowActivity.class);
startActivity(intent);
}
}
其中拍照流程 復制下面加粗函式在代碼中查看

1.權限申請,以及權限回呼,沒有權限回呼第一次安裝程式申請權限之后還是不會進入預覽界面,重啟程式后才能正常預覽拍照
2.因為使用的ViewPage加Fragment所以需要setUserVisibleHint函式來保證Fragment可見的時候在對其進行初始化,不然兩個Fragment同時執行初始化相機打開相機等會報錯.
3.初始化控制元件,在該類中用的是initView
4.監聽控制元件onClick
5.初始化 Surface 并在其回呼函式中執行配置相機打開相機等操作 因為我使用的是默認的TextureView所以直接寫回呼函式TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener()....
6.通過CameraManager配置相機引數選擇相機id setupCamera()
7.選擇sizeMap中大于并且接近width和height的size getOptimalSize 如果是自己寫demo這可可以不要自己設定個指定的值也可以,包括旋轉螢屏處理也可以不要我在其中就注釋了
8.打開相機通過CameraManager getSystemService(Context.CAMERA_SERVICE)在我的demo中在openCamera()函式中實作
9.打開相機又需要用到攝像頭狀態回呼 CameraDevice.StateCallback 在onOpened中主要是把獲取到的cameraDevice拿到 以及執行開啟預覽
10.開啟預覽之前需要設定ImageReader在我的demo中是setupImageReader()中實作
11.創建預覽就需要先通過cameraDevice創建createCaptureSession 創建預覽申請中又用到CameraCaptureSession.StateCallback回呼在這個回呼函式的onConfigured獲取到mCaptureSession(捕捉會話)并在其中呼叫重復預覽
12.創建重復預覽請求通過captureRequest.Builder獲取到預覽請求的build再通過mPreviewRequestBuilder.build();獲取到預覽請求mPreviewRequest再通過
mCaptureSession.setRepeatingRequest(mPreviewRequest, mPreviewCaptureCallback, null);
實作反復進行預覽請求以及反復捕獲請求會話 在我的demo中repeatPreview()中實作
13.按照上面預覽就成功了接下來就是拍照在demo中takePhoto()實作拍照時又會呼叫
CameraCaptureSession.CaptureCallback 定義的mCaptureSession進行拍照處理
14.拍完照片就是保存照片創建子執行緒保存圖片 創建子執行緒保存照片就是為了提高效率做到拍完就保存而且保存照片又牽扯到廣播通知相冊重繪 在該demo中是通過內部類 ImageSaver 開啟子執行緒
15.我之前寫了把拍完照片的路徑通過Hander 傳message傳出來 但是后來沒用上 讀者可以拋棄
16.廣播通知相冊更新 主要是通過Itent 加uri實作 具體在demo中的 broadcast()函式中實作
17.通過縮略圖顯示最新的一張圖片也就是最后一張圖片demo中通過setLastImagePath()和setImageBitmap() 實作 主要是用到將String轉Bitmap 看看就懂了 中間用到的GetImageFilePath.getFilePath 是另外寫的一個類用來遍歷系統相冊的所有路徑我沒有對其進行排序如果有需要 可以根據修改時間排序 忘了補充在其中還判斷了是圖片還是視頻如果是視頻顯示第一幀
18.切換攝像頭 很簡單 demo中changCamera()實作
19.最后就是通過縮略圖進入到系統相冊openAlbum() 在這個函式中通過Itent啟動另一個活動
就是ImageShowActivity
5.GetImageFilePath.java中
package com.example.camera;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.util.ArrayList;
public class GetImageFilePath {
//獲取相冊camera 圖片路徑
static ArrayList<String> imageList = new ArrayList<>() ;
public static ArrayList<String> getFilePath() {
File file= new File(Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera");
File[] dirEpub = file.listFiles();
if (dirEpub.length != 0){
for (int i = 0; i < dirEpub.length; i++){
String fileName = dirEpub[i].toString();
imageList.add(fileName);
Log.i("File", "File name = " + fileName);
}
}
return imageList;
}
}
就是遍歷系統相冊下的所有檔案路徑
6.ImageShowActivity.java中
package com.example.camera;
import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
public class ImageShowActivity extends AppCompatActivity {
private final static String TAG = "ImageShowActivity";
private String lastImagePath;
//圖片集合
private ArrayList<String> imageList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG, "onCreate success !!!!!");
setContentView(R.layout.activity_image_show);
// 呼叫獲取圖片路徑類中的靜態方法
imageList = GetImageFilePath.getFilePath();
lastImagePath = imageList.get(imageList.size() - 1);
gotoGallery(lastImagePath);
}
// 轉到畫廊 就是圖庫
public void gotoGallery(String path) {
Log.e("TAG", "gotoGallery success !!!!!");
Uri uri =getMediaUriFromPath(this, path);
Log.i("asdddd", "uri: "+uri);
Intent intent = new Intent("com.android.camera.action.REVIEW", uri);
intent.setData(uri);
startActivity(intent);
finish();
}
@SuppressLint("Range")
public Uri getMediaUriFromPath(Context context, String path) {
Log.e("TAG", "getMediaUriFromPath success !!!!!");
Uri uri = null;
if(path.contains("jpg")){
Uri picUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = context.getContentResolver().query(picUri,
null,
MediaStore.Images.Media.DISPLAY_NAME + "= ?",
new String[] {path.substring(path.lastIndexOf("/") + 1)},
null);
if(cursor.moveToFirst()) {
uri = ContentUris.withAppendedId(picUri,
cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID)));
}
cursor.close();
}else if(path.contains("mp4")){
Uri mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = context.getContentResolver().query(mediaUri,
null,
MediaStore.Video.Media.DISPLAY_NAME + "= ?",
new String[] {path.substring(path.lastIndexOf("/") + 1)},
null);
if(cursor.moveToFirst()) {
uri = ContentUris.withAppendedId(mediaUri,
cursor.getLong(cursor.getColumnIndex(MediaStore.Video.Media._ID)));
}
cursor.close();
}
return uri;
}
}
重點就是gotoGallery()中通過Itent
Intent intent = new Intent("com.android.camera.action.REVIEW", uri);
就可以實作呼叫相冊的功能
7.ImageShowActivity.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/show_pictures"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
就一個ImageView
8.RecoderVideoFragment.xml中
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextureView
android:id="@+id/textureView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<ImageButton
android:background="@drawable/shape_white_ring"
android:id="@+id/recording"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/shape_recorder"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="50dp"
/>
<ImageView
android:id="@+id/image_show"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:layout_marginBottom="50dp"
android:layout_marginLeft="50dp"
/>
<ImageButton
android:id="@+id/change"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_baseline_flip_camera_android_24"
android:layout_alignParentBottom="true"
android:layout_marginRight="50dp"
android:layout_marginBottom="50dp"
android:layout_alignParentRight="true"
/>
<Chronometer
android:id="@+id/timer"
android:textColor="#f00"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:format="%s"
android:gravity="center"
android:textSize="40sp" />
</RelativeLayout>
其實完全可以不用這樣重寫一次 完全可以包含上一個takepicture的xml包進行寫就行其實是一模一樣的就是加了個計時器控制元件而已
9.RecoderVideoFragment.java中
package com.example.camera;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.MediaMetadataRetriever;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Chronometer;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class RecorderVideoFragment extends Fragment implements View.OnClickListener {
private String TAG = "RecorderVideoFragment";
private ImageButton videoButton; //用來重新設定錄像按鈕
private TextureView mTextureView; //預覽框控制元件
private CaptureRequest.Builder mPreviewCaptureRequest; //獲取請求創建者
private CameraDevice mCameraDevice; //camera設備
private MediaRecorder mMediaRecorder; //音視頻錄制
//攝像頭ID 默認置為后置BACK FRONT值為0 == BACK
private String mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_FRONT);
private Chronometer timer; //計時器
private ArrayList<String> imageList = new ArrayList<>(); // 路徑集合
private static CameraCaptureSession mCameraCaptureSession; //獲取會話
private Handler mChildHandler; //子執行緒
private CameraManager mCameraManager; //攝像頭管理者
private boolean isVisible = false; //fragment是否可見
private boolean isRecording = false; //是否在錄制視頻
private HandlerThread mHandlerThread; //執行緒處理者
private ImageView mImageView; //縮略圖按鈕
private ImageButton change; //前后置切換按鈕
private static final SparseIntArray ORIENTATION = new SparseIntArray();
static {
ORIENTATION.append(Surface.ROTATION_0, 90);
ORIENTATION.append(Surface.ROTATION_90, 0);
ORIENTATION.append(Surface.ROTATION_180, 270);
ORIENTATION.append(Surface.ROTATION_270, 180);
}
//Fragment 中 onCreateView回傳的就是fragment要顯示的view.
@Nullable
@Override
/**
* 第一個引數LayoutInflater inflater第二個引數ViewGroup container第三個引數 Bundle savedInstanceState
* LayoutInflater inflater:作用類似于findViewById()用來尋找xml布局下的具體的控制元件Button、TextView等,
* LayoutInflater inflater()用來找res/layout/下的xml布局檔案
* ViewGroup container:表示容器,View放在里面
* Bundle savedInstanceState:保存當前的狀態,在活動的生命周期中,只要離開了可見階段,活動很可能就會被行程終止,
* 這種機制能保存當時的狀態
*/
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
Log.d(TAG, "onCreateView: success");
View view = inflater.inflate(R.layout.fragment_recorder, container, false);
initView(view);
//監聽視頻按鈕
videoButton.setOnClickListener(this);
//監聽縮略圖按鈕
mImageView.setOnClickListener(this);
//監聽前后置切換按鈕
change.setOnClickListener(this);
return view;
}
//判斷Fragment是否可見
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
//如果Fragment可見 把isVisible置為true
isVisible = true;
Log.d(TAG, "setUserVisibleHint: true");
//設定顯示最后一張圖片(視頻第一幀)
setLastImagePath();
//初始化子執行緒
initChildHandler();
//如果textureView可用
if (mTextureView.isAvailable()) {
openCamera();
} else {
initTextureView();
}
} else {
closeCamera();
return;
}
}
@Override
public void onResume() {
super.onResume();
if (isVisible) {
初始化子執行緒
initChildHandler();
if (mTextureView.isAvailable()) {
openCamera();
} else {
initTextureView();
}
}
}
//初始化監聽控制元件
private void initView(View view){
mImageView = view.findViewById(R.id.image_show);
mTextureView = view.findViewById(R.id.textureView);
timer = view.findViewById(R.id.timer);
videoButton = view.findViewById(R.id.recording);
change = view.findViewById(R.id.change);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.recording:
if (isRecording) {
//再次按下將停止錄制
stopRecorder();
isRecording = false;
} else {
//第一次按下將isRecording置為ture
//配置并開始錄制
isRecording = true;
configSession();
startRecorder();
}
break;
case R.id.image_show:
openAlbum();
break;
case R.id.change:
changeCamera();
break;
}
}
//1.攝像頭狀態回呼
private CameraDevice.StateCallback mCameraDeviceStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
//攝像頭被打開
mCameraDevice = camera;
startPreview();
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
//攝像頭斷開
}
@Override
public void one rror(@NonNull CameraDevice camera, int error) {
//例外
}
};
//2.錄像時訊息捕獲回呼
private CameraCaptureSession.CaptureCallback mSessionCaptureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
long timestamp, long frameNumber) {
super.onCaptureStarted(session, request, timestamp, frameNumber);
}
@Override
public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
@NonNull CaptureResult partialResult) {
super.onCaptureProgressed(session, request, partialResult);
}
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
super.onCaptureCompleted(session, request, result);
}
@Override
public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
@NonNull CaptureFailure failure) {
super.onCaptureFailed(session, request, failure);
}
};
//3.錄像時會話狀態回呼
private CameraCaptureSession.StateCallback mSessionStateCallback = new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mCameraCaptureSession = session;
updatePreview();
try {
//執行重復獲取資料請求,等于一直獲取資料呈現預覽畫面,mSessionCaptureCallback會回傳此次操作的資訊回呼
mCameraCaptureSession.setRepeatingRequest(mPreviewCaptureRequest.build(),
mSessionCaptureCallback, mChildHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
}
};
//設定模式 閃光燈用
private void setUpCaptureRequestBuilder(CaptureRequest.Builder builder) {
builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
}
/**
* 初始化TextureView的紋理生成監聽,只有紋理生成準備好了,才能去進行攝像頭的初始化作業讓TextureView接收攝像頭預覽畫面
*/
private void initTextureView() {
mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
//可以使用紋理
initCameraManager();
selectCamera();
openCamera();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
//紋理尺寸變化
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
//紋理被銷毀
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
//紋理更新
}
});
}
/**
* 計算需要的使用的攝像頭解析度
*
* @return
*/
private Size getMatchingSize() {
Size selectSize = null;
try {
CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId);
StreamConfigurationMap streamConfigurationMap = cameraCharacteristics.get
(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Size[] sizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG);
//這里是將預覽鋪滿螢屏,所以直接獲取螢屏解析度
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
//螢屏解析度寬
int deviceWidth = displayMetrics.widthPixels;
//螢屏解析度高
int deviceHeight = displayMetrics.heightPixels;
/**
* 回圈40次,讓寬度范圍從最小逐步增加,找到最符合螢屏寬度的解析度,
* 你要是不放心那就增加回圈,肯定會找到一個解析度,不會出現此方法回傳一個null的Size的情況
* ,但是回圈越大后獲取的解析度就越不匹配
*/
for (int j = 1; j < 41; j++) {
for (int i = 0; i < sizes.length; i++) { //遍歷所有Size
Size itemSize = sizes[i];
//判斷當前Size高度小于螢屏寬度+j*5 && 判斷當前Size高度大于螢屏寬度-j*5 && 判斷當前Size寬度小于當前螢屏高度
if (itemSize.getHeight() < (deviceWidth + j * 5) && itemSize.getHeight() > (deviceWidth - j * 5)) {
if (selectSize != null) { //如果之前已經找到一個匹配的寬度
if (Math.abs(deviceHeight - itemSize.getWidth()) < Math.abs(deviceHeight - selectSize.getWidth())) { //求絕對值算出最接近設備高度的尺寸
selectSize = itemSize;
continue;
}
} else {
selectSize = itemSize;
}
}
}
if (selectSize != null) { //如果不等于null 說明已經找到了 跳出回圈
break;
}
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
Log.e(TAG, "getMatchingSize: 選擇的解析度寬度=" + selectSize.getWidth());
Log.e(TAG, "getMatchingSize: 選擇的解析度高度=" + selectSize.getHeight());
return selectSize;
}
/**
* 初始化Camera2的相機管理,CameraManager用于獲取攝像頭解析度,攝像頭方向,攝像頭id與打開攝像頭的作業
*/
private void initCameraManager() {
mCameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
}
/**
* 選擇一顆我們需要使用的攝像頭,主要是選擇使用前攝還是后攝或者是外接攝像頭
*/
private void selectCamera() {
if (mCameraManager != null) {
Log.e(TAG, "selectCamera: CameraManager is null");
}
try {
String[] cameraIdList = mCameraManager.getCameraIdList(); //獲取當前設備的全部攝像頭id集合
if (cameraIdList.length == 0) {
Log.e(TAG, "selectCamera: cameraIdList length is 0");
}
for (String cameraId : cameraIdList) {
//遍歷所有攝像頭
//螢屏方向
int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
//設定拍照方向
// if (mCameraId.equals("1")) {
// rotation = 2;
// }
mPreviewCaptureRequest.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));
if (rotation == CameraCharacteristics.LENS_FACING_BACK) {
//這里選擇了后攝像頭
mCameraId = cameraId;
}
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@SuppressLint("MissingPermission")
private void openCamera() {
try {
if (mCameraManager == null) {
initCameraManager();
}
mCameraManager.openCamera(mCameraId, mCameraDeviceStateCallback, mChildHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void closeCamera() {
// 關閉預覽就是關閉捕獲會話
stopPreview();
// 關閉當前相機
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
if (null != mMediaRecorder) {
mMediaRecorder.release();
mMediaRecorder = null;
}
if (mHandlerThread != null) {
stopBackgroundThread();
}
}
/**
* 開啟預覽
* 使用TextureView顯示相機預覽資料,
* 預覽和拍照資料都是使用CameraCaptureSession會話來請求
*/
private void startPreview() {
stopPreview();
SurfaceTexture mSurfaceTexture = mTextureView.getSurfaceTexture();
Size cameraSize = getMatchingSize();
//設定TextureView的緩沖區大小
mSurfaceTexture.setDefaultBufferSize(cameraSize.getWidth(),
cameraSize.getHeight());
//獲取Surface顯示預覽資料
Surface previewSurface = new Surface(mSurfaceTexture);
try {
//創建CaptureRequestBuilder,TEMPLATE_PREVIEW比表示預覽請求
mPreviewCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
//設定Surface作為預覽資料的顯示界面
mPreviewCaptureRequest.addTarget(previewSurface);
//創建相機捕獲會話,第一個引數是捕獲資料Surface串列,
// 第二個引數是CameraCaptureSession的狀態回呼介面,
//當他創建好后會回呼onConfigured方法,第三個引數用來確定Callback在哪個執行緒執行
mCameraDevice.createCaptureSession(Collections.singletonList(previewSurface),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mCameraCaptureSession = session;
updatePreview();
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
Toast.makeText(getActivity().getApplicationContext(), "Faileedsa ", Toast.LENGTH_SHORT).show();
}
}, mChildHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 更新預覽
*/
private void updatePreview() {
if (null == mCameraDevice) {
return;
}
try {
setUpCaptureRequestBuilder(mPreviewCaptureRequest);
HandlerThread thread = new HandlerThread("CameraPreview");
thread.start();
mCameraCaptureSession.setRepeatingRequest(mPreviewCaptureRequest.build(), null, mChildHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 關閉預覽
*/
private void stopPreview() {
//關閉預覽就是關閉捕獲會話
if (mCameraCaptureSession != null) {
mCameraCaptureSession.close();
mCameraCaptureSession = null;
}
}
/**
* 初始化子執行緒Handler,操作Camera2需要一個子執行緒的Handler
*/
private void initChildHandler() {
mHandlerThread = new HandlerThread("DangJunHaoDemo");
mHandlerThread.start();
mChildHandler = new Handler(mHandlerThread.getLooper());
}
/**
* 關閉執行緒
*/
public void stopBackgroundThread() {
if (mHandlerThread != null) {
//quitSafely 安全退出
mHandlerThread.quitSafely();
try {
mHandlerThread.join();
mHandlerThread = null;
mHandlerThread = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 配置錄制視頻相關資料
*/
private void configMediaRecorder() {
File file = new File(Environment.getExternalStorageDirectory() +
"/DCIM/camera/myMp4" + System.currentTimeMillis() + ".mp4");
if (file.exists()) {
file.delete();
}
if (mMediaRecorder == null) {
mMediaRecorder = new MediaRecorder();
}
//設定音頻來源
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
//設定視頻來源
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
//設定輸出格式
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
//設定音頻編碼格式,請注意這里使用默認,實際app專案需要考慮兼容問題,應該選擇AAC
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
//設定視頻編碼格式,請注意這里使用默認,實際app專案需要考慮兼容問題,應該選擇H264
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
//設定位元率 一般是 1*解析度 到 10*解析度 之間波動,位元率越大視頻越清晰但是視頻檔案也越大,
mMediaRecorder.setVideoEncodingBitRate(8 * 1024 * 1920);
//設定幀數 選擇 30即可, 過大幀數也會讓視頻檔案更大當然也會更流暢,但是沒有多少實際提升,人眼極限也就30幀了,
mMediaRecorder.setVideoFrameRate(30);
Size size = getMatchingSize();
mMediaRecorder.setVideoSize(size.getWidth(), size.getHeight());
mMediaRecorder.setOrientationHint(90);
//如果是前置
if(mCameraId.equals("1")){
mMediaRecorder.setOrientationHint(270);
}
Surface surface = new Surface(mTextureView.getSurfaceTexture());
mMediaRecorder.setPreviewDisplay(surface);
mMediaRecorder.setOutputFile(file.getAbsolutePath());
try {
mMediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 配置錄制視頻時的CameraCaptureSession
*/
private void configSession() {
try {
if (mCameraCaptureSession != null) {
mCameraCaptureSession.stopRepeating();//停止預覽,準備切換到錄制視頻
mCameraCaptureSession.close();//關閉預覽的會話,需要重新創建錄制視頻的會話
mCameraCaptureSession = null;
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
configMediaRecorder();
Size cameraSize = getMatchingSize();
SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
surfaceTexture.setDefaultBufferSize(cameraSize.getWidth(), cameraSize.getHeight());
Surface previewSurface = new Surface(surfaceTexture);
Surface recorderSurface = mMediaRecorder.getSurface();//從獲取錄制視頻需要的Surface
try {
mPreviewCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewCaptureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
mPreviewCaptureRequest.addTarget(previewSurface);
mPreviewCaptureRequest.addTarget(recorderSurface);
//請注意這里設定了Arrays.asList(previewSurface,recorderSurface) 2個Surface,很好理解錄制視頻也需要有畫面預覽,
// 第一個是預覽的Surface,第二個是錄制視頻使用的Surface
mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, recorderSurface),
mSessionStateCallback, mChildHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 開始錄制視頻
*/
private void startRecorder() {
mMediaRecorder.start();
//開始計時
startTime();
}
/**
* 暫停錄制視頻(暫停后視頻檔案會自動保存)
*/
private void stopRecorder() {
if (mMediaRecorder != null) {
mMediaRecorder.stop();
mMediaRecorder.reset();
//停止計時
endTime();
}
broadcast();
setLastImagePath();
startPreview();
}
// 廣播通知相冊更新
public void broadcast() {
Log.d(TAG, "broadcast: success");
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/";
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri uri = Uri.fromFile(new File(path));
intent.setData(uri);
getActivity().sendBroadcast(intent);
}
// 改變前后攝像頭
private void changeCamera() {
Log.d(TAG, "changeCamera: success");
if (mCameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_BACK))) {
Toast.makeText(getContext(), "前置轉后置", Toast.LENGTH_SHORT).show();
mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_FRONT);
} else {
Toast.makeText(getContext(), "后置轉前置", Toast.LENGTH_SHORT).show();
mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_BACK);
}
mCameraDevice.close();
openCamera();
}
//找到最后一張的路徑
private void setLastImagePath() {
Log.d(TAG, "setLastImagePath: success");
imageList = GetImageFilePath.getFilePath();
String string = imageList.get(imageList.size() - 1);
if (string.contains(".jpg")) {
setImageBitmap(string);
} else {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(string);
//獲取第1幀
Bitmap bitmap = retriever.getFrameAtTime(1);
mImageView.setImageBitmap(bitmap);
}
}
// 設定縮略圖顯示
private void setImageBitmap(String path) {
Log.d(TAG, "setImageBitmap: success");
Bitmap bitmap = BitmapFactory.decodeFile(path);
//通過ImageView顯示縮略圖
mImageView.setImageBitmap(bitmap);
}
//打開相冊
private void openAlbum() {
Log.d(TAG, "openAlbum: success");
Intent intent = new Intent();
// 在ImageShowActivity中直接從相冊中遍歷 不需要傳遞過去
//intent.putStringArrayListExtra("myList", imageList);
intent.setClass(getContext(), ImageShowActivity.class);
startActivity(intent);
}
private void startTime() {
timer.setBase(SystemClock.elapsedRealtime());//計時器清零
timer.start();
}
private void endTime(){
timer.stop();
timer.setBase(SystemClock.elapsedRealtime());//計時器清零
}
}
視頻錄制的流程跟拍照很相似
創建的請求不一樣僅此而已
1.因為我在拍照的Fragment中獲取過權限所以不在需要申請權限
2.因為使用的ViewPage加Fragment所以需要setUserVisibleHint函式來保證Fragment可見的時候在對其進行初始化,不然兩個Fragment同時執行初始化相機打開相機等會報錯.
3.初始化控制元件,在該類中用的是initView
4.監聽控制元件onClick
5.初始化TextureView 監聽 在demo中 initTextureVIew()
6.計算使用的攝像頭解析度 getMatchingSize()
7.初始化相機管理 initCameraManager()
只是獲取 CameraManager
8.選擇攝像頭 主要是選擇前后 selectCamera()
9.openCamera() 還是一樣openCamera 的時候需要用到CameraDevice.StateCallback 在里面 開啟預覽
10.開啟預覽 這里是把創建預覽請求mPreviewCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewCaptureRequest.addTarget(previewSurface);
寫到了startPreview()中捕獲請求的回呼CameraCaptureSession.StateCallback() 也寫到這個方法里在回呼中呼叫重復預覽 repeatPreview()
11.重復預覽 repeatPreview()
mCameraCaptureSession.setRepeatingRequest(mPreviewCaptureRequest.build(), null, mChildHandler);
mChildHandler這里是創建了一個子執行緒處理
12.因為錄制視頻需要停止預覽stopPreview()
13.初始化子執行緒 initChildHandler() 以及關閉執行緒stopBackgroundThread()
14.配置錄制視頻的相關資料configMediaRecorder()
mMediaRecorder.setOrientationHint(270); 通過這個來處理保存的視頻方向前置需要設為270 后置需要設定90
15.配置錄制視頻的CameraCaptureSession 這里跟拍照不一樣configSession()
16.開始錄制startRecorder()
停止錄制stopRecorder()
17.廣播重繪相冊broadcast()
18. 改變前后攝像頭changeCamera()
19.設定顯示最后一樣圖片設定最后一張路徑setLastImagePath() 把路徑轉圖片顯示setImageBitmap
20.打開相冊 openAlbum()
21.錄制視頻需要用的計時器控制元件startTime() endTime()
總結幾個回呼使用與區別:
1.CameraDevice.StateCallback openCamera會用到
2.CameraCaptureSession.CaptureCallback 拍斬訓者是錄像的時候用到
3.CameraCaptureSession.StateCallback 處理請求的回呼
4.onRequestPermissionResult 權限回呼
5.TextureView.SurfaceTextureListener() SurfaceView狀態回呼
這是我學習的程序不一定對 但是對小白肯定有幫助
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/303907.html
標籤:其他
