• 需求

最近有个需求要求界面上使用圆形相机预览进行面部检测 , 具体需求如下图

关于Camera之前接触得比较多 , 主要就是通过SurfaceView显示预览视图 , 因此需要展示圆形预览界面, 只需要控制SurfaceView的显示范围就可以了.

  • 实现

由于较为简单 , 下面我们直接给出实现代码:


import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.hardware.Camera;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView; import java.io.IOException;
import java.util.List; public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = "CameraPreview"; private Camera mCamera;
private SurfaceHolder mHolder;
private Activity mContext;
private CameraListener listener;
private int cameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
private int displayDegree = 90; public CameraPreview(Activity context) {
super(context);
mContext = context;
mCamera = Camera.open(cameraId);
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
} public void setCameraListener(CameraListener listener) {
this.listener = listener;
} /**
* 拍照获取bitmap
*/
public void captureImage() {
try {
mCamera.takePicture(null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
if (null != listener) {
Bitmap bitmap = rotateBitmap(BitmapFactory.decodeByteArray(data, 0, data.length),
displayDegree);
listener.onCaptured(bitmap);
}
}
});
} catch (Exception e) {
e.printStackTrace();
if (null != listener) {
listener.onCaptured(null);
}
}
} /**
* 预览拍照
*/
public void startPreview() {
mCamera.startPreview();
} @Override
public boolean onTouchEvent(MotionEvent event) {
if (null != mCamera) {
mCamera.autoFocus(null);
}
return super.onTouchEvent(event);
} @Override
public void surfaceCreated(SurfaceHolder holder) {
try {
startCamera(holder);
} catch (IOException e) {
e.printStackTrace();
}
} @Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (mHolder.getSurface() == null) {
return;
}
try {
mCamera.stopPreview();
} catch (Exception e) {
e.printStackTrace();
}
try {
startCamera(mHolder);
} catch (Exception e) {
Log.e(TAG, e.toString());
}
} private void startCamera(SurfaceHolder holder) throws IOException {
mCamera.setPreviewDisplay(holder);
setCameraDisplayOrientation(mContext, cameraId, mCamera); Camera.Size preSize = getCameraSize(); Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(preSize.width, preSize.height);
parameters.setPictureSize(preSize.width, preSize.height);
parameters.setJpegQuality(100);
mCamera.setParameters(parameters);
mCamera.startPreview();
} public Camera.Size getCameraSize() {
if (null != mCamera) {
Camera.Parameters parameters = mCamera.getParameters();
DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
Camera.Size preSize = Util.getCloselyPreSize(true, metrics.widthPixels, metrics.heightPixels,
parameters.getSupportedPreviewSizes());
return preSize;
}
return null;
} @Override
public void surfaceDestroyed(SurfaceHolder holder) {
releaseCamera();
} /**
* Android API: Display Orientation Setting
* Just change screen display orientation,
* the rawFrame data never be changed.
*/
private 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;
}
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
displayDegree = (info.orientation + degrees) % 360;
displayDegree = (360 - displayDegree) % 360; // compensate the mirror
} else {
displayDegree = (info.orientation - degrees + 360) % 360;
}
camera.setDisplayOrientation(displayDegree);
} /**
* 将图片按照某个角度进行旋转
*
* @param bm 需要旋转的图片
* @param degree 旋转角度
* @return 旋转后的图片
*/
private Bitmap rotateBitmap(Bitmap bm, int degree) {
Bitmap returnBm = null; // 根据旋转角度,生成旋转矩阵
Matrix matrix = new Matrix();
matrix.postRotate(degree);
try {
// 将原始图片按照旋转矩阵进行旋转,并得到新的图片
returnBm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(),
bm.getHeight(), matrix, true);
} catch (OutOfMemoryError e) {
e.printStackTrace();
}
if (returnBm == null) {
returnBm = bm;
}
if (bm != returnBm) {
bm.recycle();
}
return returnBm;
} /**
* 释放资源
*/
public synchronized void releaseCamera() {
try {
if (null != mCamera) {
mCamera.setPreviewCallback(null);
mCamera.stopPreview();//停止预览
mCamera.release(); // 释放相机资源
mCamera = null;
}
if (null != mHolder) {
mHolder.removeCallback(this);
mHolder = null;
}
} catch (Exception e) {
e.printStackTrace();
}
} }
封装了一个CameraPreview ,与相机预览相关的逻辑全部放在里面了 , 同时对外暴露了一个CameraListener 可以提供拍照、预览等回调(取决于自己定义)

接下来就是控制CameraPreview的显示了, 用一个RelativeLayout包裹起来, 并且切割成圆形
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Rect;
import android.hardware.Camera;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.RequiresApi;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.widget.FrameLayout;
import android.widget.RelativeLayout; import com.dong.circlecamera.R; import java.util.Timer;
import java.util.TimerTask; public class CircleCameraLayout extends RelativeLayout { public CircleCameraLayout(Context context) {
super(context);
init(context, null, -1, -1);
} public CircleCameraLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, -1, -1);
} public CircleCameraLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr, -1);
} @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public CircleCameraLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs, defStyleAttr, defStyleRes);
} private Timer timer;
private TimerTask pressTask;
private Context mContext;
private int circleWidth = 0;//指定半径
private int borderWidth = 0;//指定边框
private CameraPreview cameraPreview;//摄像预览 private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mContext = context;
timer = new Timer();
if (attrs != null && defStyleAttr == -1 && defStyleRes == -1) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleCameraLayout, defStyleAttr, defStyleRes);
circleWidth = (int) typedArray.getDimension(R.styleable.CircleCameraLayout_circle_camera_width, ViewGroup.LayoutParams.WRAP_CONTENT);
borderWidth = (int) typedArray.getDimension(R.styleable.CircleCameraLayout_border_width, 5);
typedArray.recycle();
}
startView();
} /**
* 设置照相预览
*
* @param cameraPreview
*/
public void setCameraPreview(CameraPreview cameraPreview) {
this.cameraPreview = cameraPreview;
} /**
* 释放回收
*/
public void release() {
if (null != pressTask) {
pressTask.cancel();
pressTask = null;
}
if (null != timer) {
timer.cancel();
timer = null;
}
} //延时启动摄像头
public void startView() {
pressTask = new TimerTask() {
@Override
public void run() {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
pressTask.cancel();
pressTask = null;
if (null != cameraPreview) {
show();
} else {
startView();
}
}
});
}
};
timer.schedule(pressTask, 50);
} @TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void show() {
//cmaera根view--layout
RelativeLayout cameraRoot = new RelativeLayout(mContext);
RelativeLayout.LayoutParams rootParams = new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
rootParams.addRule(CENTER_IN_PARENT, TRUE);
cameraRoot.setBackgroundColor(Color.TRANSPARENT);
cameraRoot.setClipChildren(false); //camera--layout
FrameLayout cameraLayout = new FrameLayout(mContext);
Camera.Size preSize = cameraPreview.getCameraSize();
int cameraHeight = (int) ((float) preSize.width / (float) preSize.height * circleWidth);
RelativeLayout.LayoutParams cameraParams = new RelativeLayout.LayoutParams(circleWidth, cameraHeight);
cameraParams.addRule(CENTER_IN_PARENT, TRUE);
cameraLayout.setLayoutParams(cameraParams);
cameraLayout.addView(cameraPreview); cameraLayout.setOutlineProvider(viewOutlineProvider);//把自定义的轮廓提供者设置给imageView
cameraLayout.setClipToOutline(true);//开启裁剪 //circleView--layout
// CircleView circleView = new CircleView(mContext);
CircleView2 circleView = new CircleView2(mContext);
circleView.setBorderWidth(circleWidth, borderWidth); //设置margin值---隐藏超出部分布局
int margin = (cameraHeight - circleWidth) / 2 - borderWidth / 2;
rootParams.setMargins(0, -margin, 0, -margin);
cameraRoot.setLayoutParams(rootParams); //添加camera
cameraRoot.addView(cameraLayout);
//添加circle
cameraRoot.addView(circleView);
//添加根布局
this.addView(cameraRoot);
} //自定义一个轮廓提供者
public ViewOutlineProvider viewOutlineProvider = new ViewOutlineProvider() {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void getOutline(View view, Outline outline) {
//裁剪成一个圆形
int left0 = 0;
int top0 = (view.getHeight() - view.getWidth()) / 2;
int right0 = view.getWidth();
int bottom0 = (view.getHeight() - view.getWidth()) / 2 + view.getWidth();
outline.setOval(left0, top0, right0, bottom0);
}
}; }

接着再看一下如何在MainActivity使用的

package com.dong.circlecamera;

import android.Manifest;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast; import com.dong.circlecamera.view.CameraListener;
import com.dong.circlecamera.view.CameraPreview;
import com.dong.circlecamera.view.CircleCameraLayout;
import com.dong.circlecamera.view.Util; /**
* @create 2018/12/1
* @Describe 自定义圆形拍照、解决非全屏(竖屏)下预览相机拉伸问题。
*/
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private static final int PERMISSION_REQUEST_CODE = 10;
private String[] mPermissions = {Manifest.permission.CAMERA}; private CircleCameraLayout rootLayout;
private ImageView imageView;
private CameraPreview cameraPreview;
private boolean hasPermissions;
private boolean resume = false;//解决home键黑屏问题 @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); findViewById(R.id.bt_take_photo).setOnClickListener(this);
findViewById(R.id.bt_re_take_photo).setOnClickListener(this);
rootLayout = findViewById(R.id.rootLayout);
imageView = findViewById(R.id.image); //权限检查
if (Util.checkPermissionAllGranted(this, mPermissions)) {
hasPermissions = true;
} else {
ActivityCompat.requestPermissions(this, mPermissions, PERMISSION_REQUEST_CODE);
} } @Override
protected void onResume() {
super.onResume();
if (hasPermissions) {
startCamera();
resume = true;
}
} private void startCamera() {
if (null != cameraPreview) cameraPreview.releaseCamera();
cameraPreview = new CameraPreview(this);
rootLayout.removeAllViews();
rootLayout.setCameraPreview(cameraPreview);
if (!hasPermissions || resume) {
rootLayout.startView();
}
cameraPreview.setCameraListener(new CameraListener() {
@Override
public void onCaptured(Bitmap bitmap) {
if (null != bitmap) {
imageView.setImageBitmap(bitmap);
Toast.makeText(MainActivity.this, "拍照成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "拍照失败", Toast.LENGTH_SHORT).show();
}
}
});
} @Override
public void onClick(View v) {
if (null == cameraPreview) return;
switch (v.getId()) {
case R.id.bt_take_photo:
cameraPreview.captureImage();//抓取照片
break;
case R.id.bt_re_take_photo:
cameraPreview.startPreview();
break;
}
} @Override
protected void onDestroy() {
super.onDestroy();
if (null != cameraPreview) {
cameraPreview.releaseCamera();
}
rootLayout.release();
} /**
* 申请权限结果返回处理
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
boolean isAllGranted = true;
for (int grant : grantResults) { // 判断是否所有的权限都已经授予了
if (grant != PackageManager.PERMISSION_GRANTED) {
isAllGranted = false;
break;
}
}
if (isAllGranted) { // 所有的权限都授予了
startCamera();
} else {// 提示需要权限的原因
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("拍照需要允许权限, 是否再次开启?")
.setTitle("提示")
.setPositiveButton("确认", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MainActivity.this, mPermissions, PERMISSION_REQUEST_CODE);
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
finish();
}
});
builder.create().show();
}
}
}
}
  • 最后

圆形预览框 , 主要是通过一个relativelayout包裹住封装好的surfaceview , 并且裁剪显示的区域为圆形实现的 , 写下来记录一下 , 后面如果要用到的话方便自己到这里来查看.

圆形Camera预览实现的更多相关文章

  1. opencv-android笔记1:android studio 2.3 + opencv-android-sdk 实现 camera预览

    Android studio环境配置不再赘述,可以参照我的其他博客. Android应用程序开发环境搭建:http://blog.csdn.net/ja33son/article/details/61 ...

  2. Android Camera 预览图像被拉伸变形的解决方法【转】

    问题描述: 预览图像被拉伸变形 问题原因: 由于预览图像大小跟SurfaceView 大小不一致引起 解决方法: 获取系统支持的所有预览尺寸[getSupportedPictureSizes],然后再 ...

  3. Camera 预览变形问题解决

    最近开发一款自定义相机采集照片的demo,花了一个上午开发了一个在测试机上功能正常的apk连同测试机一起交付(需求方反馈没有Android设备),然而晚上被喊去说是在华为畅玩某型号上预览会变形,拍到的 ...

  4. Android手势识别 Camera 预览界面上显示文字 布局注意事项(merge布局)

    通常在Surfaceview作为预览视频帧的载体,有时需在上面显示提示文字.曾经我弄的都好好的.今天忽然发现叠加的TextView不管咋弄都出不来文字了,跟Surfaceview一起放在FrameLa ...

  5. android camera 摄像头预览画面变形

    问题:最近在处理一下camera的问题,发现在竖屏时预览图像会变形,而横屏时正常.但有的手机则是横竖屏都会变形. 结果:解决了预览变形的问题,同时支持前后摄像头,预览无变形,拍照生成的jpg照片方向正 ...

  6. 玩转Android Camera开发(四):预览界面四周暗中间亮,仅仅拍摄矩形区域图片(附完整源代码)

    杂家前文曾写过一篇关于仅仅拍摄特定区域图片的demo.仅仅是比較简陋.在坐标的换算上不是非常严谨,并且没有完毕预览界面四周暗中间亮的效果,深以为憾.今天把这个补齐了. 在上代码之前首先交代下,这里面存 ...

  7. Android开发实践:掌握Camera的预览方向和拍照方向

    http://ticktick.blog.51cto.com/823160/1592267?utm_source=tuicool&utm_medium=referral Android的Cam ...

  8. OV5640全景模式预览倒180度,拍照正常的问题

    此方法基本上适用于所有android平台上全景模式预览倒180度,拍照正常的问题. 首先说明的是,影响camera方向的有三个地方,分别是系统方向,内核camera方向和驱动镜像.全景模式预览只跟系统 ...

  9. Android多种方式实现相机圆形预览

    效果图如下: 一.为预览控件设置圆角 为控件设置ViewOutlineProvider public RoundTextureView(Context context, AttributeSet at ...

随机推荐

  1. C++反汇编-结构体和类

    学无止尽,积土成山,积水成渊-<C++反汇编与逆向分析技术揭秘> 读书笔记 对象的内存布局 一般计算公式: 对象内存大小 = sizeof(数据成员1)+ sizeof(数据成员2) +. ...

  2. Spring @Value 用法小结,#与$的区别

    20161016更新:这货其实是SpEL的功能,来这里看看吧: Spring 4 官方文档学习(五)核心技术之SpEL 起因 一直的用法是 @Value("${jdbc.driverClas ...

  3. My simplified pickit2 clone

    http://simon.derr.free.fr/site/spip/spip.php?article11 This is a description of my very simplified h ...

  4. jvm执行流程

    首先给一个简单的Java示例,源代码如下: public class Main { private static int size=1; public static void main(String  ...

  5. [Shell学习笔记] 命令行下的高级网络工具cURL命令

    原文: http://www.1987.name/365.html Linux curl命令是一个利用URL规则在命令行下工作的文件传输工具.它支持文件的上传和下载,所以是综合传输工具,但按传统,习惯 ...

  6. no scheme 问题

    用xcode4打开xcode3建立的工程,有时候,不能自动转换版本,就会显示no scheme. 这个是由于XXX..xcodeproj包中xcuserdata文件夹中user.xcuserdatad ...

  7. 转 UIAlertView 不显示、屏幕变灰

    UIAlertView 不显示.屏幕变灰 SvenFang 票 在 [[NSNotificationCenter defaultCenter] addObserver:self selector:@s ...

  8. MAC之tar解压与压缩打包命令

    tar [-cxtzjvfpPN] 文件与目录 ....参数:-c :建立一个压缩文件的参数指令(create 的意思):-x :解开一个压缩文件的参数指令!-t :查看 tarfile 里面的文件! ...

  9. jsonConfig使用方法

    1.先编写jsonConfig的初始化代码 private JsonConfig jsonConfig; public action构造方法() { jsonConfig = new JsonConf ...

  10. [MAC OS] NSOpenPanel 使用

    Mac OS开启沙盒之后,文件的保存会涉及到一个权限问题.如下图,在Capabilities中,可以勾选的权限一共有5种. User Selected File 必须勾选,否则 NSOpenPanel ...