您的位置:首页 > 移动开发 > Android开发

自定义 View 初探,一个简单的验证码View (一)

2017-08-14 21:13 459 查看
想要学好 android, 除了使用 google 提供好的 view 外,还必须会在 google 提供的框架上会自定义新的 View, 供不同的场合使用。

这里使用一个简单的验证码 View 作为入门选择。

首先,我们明确一下自定义 View 的过程

1. 定义自定义 view 的属性

2. 获取定义的属性

3. 重写 onMeasure()

4. 重写 onDraw()

网络上简单过很多验证码,让我们很无奈,有时候烦不胜烦,但这又有存在的必要性。

这里一步一步来实现一个验证码的 View,一般而言,有下面这些特征:

设置文字

文字大小不一致

文字颜色不一致

干扰圆点

干扰线条

那么根据上面这些特征,我们首先定义 View 的

1. 在 res/values/ 目录下新建 attrs.xml 在里面定义我们的样式

<?xml version="1.0" encoding="utf-8"?>
<resources>

<attr name="mix_color" format="boolean" />
<attr name="text" format="string" />
<attr name="max_text_size" format="dimension" />
<attr name="min_text_size" format="dimension" />

<declare-styleable name="VerificationCodeView">
<!--文字颜色是否不一致-->
<attr name="mix_color"/>
<!--文字-->
<attr name="text"/>
<!--文字大小随机范围的小值-->
<attr name="max_text_size"/>
<!--文字大小随机范围的大值-->
<attr name="min_text_size"/>
<!-- 文字颜色 -->
<attr name="color" format="color" />
</declare-styleable>

</resources>


通过这样的一个属性,我们确定了我们需要的一些属性。然后,根据我们的需求,这里选择直接继承 View 来进行实现, 先定义一些所需要的 field并在构造函数中对所需要的值进行初始化

/**
* 文字内容
*/
private String mText;
/**
* 文字最大字号
*/
private int mMaxTextSize;
/**
* 文字最小字号
*/
private int mMinTextSize;
/**
* 是否混合颜色
*/
private boolean mMixColor;

/**
* 字的颜色,在非混合颜色时生效
*/
private int mColor;

/**
* 绘制字体时占用的边界
*/
private Rect mBounds;
/**
* 文字画笔
*/
private Paint mPaint;
/**
* 干扰点画笔
*/
private Paint mPointPaint;
/**
* 干扰线画笔
*/
private Paint mPathPaint;
private Random rnd;

private Point point = new Point();
private Path path = new Path();
/**
* 单个字符所占宽度
*/
private int charWidth;

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

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

public VerificationCodeView(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
/*
* 获取自定义样式
*/
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.VerificationCodeView, defStyle, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++)
{
int attr = a.getIndex(i);
switch (attr)
{
case R.styleable.VerificationCodeView_text:
mText = a.getString(attr);
break;
case R.styleable.VerificationCodeView_mix_color:
mMixColor = a.getBoolean(attr, true);
break;
case R.styleable.VerificationCodeView_max_text_size:
mMaxTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, DEFAULT_MAX_TEXT_
4000
SIZE,
getResources().getDisplayMetrics()));
break;
case R.styleable.VerificationCodeView_min_text_size:
mMinTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, DEFAULT_MIN_TEXT_SIZE,
getResources().getDisplayMetrics()));
break;
case R.styleable.VerificationCodeView_color:
mColor = a.getColor(attr, Color.BLACK);
}
}
a.recycle();
this.mPaint = new Paint();
this.mPointPaint = new Paint();
this.mPathPaint = new Paint();
this.mBounds = new Rect();
this.rnd = new Random(10);
if (mText == null){
mPaint.setTextSize(mMaxTextSize);
mPaint.getTextBounds(NONE_TEXT_TIP, 0, NONE_TEXT_TIP.length(), mBounds);
}else{
mPaint.setTextSize(mMaxTextSize);
mPaint.getTextBounds(mText, 0, mText.length(), mBounds);
}
// 干扰点及干扰线的属性
mPointPaint.setStrokeWidth(5);
mPointPaint.setStrokeCap(Paint.Cap.ROUND);
mPathPaint.setStrokeWidth(5);
mPathPaint.setStyle(Paint.Style.STROKE);
mPathPaint.setStrokeCap(Paint.Cap.ROUND);
}


接下来重写 onMeasure, 在 wrap_content 这样的配置时进行一些简单的计算,这里选择将值都 × 2分开这些字符。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.i(TAG, "onMeasure");
int width;
int height;
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY){
width = MeasureSpec.getSize(widthMeasureSpec);
}else{
if (mText == null){
width = NONE_TEXT_SIZE;
}else{
width = mBounds.width() * 2 + getPaddingLeft() + getPaddingRight();
}
}

if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY){
height = MeasureSpec.getSize(heightMeasureSpec);
}else{
if (mText == null){
height = NONE_TEXT_SIZE;
}else{
height = mBounds.height() * 2 + getPaddingBottom() + getPaddingTop();
}
}

if(mText != null && mText.length() > 0){
charWidth = width / mText.length();
}
setMeasuredDimension(width, height);
}


在 view 的规格确定了之后,可以开始在 view 内画出 view 的内容

@Override
protected void onDraw(Canvas canvas) {
if (mText == null){
mPaint.setTextSize(mMinTextSize);
mPaint.setAlpha(33);
canvas.drawText(NONE_TEXT_TIP, 0, getWidth()/2, mPaint);
}else{
for (int i = 0; i < mText.length(); i++){
if (mMixColor){
mPaint.setColor(getNextForegroundColor());
}else{
mPaint.setColor(mColor);
}
int offsetDegree = 15 - rnd.nextInt(30);
canvas.save();
canvas.rotate(offsetDegree, getWidth()/2, getHeight()/2);
mPaint.setTextSize(rnd.nextInt(mMaxTextSize-mMinTextSize) + mMinTextSize);
canvas.drawText(mText, i, i+1, getPaddingLeft() + charWidth * i + charWidth/2,
getHeight() / 2 + mBounds.height() / 2, mPaint);
canvas.restore();
}
for (int i = 0; i < DEFAULT_POINT_NUM; i++){
point.set(rnd.nextInt(getWidth()), rnd.nextInt(getHeight()));
mPointPaint.setColor(getNextForegroundColor());
canvas.drawPoint(point.x, point.y, mPointPaint);
}
mPathPaint.setColor(getNextForegroundColor());
path.reset();
path.moveTo(0, rnd.nextInt(getHeight()));
path.quadTo(getWidth()/2, getHeight()/2, getWidth(), rnd.nextInt(getHeight()));
canvas.drawPath(path, mPathPaint);
}
}


在确定了内容存在后,按顺序画出内容,这里选择按文字,干扰点,干扰线的顺序画出内容。

为了让文字不是正向,这里采用倾斜画布(canvas.rotate)的方式来实现,也可以使用 canvas.drawTextOnPath 的方式来实现。

然后讲干扰点,干扰线画在画布上,这里选择的是使用一个。可能会出现当父view 需要 scroll 或者类似的变化时,颜色发生变化,可以采用初始化一个 List 的 Point 以及 一个 List 的 Path 来实现。

接下来我们需要在 layout_main.xml 中添加本 view 进去。注意,需要 xmlns:app=”http://schemas.android.com/apk/res-auto” 这样的一行内容来引入自定义属性。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="me.leo.view.MainActivity">

<me.leo.view.widget.VerificationCodeView
android:id="@+id/verification_1"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorPrimaryDark"
app:mix_color="true"
app:max_text_size="28sp"
app:min_text_size="20sp"
app:text="@string/app_name"/>

<me.leo.view.widget.VerificationCodeView
android:id="@+id/verification_2"
android:layout_marginTop="10dp"
android:layout_below="@id/verification_1"
android:layout_centerHorizontal="true"
android:layout_width="300dp"
android:layout_height="200dp"
android:background="@color/colorPrimaryDark"
app:mix_color="false"
app:max_text_size="28sp"
app:min_text_size="20sp"
app:color="@color/colorAccent"
app:text="@string/app_name"/>

</RelativeLayout>


下面开始运行:



可以看到,我们的 验证码View 基本实现了,然后就可以根据具体项目要求,对于配置进行自定义调制。

比如说在属性配置中把干扰线,干扰点进行定制处理,增加对应的文字设置事件等。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 自定义view