• 圆形Camera预览实现


    • 需求

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

    关于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 , 并且裁剪显示的区域为圆形实现的 , 写下来记录一下 , 后面如果要用到的话方便自己到这里来查看.


  • 相关阅读:
    最精简的django程序
    spring+mongo
    从零搭建mongo分片集群的简洁方法
    java对redis的基本操作
    awk输出指定列
    sed输出指定行
    Bash的循环结构(for和while)
    用ffmpeg切割音频文件
    Python判断字符串是否全是字母或数字
    Python函数: any()和all()的用法
  • 原文地址:https://www.cnblogs.com/fuyaozhishang/p/10044836.html
Copyright © 2020-2023  润新知