当前位置: 移动技术网 > 移动技术>移动开发>Android > Android自定义View实现验证码

Android自定义View实现验证码

2019年07月24日  | 移动技术网移动技术  | 我要评论

本文章是基于鸿洋的android 自定义view (一) 的一些扩展,以及对android自定义view构造函数详解里面内容的一些转载。

首先我们定义一个declare-styleable标签declare-styleable标签的作用是给自定义控件添加自定义属性用的例如这样
(我们定义了文字的颜色,大小,长度,跟背景的颜色)

<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"};

然后我们重写三个构造方法,我们需要注意的是

1、在代码中直接new一个自定义view实例的时候,会调用第一个构造函数.
2、在xml布局文件中调用自定义view的时候,会调用第二个构造函数.
3、在xml布局文件中调用自定义view,并且自定义标签中还有自定义属性时,这里调用的还是第二个构造函数.

也就是说,系统默认只会调用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);
 }

obtainstyledattributes的第二个属性为调用你刚在在attrs.xml文件里生命的declare-styleable标签的name
然后我们重写一下onmeasure方法,通过getmeasuredlength方法计算出宽和高

/**
 * 计算宽高
 *
 * @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;
 }
 }

这样就可以点击改变一次验证码内容了,并且我们开放出两个方法作为判断验证码或得到验证码,我这只是简单的一个验证码,大家可以自己加入更多的东西。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网