zoukankan      html  css  js  c++  java
  • android图片验证码--自绘控件

    自绘控件的内容都是自己绘制出来的 大致流程如下:

    1.定义一个类继承view
    1. 使用TypedArray初始化属性集合
      在view的构造方法中 有一个AttributeSet的参数 很明显是用来保存控件属性信息的 我们也的确可以通过循环然后用键值对的方式获取信息 而TypedArray是用来简化我们的工作的
    2. 重写onMeasure 测量控件大小
    3. 重写onDraw 绘制控件
    2.根据需求在attrs文件中自定义属性

    declare-styleable 声明自定义属性 可以自定义一个新属性 也可以引用已经存在的属性 两者的区别就是 新属性需要添加format 进行类型的定义

    3.在activity的布局文件使用

    自定义图片验证码 演示效果

    示例代码

        <declare-styleable name="VerifyCode">
            <attr name="codeTextSize" format="dimension"/>
            <attr name="codeBackground" format="color"/>
            <attr name="codeLength" format="integer"/>
            <attr name="isContainChar" format="boolean"/>
            <attr name="pointNum" format="integer"/>
            <attr name="linNum" format="integer"/>
        </declare-styleable>
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Bitmap;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.PointF;
    import android.graphics.Rect;
    import android.util.AttributeSet;
    import android.util.TypedValue;
    import android.view.MotionEvent;
    import android.view.View;
    
    import java.util.Random;
    
    /**
     * 类描述:自定义验证码
     * 创建者:lb
     */
    
    public class VerifyCode extends View {
    
        private String mCodeText;//文本内容
        private int mCodeTextSize;//文本大小
        private int mCodeLength;//验证码长度
        private int mCodeBackground;//背景色
        private boolean isContainChar;//验证码是否包含字母
        private int mPointNum;//干扰点数
        private int mLineNum;//干扰线数
    
        private Paint mPaint;//画笔
        private Rect mBound;//绘制范围
        private Bitmap bitmap;//验证码图片
    
        private static Random mRandom = new Random();
        private static int mWidth;//控件的宽度
        private static int mHeight;//控件的高度
    
    
        public VerifyCode(Context context) {
            super(context);
        }
    
        public VerifyCode(Context context, AttributeSet attrs) {
            super(context, attrs);
            initAttrValues(context,attrs);
            initData();
        }
    
        /**
         * 初始化属性集合
         * @param context
         * @param attrs
         */
        private void initAttrValues(Context context, AttributeSet attrs){
            // //获取在AttributeSet中定义的 VerifyCode 中声明的属性的集合
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.VerifyCode);
            //获取TypeArray的长度
            int count=typedArray.getIndexCount();
            for (int i=0;i<count;i++){
                //获取此项属性的ID
                int index=typedArray.getIndex(i);
                switch (index){
                    case R.styleable.VerifyCode_codeTextSize:
                        // 默认设置为16sp,TypeValue类 px转sp 一个转换类
                        mCodeTextSize =typedArray.getDimensionPixelSize(index,(int) TypedValue.applyDimension(
                                TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
                        break;
                    case R.styleable.VerifyCode_codeBackground:
                        mCodeBackground=typedArray.getColor(index,Color.WHITE);
                        break;
                    case R.styleable.VerifyCode_codeLength:
                        mCodeLength=typedArray.getInteger(index,4);
                        break;
                    case R.styleable.VerifyCode_isContainChar:
                        isContainChar=typedArray.getBoolean(index,false);
                        break;
                    case R.styleable.VerifyCode_pointNum:
                        mPointNum=typedArray.getInteger(index,100);
                        break;
                    case R.styleable.VerifyCode_linNum:
                        mLineNum=typedArray.getInteger(index,3);
                        break;
                }
            }
            //Recycles the TypedArray, to be re-used by a later caller
            //官方解释:回收TypedArray 以便后面的使用者重用
            typedArray.recycle();
        }
    
        /**
         * 初始化数据
         */
        private void initData(){
            mCodeText=getValidationCode(mCodeLength,isContainChar);
            mPaint=new Paint();
            mPaint.setAntiAlias(true);
            mBound=new Rect();
            //计算文字所在矩形,可以得到宽高
            mPaint.getTextBounds(mCodeText,0, mCodeText.length(),mBound);
    
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            //获取控件宽高的显示模式
            int widthMode=MeasureSpec.getMode(widthMeasureSpec);
            int heightMode=MeasureSpec.getMode(heightMeasureSpec);
            //获取宽高的尺寸值  固定值的宽度
            int widthSize=MeasureSpec.getSize(widthMeasureSpec);
            int heightSize=MeasureSpec.getSize(heightMeasureSpec);
            //设置宽高默认为建议的最小宽高
            int width= getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec) ;
            int height=getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec);
    
    //        MeasureSpec父布局传递给后代的布局要求 包含 确定大小和三种模式
    //        EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
    //        AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
    //        UNSPECIFIED:表示子布局想要多大就多大,很少使用
            if (widthMode==MeasureSpec.EXACTLY){
                width=widthSize;
            }else{
                mPaint.setTextSize(mCodeTextSize);
                mPaint.getTextBounds(mCodeText,0,mCodeText.length(),mBound);
                float textWidth=mBound.width();
                int tempWidth=(int)(getPaddingLeft()+textWidth+getPaddingRight());
                width=tempWidth;
            }
            if (heightMode == MeasureSpec.EXACTLY)
            {
                height = heightSize;
            } else
            {
                mPaint.setTextSize(mCodeTextSize);
                mPaint.getTextBounds(mCodeText, 0, mCodeText.length(), mBound);
                float textHeight = mBound.height();
                int tempHeight = (int) (getPaddingTop() + textHeight + getPaddingBottom());
                height = tempHeight;
            }
            //设置测量的宽高
            setMeasuredDimension(width,height);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            mWidth=getWidth();
            mHeight=getHeight();
    
            if (bitmap==null){
                bitmap=createBitmapValidate();
            }
            canvas.drawBitmap(bitmap,0,0,mPaint);
    
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()){
                case MotionEvent.ACTION_DOWN:
                    refresh();
                    break;
            }
            return super.onTouchEvent(event);
        }
    
        /**
         * 创建图片验证码
         * @return
         */
        private Bitmap createBitmapValidate(){
            if(bitmap != null && !bitmap.isRecycled()){
                //回收并且置为null
                bitmap.recycle();
                bitmap = null;
            }
            //创建图片
            Bitmap sourceBitmap=Bitmap.createBitmap(mWidth,mHeight, Bitmap.Config.ARGB_8888);
            //创建画布
            Canvas canvas=new Canvas(sourceBitmap);
            //画上背景颜色
            canvas.drawColor(mCodeBackground);
            //初始化文字画笔
            mPaint.setStrokeWidth(3f);
            mPaint.setTextSize(mCodeTextSize);
            //测量验证码字符串显示的宽度值
            float textWidth=mPaint.measureText(mCodeText);
            //画上验证码
            int length = mCodeText.length();
            //计算一个字符的所占位置
            float charLength = textWidth / length;
            for (int i = 1; i <= length; i++) {
                int offsetDegree = mRandom.nextInt(15);
                //这里只会产生0和1,如果是1那么正旋转正角度,否则旋转负角度
                offsetDegree = mRandom.nextInt(2) == 1 ? offsetDegree : -offsetDegree;
                //用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。
                canvas.save();
                //设置旋转
                canvas.rotate(offsetDegree, mWidth / 2, mHeight / 2);
                //给画笔设置随机颜色
                mPaint.setARGB(255, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20,
                        mRandom.nextInt(200) + 20);
                //设置字体的绘制位置
                canvas.drawText(String.valueOf(mCodeText.charAt(i - 1)), (i - 1) * charLength+5,
                        mHeight * 4 / 5f, mPaint);
                //用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。
                canvas.restore();
            }
    
            //重新设置画笔
            mPaint.setARGB(255, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20,
                    mRandom.nextInt(200) + 20);
            mPaint.setStrokeWidth(1);
            //产生干扰效果1 -- 干扰点
            for (int i = 0; i < mPointNum; i++) {
                drawPoint(canvas, mPaint);
            }
            //生成干扰效果2 -- 干扰线
            for (int i = 0; i < mLineNum; i++) {
                drawLine(canvas, mPaint);
            }
            return sourceBitmap;
        }
    
        /**
         * 生成干扰点
         */
        private static void drawPoint(Canvas canvas, Paint paint) {
            PointF pointF = new PointF(mRandom.nextInt(mWidth) + 10, mRandom.nextInt(mHeight) + 10);
            canvas.drawPoint(pointF.x, pointF.y, paint);
        }
    
        /**
         * 生成干扰线
         */
        private static void drawLine(Canvas canvas, Paint paint) {
            int startX = mRandom.nextInt(mWidth);
            int startY = mRandom.nextInt(mHeight);
            int endX = mRandom.nextInt(mWidth);
            int endY = mRandom.nextInt(mHeight);
            canvas.drawLine(startX, startY, endX, endY, paint);
        }
    
        /**
         * 获取验证码
         *
         * @param length 生成随机数的长度
         * @param contains 是否包含字符串
         * @return
         */
        public String getValidationCode(int length,boolean contains) {
            String val = "";
            Random random = new Random();
    
            for (int i = 0; i < length; i++) {
                if (contains){
                    //字母或数字
                    String code = random.nextInt(2) % 2 == 0 ? "char" : "num";
                    //字符串
                    if ("char".equalsIgnoreCase(code)) {
                        //大写或小写字母
                        int choice = random.nextInt(2) % 2 == 0 ? 65 : 97;
                        val += (char) (choice + random.nextInt(26));
                    } else if ("num".equalsIgnoreCase(code)) {
                        val += String.valueOf(random.nextInt(10));
                    }
                }else{
                    val += String.valueOf(random.nextInt(10));
                }
    
            }
            return val;
        }
    
        /**
         *判断验证码是否一致 忽略大小写
         */
        public Boolean isEqualsIgnoreCase(String CodeString) {
            return mCodeText.equalsIgnoreCase(CodeString);
        }
    
        /**
         * 判断验证码是否一致 不忽略大小写
         */
        public Boolean isEquals(String CodeString) {
            return mCodeText.equals(CodeString);
        }
    
        /**
         * 提供外部调用的刷新方法
         */
        public void refresh(){
            mCodeText= getValidationCode(mCodeLength,isContainChar);
            bitmap = createBitmapValidate();
            invalidate();
        }
    }
    
    
  • 相关阅读:
    愤怒的小鸟(angry bird )
    1101模拟
    1029模拟题解
    1028题解
    图床
    数据结构
    博弈论
    差分
    前缀和
    快读和快写
  • 原文地址:https://www.cnblogs.com/r-decade/p/6246749.html
Copyright © 2011-2022 走看看