<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的参数有
//文本 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"};然后我们重写三个构造方法,我们需要注意的是
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
/** * 计算宽高 * * @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的长度。
//xx表示widthMeasureSpec或者heightMeasureSpecif(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()文字居中
<?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可以是任何字母
既然是验证码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; } }这样就可以点击改变一次验证码内容了,并且我们开放出两个方法作为判断验证码或得到验证码,我这只是简单的一个验证码,大家可以自己加入更多的东西。