Android 仿支付宝人脸识别UI

前言

前一段时间做人脸识别,UI设计师也是惯用“cv”大法,做了一个仿支付宝的UI,这里就总结一下自定义仿支付宝人脸识别界面UI,人脸识别产品为公司产品不便于贴出来,这里就用前置摄像头画面代替。

需求

首先我们看看支付宝人脸识别UI界面

界面1

界面2

需求分析:
根据人脸的情况中间提示语不断变化,外边的进度条也会根据人脸情况动态变化,根据支付宝界面实现效果。


实现效果

实现

这里用两种方式实现一种继承SurfaceView自定义,另一种是继承View自定义,第一种方式将预览视频流一起绘制,也可以作为一个蒙版覆盖上面。

1、attrs.xml配置自定义属性
<resources>
    <!--人脸识别界面-->
    <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>

这里只将提示框文字的大小、颜色、内容暴露出来,其他控件颜色可自行修改

2、控件代码实现
SurfaceView实现
public class FaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
    private final String TAG = "FaceView";
    private SurfaceHolder mSurfaceHolder;
    /**
     * 是否可以开始绘制了
     */
    private boolean mStart = false;
    /**
     * 是否结束
     */
    private volatile boolean mFinsh = false;
    /**
     * 默认中间圆的半径从0开始
     */
    private float currentRadius = 0;
    /**
     * 控件的宽度(默认)
     */
    private int mViewWidth = 400;
    /**
     * 控件高度
     */
    private int mViewHeight = 400;
    /**
     * 中心圆屏幕边距
     */
    private int margin;
    /**
     * 提示文本画笔
     */
    private Paint mTextBgPaint;
    /**
     * 提示文本
     */
    private String mTipText;
    /**
     * 提示文本颜色
     */
    private int mTipTextColor;
    /**
     * 提示文本颜色
     */
    private int mTipTextSize;
    /**
     * 内圆半径
     */
    private int mRadius;
    /**
     * 背景弧宽度
     */
    private float mBgArcWidth;

    /**
     * 圆心点坐标
     */
    private Point mCenterPoint = new Point();
    /**
     * 圆弧边界
     */
    private RectF mBgRectF = new RectF();

    /**
     * 开始角度
     */
    private int mStartAngle = 105;

    /**
     * 结束角度
     */
    private int mEndAngle = 330;

    /**
     * 圆弧背景画笔
     */
    private Paint mBgArcPaint;
    /**
     * 提示语画笔
     */
    private Paint mTextPaint;

    /**
     * 圆弧画笔
     */
    private Paint mArcPaint;
    /**
     * 渐变器
     */
    private SweepGradient mSweepGradient;

    /**
     * 是否开始
     */
    private boolean isRunning = true;

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

    /**
     * 设置默认转动角度0
     */
    float currentAngle = 0;

    /**
     * 声明
     */
    Canvas canvas = null;

    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构造");
        initHolder(context);
    }

    /**
     * 初始化控件View
     */
    private void initHolder(Context context) {
        //获得SurfaceHolder对象
        mSurfaceHolder = getHolder();
        //设置透明背景
        mSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
        //添加回调
        mSurfaceHolder.addCallback(this);
        //显示顶层
        setZOrderOnTop(true);
        //防止遮住控件
        setZOrderMediaOverlay(true);
        //屏蔽界面焦点
        setFocusable(true);
        //保持屏幕长亮
        setKeepScreenOn(true);

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

        //绘制文字画笔
        mTextPaint = new Paint();
        mTextPaint.setStyle(Paint.Style.FILL);
        mTextPaint.setStrokeWidth(8);
        mTextPaint.setColor(mTipTextColor);
        mTextPaint.setTextSize(mTipTextSize);
        mTextPaint.setTextAlign(Paint.Align.CENTER);

        //初始化文字背景画笔
        mTextBgPaint = new Paint();
        mTextBgPaint.setAntiAlias(true);
        mTextBgPaint.setColor(getResources().getColor(R.color.colorAccent));
        mTextBgPaint.setStyle(Paint.Style.FILL);

        // 圆弧背景
        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);

        //开启线程检测
        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) {
        //测量view的宽度
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        if (widthMode == MeasureSpec.EXACTLY) {
            mViewWidth = MeasureSpec.getSize(widthMeasureSpec);
        }

        //测量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);

        //获取圆的相关参数
        mCenterPoint.x = mViewWidth / 2;
        mCenterPoint.y = mViewHeight / 2;

        //外环圆的半径
        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;

        //进度条颜色 -mStartAngle将位置便宜到原处
        mSweepGradient = new SweepGradient(mCenterPoint.x - mStartAngle, mCenterPoint.y - mStartAngle, getResources().getColor(R.color.colorPrimary), getResources().getColor(R.color.colorPrimaryDark));
    }

    @Override
    public void run() {
        //循环绘制画面内容
        while (!mFinsh) {
            if (mStart) {
                drawView();
            }
        }
    }


    private void drawView() {
        try {
            //获得canvas对象
            canvas = mSurfaceHolder.lockCanvas();
            if (canvas != null) {
                //清除画布上面里面的内容
                canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
                //绘制画布内容
                drawContent(canvas);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (canvas != null) {
                //释放canvas锁,并且显示视图
                mSurfaceHolder.unlockCanvasAndPost(canvas);
            }
        }
    }

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

    private void drawContent(Canvas canvas) {
        //防止save()和restore()方法代码之后对Canvas执行的操作,继续对后续的绘制会产生影响
        canvas.save();
        //先画提示语
        drawHintText(canvas);
        //绘制正方形的框内类似人脸识别
//        drawFaceRectTest(canvas);
        //绘制人脸识别部分
        drawFaceCircle(canvas);
        //画外边进度条
        drawRoundProgress(canvas);
        canvas.restore();
    }

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

        //设置画板样式
        Path path = new Path();
        //以(400,200)为圆心,半径为100绘制圆 指创建顺时针方向的矩形路径
        path.addCircle(mCenterPoint.x, mCenterPoint.y, currentRadius, Path.Direction.CW);
        //是A形状中不同于B的部分显示出来
        canvas.clipPath(path, Region.Op.DIFFERENCE);
        // 半透明背景效果
        canvas.clipRect(0, 0, mViewWidth, mViewHeight);
        //绘制背景颜色
        canvas.drawColor(getResources().getColor(R.color.viewBgWhite));
    }

    /**
     * 绘制人脸识别界面进度条
     *
     * @param canvas canvas
     */
    private void drawRoundProgress(Canvas canvas) {
        // 逆时针旋转105度
        canvas.rotate(mStartAngle, mCenterPoint.x, mCenterPoint.y);
        // 设置圆环背景
        canvas.drawArc(mBgRectF, 0, mEndAngle, false, mBgArcPaint);
        //判断是否正在运行
        if (isRunning) {
            if (isBack) {
                currentAngle -= speed;
                if (currentAngle <= 0)
                    currentAngle = 0;
            } else {
                currentAngle += speed;
                if (currentAngle >= mEndAngle)
                    currentAngle = mEndAngle;
            }
        }
        // 设置渐变颜色
        mArcPaint.setShader(mSweepGradient);
        canvas.drawArc(mBgRectF, 0, currentAngle, false, mArcPaint);
    }

    /**
     * 从头位置开始动画
     */
    public void resetPositionStart() {
        currentAngle = 0;
        isBack = false;
    }

    /**
     * 动画直接完成
     */
    public void finnishAnimator() {
        currentAngle = mEndAngle;
        isBack = false;
    }

    /**
     * 停止动画
     */
    public void pauseAnimator() {
        isRunning = false;
    }

    /**
     * 开始动画
     */
    public void startAnimator() {
        isRunning = true;
    }

    /**
     * 动画回退
     */
    public void backAnimator() {
        isRunning = true;
        isBack = true;
    }

    /**
     * 动画前进
     */
    public void forwardAnimator() {
        isRunning = true;
        isBack = false;
    }

    /**
     * 销毁视图,释放资源
     */
    public void destroyView() {
        //停止运行
        mFinsh = true;
        mStart = false;
        isRunning = false;
        isBack = false;
    }


    /**
     * 绘制人脸识别提示
     *
     * @param canvas canvas
     */
    private void drawHintText(Canvas canvas) {
        //圆视图宽度 (屏幕减去两边距离)
        int cameraWidth = mViewWidth - 2 * margin;
        //x轴起点(文字背景起点)
        int x = margin;
        //宽度(提示框背景宽度)
        int width = cameraWidth;
        //y轴起点
        int y = (int) (mCenterPoint.y - mRadius);
        //提示框背景高度
        int height = cameraWidth / 4;
        Rect rect = new Rect(x, y, x + width, y + height);
        canvas.drawRect(rect, mTextBgPaint);

        //计算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);
    }

    /**
     * 绘制人脸识别矩形区域
     *
     * @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);
        mTextBgPaint.setColor(Color.GREEN);
        mTextBgPaint.setStyle(Paint.Style.STROKE);
        canvas.drawRect(rect, mTextBgPaint);
    }
}

当获取canvas 实例后,可以将视频流一样画置SurfaceView上面

View实现

步骤:
1、自定义View的属性
2、在自定义View的构造方法中获取View属性值
3、重写onMeasure(int,int)方法
4、重写onDraw(Canvas canvas)方法

public class FaceView2 extends View implements Runnable {
    private final String TAG = "FaceView";
    /**
     * 是否可以开始绘制了
     */
    private boolean mStart = false;
    /**
     * 是否结束
     */
    private volatile boolean mFinsh = false;
    /**
     * 默认中间圆的半径从0开始
     */
    private float currentRadius = 0;
    /**
     * 控件的宽度(默认)
     */
    private int mViewWidth = 400;
    /**
     * 控件高度
     */
    private int mViewHeight = 400;
    /**
     * 中心圆屏幕边距
     */
    private int margin;
    /**
     * 圆圈画笔
     */
    private Paint mPaint;
    /**
     * 提示文本
     */
    private String mTipText;
    /**
     * 提示文本颜色
     */
    private int mTipTextColor;
    /**
     * 提示文本颜色
     */
    private int mTipTextSize;
    /**
     * 内圆半径
     */
    private int mRadius;
    /**
     * 背景弧宽度
     */
    private float mBgArcWidth;

    /**
     * 圆心点坐标
     */
    private Point mCenterPoint = new Point();
    /**
     * 圆弧边界
     */
    private RectF mBgRectF = new RectF();

    /**
     * 开始角度
     */
    private int mStartAngle = 105;

    /**
     * 结束角度
     */
    private int mEndAngle = 330;

    /**
     * 设置默认转动角度0
     */
    float currentAngle = 0;

    /**
     * 圆弧背景画笔
     */
    private Paint mBgArcPaint;
    /**
     * 提示语画笔
     */
    private Paint mTextPaint;

    /**
     * 圆弧画笔
     */
    private Paint mArcPaint;
    /**
     * 渐变器
     */
    private SweepGradient mSweepGradient;

    /**
     * 是否开始
     */
    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构造");
        initPaint(context);
    }

    /**
     * 初始化控件View
     */
    private void initPaint(Context context) {
        //获取界面焦点
        setFocusable(true);
        //保持屏幕长亮
        setKeepScreenOn(true);

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

        //初始化画笔
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(getResources().getColor(R.color.colorAccent));
        mPaint.setStyle(Paint.Style.FILL);

        //绘制文字画笔
        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);

        //开启线程检测
        new Thread(this).start();
    }

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

        //测量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);

        //获取圆的相关参数
        mCenterPoint.x = mViewWidth / 2;
        mCenterPoint.y = mViewHeight / 2;

        //外环圆的半径
        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;

        //进度条颜色 -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() {
        //循环绘制画面内容
        while (!mFinsh) {
            if (mStart) {
                try {
                    changeValue();
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 动态检测改变值
     */
    private void changeValue() {
        // 内圆形,放大效果
        currentRadius += 20;
        if (currentRadius > mRadius)
            currentRadius = mRadius;

        //外部圈的动画效果
        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);
        //绘制画布内容
        drawContent(canvas);
    }

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

    private void drawContent(Canvas canvas) {
        //防止save()和restore()方法代码之后对Canvas执行的操作,继续对后续的绘制会产生影响
        canvas.save();
        //先画提示语
        drawHintText(canvas);
        //绘制正方形的框内类似人脸识别
//        drawFaceRectTest(canvas);
        //绘制人脸识别部分
        drawFaceCircle(canvas);
        //画外边进度条
        drawRoundProgress(canvas);
        canvas.restore();
    }

    private void drawFaceCircle(Canvas canvas) {
        //设置画板样式
        Path path = new Path();
        //以(400,200)为圆心,半径为100绘制圆 指创建顺时针方向的矩形路径
        path.addCircle(mCenterPoint.x, mCenterPoint.y, currentRadius, Path.Direction.CW);
        // 是A形状中不同于B的部分显示出来
        canvas.clipPath(path, Region.Op.DIFFERENCE);
        // 半透明背景效果
        canvas.clipRect(0, 0, mViewWidth, mViewHeight);
        //绘制背景颜色
        canvas.drawColor(getResources().getColor(R.color.viewBgWhite));
    }


    /**
     * 绘制人脸识别界面进度条
     *
     * @param canvas canvas
     */
    private void drawRoundProgress(Canvas canvas) {
        // 逆时针旋转105度
        canvas.rotate(mStartAngle, mCenterPoint.x, mCenterPoint.y);
        // 设置圆环背景
        canvas.drawArc(mBgRectF, 0, mEndAngle, false, mBgArcPaint);
        // 设置渐变颜色
        mArcPaint.setShader(mSweepGradient);
        canvas.drawArc(mBgRectF, 0, currentAngle, false, mArcPaint);
    }

    /**
     * 从头位置开始动画
     */
    public void resetPositionStart() {
        currentAngle = 0;
        isBack = false;
    }

    /**
     * 动画直接完成
     */
    public void finnishAnimator() {
        currentAngle = mEndAngle;
        isBack = false;
    }

    /**
     * 停止动画
     */
    public void pauseAnimator() {
        isRunning = false;
    }

    /**
     * 开始动画
     */
    public void startAnimator() {
        isRunning = true;
    }


    /**
     * 动画回退
     */
    public void backAnimator() {
        isRunning = true;
        isBack = true;
    }

    /**
     * 动画前进
     */
    public void forwardAnimator() {
        isRunning = true;
        isBack = false;
    }

    /**
     * 销毁视图,释放资源
     */
    public void destroyView() {
        //停止运行
        mFinsh = true;
        mStart = false;
        isRunning = false;
        isBack = false;
    }

    /**
     * 绘制人脸识别提示
     *
     * @param canvas canvas
     */
    private void drawHintText(Canvas canvas) {
        //圆视图宽度 (屏幕减去两边距离)
        int cameraWidth = mViewWidth - 2 * margin;
        //x轴起点(文字背景起点)
        int x = margin;
        //宽度(提示框背景宽度)
        int width = cameraWidth;
        //y轴起点
        int y = (int) (mCenterPoint.y - mRadius);
        //提示框背景高度
        int height = cameraWidth / 4;
        Rect rect = new Rect(x, y, x + width, y + height);
        canvas.drawRect(rect, mPaint);

        //计算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);
    }

    /**
     * 绘制人脸识别矩形区域
     *
     * @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);
    }
}

这里值得注意的是这几个方法,使用的时候可以根据实际调用这几个动画。

 /**
     * 从头位置开始动画
     */
    public void resetPositionStart() {
        currentAngle = 0;
        isBack = false;
    }

    /**
     * 动画直接完成
     */
    public void finnishAnimator() {
        currentAngle = mEndAngle;
        isBack = false;
    }

    /**
     * 停止动画
     */
    public void pauseAnimator() {
        isRunning = false;
    }

    /**
     * 开始动画
     */
    public void startAnimator() {
        isRunning = true;
    }

    /**
     * 动画回退
     */
    public void backAnimator() {
        isRunning = true;
        isBack = true;
    }

    /**
     * 动画前进
     */
    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">

    <!--相机预览显示-->
    <com.demo.facerecognition.view.FaceView
        android:id="@+id/fv_title"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:tip_text="请闭眼"
        app:tip_text_size="20sp"/>

    <!--关闭按钮-->
    <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[]{
            //获取照相机权限
            Manifest.permission.CAMERA,
    };

    /**
     * 设置Android6.0的权限申请
     */
    private void setPermissions() {
        if (ContextCompat.checkSelfPermission(PreviewActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            //Android 6.0申请权限
            ActivityCompat.requestPermissions(this, PERMISSION, 1);
        } else {
            Log.i(TAG, "权限申请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);
        //申请手机的权限
        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("没有检测人脸");
                        break;
                    case 2:
                        faceView.backAnimator();
                        faceView.updateTipsInfo("请露正脸");
                        break;
                    case 3:
                        faceView.pauseAnimator();
                        faceView.updateTipsInfo(" 眨眨眼");
                        break;
                    case 4:
                        faceView.startAnimator();
                        faceView.updateTipsInfo("离近一点");
                        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实例
        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 {
                //打开硬件摄像头,这里导包得时候一定要注意是android.hardware.Camera
                // Camera,open() 默认返回的后置摄像头信息
                //设置角度,此处 CameraId 我默认 为 1 (前置)
                if (Camera.getNumberOfCameras() > 1) {
                    camera = Camera.open(1);
                } else {
                    camera = Camera.open(0);
                }
                //设置相机角度
                camera.setDisplayOrientation(90);
                //通过SurfaceView显示取景画面
                camera.setPreviewDisplay(surfaceHolder);
                //开始预览
                camera.startPreview();
                //设置是否预览参数为真
                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) {//正在预览
                    try {
                        camera.stopPreview();
                        camera.release();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    };

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

视频流用的前置摄像头,这里的提示和动画也是随机,项目中可以根据真实情况展示。


各个view的位置

【注意】自定义view应该注意前后绘制顺序和叠加模式

总结

项目中用到就简单抽了一下,有遇到类似需求可以参考一下,最近在学Flutter,学的差不多了写个Flutter版本在更新一下。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,019评论 4 62
  • Android 的音视频入门学习,首先了解一下绘制图片。在 Android 平台绘制一张图片,使用至少 3 种不同...
    安仔夏天勤奋阅读 2,347评论 0 8
  • 1. Outline 本文主要从以下三个大的方面来说明一下2D Graphic 绘图的一些相关函数及应用。 Col...
    lee_3do阅读 3,012评论 0 11
  • 2018-03-13 星期二 天气晴 新的一天又开始了,日复一日的做着同一件工作,今天又是下夜班,没送...
    谢镇泽妈妈阅读 115评论 0 0
  • 喜欢做烘焙的妈妈们一定对黄油很熟悉,做饼干、面包过程中必不可少。我家冰箱还屯了一箱黄油呢。 最近关注的一个辅食公众...
    兔妈帮阅读 4,992评论 0 1