圓形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 , 並且裁剪顯示的區域為圓形實現的 , 寫下來記錄一下 , 后面如果要用到的話方便自己到這里來查看.



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM