Android自定义View(一)(验证码)
自定义View在Android开发中是我们避免不了的,如果你需要写出漂亮的界面和一些特殊样式和功能的组件,你用安卓自带的布局来写是相当困难的,所以你就必须要用到自定义的View。但是许多朋友对于自定义View还是有点恐惧的,我这里说下标准的自定义View的步骤及快速写法,以后套用即可,不用则写一堆代码且思路不清晰。总结了下自定义View的步骤:
1、自定义View的属性
2、在View的构造方法中获得我们自定义的属性
[ 3、重写onMesure ]
4、重写onDraw
我把3用[]标出了,所以说3不一定是必须的,当然了大部分情况下还是需要重写的。
1、自定义View的属性,首先在res/values/ 下建立一个attrs.xml , 在里面定义我们的属性和声明我们的整个样式。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="CustomView1_Text" format="string" />
<attr name="CustomView1_TextColor" format="color" />
<attr name="CustomView1_TextSize" format="dimension" />
<declare-styleable name="CusotomView1">
<attr name="CustomView1_Text" />
<attr name="CustomView1_TextColor" />
<attr name="CustomView1_TextSize" />
</declare-styleable>
</resources>
这里定义了字体,字体颜色,字体大小3个属性,format是值该属性的取值类型:
一共有:string,color,demension,integer,enum,reference,float,boolean,fraction,flag;不清楚的可以问下度娘。
然后在布局中声明我们的自定义View
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res/com.xue.customView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="${relativePackage}.${activityClass}" >
<com.xue.customView1.Customview01
android:id="@+id/customview1"
android:layout_width="200dp"
android:layout_height="200dp"
custom:CustomView1_Text="1234"
custom:CustomView1_TextColor="#ff0000"
custom:CustomView1_TextSize="40sp"/>
</RelativeLayout>
一定要引入
xmlns:custom=”http://schemas.android.com/apk/res/com.xue.customView1”我们的命名空间,前面xmlns:后面的custom是自定义的命名,这里写了什么,后面自己的组件中写属性就要用什么;例如: custom:CustomView1_Text=”我是个自定义的!”
后面的包路径指的是项目的package
2、在View的构造方法中,获得我们的自定义的样式
下面是我的自定义的View类
package com.xue.customView1;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
/**
* 自定义View(一)
* @author xyu
*
*/
@SuppressLint("NewApi")
public class Customview01 extends View {
/**
* 文本内容
*/
private String MyText;
/**
* 文本的颜色
*/
private int myTextColor;
/**
* 文本大小
*/
private int myTextSize;
/**
* 绘制时控制文本绘制的范围
*/
private Rect mBound;
private Paint myPaint;
public Customview01(Context context) {
this(context, null);
}
public Customview01(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* 获得我们所定义的自定义样式属性
*
* @param context
* @param attrs
* @param defStyleAttr
*/
public Customview01(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
/**
* 获得我们所自定义的样式属性
*/
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.CusotomView1, defStyleAttr, 0);
// 获得属性数量
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.CusotomView1_CustomView1_Text:
MyText=a.getString(attr);
break;
case R.styleable.CusotomView1_CustomView1_TextColor:
//默认颜色设置为黑色
myTextColor=a.getColor(attr, Color.BLACK);
break;
case R.styleable.CusotomView1_CustomView1_TextSize:
myTextSize=a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,16,getResources().getDisplayMetrics()));
break;
}
}
//记得回收下a,避免资源浪费
a.recycle();
/**
* 获得绘制文本的宽和高
*/
myPaint=new Paint();
myPaint.setTextSize(myTextSize);
mBound=new Rect();
myPaint.getTextBounds(MyText, 0, MyText.length(), mBound);
}
}
这里重写了3个构造方法,默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造调用我们的三个参数的构造,我们在三个参数的构造中获得自定义属性。
3、我们重写onDraw,onMesure调用系统提供的:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
myPaint.setColor(Color.GREEN);
//getWidth()、getHeight: View在设定好布局后整个View的宽度、高度。
//getMeasuredWidth()、getMeasuredHeight: 对View上的內容进行测量后得到的View內容占据的宽度、高度
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), myPaint);
myPaint.setColor(myTextColor);
canvas.drawText(MyText, getWidth()/2-mBound.width()/2, getHeight()/2+mBound.height()/2, myPaint);
}
此时的效果是:
是不是觉得还不错,基本已经实现了自定义View。但是此时如果我们把布局文件的宽和高写成wrap_content,会发现效果并不是我们的预期:
系统帮我们测量的高度和宽度都是match_parent,当我们设置明确的宽度获得高度时,系统帮我们测量的结果就是我们设置的结果,当我们设置为wrap_content,或者match_parent系统帮我们测量的结果就是match_parent的长度。
所以,当前设置为wrap_content时,我们需要自己进行测量,即重写onMeasure方法:
重写之前,先了解下MeasureSpec的specMode,一共三种类型:
EXACTLY:一般设置了明确的值或者是match_parent
AT_MOST:表示子布局限制在一个最大值内,一般为wrap_content
UNSPECIFIED:表示子布局想要多大就多大,很少使用
下面是重写的onMeasure代码:
int width = 0;
int height = 0;
/**
* 设置宽度
*/
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
switch (specMode) {
case MeasureSpec.EXACTLY:// 明确指定了
width = getPaddingLeft() + getPaddingRight() + specSize;
break;
case MeasureSpec.AT_MOST:// 一般为WARP_CONTENT
width = getPaddingLeft() + getPaddingRight() + mBound.width();
break;
}
/**
* 设置高度
*/
specMode = MeasureSpec.getMode(heightMeasureSpec);
specSize = MeasureSpec.getSize(heightMeasureSpec);
switch (specMode) {
case MeasureSpec.EXACTLY:// 明确指定了
height = getPaddingTop() + getPaddingBottom() + specSize;
break;
case MeasureSpec.AT_MOST:// 一般为WARP_CONTENT
height = getPaddingTop() + getPaddingBottom() + mBound.height();
break;
}
setMeasuredDimension(width, height);
此时修改下布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res/com.xue.customView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="${relativePackage}.${activityClass}" >
<com.xue.customView1.Customview01
android:id="@+id/customview1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="10dp"
custom:CustomView1_Text="1234"
custom:CustomView1_TextColor="#ff0000"
custom:CustomView1_TextSize="40sp" />
</RelativeLayout>
现在的效果是:
完全复合我们的预期,现在可以对高度、宽度进行随便的设置了,基本可以满足我们的需求。
当然了,这样下来这个自定义View与TextView相比岂不是没什么优势,所有我们觉得给自定义View添加一个事件(实现类似于验证码的功能):
在构造中添加:
/**
* 对本对象添加点击事件(即这个自定义是view)
*/
this.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
MyText = randomText();
postInvalidate();
}
});
randomText方法:
/**
* 随机生成文本内容(4个数字)
* @return 随机生成并组合后的文本(4个数字)
*/
protected String randomText() {
Random random = new Random();
Set<Integer> set = new HashSet<Integer>();
while (set.size() < 4) {
int randomInt = random.nextInt(10);
set.add(randomInt);
}
StringBuffer sb = new StringBuffer();
for (Integer i : set) {
sb.append("" + i);
}
return sb.toString();
}
下面是最新运行效果为:
我们添加了一个点击事件,每次让它随机生成一个4位的随机数,类似验证码的就出来了。有兴趣的同学在onDraw方法中添加一点噪点,就可以实现现在许多app用的验证码功能。
[源码点击此处下载](http://download.youkuaiyun.com/detail/u010123643/9181039)