前言
项目集成第三方H5页面需要调用本地相机进行拍照并回传给H5,本来H5可以直接调用相机但是进入的是一个相册列表然后再进拍照,业务要求直接打开相机拍照并且必须是刚拍的照片,所以最后是通过JSBridge调用原生相机。哪位大牛知道H5怎么直接进入相机的还请指教,感激不尽。
正文
由于拍证件需要有一个简单的取景框,我用自定义view来实现,详情请移步Android 自定义View实现简单的相机预览取景框。下面说下拍照部分主要实现。
实现相机预览界面
自定义PreviewSurfaceView继承SurfaceView实现SurfaceHolder.Callback,SurfaceView用于实时预览界面,实现构造方法添加SurfaceHolder.Callback
public PreviewSurfaceView(Context context) {
this(context, null);
}
public PreviewSurfaceView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PreviewSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
init();
}
private void init() {
SurfaceHolder holder = getHolder();
holder.addCallback(this);
// holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
重写三个方法surfaceCreated、surfaceChanged、surfaceDestroyed用于在界面发生改变时对Camera的处理。Camera的预览方向需要旋转90°才和实际看到的一致,保存照片时也要先旋转90°。
@Override
public void surfaceCreated(SurfaceHolder holder) {
mHolder = holder;
// camera动态请求权限,需要在相应的activity中处理请求权限的回调然后调用initCamera方法
int checkSelfPermission = ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA);
if (checkSelfPermission == PackageManager.PERMISSION_GRANTED) {
initCamera();
} else {
ActivityCompat.requestPermissions((Activity) mContext, new String[]{Manifest.permission.CAMERA}, REQUEST_CODE_CAMERA);
}
}
public void initCamera() {
if (mCamera == null) {
mCamera = Camera.open();
}
try {
mCamera.setPreviewDisplay(mHolder);
setCameraDisplayOrientation((Activity) mContext, 0, mCamera);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* google官方建议的处理camera预览方向
* @param activity
* @param cameraId
* @param camera
*/
public static void setCameraDisplayOrientation(Activity activity, int cameraId, Camera camera) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}
camera.setDisplayOrientation(result);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
setCameraParams();
mCamera.startPreview();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
releaseCamera();
}
private void releaseCamera() {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
mHolder = null;
}
/**
* 设置Camera参数
*/
private void setCameraParams() {
Camera.Parameters parameters = mCamera.getParameters();
// 获取摄像头支持的PictureSize列表
List<Camera.Size> pictureSizeList = parameters.getSupportedPictureSizes();
for (Camera.Size size : pictureSizeList) {
Log.i(TAG, "pictureSizeList size.width=" + size.width + " size.height=" + size.height);
}
/**从列表中选取合适的分辨率*/
Camera.Size picSize = getProperSize(pictureSizeList, ((float) mScreenHeight / mScreenWidth));
if (null == picSize) {
Log.i(TAG, "null == picSize");
picSize = parameters.getPictureSize();
}
Log.i(TAG, "picSize.width=" + picSize.width + " picSize.height=" + picSize.height);
// 根据选出的PictureSize重新设置SurfaceView大小
float w = picSize.width;
float h = picSize.height;
parameters.setPictureSize(picSize.width, picSize.height);
this.setLayoutParams(new ConstraintLayout.LayoutParams((int) (mScreenHeight * (h / w)), mScreenHeight));
// 获取摄像头支持的PreviewSize列表
List<Camera.Size> previewSizeList = parameters.getSupportedPreviewSizes();
for (Camera.Size size : previewSizeList) {
Log.i(TAG, "previewSizeList size.width=" + size.width + " size.height=" + size.height);
}
Camera.Size preSize = getProperSize(previewSizeList, ((float) mScreenHeight) / mScreenWidth);
if (null != preSize) {
Log.i(TAG, "preSize.width=" + preSize.width + " preSize.height=" + preSize.height);
parameters.setPreviewSize(preSize.width, preSize.height);
}
parameters.setJpegQuality(100); // 设置照片质量
if (parameters.getSupportedFocusModes().contains(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
parameters.setFocusMode(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);// 连续对焦模式
}
mCamera.cancelAutoFocus();//自动对焦。
mCamera.setDisplayOrientation(90);// 设置PreviewDisplay的方向,效果就是将捕获的画面旋转多少度显示
mCamera.setParameters(parameters);
}
/**
* 从列表中选取合适的分辨率
* 默认w:h = 4:3
* <p>注意:这里的w对应屏幕的height
* h对应屏幕的width<p/>
*/
private Camera.Size getProperSize(List<Camera.Size> pictureSizeList, float screenRatio) {
Camera.Size result = null;
for (Camera.Size size : pictureSizeList) {
float currentRatio = ((float) size.width) / size.height;
if (currentRatio - screenRatio == 0) {
result = size;
break;
}
}
if (null == result) {
for (Camera.Size size : pictureSizeList) {
float curRatio = ((float) size.width) / size.height;
if (curRatio == 4f / 3) {// 默认w:h = 4:3
result = size;
break;
}
}
}
return result;
}
Camera在surfaceCreated中初始化,在surfaceDestroyed中记得释放资源,注意动态申请相机权限,manifest中也要添加权限
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
初始化操作完成后可以在xml中使用了,此时可以看到预览画面了
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.customcamera.PreviewSurfaceView
android:id="@+id/previewSurfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!--取景框-->
<com.example.customcamera.BackgroundView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:rectLeft="30dp"
app:rectTop="100dp"
app:rectRight="30dp"
app:rectBottom="300dp"
app:rectCornerRadius="10dp"
app:rectOutColor="#99ff0000" />
<Button
android:id="@+id/take_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="100dp"
android:text="拍照"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>
接下来调用拍照功能将照片保存到本地
/**
* 调用camera拍照
* @param callBack
*/
public void takePhotos(final CommonCallBack callBack) {
mCallBack = callBack;
mCamera.takePicture(null, null, (data, camera) -> {
if (data != null) {
savePhotos(data);
}
});
}
/**
* 保存图片到私有目录
* @param data
*/
@SuppressLint("WrongThread")
public void savePhotos(byte[] data) {
ByteArrayInputStream bais = new ByteArrayInputStream(data);// camera返回的字节数组直接转bitmap不行,是YuvImage格式的;曲线救国:先转流再转bitmap
Bitmap bitmap = BitmapFactory.decodeStream(bais);
Matrix matrix = new Matrix();
matrix.postRotate(90);// 图片旋转90度;camera生成的图片和预览方向都是以手机屏幕右上角为原点向下为X轴正方向,向左为Y轴正方向的坐标系,所以预览方向和生成图片后都需要旋转90度
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
ByteArrayOutputStream baos = BitmapUtils.compressImage2Stream(bitmap);
FileOutputStream fos = null;
try {
File privateImgFile = FileUtils.getPrivateImgFile(mContext, true);
File file = File.createTempFile("img_001", ".jpeg", privateImgFile);
fos = new FileOutputStream(file);
fos.write(baos.toByteArray());
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos != null) fos.close();
if (bais != null) bais.close();
if (baos != null) baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
mCallBack.accept(null);
}
保存照片时涉及到转换和压缩操作比较耗时,后续考虑进一步优化。至此,一个带取景框的自定义相机功能就实现了。
网友评论