Android自定义View 做个简单的验证码控件

xiaoxiao2021-02-28  16

首先感谢鸿洋大神,因为我看了他的文章后才写了这篇博文。 传送门: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:代码地址,点我传送

转载请注明原文地址: https://www.6miu.com/read-1400064.html

最新回复(0)