android 自定义view实现验证码效果(一)
此博客来自:http://blog.csdn.net/lmj623565791/article/details/24252901,感谢博客的无私奉献,在这拿来自己学习下。
自定义控件一直对我来说都比较恐怖,就此有时间好好学习下,
我们知道一个View对象要经过onMeasure()测量 ,onLayout()计算大小,onDraw()到屏幕上,然后根据你的需求看需要那方面就使用了,这是最简单的自定义view,先从最简单的做起
新建一个项目customview1
第一步:先自定义view的属性,首先在res/values/ 下建立一个attrs.xml , 在里面定义我们的属性和声明我们的整个样式。
声明属性有二种方法
第一种:
<?xml version="1.0" encoding="utf-8"?> <resources> <attr name="titleText" format="string" /> <attr name="titleTextColor" format="color" /> <attr name="titleTextSize" format="dimension" /> <declare-styleable name="CustomView"> <attr name="titleText" /> <attr name="titleTextColor" /> <attr name="titleTextSize" /> </declare-styleable> </resources>第二种:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomView">
<attr name="titleText" format="string" />
<attr name="titleTextColor" format="color"/>
<attr name="titleTextSize" format="dimension" />
</declare-styleable>
</resources>
其实都是一样的,只是把定义的属性和属性值分开写了而已,在这说下,就是怕以后有人这么写不明白,
我们定义了字体,字体颜色,字体大小3个属性,format是值该属性的取值类型:
一共有:string,color,demension,integer,enum,reference,float,boolean,fraction,flag;不清楚的可以google一把。
然后在布局中声明我们的自定义View
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res/com.example.customview1"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.example.customview1.CustomView
android:layout_width="200dp"
android:layout_height="100dp"
custom:titleText="1199"
custom:titleTextColor="#ff0000"
custom:titleTextSize="40sp" />
</RelativeLayout>
一定要引入 xmlns:custom="http://schemas.android.com/apk/res/com.example.customview1"我们的命名空间,后面的包路径指的是项目的package
第二步:在View的构造方法中,获得我们的自定义的样式
package com.example.customview1; import java.util.HashSet; import java.util.Random; import java.util.Set; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; public class CustomView extends View { /** * 文本 */ private String mTitleText; /** * 文本的颜色 */ private int mTitleTextColor; /** * 文本的大小 */ private int mTitleTextSize; /** * 绘制时控制文本绘制的范围 */ private Rect mBound; private Paint mPaint; public CustomView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CustomView(Context context) { this(context, null); } /** * 获得我自定义的样式属性 * * @param context * @param attrs * @param defStyle */ public CustomView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); /** * 获得我们所定义的自定义样式属性 */ TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomView, defStyle, 0); int n = a.getIndexCount(); for (int i = 0; i < n; i++) { int attr = a.getIndex(i); switch (attr) { case R.styleable.CustomView_titleText: mTitleText = a.getString(attr); break; case R.styleable.CustomView_titleTextColor: // 默认颜色设置为黑色 mTitleTextColor = a.getColor(attr, Color.BLACK); break; case R.styleable.CustomView_titleTextSize: // 默认设置为16sp,TypeValue也可以把sp转化为px mTitleTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics())); break; } } a.recycle(); /** * 获得绘制文本的宽和高 */ mPaint = new Paint(); mPaint.setTextSize(mTitleTextSize); // mPaint.setColor(mTitleTextColor); mBound = new Rect(); mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound); } @Override protected void onDraw(Canvas canvas) { mPaint.setColor(Color.YELLOW);//设置矩形颜色 canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);//画一个矩形 mPaint.setColor(mTitleTextColor);//设置字体颜色 canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint); } }
我们重写了3个构造方法,默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造调用我们的三个参数的构造,我们在三个参数的构造中获得自定义属性
我们想要在屏幕上显示文字,那必须要重写onDraw()方法,然后通过canvas(画布)画在屏幕上
@Override protected void onDraw(Canvas canvas) { mPaint.setColor(Color.YELLOW);//设置矩形颜色 canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);//画一个矩形 mPaint.setColor(mTitleTextColor);//设置字体颜色 canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint); }
效果图:
如果想让这个显示在屏幕的中间,有2个办法
1:把他的父布局为RelativeLayout,就很好搞定,
2:计算屏幕的宽和高,自身控件的宽和高,,因为在屏幕上画一个矩形其实就是定义2个坐标点,
我们自定义宽和高都是写死的在布局文件中是这么写的
android:layout_width="200dp"
android:layout_height="100dp"
一般在开发中很少去这么做,那我们一般写成wrap_content,现在把宽和高改成wrap_content试试
效果出乎我们的意料:
这是为什么呢?
所有的子view的宽和高都是父view给定的
系统帮我们测量的高度和宽度都是MATCH_PARNET,当我们设置明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果,当我们设置为WRAP_CONTENT,或者MATCH_PARENT系统帮我们测量的结果就是MATCH_PARENT的长度。
记得在开头说过一个测量的方法onMeasure()
这个时候就要重写这个方法了,先看下View.java源码中的测量方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { // first clears the measured dimension flag mPrivateFlags &= ~MEASURED_DIMENSION_SET; if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE); } // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); // flag not set, setMeasuredDimension() was not invoked, we raise // an exception to warn the developer if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) { throw new IllegalStateException("onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); } mPrivateFlags |= LAYOUT_REQUIRED; } mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; }
我们看到measure()方法被final修饰了,所以这个方法不能重写,但是在阅读代码时发现
onMeasure(widthMeasureSpec, heightMeasureSpec);方法,.这就是为什么我们要重写这个方法的原因,
在onMeasure()方法中是调用了setMeasuredDimension()实现的
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
getDefaultSize()方法
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
发现一个控件的宽和高是由size和mode构成的,而mode有三个MeasureSpec.UNSPECIFIED, MeasureSpec.AT_MOST,MeasureSpec.EXACTLY
这是什么意思呢,看下面解释
MeasureSpec.UNSPECIFIED:表示子布局想要多大就多大,很少使用
MeasureSpec.AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
MeasureSpec.EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
现在我们来重写onMeasure()方法
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = 0; int height = 0; /** * 设置宽度 */ int specMode = MeasureSpec.getMode(widthMeasureSpec);//获取宽度的mode int specSize = MeasureSpec.getSize(widthMeasureSpec);//获取宽度大小 Log.e("onMeasure","specMode="+specMode); Log.e("onMeasure","specSize="+specSize); 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);//获取高度的mode specSize = MeasureSpec.getSize(heightMeasureSpec);//获取高度大小 Log.e("onMeasure","specMode="+specMode); Log.e("onMeasure","specSize="+specSize); switch (specMode) { case MeasureSpec.EXACTLY:// 明确指定了 height = getPaddingTop() + getPaddingBottom() + specSize; break; case MeasureSpec.AT_MOST:// 一般为WARP_CONTENT height = getPaddingTop() + getPaddingBottom() + mBound.height(); break; } Log.e("onMeasure","width="+width+"height="+height); setMeasuredDimension(width, height);//重写测量 }
记得我们的博客标题么.要实现验证码效果,其实就是几个数字在循环的变动,这个很简单
给view设置一个点击事件:
this.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
mTitleText = randomText();
postInvalidate();
}
});
生成四个随机数的方法
private 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();
}
我们的验证码效果就出来了
ok 休息下,累死了
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。