这篇文章主要为大家详细介绍了 Android 自定义 View 实现验证码的相关资料, 具有一定的参考价值,感兴趣的小伙伴们可以参考一下
Android 是一种基于 Linux 的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑,由 Google 公司和开放手机联盟领导及开发。尚未有统一中文名称,中国大陆地区较多人使用 "安卓" 或 "安致"。
本文章是基于鸿洋的 {aa1aa} 的一些扩展,以及对 {aa0aa} 里面内容的一些转载。
- <declare-styleable name="CustomTitleView">
- <attr name="titleColor" format="color" />
- <attr name="titleSize" format="dimension" />
- <attr name="titleBackground" format="color" />
- <attr name="titleLenth" format="integer" />
- </declare-styleable>
Android 提供了自定义属性的方法,其中的 format 的参数有
(reference、color、boolean、dimension、float、integer、string、fraction、enum、flag)
1.reference: 资源 ID:
如果设置了这个属性那么这个属性相当于 @string|@drawable 等调用资源文件的作用
2. color:
这个属性的作用为设置颜色值 8 或者 6 位的 16 进制的颜色值,如设置 TextView 的 textColor 等属性的作用相同 (如 #ff000 设置为红色等)
3.boolean:
这个参数的作用为设置 true 或者 false
4.dimension:
这个参数的作用为设置尺寸值,如 px、dip、dp、sp 等
5.float:
这个参数的作用为设置浮点型数据
6.integer:
这个参数的作用为设置整形数据
7.string:
这个参数的作用为设置字符串数据,如 TextView 的 text 属性
8.fraction:
这个参数的作用为设置百分比数据
9:enum:
这个参数相当于给这个 attr 的 name 属性设置固定的参数,如线性布局的 orientation 属性只能设置 vertical 或者 horizontal
10:flag:
这个参数作用为:位或运算
一个自定义 View 的步骤为
1、自定义 View 的属性
2、在 View 的构造方法中获得我们自定义的属性
3、重写 onMeasure
4、重写 onDraw
有的时候 onMeasure 方法是不用重写的例如系统自带组件等
然后我们定义一下需要的属性
- //文本
- private StringBuffer mTitleText;
- //文本的颜色
- private int mTitleColor;
- //文本的大小
- private int mTitleSize;
- //背景颜色
- private int mBackground;
- //控制生成的随机字符串长度
- private int mLenth;
- //绘制时控制文本绘制的范围
- private Rect mBound;
- //画笔
- private Paint mPaint;
- //随机数对象
- private Random random = new Random();
- //字符串边距
- private int padding_left;
- //随机的值
- String[] data = {
- "a",
- "b",
- "c",
- "d",
- "e",
- "f",
- "g",
- "h",
- "i",
- "j",
- "k",
- "l",
- "m",
- "n",
- "o",
- "p",
- "q",
- "r",
- "s",
- "t",
- "u",
- "v",
- "w",
- "x",
- "y",
- "z",
- "A",
- "B",
- "C",
- "D",
- "E",
- "F",
- "G",
- "H",
- "I",
- "J",
- "K",
- "L",
- "M",
- "N",
- "O",
- "P",
- "Q",
- "R",
- "S",
- "T",
- "U",
- "V",
- "W",
- "X",
- "Y",
- "Z",
- "0",
- "1",
- "2",
- "3",
- "4",
- "5",
- "6",
- "7",
- "8",
- "9"
- };
然后我们重写三个构造方法,我们需要注意的是
也就是说, 系统默认只会调用 Custom View 的前两个构造函数, 至于第三个构造函数的调用, 通常是我们自己在构造函数中主动调用的(例如, 在第二个构造函数中调用第三个构造函数).
至于自定义属性的获取, 通常是在构造函数中通过 obtainStyledAttributes 函数实现的。
- public CustomTitleView(Context context) {
- this(context, null);
- }
- public CustomTitleView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
- public CustomTitleView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- setOnClickListener(this);
- TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomTitleView);
- int n = typedArray.getIndexCount();
- for (int i = 0; i < n; i++) {
- int attr = typedArray.getIndex(i);
- switch (attr) {
- case R.styleable.CustomTitleView_titleColor:
- mTitleColor = typedArray.getColor(attr, Color.BLACK);
- break;
- case R.styleable.CustomTitleView_titleSize:
- mTitleSize = typedArray.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
- break;
- case R.styleable.CustomTitleView_titleBackground:
- mBackground = typedArray.getColor(attr, Color.BLACK);
- break;
- case R.styleable.CustomTitleView_titleLenth:
- mLenth = typedArray.getInteger(attr, 4);
- break;
- }
- }
- //回收
- typedArray.recycle();
- mPaint = new Paint();
- randomText();
- mPaint.setTextSize(mTitleSize);
- //创建一个矩形
- mBound = new Rect();
- //第一个参数为要测量的文字,第二个参数为测量起始位置,第三个参数为测量的最后一个字符串的位置,第四个参数为rect对象
- mPaint.getTextBounds(mTitleText.toString(), 0, mTitleText.length(), mBound);
- }
- /**
- * 计算宽高
- *
- * @param lenth widthMeasureSpec或heightMeasureSpec
- * @param isWidth true为计算宽度,false为计算高度
- */
- private int getMeasuredLength(int lenth, boolean isWidth) {
- if (isWidth) {
- if (MeasureSpec.getMode(lenth) == MeasureSpec.EXACTLY) {
- //设置了精确尺寸,通过MeasureSpec.getSize()获得尺寸返回宽度
- return MeasureSpec.getSize(lenth);
- } else {
- //设置了warp_content,则需要我们自己计算
- /**
- * 首先给画笔设置文字大小
- * 通过getTextBounds方法获得绘制的Text的宽度
- * 然后因为我们的自定义View只有一个text所以我们只需要getPaddingLeft()+getPaddingRight()+textwidth即可计算出显示出view所需要最小的宽度
- * 一般计算宽度为getPaddingLeft()+getPaddingRight()+自己绘画的文字或者图片的宽度,因为计算的是所需宽度,假设我们绘制了图片+文字,那么就需要判断图片的宽度跟文字的宽度那个更大比如getPaddingLeft()+getPaddingRight()+Math.max(图片的宽度,文字的宽度)即得出所需宽度
- */
- if (MeasureSpec.getMode(lenth) == MeasureSpec.AT_MOST) {
- mPaint.setTextSize(mTitleSize);
- mPaint.getTextBounds(mTitleText.toString(), 0, mTitleText.length(), mBound);
- float textwidth = mBound.width();
- int desired = (int) (getPaddingLeft() + textwidth + getPaddingRight());
- return Math.min(desired,MeasureSpec.getSize(lenth));
- }
- }
- } else {
- if (MeasureSpec.getMode(lenth) == MeasureSpec.EXACTLY) {
- //用户设置了精确尺寸,通过MeasureSpec.getSize()获得尺寸返回高度
- return MeasureSpec.getSize(lenth);
- } else {
- if (MeasureSpec.getMode(lenth) == MeasureSpec.AT_MOST) {
- //设置了warp_content,则需要我们自己计算
- mPaint.setTextSize(mTitleSize);
- mPaint.getTextBounds(mTitleText.toString(), 0, mTitleText.length(), mBound);
- float texthgeight = mBound.height();
- int desired = (int) (getPaddingTop() + texthgeight + getPaddingBottom());
- return Math.min(desired,MeasureSpec.getSize(lenth));
- }
- }
- }
- return 0;
- }
然后在 onMeasure 方法里调用
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(getMeasuredLength(widthMeasureSpec, true), getMeasuredLength(heightMeasureSpec, false));
- }
系统帮我们测量的高度和宽度都是 MATCH_PARNET,当我们设置明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果,当我们设置为 WRAP_CONTENT, 或者 MATCH_PARENT 系统帮我们测量的结果就是 MATCH_PARENT 的长度。
所以,当设置了 WRAP_CONTENT 时,我们需要自己进行测量,即重写 onMeasure 方法
重写之前先了解 MeasureSpec 的 specMode, 一共三种类型:
EXACTLY:一般是设置了明确的值或者是 MATCH_PARENT
AT_MOST:表示子布局限制在一个最大值内,一般为 WARP_CONTENT
UNSPECIFIED:表示子布局想要多大就多大,很少使用
在这里有些初学者可能不理解 getMode 跟 getSize 的作用,首先 getMode() 用于判断宽高设置的模式,获得到之后即可判断, 例如
- //xx表示widthMeasureSpec或者heightMeasureSpec
- if(MeasureSpec.getMode(xx)==MeasureSpec.EXACTLY){
- //进入这里则代表设置了match_parent或者将控件的layout_width或layout_height指定为具体数值时如andorid:layout_width="100dp",这样我们就可以直接通过MeasureSpec.getSize(xx)方法获得宽或高
- }else if(MeasureSpec.getMode(xx)==MeasureSpec.EXACTLY){
- //进入这里代表设置了wrap_content,那么则需要我们自己计算宽或高
- }else{
- //进入这个则代表代表是未指定尺寸,这种情况不多,一般都是父控件是AdapterView,通过measure方法传入的模式
- }
然后我们重写一下 onDraw 方法、
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- padding_left = 0;
- mPaint.setColor(mBackground);
- canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
- mPaint.setColor(mTitleColor);
- for (int i = 0; i < mTitleText.length(); i++) {
- randomTextStyle(mPaint);
- padding_left += mPaint.measureText(String.valueOf(mTitleText.charAt(i)))+10;
- canvas.drawText(String.valueOf(mTitleText.charAt(i)), padding_left, getHeight() / 2 + mBound.height() / 2, mPaint);
- }
- }
- private void randomTextStyle(Paint paint) {
- paint.setFakeBoldText(random.nextBoolean()); //true为粗体,false为非粗体
- float skewX = random.nextInt(11) / 10;
- skewX = random.nextBoolean() ? skewX : -skewX;
- paint.setTextSkewX(skewX); //float类型参数,负数表示右斜,整数左斜
- paint.setUnderlineText(true); //true为下划线,false为非下划线
- paint.setStrikeThruText(false); //true为删除线,false为非删除线
- }
这里绘制了多个字符串,并且使每个绘制的字符串都歪歪扭扭的,这样我们采用 randomTextStyle() 即可在每次绘制字符的时候设置每个字符都为不同的样式,在这里我们讲一下 drawText 的几个参数,第一个参数就是要绘制的文字内容,第二个参数为 x 轴,作用相当于左边距,第三个参数为 Y 轴,第四个参数为 paint 的实例,我的朋友具体讲了一下 drawText 的绘制坐标有兴趣的可以去看一下 android canvas drawText() 文字居中
最后我们在布局文件中引用我们的自定义 view
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:cq="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal">
- <chapter.com.rxjavachapter.CustomTitleView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerInParent="true"
- android:padding="10dp"
- cq:titleBackground="@android:color/black"
- cq:titleColor="#ff0000"
- cq:titleLenth="4"
- cq:titleSize="10sp" />
- </RelativeLayout>
在根布局添加 xmlns:xx="http://schemas.android.com/apk/res-auto" 这里的 xx 可以是任何字母
然后用 xx 去点我们在 attr 的 name 去设值,最后实现出的效果是这样
既然是验证码 view,那么我们自然要开放出点击改变验证码内容的点击事件,在第三个构造方法中添加 click 事件
- @Override
- public void onClick(View v) {
- randomText();
- postInvalidate();
- }
- /**
- * 获得随机的字符串
- *
- * @return
- */
- private void randomText() {
- mTitleText = new StringBuffer();
- for (int i = 0; i < mLenth; i++) {
- mTitleText.append(data[(int) (Math.random() * data.length)]);
- }
- }
- /**
- * 获得到随机的值
- *
- * @return
- */
- public String getCode() {
- return mTitleText.toString();
- }
- /**
- * 判断是否相同
- *
- * @return
- */
- public boolean isEqual(String code) {
- if (code != null) {
- return code.toUpperCase().equals(getCode().toUpperCase()) ? true : false;
- } else {
- return false;
- }
- }
这样就可以点击改变一次验证码内容了,并且我们开放出两个方法作为判断验证码或得到验证码,我这只是简单的一个验证码,大家可以自己加入更多的东西。
来源: http://www.phperz.com/article/17/0319/301156.html