Android 仿支付寶人臉識(shí)別UI

<meta charset="utf-8">

前言

前一段時(shí)間做人臉識(shí)別,UI設(shè)計(jì)師也是慣用“cv”大法,做了一個(gè)仿支付寶的UI景描,這里就總結(jié)一下自定義仿支付寶人臉識(shí)別界面UI万哪,人臉識(shí)別產(chǎn)品為公司產(chǎn)品不便于貼出來(lái),這里就用前置攝像頭畫(huà)面代替脐恩。

需求

首先我們看看支付寶人臉識(shí)別UI界面

image
image

需求分析:
根據(jù)人臉的情況中間提示語(yǔ)不斷變化镐侯,外邊的進(jìn)度條也會(huì)根據(jù)人臉情況動(dòng)態(tài)變化,根據(jù)支付寶界面實(shí)現(xiàn)效果驶冒。

image

實(shí)現(xiàn)

這里用兩種方式實(shí)現(xiàn)一種繼承SurfaceView自定義苟翻,另一種是繼承View自定義,第一種方式將預(yù)覽視頻流一起繪制骗污,也可以作為一個(gè)蒙版覆蓋上面崇猫。

1、attrs.xml配置自定義屬性
<resources>
    <!--人臉識(shí)別界面-->
    <declare-styleable name="FaceView">
        <!--文本-->
        <attr name="tip_text" format="string"/>
        <!--顏色-->
        <attr name="tip_text_color" format="color"/>
        <!--文字大小-->
        <attr name="tip_text_size" format="dimension"/>

    </declare-styleable>
</resources>

這里只將提示框文字的大小需忿、顏色诅炉、內(nèi)容暴露出來(lái)蜡歹,其他控件顏色可自行修改

2、控件代碼實(shí)現(xiàn)
SurfaceView實(shí)現(xiàn)
public class FaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
    private final String TAG = "FaceView";
    private SurfaceHolder mSurfaceHolder;
    /**
     * 是否可以開(kāi)始繪制了
     */
    private boolean mStart = false;
    /**
     * 默認(rèn)中間圓的半徑從0開(kāi)始
     */
    private float currentRadius = 0;
    /**
     * 控件的寬度(默認(rèn))
     */
    private int mViewWidth = 400;
    /**
     * 控件高度
     */
    private int mViewHeight = 400;
    /**
     * 中心圓屏幕邊距
     */
    private int margin;
    /**
     * 圓圈畫(huà)筆
     */
    private Paint mPaint;
    /**
     * 提示文本
     */
    private String mTipText;
    /**
     * 提示文本顏色
     */
    private int mTipTextColor;
    /**
     * 提示文本顏色
     */
    private int mTipTextSize;
    /**
     * 內(nèi)圓半徑
     */
    private int mRadius;
    /**
     * 背景弧寬度
     */
    private float mBgArcWidth;

    /**
     * 圓心點(diǎn)坐標(biāo)
     */
    private Point mCenterPoint = new Point();
    /**
     * 圓弧邊界
     */
    private RectF mBgRectF = new RectF();

    /**
     * 開(kāi)始角度
     */
    private int mStartAngle = 105;

    /**
     * 結(jié)束角度
     */
    private int mEndAngle = 330;

    /**
     * 圓弧背景畫(huà)筆
     */
    private Paint mBgArcPaint;
    /**
     * 提示語(yǔ)畫(huà)筆
     */
    private Paint mTextPaint;

    /**
     * 圓弧畫(huà)筆
     */
    private Paint mArcPaint;
    /**
     * 漸變器
     */
    private SweepGradient mSweepGradient;

    /**
     * 是否開(kāi)始
     */
    private boolean isRunning = true;

    /**
     * 是否后退
     */
    private boolean isBack = false;
    /**
     * 繪制速度
     */
    private int speed = 5;

    /**
     * 設(shè)置默認(rèn)轉(zhuǎn)動(dòng)角度0
     */
    float currentAngle = 0;

    public FaceView(Context context) {
        this(context, null);
    }

    public FaceView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FaceView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //獲取xml里面的屬性值
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.FaceView);
        mTipText = array.getString(R.styleable.FaceView_tip_text);
        mTipTextColor = array.getColor(R.styleable.FaceView_tip_text_color, Color.WHITE);
        mTipTextSize = array.getDimensionPixelSize(R.styleable.FaceView_tip_text_size, ScreenUtils.sp2px(context, 12));
        array.recycle();
        Log.d(TAG, "FaceView構(gòu)造");
        initHolder(context);
    }

    /**
     * 初始化控件View
     */
    private void initHolder(Context context) {
        //獲得SurfaceHolder對(duì)象
        mSurfaceHolder = getHolder();
        //設(shè)置透明背景
        mSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
        //添加回調(diào)
        mSurfaceHolder.addCallback(this);
        //顯示頂層
        setZOrderOnTop(true);
        //防止遮住控件
        setZOrderMediaOverlay(true);
        //屏蔽界面焦點(diǎn)
        setFocusable(true);
        //保持屏幕長(zhǎng)亮
        setKeepScreenOn(true);

        //初始化值
        margin = ScreenUtils.dp2px(context, 60);
        mBgArcWidth = ScreenUtils.dp2px(context, 5);

        //初始化畫(huà)筆
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(getResources().getColor(R.color.colorAccent));
        mPaint.setStyle(Paint.Style.FILL);

        //繪制文字畫(huà)筆
        mTextPaint = new Paint();
        mTextPaint.setStyle(Paint.Style.FILL);
        mTextPaint.setStrokeWidth(8);
        mTextPaint.setColor(mTipTextColor);
        mTextPaint.setTextSize(mTipTextSize);
        mTextPaint.setTextAlign(Paint.Align.CENTER);

        // 圓弧背景
        mBgArcPaint = new Paint();
        mBgArcPaint.setAntiAlias(true);
        mBgArcPaint.setColor(getResources().getColor(R.color.circleBg));
        mBgArcPaint.setStyle(Paint.Style.STROKE);
        mBgArcPaint.setStrokeWidth(mBgArcWidth);
        mBgArcPaint.setStrokeCap(Paint.Cap.ROUND);

        // 圓弧
        mArcPaint = new Paint();
        mArcPaint.setAntiAlias(true);
        mArcPaint.setStyle(Paint.Style.STROKE);
        mArcPaint.setStrokeWidth(mBgArcWidth);
        mArcPaint.setStrokeCap(Paint.Cap.ROUND);

        //開(kāi)啟線程檢測(cè)
        new Thread(this).start();
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        mStart = true;
        Log.d(TAG, "surfaceCreated()");
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
        Log.d(TAG, "surfaceChanged()");
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        mStart = false;
        Log.d(TAG, "surfaceDestroyed()");
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //測(cè)量view的寬度
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        if (widthMode == MeasureSpec.EXACTLY) {
            mViewWidth = MeasureSpec.getSize(widthMeasureSpec);
        }

        //測(cè)量view的高度
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (heightMode == MeasureSpec.EXACTLY) {
            mViewHeight = MeasureSpec.getSize(heightMeasureSpec);
        }

        setMeasuredDimension(mViewWidth, mViewHeight);
        Log.d(TAG, "onMeasure  mViewWidth : " + mViewWidth + "  mViewHeight : " + mViewHeight);

        //獲取圓的相關(guān)參數(shù)
        mCenterPoint.x = mViewWidth / 2;
        mCenterPoint.y = mViewHeight / 2;

        //外環(huán)圓的半徑
        mRadius = mCenterPoint.x - margin;

        //繪制背景圓弧的邊界
        mBgRectF.left = mCenterPoint.x - mRadius - mBgArcWidth / 2;
        mBgRectF.top = mCenterPoint.y - mRadius - mBgArcWidth / 2;
        mBgRectF.right = mCenterPoint.x + mRadius + mBgArcWidth / 2;
        mBgRectF.bottom = mCenterPoint.y + mRadius + mBgArcWidth / 2;

        //進(jìn)度條顏色 -mStartAngle將位置便宜到原處
        mSweepGradient = new SweepGradient(mCenterPoint.x - mStartAngle, mCenterPoint.y - mStartAngle, getResources().getColor(R.color.colorPrimary), getResources().getColor(R.color.colorPrimaryDark));
    }

    @Override
    public void run() {
        //循環(huán)繪制畫(huà)面內(nèi)容
        while (true) {
            if (mStart) {
                drawView();
            }
        }
    }

    private void drawView() {
        Canvas canvas = null;
        try {
            //獲得canvas對(duì)象
            canvas = mSurfaceHolder.lockCanvas();
            //清除畫(huà)布上面里面的內(nèi)容
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            //繪制畫(huà)布內(nèi)容
            drawContent(canvas);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (canvas != null) {
                //釋放canvas鎖涕烧,并且顯示視圖
                mSurfaceHolder.unlockCanvasAndPost(canvas);
            }
        }
    }

    /**
     * 跟新提示信息
     *
     * @param title
     */
    public void updateTipsInfo(String title) {
        mTipText = title;
    }

    private void drawContent(Canvas canvas) {
        //防止save()和restore()方法代碼之后對(duì)Canvas執(zhí)行的操作月而,繼續(xù)對(duì)后續(xù)的繪制會(huì)產(chǎn)生影響
        canvas.save();
        //先畫(huà)提示語(yǔ)
        drawHintText(canvas);
        //繪制正方形的框內(nèi)類(lèi)似人臉識(shí)別
//        drawFaceRectTest(canvas);
        //繪制人臉識(shí)別部分
        drawFaceCircle(canvas);
        //畫(huà)外邊進(jìn)度條
        drawRoundProgress(canvas);
        canvas.restore();
    }

    private void drawFaceCircle(Canvas canvas) {
        // 圓形,放大效果
        currentRadius += 20;
        if (currentRadius > mRadius)
            currentRadius = mRadius;

        //設(shè)置畫(huà)板樣式
        Path path = new Path();
        //以(400,200)為圓心议纯,半徑為100繪制圓 指創(chuàng)建順時(shí)針?lè)较虻木匦温窂?        path.addCircle(mCenterPoint.x, mCenterPoint.y, currentRadius, Path.Direction.CW);
        // 是A形狀中不同于B的部分顯示出來(lái)
        canvas.clipPath(path, Region.Op.DIFFERENCE);
        // 半透明背景效果
        canvas.clipRect(0, 0, mViewWidth, mViewHeight);
        //繪制背景顏色
        canvas.drawColor(getResources().getColor(R.color.viewBgWhite));
    }

    /**
     * 繪制人臉識(shí)別界面進(jìn)度條
     *
     * @param canvas canvas
     */
    private void drawRoundProgress(Canvas canvas) {
        // 逆時(shí)針旋轉(zhuǎn)105度
        canvas.rotate(mStartAngle, mCenterPoint.x, mCenterPoint.y);
        // 設(shè)置圓環(huán)背景
        canvas.drawArc(mBgRectF, 0, mEndAngle, false, mBgArcPaint);
        //判斷是否正在運(yùn)行
        if (isRunning) {
            if (isBack) {
                currentAngle -= speed;
                if (currentAngle <= 0)
                    currentAngle = 0;
            } else {
                currentAngle += speed;
                if (currentAngle >= mEndAngle)
                    currentAngle = mEndAngle;
            }
        }
        // 設(shè)置漸變顏色
        mArcPaint.setShader(mSweepGradient);
        canvas.drawArc(mBgRectF, 0, currentAngle, false, mArcPaint);
    }

    /**
     * 從頭位置開(kāi)始動(dòng)畫(huà)
     */
    public void resetPositionStart() {
        currentAngle = 0;
        isBack = false;
    }

    /**
     * 動(dòng)畫(huà)直接完成
     */
    public void finnishAnimator() {
        currentAngle = mEndAngle;
        isBack = false;
    }

    /**
     * 停止動(dòng)畫(huà)
     */
    public void pauseAnimator() {
        isRunning = false;
    }

    /**
     * 開(kāi)始動(dòng)畫(huà)
     */
    public void startAnimator() {
        isRunning = true;
    }

    /**
     * 動(dòng)畫(huà)回退
     */
    public void backAnimator() {
        isRunning = true;
        isBack = true;
    }

    /**
     * 動(dòng)畫(huà)前進(jìn)
     */
    public void forwardAnimator() {
        isRunning = true;
        isBack = false;
    }

    /**
     * 繪制人臉識(shí)別提示
     *
     * @param canvas canvas
     */
    private void drawHintText(Canvas canvas) {
        //圓視圖寬度 (屏幕減去兩邊距離)
        int cameraWidth = mViewWidth - 2 * margin;
        //x軸起點(diǎn)(文字背景起點(diǎn))
        int x = margin;
        //寬度(提示框背景寬度)
        int width = cameraWidth;
        //y軸起點(diǎn)
        int y = (int) (mCenterPoint.y - mRadius);
        //提示框背景高度
        int height = cameraWidth / 4;
        Rect rect = new Rect(x, y, x + width, y + height);
        canvas.drawRect(rect, mPaint);

        //計(jì)算baseline
        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        float distance = (fontMetrics.bottom - fontMetrics.top) / 4;
        float baseline = rect.centerY() + distance;
        canvas.drawText(mTipText, rect.centerX(), baseline, mTextPaint);
    }

    /**
     * 繪制人臉識(shí)別矩形區(qū)域
     *
     * @param canvas canvas
     */
    private void drawFaceRectTest(Canvas canvas) {
        int cameraWidth = mViewWidth - 2 * margin;
        int x = margin + cameraWidth / 6;
        int width = cameraWidth * 2 / 3;
        int y = mCenterPoint.x + (width / 2);
        int height = width;
        Rect rect = new Rect(x, y, x + width, y + height);
        mPaint.setColor(Color.GREEN);
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawRect(rect, mPaint);
    }
}

當(dāng)獲取canvas 實(shí)例后父款,可以將視頻流一樣畫(huà)置SurfaceView上面

View實(shí)現(xiàn)

步驟:
1、自定義View的屬性
2瞻凤、在自定義View的構(gòu)造方法中獲取View屬性值
3铛漓、重寫(xiě)onMeasure(int,int)方法
4、重寫(xiě)onDraw(Canvas canvas)方法

public class FaceView2 extends View implements Runnable {
    private final String TAG = "FaceView";
    /**
     * 是否可以開(kāi)始繪制了
     */
    private boolean mStart = false;
    /**
     * 默認(rèn)中間圓的半徑從0開(kāi)始
     */
    private float currentRadius = 0;
    /**
     * 控件的寬度(默認(rèn))
     */
    private int mViewWidth = 400;
    /**
     * 控件高度
     */
    private int mViewHeight = 400;
    /**
     * 中心圓屏幕邊距
     */
    private int margin;
    /**
     * 圓圈畫(huà)筆
     */
    private Paint mPaint;
    /**
     * 提示文本
     */
    private String mTipText;
    /**
     * 提示文本顏色
     */
    private int mTipTextColor;
    /**
     * 提示文本顏色
     */
    private int mTipTextSize;
    /**
     * 內(nèi)圓半徑
     */
    private int mRadius;
    /**
     * 背景弧寬度
     */
    private float mBgArcWidth;

    /**
     * 圓心點(diǎn)坐標(biāo)
     */
    private Point mCenterPoint = new Point();
    /**
     * 圓弧邊界
     */
    private RectF mBgRectF = new RectF();

    /**
     * 開(kāi)始角度
     */
    private int mStartAngle = 105;

    /**
     * 結(jié)束角度
     */
    private int mEndAngle = 330;

    /**
     * 設(shè)置默認(rèn)轉(zhuǎn)動(dòng)角度0
     */
    float currentAngle = 0;

    /**
     * 圓弧背景畫(huà)筆
     */
    private Paint mBgArcPaint;
    /**
     * 提示語(yǔ)畫(huà)筆
     */
    private Paint mTextPaint;

    /**
     * 圓弧畫(huà)筆
     */
    private Paint mArcPaint;
    /**
     * 漸變器
     */
    private SweepGradient mSweepGradient;

    /**
     * 是否開(kāi)始
     */
    private boolean isRunning = true;

    /**
     * 是否后退
     */
    private boolean isBack = false;
    /**
     * 繪制速度
     */
    private int speed = 5;

    public FaceView2(Context context) {
        this(context, null);
    }

    public FaceView2(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FaceView2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //獲取xml里面的屬性值
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.FaceView);
        mTipText = array.getString(R.styleable.FaceView_tip_text);
        mTipTextColor = array.getColor(R.styleable.FaceView_tip_text_color, Color.WHITE);
        mTipTextSize = array.getDimensionPixelSize(R.styleable.FaceView_tip_text_size, ScreenUtils.sp2px(context, 12));
        array.recycle();

        Log.d(TAG, "FaceView構(gòu)造");
        initPaint(context);
    }

    /**
     * 初始化控件View
     */
    private void initPaint(Context context) {
        //獲取界面焦點(diǎn)
        setFocusable(true);
        //保持屏幕長(zhǎng)亮
        setKeepScreenOn(true);

        //初始化值
        margin = ScreenUtils.dp2px(context, 60);
        mBgArcWidth = ScreenUtils.dp2px(context, 5);

        //初始化畫(huà)筆
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(getResources().getColor(R.color.colorAccent));
        mPaint.setStyle(Paint.Style.FILL);

        //繪制文字畫(huà)筆
        mTextPaint = new Paint();
        mTextPaint.setStyle(Paint.Style.FILL);
        mTextPaint.setStrokeWidth(8);
        mTextPaint.setColor(mTipTextColor);
        mTextPaint.setTextSize(mTipTextSize);
        mTextPaint.setTextAlign(Paint.Align.CENTER);

        // 圓弧背景
        mBgArcPaint = new Paint();
        mBgArcPaint.setAntiAlias(true);
        mBgArcPaint.setColor(getResources().getColor(R.color.circleBg));
        mBgArcPaint.setStyle(Paint.Style.STROKE);
        mBgArcPaint.setStrokeWidth(mBgArcWidth);
        mBgArcPaint.setStrokeCap(Paint.Cap.ROUND);

        // 圓弧
        mArcPaint = new Paint();
        mArcPaint.setAntiAlias(true);
        mArcPaint.setStyle(Paint.Style.STROKE);
        mArcPaint.setStrokeWidth(mBgArcWidth);
        mArcPaint.setStrokeCap(Paint.Cap.ROUND);

        //開(kāi)啟線程檢測(cè)
        new Thread(this).start();
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //測(cè)量view的寬度
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        if (widthMode == MeasureSpec.EXACTLY) {
            mViewWidth = MeasureSpec.getSize(widthMeasureSpec);
        }

        //測(cè)量view的高度
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (heightMode == MeasureSpec.EXACTLY) {
            mViewHeight = MeasureSpec.getSize(heightMeasureSpec);
        }

        setMeasuredDimension(mViewWidth, mViewHeight);
        Log.d(TAG, "onMeasure  mViewWidth : " + mViewWidth + "  mViewHeight : " + mViewHeight);

        //獲取圓的相關(guān)參數(shù)
        mCenterPoint.x = mViewWidth / 2;
        mCenterPoint.y = mViewHeight / 2;

        //外環(huán)圓的半徑
        mRadius = mCenterPoint.x - margin;

        //繪制背景圓弧的邊界
        mBgRectF.left = mCenterPoint.x - mRadius - mBgArcWidth / 2;
        mBgRectF.top = mCenterPoint.y - mRadius - mBgArcWidth / 2;
        mBgRectF.right = mCenterPoint.x + mRadius + mBgArcWidth / 2;
        mBgRectF.bottom = mCenterPoint.y + mRadius + mBgArcWidth / 2;

        //進(jìn)度條顏色 -mStartAngle/2將位置到原處
        mSweepGradient = new SweepGradient(mCenterPoint.x - mStartAngle / 2,
                mCenterPoint.y - mStartAngle / 2, getResources().getColor(R.color.colorPrimary), getResources().getColor(R.color.colorPrimaryDark));
    }

    @Override
    protected void onVisibilityChanged(View changedView, int visibility) {
        super.onVisibilityChanged(changedView, visibility);
        mStart = (visibility == VISIBLE);
    }

    @Override
    public void run() {
        //循環(huán)繪制畫(huà)面內(nèi)容
        while (true) {
            if (mStart) {
                try {
                    changeValue();
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 動(dòng)態(tài)檢測(cè)改變值
     */
    private void changeValue() {
        // 內(nèi)圓形鲫构,放大效果
        currentRadius += 20;
        if (currentRadius > mRadius)
            currentRadius = mRadius;

        //外部圈的動(dòng)畫(huà)效果
        if (isRunning) {
            if (isBack) {
                currentAngle -= speed;
                if (currentAngle <= 0)
                    currentAngle = 0;
            } else {
                currentAngle += speed;
                if (currentAngle >= mEndAngle)
                    currentAngle = mEndAngle;
            }
        }
        //重繪view
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //繪制畫(huà)布內(nèi)容
        drawContent(canvas);
    }

    /**
     * 跟新提示信息
     *
     * @param title
     */
    public void updateTipsInfo(String title) {
        mTipText = title;
    }

    private void drawContent(Canvas canvas) {
        //防止save()和restore()方法代碼之后對(duì)Canvas執(zhí)行的操作浓恶,繼續(xù)對(duì)后續(xù)的繪制會(huì)產(chǎn)生影響
        canvas.save();
        //先畫(huà)提示語(yǔ)
        drawHintText(canvas);
        //繪制正方形的框內(nèi)類(lèi)似人臉識(shí)別
//        drawFaceRectTest(canvas);
        //繪制人臉識(shí)別部分
        drawFaceCircle(canvas);
        //畫(huà)外邊進(jìn)度條
        drawRoundProgress(canvas);
        canvas.restore();
    }

    private void drawFaceCircle(Canvas canvas) {
        //設(shè)置畫(huà)板樣式
        Path path = new Path();
        //以(400,200)為圓心,半徑為100繪制圓 指創(chuàng)建順時(shí)針?lè)较虻木匦温窂?        path.addCircle(mCenterPoint.x, mCenterPoint.y, currentRadius, Path.Direction.CW);
        // 是A形狀中不同于B的部分顯示出來(lái)
        canvas.clipPath(path, Region.Op.DIFFERENCE);
        // 半透明背景效果
        canvas.clipRect(0, 0, mViewWidth, mViewHeight);
        //繪制背景顏色
        canvas.drawColor(getResources().getColor(R.color.viewBgWhite));
    }

    /**
     * 繪制人臉識(shí)別界面進(jìn)度條
     *
     * @param canvas canvas
     */
    private void drawRoundProgress(Canvas canvas) {
        // 逆時(shí)針旋轉(zhuǎn)105度
        canvas.rotate(mStartAngle, mCenterPoint.x, mCenterPoint.y);
        // 設(shè)置圓環(huán)背景
        canvas.drawArc(mBgRectF, 0, mEndAngle, false, mBgArcPaint);
        // 設(shè)置漸變顏色
        mArcPaint.setShader(mSweepGradient);
        canvas.drawArc(mBgRectF, 0, currentAngle, false, mArcPaint);
    }

    /**
     * 從頭位置開(kāi)始動(dòng)畫(huà)
     */
    public void resetPositionStart() {
        currentAngle = 0;
        isBack = false;
    }

    /**
     * 動(dòng)畫(huà)直接完成
     */
    public void finnishAnimator() {
        currentAngle = mEndAngle;
        isBack = false;
    }

    /**
     * 停止動(dòng)畫(huà)
     */
    public void pauseAnimator() {
        isRunning = false;
    }

    /**
     * 開(kāi)始動(dòng)畫(huà)
     */
    public void startAnimator() {
        isRunning = true;
    }

    /**
     * 動(dòng)畫(huà)回退
     */
    public void backAnimator() {
        isRunning = true;
        isBack = true;
    }

    /**
     * 動(dòng)畫(huà)前進(jìn)
     */
    public void forwardAnimator() {
        isRunning = true;
        isBack = false;
    }

    /**
     * 繪制人臉識(shí)別提示
     *
     * @param canvas canvas
     */
    private void drawHintText(Canvas canvas) {
        //圓視圖寬度 (屏幕減去兩邊距離)
        int cameraWidth = mViewWidth - 2 * margin;
        //x軸起點(diǎn)(文字背景起點(diǎn))
        int x = margin;
        //寬度(提示框背景寬度)
        int width = cameraWidth;
        //y軸起點(diǎn)
        int y = (int) (mCenterPoint.y - mRadius);
        //提示框背景高度
        int height = cameraWidth / 4;
        Rect rect = new Rect(x, y, x + width, y + height);
        canvas.drawRect(rect, mPaint);

        //計(jì)算baseline
        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        float distance = (fontMetrics.bottom - fontMetrics.top) / 4;
        float baseline = rect.centerY() + distance;
        canvas.drawText(mTipText, rect.centerX(), baseline, mTextPaint);
    }

    /**
     * 繪制人臉識(shí)別矩形區(qū)域
     *
     * @param canvas canvas
     */
    private void drawFaceRectTest(Canvas canvas) {
        int cameraWidth = mViewWidth - 2 * margin;
        int x = margin + cameraWidth / 6;
        int width = cameraWidth * 2 / 3;
        int y = mCenterPoint.x + (width / 2);
        int height = width;
        Rect rect = new Rect(x, y, x + width, y + height);
        mPaint.setColor(Color.GREEN);
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawRect(rect, mPaint);
    }
}

這里值得注意的是這幾個(gè)方法结笨,使用的時(shí)候可以根據(jù)實(shí)際調(diào)用這幾個(gè)動(dòng)畫(huà)包晰。

 /**
     * 從頭位置開(kāi)始動(dòng)畫(huà)
     */
    public void resetPositionStart() {
        currentAngle = 0;
        isBack = false;
    }

    /**
     * 動(dòng)畫(huà)直接完成
     */
    public void finnishAnimator() {
        currentAngle = mEndAngle;
        isBack = false;
    }

    /**
     * 停止動(dòng)畫(huà)
     */
    public void pauseAnimator() {
        isRunning = false;
    }

    /**
     * 開(kāi)始動(dòng)畫(huà)
     */
    public void startAnimator() {
        isRunning = true;
    }

    /**
     * 動(dòng)畫(huà)回退
     */
    public void backAnimator() {
        isRunning = true;
        isBack = true;
    }

    /**
     * 動(dòng)畫(huà)前進(jìn)
     */
    public void forwardAnimator() {
        isRunning = true;
        isBack = false;
    }

3、使用布局UI
<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/cl_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".PreviewActivity">

    <!--相機(jī)預(yù)覽顯示-->
    <com.demo.facerecognition.view.FaceView
        android:id="@+id/fv_title"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:tip_text="請(qǐng)閉眼"
        app:tip_text_size="20sp"/>

    <!--關(guān)閉按鈕-->
    <android.support.v7.widget.AppCompatImageView
        android:id="@+id/iv_close"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentLeft="true"
        android:padding="5dp"
        android:src="@drawable/iv_svg_close"/>

    <!--界面提示信息-->
    <TextView
        android:id="@+id/tv_tip"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/iv_close"
        android:layout_marginTop="10dp"
        android:text="@string/face_tip"
        android:textColor="@color/textColor"
        android:textSize="20sp"
        android:textStyle="bold"
        app:layout_constraintLeft_toRightOf="parent"
        app:layout_constraintRight_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/iv_close"/>
</android.support.constraint.ConstraintLayout>

4炕吸、Activity使用
public class PreviewActivity extends AppCompatActivity {
    private final String TAG = "PreviewActivity";
    private Camera camera;
    private boolean isPreview = false;

    static final String[] PERMISSION = new String[]{
            //獲取照相機(jī)權(quán)限
            Manifest.permission.CAMERA,
    };

    /**
     * 設(shè)置Android6.0的權(quán)限申請(qǐng)
     */
    private void setPermissions() {
        if (ContextCompat.checkSelfPermission(PreviewActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            //Android 6.0申請(qǐng)權(quán)限
            ActivityCompat.requestPermissions(this, PERMISSION, 1);
        } else {
            Log.i(TAG, "權(quán)限申請(qǐng)ok");
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_preview);
        //初始化布局
        ConstraintLayout constraintLayout = findViewById(R.id.cl_root);
        final FaceView faceView = findViewById(R.id.fv_title);
        ImageView imageView = findViewById(R.id.iv_close);
        //申請(qǐng)手機(jī)的權(quán)限
        setPermissions();

        imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int i = (int) (1 + Math.random() * 4);
                switch (i) {
                    case 1:
                        faceView.resetPositionStart();
                        faceView.updateTipsInfo("沒(méi)有檢測(cè)人臉");
                        break;
                    case 2:
                        faceView.backAnimator();
                        faceView.updateTipsInfo("請(qǐng)露正臉");
                        break;
                    case 3:
                        faceView.pauseAnimator();
                        faceView.updateTipsInfo(" 眨眨眼");
                        break;
                    case 4:
                        faceView.startAnimator();
                        faceView.updateTipsInfo("離近一點(diǎn)");
                        break;
                    default:
                        break;
                }
            }
        });

        //添加布局
        SurfaceView mSurfaceView = new SurfaceView(this);
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT);
        params.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
        mSurfaceView.setLayoutParams(params);
        constraintLayout.addView(mSurfaceView, 0);
        //得到getHolder實(shí)例
        SurfaceHolder mSurfaceHolder = mSurfaceView.getHolder();
        mSurfaceHolder.setFormat(PixelFormat.TRANSPARENT);
        // 添加 Surface 的 callback 接口
        mSurfaceHolder.addCallback(mSurfaceCallback);
    }

    private SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(SurfaceHolder surfaceHolder) {
            try {
                //打開(kāi)硬件攝像頭伐憾,這里導(dǎo)包得時(shí)候一定要注意是android.hardware.Camera
                // Camera,open() 默認(rèn)返回的后置攝像頭信息
                //設(shè)置角度,此處 CameraId 我默認(rèn) 為 1 (前置)
                if (Camera.getNumberOfCameras() > 1) {
                    camera = Camera.open(1);
                } else {
                    camera = Camera.open(0);
                }
                //設(shè)置相機(jī)角度
                camera.setDisplayOrientation(90);
                //通過(guò)SurfaceView顯示取景畫(huà)面
                camera.setPreviewDisplay(surfaceHolder);
                //開(kāi)始預(yù)覽
                camera.startPreview();
                //設(shè)置是否預(yù)覽參數(shù)為真
                isPreview = true;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
            if (camera != null) {
                if (isPreview) {//正在預(yù)覽
                    try {
                        camera.stopPreview();
                        camera.release();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    };

    @Override
    protected void onDestroy() {
        if (camera != null) {
            if (isPreview) {//正在預(yù)覽
                try {
                    camera.stopPreview();
                    camera.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        super.onDestroy();
    }
}

視頻流用的前置攝像頭赫模,這里的提示和動(dòng)畫(huà)也是隨機(jī)树肃,項(xiàng)目中可以根據(jù)真實(shí)情況展示。

image

【注意】自定義view應(yīng)該注意前后繪制順序和疊加模式

總結(jié)

項(xiàng)目中用到就簡(jiǎn)單抽了一下瀑罗,有遇到類(lèi)似需求可以參考一下胸嘴,最近在學(xué)Flutter,學(xué)的差不多了寫(xiě)個(gè)Flutter版本在更新一下斩祭。劣像。。


聲明:

轉(zhuǎn)載請(qǐng)注明出處

作者:戎碼蟲(chóng)
鏈接:http://www.reibang.com/p/625266dfab1d

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末摧玫,一起剝皮案震驚了整個(gè)濱河市耳奕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌诬像,老刑警劉巖屋群,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異坏挠,居然都是意外死亡芍躏,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)癞揉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)纸肉,“玉大人溺欧,你說(shuō)我怎么就攤上這事“胤荆” “怎么了姐刁?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)烦味。 經(jīng)常有香客問(wèn)我聂使,道長(zhǎng),這世上最難降的妖魔是什么谬俄? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任柏靶,我火速辦了婚禮,結(jié)果婚禮上溃论,老公的妹妹穿的比我還像新娘屎蜓。我一直安慰自己,他們只是感情好钥勋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布炬转。 她就那樣靜靜地躺著,像睡著了一般算灸。 火紅的嫁衣襯著肌膚如雪扼劈。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天菲驴,我揣著相機(jī)與錄音荐吵,去河邊找鬼。 笑死赊瞬,一個(gè)胖子當(dāng)著我的面吹牛先煎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播森逮,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼榨婆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼磁携!你這毒婦竟也來(lái)了褒侧?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤谊迄,失蹤者是張志新(化名)和其女友劉穎闷供,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體统诺,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡歪脏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了粮呢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片婿失。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡钞艇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出豪硅,到底是詐尸還是另有隱情哩照,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布懒浮,位于F島的核電站飘弧,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏砚著。R本人自食惡果不足惜次伶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望稽穆。 院中可真熱鬧冠王,春花似錦、人聲如沸舌镶。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)乎折。三九已至绒疗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間骂澄,已是汗流浹背吓蘑。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坟冲,地道東北人磨镶。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像健提,于是被迫代替她去往敵國(guó)和親琳猫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355