首先感谢鸿洋大神,因为我看了他的文章后才写了这篇博文。
传送门:http://blog.csdn.net/lmj623565791/article/details/24252901
从15年工作到现在两年多了,从一个什么都不会的小菜鸟到现在会写点小程序的老菜鸟。两年来,做了十来个项目,有大有小,有团队开发也有单独开发,虽然比刚入职有了进步,但是总体来说总是差强人意,重复的用轮子,让自己的代码千篇一律毫无亮点。
两年来虽然自己不是完全没有写过自定义控件,但是也是极少写的,一直都感觉自定义控件十分复杂,写起来也比较费脑子(本人懒癌晚期患者),能不写就不写,随便百度一个相似控件,改改能用就行,不考虑是否与界面一致,色值是否协调,这也让我在开发中吃了不少亏,界面不协调改,色值相差大改,虽然在使用轮子中减少了工作量,但是在后续修改中也花费了不少工作时间,如果你是新人,千万不要学我。
虽然在开发界常说不要重复造轮子,但是呢这句话得这样看,不重复是对的,但是怎么造轮子这是门技术,这是需要我们学习的
第一步
自定义view的属性:
在你的项目/res/velues目录下新增attrs.xml,在这里你可以定义你需要定义的属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--设置参数 -->
<attr name="authCodeText" format="string"/><!--验证码显示值-->
<attr name="authCodeBackground" format="color"/><!--验证码背景-->
<attr name="authCodeTextSize" format="dimension"/><!--验证码位数-->
<!--声明-->
<declare-styleable name="AuthCodeUtils">
<attr name="authCodeText"/>
<attr name="authCodeBackground"/>
<attr name="authCodeTextSize"/>
</declare-styleable>
</resources>
第二步
获得自定义样式
写一个工具类并继承View(AuthCodeUtils),在构造方法中调用我们的样式
public class AuthCodeUtils extends View {
private String authCodeText;//文本
private int authCodeBackground;//背景颜色
private int authCodeSize;//验证码长度
private Rect mRect;//绘制矩形
private Paint mPaint;//
//构造方法
public AuthCodeUtils(Context context, AttributeSet attributeSet) {
super(context, attributeSet, 0);
initializeProperty(context, attributeSet);
}
/**
* 初始化属性
*
* @param context
* @param attributeSet
*/
private void initializeProperty(Context context, AttributeSet attributeSet) {
TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.TestTextUtil);
//获取typedArray长度
int num = typedArray.getIndexCount();
for (int i = 0; i < num; i++) {
//获取属性ID
int id = typedArray.getIndex(i);
switch (id) {
case R.styleable.TestTextUtil_authCodeText:
authCodeText = typedArray.getString(id);
break;
case R.styleable.TestTextUtil_authCodeTextSize:
authCodeSize = typedArray.getDimensionPixelSize(id, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
break;
case R.styleable.TestTextUtil_authCodeBackground:
authCodeBackground = typedArray.getColor(id, Color.YELLOW);
break;
}
}
typedArray.recycle();
//获取文本的长和宽
mPaint = new Paint();
mPaint.setTextSize(authCodeSize);
mRect = new Rect();
mPaint.getTextBounds(authCodeText, 0, authCodeText.length(), mRect);
}
然后重新onDraw方法
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//添加背景色
mPaint.setColor(Color.YELLOW);
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
//字体颜色
mPaint.setColor(authCodeBackground);
canvas.drawText(authCodeText, getWidth() / 2 - mRect.width() / 2, getHeight() / 2 + mRect.height() / 2, mPaint);
}
然后在布局文件中声明并调用
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--注意此句xmlns:custom="http://schemas.android.com/apk/res-auto",如果不写,则无法获得我们自定义的属性-->
<!--声明并调用-->
<cm.cui.publicbase.test.AuthCodeUtils
android:id="@+id/testText"
android:layout_width="70dp"
android:layout_height="30dp"
android:layout_centerInParent="true"
android:padding="10dp"
custom:authCodeBackground="@color/colorAccent"
custom:authCodeText="3712"
custom:authCodeTextSize="16sp"/>
</RelativeLayout>
运行程序,效果如图
第三步
重写onMeasure方法
这里是抄的鸿洋大神的代码,不过我加了注释和修改了一些地方,差不多意思就是这样
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取控件宽高的显示模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//获取宽高的尺寸值 固定值的宽度
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int width;
int height;
// MeasureSpec父布局传递给后代的布局要求 包含 确定大小和三种模式
// EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
// AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
// UNSPECIFIED:表示子布局想要多大就多大,很少使用
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
mPaint.setTextSize(authCodeSize);
mPaint.getTextBounds(authCodeText, 0, authCodeText.length(), mRect);
float textWidth = mRect.width();
int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
width = desired;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
mPaint.setTextSize(authCodeSize);
mPaint.getTextBounds(authCodeText, 0, authCodeText.length(), mRect);
float textHeight = mRect.height();
int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
height = desired;
}
//设置测量的宽高
setMeasuredDimension(width, height);
}
编写噪点和划线方法,大意如下图
//随机生成小圆点坐标
private int[] getPoint(int height, int width) {
int[] tempCheckNum = {0, 0};
tempCheckNum[0] = (int) (Math.random() * width);
tempCheckNum[1] = (int) (Math.random() * height);
return tempCheckNum;
}
//随机生成线线
public int[] getLine(int height, int width) {
int[] tempCheckNum = {0, 0, 0, 0};
for (int i = 0; i < 4; i += 2) {
tempCheckNum[i] = (int) (Math.random() * width);
tempCheckNum[i + 1] = (int) (Math.random() * height);
}
return tempCheckNum;
}
调用生成的噪点和划线方法
final int height = getHeight();
final int width = getWidth();
// 绘制小圆点
int[] point;
for (int i = 0; i < 100; i++) {
point = getPoint(height, width);
/**
* drawCircle (float cx, float cy, float radius, Paint paint)
* float cx:圆心的x坐标。
* float cy:圆心的y坐标。
* float radius:圆的半径。
* Paint paint:绘制时所使用的画笔。
*/
canvas.drawCircle(point[0], point[1], 1, mPaint);
}
for (int i = 0; i < 50; i++) {
point = getPoint(height, width);
canvas.drawCircle(point[0], point[1], 2, mPaint);
}
for (int i = 0; i < 30; i++) {
point = getPoint(height, width);
canvas.drawCircle(point[0], point[1], 3, mPaint);
}
int[] line;
for (int i = 0; i < 10; i++) {
line = getLine(height, width);
/**
* startX:起始端点的X坐标。
*startY:起始端点的Y坐标。
*stopX:终止端点的X坐标。
*stopY:终止端点的Y坐标。
*paint:绘制直线所使用的画笔。
*/
canvas.drawLine(line[0], line[1], line[2], line[3], mPaint);
}
请自行添加点击事件……..
运行程序
Demo:代码地址,点我传送