一. 布局
AutoFitTextureView
public class AutoFitTextureView extends TextureView {
private int mRatioWidth = 0;
private int mRatioHeight = 0;
public AutoFitTextureView(Context context) {
this(context, null);
}
public AutoFitTextureView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
* calculated from the parameters. Note that the actual sizes of parameters don't matter, that
* is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
*
* @param width Relative horizontal size
* @param height Relative vertical size
*/
public void setAspectRatio(int width, int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Size cannot be negative.");
}
if (mRatioWidth == width && mRatioHeight == height) {
return;
}
mRatioWidth = width;
mRatioHeight = height;
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
if (width < height * mRatioWidth / mRatioHeight) {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
}
二. 基本设置
1. 请求权限(onResume中处理)
避坑:
a. 请求到权限之后初始化一次即可, 所以加了个标志位
private void initCamera() {
if (mFlag) {
return;
}
mFlag = true;
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}
b. 重赋权限之后返回界面没刷新依然黑屏
在TextureView.SurfaceTextureListener的监听方法中处理
添加全局标志位mFlagCameraAvailable;
onSurfaceTextureAvailable
中
onSurfaceTextureAvailable=true
onSurfaceTextureUpdated
中
if (!onSurfaceTextureAvailable) {
recreate();
onSurfaceTextureAvailable= true;
}
解释: 在设置页面重新赋予权限之后,监听mTextureView.setSurfaceTextureListener(mSurfaceTextureListener)
依然会执行, 但是onSurfaceTextureAvailable
不执行, 而onSurfaceTextureUpdated
会执行一次, 所以在该方法中重建该页面
2. 设置TextureView的监听
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener)
在onSurfaceTextureAvailable
方法中开启相机:
- 开启HandlerThread
HandlerThread thread = new HandlerThread("TextureView.Available");
thread.start();
- 创建全局Handler对象
mHandler = new Handler(thread.getLooper());
接下来调用的api都需要该mHandler对象
- 获取CameraManager对象
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
- 获取摄像头数据
mCharacteristics = manager.getCameraCharacteristics(mCameraId);
此处mCameraId为常数, 可取CameraCharacteristics.LENS_FACING_FRONT
CameraCharacteristics.LENS_FACING_BACK
即前置和后置摄像头, 此处需转为String类型;
- 获取摄像头参数:
StreamConfigurationMap map = mCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
- 获取照片分辨率列表
List<Size> sizes = map.getOutputSizes(ImageFormat.JPEG);
获取其中最大的分辨率(Collections.max(sizes, new CompareSizeByArea())
), 用于获取ImageReader
对象
- 获取预览尺寸
mPreViewSize = map.getOutputSizes(SurfaceTexture.class)[0];
- 获取
ImageReader
对象
mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, 5); //5表示同时访问的最大照片数量,越小越省内存
- 设置监听图片的监听
mImageReader.setOnImageAvailableListener(onImageAvailableListener, mHandler);
- 开启相机
manager.openCamera(mCameraId, mCameraOpenCallback, mHandler);
1-10即为下面的openCamera()
方法
CameraCharacteristics相关参数
//获取摄像头方向
characteristics.get(CameraCharacteristics.LENS_FACING)
3. 监听之mCameraOpenCallback
CameraDevice.StateCallback
onOpened
方法
- 取到CameraDevice作为全局相机设备对象, 在界面关闭的时候需要对其关闭
- 创建全局
CaptureRequest.Builder
对象, 并创建会话
mPreViewBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
surfaceTexture.setDefaultBufferSize(mPreViewSize.getWidth(), mPreViewSize.getHeight());
Surface surface = new Surface(surfaceTexture);
mPreViewBuilder.addTarget(surface);
List<Surface> surfaces = Arrays.asList(surface, mImageReader.getSurface());
camera.createCaptureSession(surfaces, mSessionStateCallBack, mHandler);
4. 监听之mSessionStateCallBack
CameraCaptureSession.StateCallback
- 取到CameraCaptureSession作为全局会话对象, 拍照时候需要用到, 在界面关闭的时候需要对其关闭
- 给会话设置
session.setRepeatingRequest(mPreViewBuilder.build(), null, mHandler);
5. 监听之mOnImageAvailableListener
ImageReader.OnImageAvailableListener
onImageAvailable()
//1. 获取到拍摄的照片
Image image = imageReader.acquireNextImage()
//2. 操作保存
mHandler.post(new Runnable() {
@Override
public void run() {
saveImage();
}
});
private void saveImage() {
String path = getExternalCacheDir().getAbsolutePath().concat(File.separator).concat("temp_image").concat(".jpg");
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(path); // todo: 2020/11/4 [V1.5.4] file
ByteBuffer buffer = reader.getPlanes()[0].getBuffer();
byte[] buff = new byte[buffer.remaining()];
buffer.get(buff);
outputStream.write(buff);
XLogUtil.d("拍摄保存图片完成", path);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
reader.close();
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
三. 拍照
CaptureRequest.Builder builder = mCameraSession.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
builder.addTarget(mImageReader.getSurface());
//自动对焦
builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
//照片方向
builder.set(CaptureRequest.JPEG_ORIENTATION, mCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION));
//拍照
mCameraSession.capture(builder.build(), null, mHandler);
四. 切换摄像头
if (mCameraId.equals(CameraCharacteristics.LENS_FACING_FRONT + "")) {
mCameraId = CameraCharacteristics.LENS_FACING_BACK + "";
closeCamera();
reopenCamera();
} else if (mCameraId.equals(CameraCharacteristics.LENS_FACING_BACK + "")) {
mCameraId = CameraCharacteristics.LENS_FACING_FRONT + "";
closeCamera();
reopenCamera();
}
private void closeCamera() {
if (mCameraDevice != null) {
mCameraDevice.close();
mCameraDevice = null;
}
if (mCameraSession != null) {
mCameraSession.close();
mCameraSession = null;
}
}
private void reopenCamera() {
if (mTextureView.isAvailable()) {
openCamera();
} else {
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}
}
//即重新执行onSurfaceTextureAvailable内的方法
private void openCamera() {
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
mCharacteristics = manager.getCameraCharacteristics(mCameraId);
StreamConfigurationMap map = mCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
List<Size> sizes = Arrays.asList(map.getOutputSizes(ImageFormat.JPEG));
Size largestImageSize = Collections.max(sizes, new CompareSizeByArea());
Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);
if (outputSizes != null && outputSizes.length > 0) {
mPreViewSize = outputSizes[0];
}
mImageReader = ImageReader.newInstance(largestImageSize.getWidth(), largestImageSize.getHeight(), ImageFormat.JPEG, 5);
mImageReader.setOnImageAvailableListener(onImageAvailableListener, mHandler);
manager.openCamera(mCameraId, mCameraOpenCallback, mHandler);
configureTransform(mPreViewSize.getWidth(), mPreViewSize.getHeight());
} catch (CameraAccessException e) {
e.printStackTrace();
}
}