自定义view之一:自定义验证码控件

转载请表明出处:

http://blog.csdn.net/bxl049/article/details/50939712

一、概述

最近学习了一些关于自定义View控件的博客,积累了一些理论知识(自我感觉良好ing,哈哈)。现在想通过一个自定义验证码

控件来检验下自己的学习成果。先来讲下自定一view步骤:

1、自定义view属性;

2、在构造方法中获得我们自定义的属性;

3、重写onMeasure方法,设置控件大小;

4、重写ondraw方法。

步骤3不一定是必须的,当layout_widht和layout_height值为wrap_content时,则需要重写。

二、自定义验证码控件

2、1 自定view属性

首先在res/values下新建文件attrs.xml,在文件中定义属性以及声明我们的属性样式。

<?xml version=1.0 encoding="utf-8"?>
<resources>
    <attr name="codeLength" format="integer"/>
    <attr name="codeSize" format="dimension"/>
<declare-styleable name="valideCode>
        <attr name="codeLength"/>
        <attr name="codeSize"/>
</  resources>

这个我们定义了验证码字符串长度、字体大小两个属性,format类型是该属性的的取值类型,

string,color,integer,enum,float,boolean,flag,fraction,dimension,reference,具体含义大家可以谷歌下,然后我们在布局文

件中声明:

<?html version=1.0 encoding="uft-8"?>
<LinearLayout                             xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com.apk/res_auto
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center">
    <com.vann.validecodetextview.ValideCodeTextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:paddingleft="10dp"
    app:codeLength="4"
    app:codeSize="16sp"/>
</LinearLayout>

注意布局文件中要引入xmlns:app=”http://schemas.android.com/apk/res-auto”,不然我们是无法获取到我们的自定属性的。

2、2构造函数初始化

新建类ValideCodeTextView继承View,重写ValideCodeTextView的构造函数,并使最后运行含有三个参数的构造函数,在含有

是三个参数的构造函数中进行自定义属性获取以及部分变量初始化

    //最大随机字符间隔
private static final int MAX_PADDING = 16;

//字体大小
private float mCodeSize;
// 验证码长度
private int mCodeLength;
//验证码
private String mCode;
//控件文本画笔、范围
private Paint mPaint;
private Rect mBound;
//每个字符左边距距离
private int paddingleft;
private int paddingTop;

private int mWidthMode;

// 噪点画笔
private Paint mPointPaint;
// 干扰线画笔
private Paint mPathPaint;

private Context mContext;
private Random mRandom = new Random();

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

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

public ValideCodeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    this.mContext = context;
    //获得自定义属性
    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.valideCode);
    mCodeLength = ta.getInt(R.styleable.valideCode_codeLength, 1);
    mCodeSize = ta.getDimension(R.styleable.valideCode_codeSize, 0);
    ta.recycle();
    mCode = getRandomText();
    //文本画笔初始化
    mPaint = new Paint();
    mPaint.setTextSize(mCodeSize);
    mBound = new Rect();
    mPaint.getTextBounds(mCode, 0, mCodeLength, mBound);

    mPathPaint = new Paint();
    mPointPaint = new Paint();
}

在三个参数的构造函数中获取到我们前面自定义的属性codeLength,codeSize,并对验证码文本画笔进行初始化。根据获取到的

自定义验证码长度获取随机验证码,验证码随机获取方法

如下:

/**
 * 获取随机位数的数字与符号的组合字符串
 *
 * @return
 */
private String getRandomText() {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < mCodeLength; i++) {
        String ch = mRandom.nextInt(2) % 2 == 0 ? "char" : "num";
        if ("char".equals(ch)) {
            int lowerOrUp = mRandom.nextInt(2) % 2 == 0 ? 65 : 97;
            char val = (char) (lowerOrUp + mRandom.nextInt(26));
            sb.append(val);
        } else if ("num".equals(ch)) {
            sb.append(String.valueOf(mRandom.nextInt(10)));
        }
    }
    return sb.toString();
}
2、3 重写onMeasure方法

当验证码控件定义的不是一个精确值时,我们需要重写该方法获得他的测量值、测量模式来确定控件的大小。重写之前先了解

MeasureSpec的

specMode,一共三种类型:

EXACTLY:一般是设置了明确的值或者是MATCH_PARENT

AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT

UNSPECIFIED:表示子布局想要多大就多大,很少使用

下面是我们重写onMeasure代码:

/**
 * 计算测量值、模式,设置控件大小
 *
 * @param widthMeasureSpec
 * @param heightMeasureSpec
 */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    mWidthMode = widthMode;
    int width, height;
    if (MeasureSpec.EXACTLY == widthMode) {
        width = widthSize;
    } else {
        mPaint.setTextSize(mCodeSize);
        randomPaintStyle(mPaint);
        int tWidth = 0;
        Rect child;
        for (int i = 0; i < mCodeLength; i++) {
            child = new Rect();
            char c = mCode.charAt(i);
            mPaint.getTextBounds(c + "", 0, 1, child);
            float w = child.width();
            int dw = (int) (w + MAX_PADDING);
            tWidth += dw;
        }
        width = tWidth + getPaddingLeft() + getPaddingRight();
    }
    if (MeasureSpec.EXACTLY == heightMode) {
        height = heightSize;
    } else {
        mPaint.setTextSize(mCodeSize);
        randomPaintStyle(mPaint);
        mPaint.getTextBounds(mCode, 0, mCodeLength, mBound);
        float tHeight = mBound.height();
        int h = (int) (tHeight + getPaddingTop() + getPaddingBottom());
        height = h + MAX_PADDING;
    }

    setMeasuredDimension(width, height);
}

在该方法中我们先获得了验证码控件的测量值和测量模式,当测量模式为MeasureSpec.EXACTLY 时,父控件用width,height,

进行setMeasureDimension大小设置,否则,对画笔样式进行随机设置,此时width等于左边距+右边距+每个字符的宽+字符

间距的最大随机数,取最大随机数是为了保证验证码文本不超出控件范围之外,文本画笔样式随机设置如下:

/**
 * 设置文本画笔的随机样式
 *
 * @param paint
 */
private void randomPaintStyle(Paint paint) {
    paint.setTextSize(mCodeSize);
    paint.setColor(getRandomColor(1));
    paint.setFakeBoldText(mRandom.nextBoolean());//是否粗体
    float skewx = mRandom.nextInt(11) / 10;
    skewx = mRandom.nextBoolean() ? skewx : -skewx;
    paint.setTextSkewX(skewx);// float类型参数,负数右倾,正数左倾

}
2、4 重写onDraw方法

通过onMeasure方法我们已经确定了控件的大小,现在通过onDraw方法来画出验证码文本,先定义画板的默认背景色为白色,

然后获取每个随机验证码的字符宽度以及左边距,并记录下左边距,在画板上通过drawText方法分别画出字符。代码如下:

@Override
protected void onDraw(Canvas canvas) {
    mPaint.setColor(Color.WHITE);
    canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
    Rect rec;
    int lastWidth = 0;
    paddingleft = getPaddingLeft();
    for (int i = 0; i < mCodeLength; i++) {
        paddingTop = getHeight() / 2 + mRandom.nextInt(MAX_PADDING);
        randomPaintStyle(mPaint);
        char ch = mCode.charAt(i);
        rec = new Rect();
        mPaint.getTextBounds(ch + "", 0, 1, rec);
        lastWidth = rec.width();
        paddingleft += lastWidth + mRandom.nextInt(MAX_PADDING);
        canvas.drawText(mCode.charAt(i) + "", paddingleft, paddingTop, mPaint);
    }
    int dw = getMeasuredWidth();
    int w = paddingleft + lastWidth;
//        Toast.makeText(mContext,"控件长度:"+dw+"验证码长度:"+w,Toast.LENGTH_SHORT).show();
    // 当模式不为EXACTLY且验证码长度大于控件长度时,重新获取验证码并绘制
    if (MeasureSpec.EXACTLY != mWidthMode && paddingleft + lastWidth > dw) {
        mCode = getRandomText();
        postInvalidate();
        return;
    }

    for (int i = 0; i < 2; i++) {
        randomPathPaintStyle(mPathPaint);
        drawLine(canvas, mPathPaint);
    }
    for (int i = 0; i < 50; i++) {
        randomPointPaintStyle(mPointPaint);
        drawPoint(canvas, mPointPaint);
    }
    canvas.save(Canvas.ALL_SAVE_FLAG);
    canvas.restore();

}

画出文本后,判断后一个字符的左边距是否大于控件的实际距离,如果大于则重新获取随机验证码字符串,如果没有,则进行噪

点和干扰线的生成操作,噪点我们固定生成150各,干扰线为2条,噪点生成的代码如下:

/**
 * 绘制噪点
 *
 * @param canvas
 * @param mPointPaint
 */
private void drawPoint(Canvas canvas, Paint mPointPaint) {
    int x = mRandom.nextInt(getWidth());
    int y = mRandom.nextInt(getHeight());
    canvas.drawPoint(x, y, mPointPaint);
}

每次画噪点前先随机获取噪点画笔的样式,以便获取到不同颜色的噪点。画干扰线的方法如下:

/**
 * 绘制干扰线
 *
 * @param canvas
 * @param mPaint
 */
private void drawLine(Canvas canvas, Paint mPaint) {
    int startX = mRandom.nextInt(getWidth());
    int startY = mRandom.nextInt(getHeight());
    int endX = mRandom.nextInt(getWidth());
    int endY = mRandom.nextInt(getHeight());
    canvas.drawLine(startX, startY, endX, endY, mPaint);
}

至此验证码已画出,当我们点击控件时需要刷新验证码文本,因此,需要给控件添加一个监听事件,在构造函数中添加添加事

件,并在添加事件中重新获取随机验证码,重绘图像,监听代码如下:

this.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            mCode = getRandomText();
            postInvalidate();
        }
    });

好了,现在看下运行结果:

这里写图片描述

参考博客:

洪洋大神的文章—Android 自定义View (一)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值