本系列到此,关于自定义view的大部分知识点已经讲述完了。本篇是此系列的最后一篇,以验证码 的实现来将前面的内容串在一起展现出来。

验证码是有随机生成的若干个数字 和 若干条干扰线组成,提供的功能主要有:

  • 刷新验证吗
  • 改变验证码个数
  • 改变干扰线个数
  • 改变验证码字体大小
  • 改变验证码字体颜色
  • 获取当前验证码

效果图如下:

Android codeium插件使用 安卓codeview_验证码

CodeView需要添加4个自定义属性:验证码个数,干扰线条数,验证码字体大小,验证码字体颜色。

1,attrs.xml中添加属性如下:

<declare-styleable name="CodeView">
      <attr name="count" format="integer" />
      <attr name="line_count" format="integer"/>
      <attr name="code_color" format="color"/>
      <attr name="font_size" format="dimension"/>
  </declare-styleable>

2,java代码读取设置的属性值

TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CodeView);
count = a.getInteger(R.styleable.CodeView_count,DEFAULT_COUNT);
lineCount = a.getInteger(R.styleable.CodeView_line_count,DEFAULT_LINE_COUNT);

fontSize = a.getDimensionPixelSize(R.styleable.CodeView_font_size, (int)TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_SP,DEFAULT_FONT_SIZE,getResources().getDisplayMetrics()
));
codeColor = a.getColor(R.styleable.CodeView_code_color,DEFAULT_COLOR);
a.recycle();

3,提供获取随机码的方法

private String getCode(){
    String code = "";
    for (int i = 0; i < count ; i++){
        code = code + random.nextInt(10);
    }
    return code;
}

4,提供获取随机码宽高的方法(参照自定义TextView)

private Rect getTextRect (){
    Rect rect = new Rect();
    paint.getTextBounds(codeString,0,codeString.length(),rect);
    return rect;
}

5,初始化画笔

private void initPaint(){
    paint.reset();
    paint.setAntiAlias(true);
    paint.setColor(codeColor);
    paint.setTextSize(fontSize);
}

6,onMeause测量CodeView

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    Rect rect = getTextRect();
    int width = measureWidth(widthMeasureSpec,rect.width());
    int height = measureHeight(heightMeasureSpec,rect.height());

    setMeasuredDimension(width,height);
}

private int measureWidth(int widthMeasureSpec, int width){
    int size = MeasureSpec.getSize(widthMeasureSpec);
    int mode = MeasureSpec.getMode(widthMeasureSpec);

    if(mode == MeasureSpec.EXACTLY){
        return size;
    }else if(mode == MeasureSpec.AT_MOST){
        return getPaddingLeft() + width + getPaddingRight();//处理padding
    }
    return 0;
}
private int measureHeight(int heightMeasureSpec, int height){
    int size = MeasureSpec.getSize(heightMeasureSpec);
    int mode = MeasureSpec.getMode(heightMeasureSpec);

    if(mode == MeasureSpec.EXACTLY){
        return size;
    }else if(mode == MeasureSpec.AT_MOST){
        return getPaddingTop() + height + getPaddingBottom();//处理padding
    }
    return 0;
}

7,绘制

@Override
protected void onDraw(Canvas canvas) {
   super.onDraw(canvas);

   int width = getMeasuredWidth();
   int height = getMeasuredHeight();

   Rect rect = new Rect(0,0,width,height);

   //绘制外边框
   paint.setStyle(Paint.Style.STROKE);
   paint.setStrokeWidth(1);
   Rect rect1 = new Rect(rect);
   //rect1.inset(2,2);
   canvas.drawRect(rect1,paint);

   //绘制随机线
   paint.setStyle(Paint.Style.FILL);
   paint.setColor(Color.GRAY);
   for(int i = 0 ; i < lineCount ; i++){
       int x1 = random.nextInt(width);
       int y1 = random.nextInt(height);

       int x2 = random.nextInt(width);
       int y2 = random.nextInt(height);

       canvas.drawLine(x1,y1,x2,y2,paint);
   }

   //绘制文字
   paint.setColor(codeColor);
   Rect textRect = getTextRect();
   Paint.FontMetrics fontMetrics = paint.getFontMetrics();
   int x = (width - textRect.width()) / 2;
   int y = (int)(height /2 + (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent);
   canvas.drawText(codeString,x,y,paint);
}

以上就是CodeView的主体代码了,现在我们就能根据xml中设置的默认值来绘制随机码了,但是我们的随机码还不能刷新,需要CodeView提供可供外界访问的修改 随机码内容、个数、颜色、字体大小、干扰线个数等接口。

//提供一些供外界调用了个刷新的方法
//刷新随机码
public void refreshCode(){
     codeString = getCode();
     invalidate();
}

//改变随机码颜色
public void changeCodeColor(int color) {
    codeColor = color;
    invalidate();
}

//改变随机码字体大小
public void changeCodeFontSize(int fontSizeSp){
    fontSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,fontSizeSp,getResources().getDisplayMetrics());
    initPaint();
    requestLayout();
}

//设置随机数个数
public void changeCodeCount(int codeCount){
    count = codeCount;
    codeString = getCode();
    requestLayout();
}

//设置干扰线条数
public void changeLineCount(int lineCounts){
    lineCount = lineCounts;
    invalidate();
}

细心的同学可能发现了,在修改了CodeView的参数后,有的接口是调用invalide()来刷新,有的接口是调用requestLayout()来刷新,那么这两个方法有什么区别呢?

invalide()是重绘,即调用view的onDraw()方法来重新绘制view的内容,而view的宽高是不会重新计算的。
requestLayout()是先调用onMeasure(),在调用onDraw(),是会重新测量view的宽高的。

知道了这两个方法的区别,就可以理解上面的那些供外界调用了个刷新的方法了,只有在改变了随机码个数,随机码字体大小的时候,CodeView的大小才会发生变化,我们才需要重新Measure下CodeView的宽高。

CodeView的代码逻辑已经完成了,感兴趣的同学可以自己尝试做一个随机码出来哦。

DONE
至此,自定义view系列已经完成。