Android:实现一个自定义View扫描框

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 27 天,点击查看活动详情

扫码功能都用过吧,打开扫码功能后都会有类似封面图的效果。其实就是一个自定义View的遮罩,话不多说,今天这篇我们就来讲解如何实现一个扫面框动效。

首先,我们先分析下动效的组成,有利于待会拆分实现:

  1. 四周类似角标的白线
  2. 角标框住的浅白色背景
  3. 一条由上而下由快到慢移动的扫描线

一经分析,其实非常简单,整体效果就是由这几个简单的元素组成。接下来我们就创建一个ScanView继承自View来实现这个动效。(由于代码古老,这里使用Java)

public final class ScanView extends View {

private Paint paint, scanLinePaint,reactPaint;//三种画笔
private Rect frame;//整个区域

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

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

public ScanView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initPaint()
}

private void initPaint() {
    /*遮罩画笔*/
    paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setColor(color);
    paint.setAlpha(CURRENT_POINT_OPACITY);
    paint.setStyle(Paint.Style.FILL);

    /*边框线画笔*/
    reactPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    reactPaint.setColor(reactColor);
    reactPaint.setStyle(Paint.Style.FILL);
    
    /*扫描线画笔*/
    scanLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    scanLinePaint.setStyle(Paint.Style.FILL);
    scanLinePaint.setDither(true);
    scanLinePaint.setColor(scanLineColor);
}
}

三种画笔初始化完成接下来就是使用画笔在画布上绘制效果图了,重写onDraw()方法

public void onDraw(Canvas canvas) {
   //绘制取景边框
   drawFrameBounds(canvas, frame);
   //绘制遮罩
   drawMaskView(canvas, frame);
   //绘制扫描线
   drawScanLight(canvas, frame);
}

再来分析,边框的四个角其实拆开来看,就是两条线组成,或者说是两个填充的矩形框垂直相交组成,那么四个角就可以按照这个思路完成,遮罩其实就是一个矩形框。

//绘制四个角,注意是外线而不是内线
private void drawFrameBounds(Canvas canvas, Rect frame) {
    // 左上角
    canvas.drawRect(frame.left - corWidth, frame.top, frame.left, frame.top + corLength, reactPaint);
    canvas.drawRect(frame.left - corWidth, frame.top - corWidth, frame.left + corLength, frame.top, reactPaint);
    // 右上角
    canvas.drawRect(frame.right, frame.top, frame.right + corWidth,frame.top + corLength, reactPaint);
    canvas.drawRect(frame.right - corLength, frame.top - corWidth, frame.right + corWidth, frame.top, reactPaint);
    // 左下角
    canvas.drawRect(frame.left - corWidth, frame.bottom - corLength,frame.left, frame.bottom, reactPaint);
    canvas.drawRect(frame.left - corWidth, frame.bottom, frame.left + corLength, frame.bottom + corWidth, reactPaint);
    // 右下角
    canvas.drawRect(frame.right, frame.bottom - corLength, frame.right + corWidth, frame.bottom, reactPaint);
    canvas.drawRect(frame.right - corLength, frame.bottom, frame.right  + corWidth, frame.bottom + corWidth, reactPaint);
}

//绘制遮罩
private void drawMaskView(Canvas canvas, Rect frame) {
     canvas.drawRect(frame.left, frame.top, frame.right, frame.bottom, paint);
}

到此,我们还剩最后一个扫描线的动画效果,这条线其实就是一张图片,首先需要将图片以Bitmap形式绘制在扫描区域内,然后通过ValueAnimator来控制图片Y坐标点,这样就能达到图片上下移动的效果,至于由快到慢的效果是添加了插值器,这里使用内置的DecelerateInterpolator,同学们可以根据自己想要的效果自己搭配。

scan_light.png

if (valueAnimator == null) {
    valueAnimator = ValueAnimator.ofInt(frame.top, frame.bottom-10);//图片Y坐标取值范围
    valueAnimator.setDuration(3000);//单次动画时间3秒
    valueAnimator.setInterpolator(new DecelerateInterpolator());//由快到慢插值器
    valueAnimator.setRepeatMode(ValueAnimator.RESTART);//重复动画
    valueAnimator.setRepeatCount(ValueAnimator.INFINITE);//无限次数
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            scanLineTop = (int) animation.getAnimatedValue();//当前时刻获取的Y值
            invalidate();//刷新视图
        }
    });
    valueAnimator.start();
}

到此就可以实现封面的效果,甚至可以添加别的酷炫效果,只要你敢想敢做。

总结

其实一些动效看似很复杂,但通过认真分析,我们可以将其拆分成多个简单的小块,将每个小块实现后再逐个拼装,最后达到完整的效果。本节主要是通过自定义View实现,用到绘制矩形框(drawRect),属性动画(ValueAnimator),两者使用也是非常简单。另外需要注意动画的使用和释放,避免导致不必要的内存泄漏。

好了,以上便是本篇所有内容,希望对大家有所帮助!

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 27 天,点击查看活动详情