Android仿斗鱼领取鱼丸文字验证(三)

今天来写最后一部分,九宫格部分,先来看一下最终的效果图:

效果图


一、分析功能

直接看下边的九宫格,九宫格里边的文字随机,文字颜色随机,并且每个文字都进行了不同程度的扭曲变形,点击看不清的时候,可以对九宫格的内容进行重置,要解决的问题差不多就这么多.

(1)对于九宫格来说,安卓原生提供了GridView,使用它可以很轻松的完成该功能.
(2)对于文字处理,随机生成文字和文字颜色,这个很简单,在上一篇文章中我们封装了一个工具类,这个不用再去处理,直接拿来用就可以了,对于文字扭曲,找了一下画布的方法,好像并没有什么绘制文字的方法可以实现想要的效果,所以换种思路,对图片进行处理有没有方法达到想要的效果呢?查阅了一些资料,找到了一个方法drawBitmapMesh,可以实现对图片进行扭曲.好了,有了解决方案,就可以来愉快的coding了.


二、代码实现

这次先来解决一下图片扭曲的问题,然后再向下进行.
drawBitmapMesh这个方法大家可以参考一下大神的博客:
自定义控件其实很简单5/12
只需要看到大波妹(嘿嘿嘿~~)那部分就可以,咳咳,回归正题哈,这里我们把代码copy出来改动一下,代码如下:

public class HanZiCode extends ImageView {  
    /**
     * 分割数
     */
    private static final int WIDTH = 9, HEIGHT = 9;
    /**
     * 焦点数
     */
    private static final int COUNT = (WIDTH + 1) * (HEIGHT + 1);
    /**
     * 位图对象
     */
    private Bitmap mBitmap;
    /**
     * 基准点坐标数组
     */
    private float[] matrixOriganal = new float[COUNT * 2];
    /**
     * 变换后点坐标数组
     */
    private float[] matrixMoved = new float[COUNT * 2];
    /**
     * 触摸屏幕时手指的xy坐标
     */
    private float clickX, clickY;
    /**
     * 基准点、变换点和线段的绘制Paint
     */
    private Paint origPaint, movePaint, linePaint;
    /**
     * 画笔
     */
    private Paint mPaint;
    /**
     * 矩形区域
     */
    private Rect mBounds;
    /**
     * 文字
     */
    private String mText = "逗"; 

    public HanZiView(Context context, AttributeSet set) {  
        super(context, set);  
        setFocusable(true);  

        // 实例画笔并设置颜色  
        origPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
        origPaint.setColor(0x660000FF);  
        movePaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
        movePaint.setColor(0x99FF0000);  
        linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
        linePaint.setColor(0xFFFFFB00);  

        // 初始化坐标数组  
        int index = 0;  
        for (int y = 0; y <= HEIGHT; y++) {  
            float fy = mBitmap.getHeight() * y / HEIGHT;  

            for (int x = 0; x <= WIDTH; x++) {  
                float fx = mBitmap.getWidth() * x / WIDTH;  
                setXY(matrixMoved, index, fx, fy);  
                setXY(matrixOriganal, index, fx, fy);  
                index += 1;  
            }  
        } 
        mPaint = new Paint();
        mPaint.setColor(Color.Red); 
        mPaint.setTextSize(80);
        // 随机使用粗体
        mPaint.setFakeBoldText(r.nextBoolean());
        mBounds = new Rect();
        mPaint.getTextBounds(mText, 0, mText.length(), mBounds);
    }  

    /** 
     * 设置坐标数组 
     *  
     * @param array 
     *            坐标数组 
     * @param index 
     *            标识值 
     * @param x 
     *            x坐标 
     * @param y 
     *            y坐标 
     */  
    private void setXY(float[] array, int index, float x, float y) {  
        array[index * 2 + 0] = x;  
        array[index * 2 + 1] = y;  
    }  

    @Override  
    protected void onDraw(Canvas canvas) {  
        // 创建带有文字的图片
        mBitmap = Bitmap.createBitmap(getWidth(), getHeight(),Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(mBitmap);
        c.drawText(mText, getWidth() / 2 - mBounds.width() / 2, mBounds.height() / 2 + getHeight() / 2, mPaint);
        // 绘制网格位图  
        canvas.drawBitmapMesh(mBitmap, WIDTH, HEIGHT, matrixMoved, 0, null, 0, null);  
        // 绘制参考元素  
        drawGuide(canvas);  
    }  

    /** 
     * 绘制参考元素 
     *  
     * @param canvas 
     *            画布 
     */  
    private void drawGuide(Canvas canvas) {  
        for (int i = 0; i < COUNT * 2; i += 2) {  
            float x = matrixOriganal[i + 0];  
            float y = matrixOriganal[i + 1];  
            canvas.drawCircle(x, y, 4, origPaint);  

            float x1 = matrixOriganal[i + 0];  
            float y1 = matrixOriganal[i + 1];  
            float x2 = matrixMoved[i + 0];  
            float y2 = matrixMoved[i + 1];  
            canvas.drawLine(x1, y1, x2, y2, origPaint);  
        }  

        for (int i = 0; i < COUNT * 2; i += 2) {  
            float x = matrixMoved[i + 0];  
            float y = matrixMoved[i + 1];  
            canvas.drawCircle(x, y, 4, movePaint);  
        }  

        canvas.drawCircle(clickX, clickY, 6, linePaint);  
    }  

    /** 
     * 计算变换数组坐标 
     */  
    private void smudge() {  
        for (int i = 0; i < COUNT * 2; i += 2) {  

            float xOriginal = matrixOriganal[i + 0];  
            float yOriginal = matrixOriganal[i + 1];  

            float dist_click_to_origin_x = clickX - xOriginal;  
            float dist_click_to_origin_y = clickY - yOriginal;  

            float kv_kat = dist_click_to_origin_x * dist_click_to_origin_x + dist_click_to_origin_y * dist_click_to_origin_y;  

            float pull = (float) (1000000 / kv_kat / Math.sqrt(kv_kat));  

            if (pull >= 1) {  
                matrixMoved[i + 0] = clickX;  
                matrixMoved[i + 1] = clickY;  
            } else {  
                matrixMoved[i + 0] = xOriginal + dist_click_to_origin_x * pull;  
                matrixMoved[i + 1] = yOriginal + dist_click_to_origin_y * pull;  
            }  
        }  
    }  

    @Override  
    public boolean onTouchEvent(MotionEvent event) {  
        clickX = event.getX();  
        clickY = event.getY();  
        smudge();  
        invalidate();  
        return true;  
    }  
}  

然后在xml中使用一下:

xml:

<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:hz="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/ll_all"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context="com.example.junweiliu.hanzicode.MainActivity">
    <com.example.junweiliu.hanzicode.HanZiView
        android:layout_width="100dp"
        android:layout_height="100dp"/>
</LinearLayout>

看一下效果图:

图片扭曲效果图

很不错,达到了想要的效果,简单分析一下这段代码,这段代码其实就是通过点击的位置,去改变原始图片的各个原始点的坐标,然后使用drawBitmapMesh方法去实现对新的坐标的图像绘制,从而实现了图片的扭曲,这里重点看一下变换坐标的方法:

    /** 
     * 计算变换数组坐标 
     */  
    private void smudge() {  
        for (int i = 0; i < COUNT * 2; i += 2) {  

            float xOriginal = matrixOriganal[i + 0];  
            float yOriginal = matrixOriganal[i + 1];  

            float dist_click_to_origin_x = clickX - xOriginal;  
            float dist_click_to_origin_y = clickY - yOriginal;  

            float kv_kat = dist_click_to_origin_x * dist_click_to_origin_x + dist_click_to_origin_y * dist_click_to_origin_y;  
            // 扭曲大小
            float pull = (float) (1000000 / kv_kat / Math.sqrt(kv_kat));  

            if (pull >= 1) {  
                matrixMoved[i + 0] = clickX;  
                matrixMoved[i + 1] = clickY;  
            } else {  
                matrixMoved[i + 0] = xOriginal + dist_click_to_origin_x * pull;  
                matrixMoved[i + 1] = yOriginal + dist_click_to_origin_y * pull;  
            }  
        }  
    }  

可能算法会有很多,可以实现不同的效果,有兴趣的同学可以去研究一下,这里只看一下这部分,其实不难理解,这部分就是去生成新的扭曲坐标,我们找到一个关键变量pull,这个变量的大小决定了扭曲度的大小.所以可以适当修改pull的值,来调节扭曲度.最关键的问题解决了,接下来就很简单了.完整代码如下:

自定义属性attr:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--HanZiView相关-->
    <!--左右宽度留白-->
    <attr name="borderWidthSize" format="dimension"/>
    <!--上下高度留白-->
    <attr name="borderHeightSize" format="dimension"/>
    <!--旋转的度数-->
    <attr name="rotateSize" format="integer"/>
    <!--扭曲的系数-->
    <attr name="smudgeCoefficieng" format="float"/>
    <!--文字的颜色-->
    <attr name="textColor" format="color"/>
    <!--文字大小-->
    <attr name="textSize" format="dimension"/>
    <declare-styleable name="HanZiView">
        <attr name="borderWidthSize"/>
        <attr name="borderHeightSize"/>
        <attr name="rotateSize"/>
        <attr name="smudgeCoefficieng"/>
        <attr name="textColor"/>
        <attr name="textSize"/>
    </declare-styleable>
</resources>

HanZiView:

package com.example.junweiliu.hanzicode;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ImageView;

import java.util.Random;

/**
 * Created by junweiliu on 16/5/3.
 */
public class HanZiView extends ImageView {
    /**
     * 分割数
     */
    private static final int WIDTH = 9, HEIGHT = 9;
    /**
     * 焦点数
     */
    private static final int COUNT = (WIDTH + 1) * (HEIGHT + 1);
    /**
     * 位图对象
     */
    private Bitmap mBitmap;
    /**
     * 基准点坐标数组
     */
    private float[] matrixOriganal = new float[COUNT * 2];
    /**
     * 变换后点坐标数组
     */
    private float[] matrixMoved = new float[COUNT * 2];
    /**
     * 避免过多的重绘
     */
    private boolean needDraw = true;
    /**
     * 画笔
     */
    private Paint mPaint;
    /**
     * 文字大小
     */
    private int mTextSize;
    /**
     * 文字
     */
    private String mText = "逗";
    /**
     * 文字颜色
     */
    private int mTextColor;
    /**
     * 矩形区域
     */
    private Rect mBounds;
    /**
     * 四周的留白宽度值
     */
    private int mBorderWidthSize;
    /**
     * 四周的留白高度值
     */
    private int mBorderHeightSize;
    /**
     * 获取扭曲点的随机位置
     */
    private float mRandomX, mRandomY;
    /**
     * 字体旋转的角度
     */
    private int DEFAULT_ROTATE_SIZE;
    /**
     * 扭曲的系数
     */
    private float SMUDGE_COEFFICIENT;
    /**
     * 旋转的角度
     */
    private int rotate;
    /**
     * 随机
     */
    private Random r = new Random();

    public HanZiView(Context context) {
        this(context, null);
    }

    public HanZiView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public HanZiView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 实例画笔并设置颜色
        origPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        origPaint.setColor(0x660000FF);
        movePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        movePaint.setColor(0x99FF0000);
        linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        linePaint.setColor(0xFFFFFB00);
        // 获取自定义属性
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.HanZiView);
        mBorderWidthSize = ta.getDimensionPixelSize(R.styleable.HanZiView_borderWidthSize, DisplayUtil.dip2px(context, 30));
        mBorderHeightSize = ta.getDimensionPixelSize(R.styleable.HanZiView_borderHeightSize, DisplayUtil.dip2px(context, 20));
        DEFAULT_ROTATE_SIZE = ta.getInteger(R.styleable.HanZiView_rotateSize, 40);
        SMUDGE_COEFFICIENT = ta.getFloat(R.styleable.HanZiView_smudgeCoefficieng, 1.0f);
        mTextSize = ta.getDimensionPixelSize(R.styleable.HanZiView_textSize, DisplayUtil.sp2px(context, 20));
        mTextColor = ta.getColor(R.styleable.HanZiView_textColor, 0);
        ta.recycle();
        init();

    }

    /**
     * 初始化数据
     */
    private void init() {
        mPaint = new Paint();
        mPaint.setTextSize(mTextSize);
        if (0 != mTextColor) {
            mPaint.setColor(mTextColor);
        } else {
            mPaint.setColor(Util.randomDarkColor());
        }
        // 随机使用粗体
        mPaint.setFakeBoldText(r.nextBoolean());
        mBounds = new Rect();
        mPaint.getTextBounds(mText, 0, mText.length(), mBounds);

    }

    /**
     * 随机生成扭曲位置的XY坐标
     */
    private void initRandomXY() {
        mRandomX = (float) Math.random() * getWidth();
        mRandomY = (float) Math.random() * getHeight();
    }

    /**
     * 设置显示的文字
     *
     * @param text
     */
    public void setText(String text) {
        this.mText = text;
        needDraw = true;
        invalidate();

    }

    /**
     * 设置显示的文字的颜色
     *
     * @param color
     */
    public void setTextColor(int color) {
        mPaint.setColor(color);
        needDraw = true;
        invalidate();

    }

    /**
     * 设置显示文字的颜色的ARGB
     *
     * @param A
     * @param R
     * @param G
     * @param B
     */
    public void setTextColor(int A, int R, int G, int B) {
        mPaint.setARGB(A, R, G, B);
        needDraw = true;
        invalidate();

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        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 + mBorderWidthSize;
                break;
            case MeasureSpec.AT_MOST:
                width = getPaddingLeft() + getPaddingRight() + mBounds.width() + mBorderWidthSize;
                break;
            case MeasureSpec.UNSPECIFIED:
                width = getPaddingLeft() + getPaddingRight() + mBounds.width() + mBorderWidthSize;
                break;
        }
        specMode = MeasureSpec.getMode(heightMeasureSpec);
        specSize = MeasureSpec.getSize(heightMeasureSpec);
        switch (specMode) {
            case MeasureSpec.EXACTLY:
                height = getPaddingTop() + getPaddingBottom() + specSize + mBorderHeightSize;
                break;
            case MeasureSpec.AT_MOST:
                height = getPaddingTop() + getPaddingBottom() + mBounds.height() + mBorderHeightSize;
                break;
            case MeasureSpec.UNSPECIFIED:
                height = getPaddingTop() + getPaddingBottom() + mBounds.height() + mBorderHeightSize;
                break;

        }

        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (needDraw) {
            // 创建带有文字的图片
            mBitmap = Bitmap.createBitmap(getWidth(), getHeight(),
                    Bitmap.Config.ARGB_8888);
            Canvas c = new Canvas(mBitmap);
            c.drawText(mText, getWidth() / 2 - mBounds.width() / 2, mBounds.height() / 2 + getHeight() / 2, mPaint);
            // 初始化坐标数组
            int index = 0;
            for (int y = 0; y <= HEIGHT; y++) {
                float fy = mBitmap.getHeight() * y / HEIGHT;

                for (int x = 0; x <= WIDTH; x++) {
                    float fx = mBitmap.getWidth() * x / WIDTH;
                    setXY(matrixMoved, index, fx, fy);
                    setXY(matrixOriganal, index, fx, fy);
                    index += 1;
                }
            }
            initRandomXY();
            smudgeMatrix();
            // 随机旋转角度
            rotate = (int) (Math.floor(Math.random() * DEFAULT_ROTATE_SIZE + 1)
                    - Math.floor(Math.random() * DEFAULT_ROTATE_SIZE * 2 + 1));
        }
        needDraw = false;
        // 绘制网格位图
        canvas.drawBitmapMesh(rotateBitmap(rotate, mBitmap), WIDTH, HEIGHT, matrixMoved, 0, null, 0, null);
    }

    /**
     * 设置坐标数组
     *
     * @param array 坐标数组
     * @param index 标识值
     * @param x     x坐标
     * @param y     y坐标
     */
    private void setXY(float[] array, int index, float x, float y) {
        array[index * 2 + 0] = x;
        array[index * 2 + 1] = y;
    }

    /**
     * 计算变换数组坐标
     */
    private void smudgeMatrix() {
        for (int i = 0; i < COUNT * 2; i += 2) {

            float xOriginal = matrixOriganal[i + 0];
            float yOriginal = matrixOriganal[i + 1];

            float dist_random_to_origin_x = mRandomX - xOriginal;
            float dist_random_to_origin_y = mRandomY - yOriginal;
            float kv_kat = dist_random_to_origin_x * dist_random_to_origin_x + dist_random_to_origin_y * dist_random_to_origin_y;

            float pull = (float) (1000 * SMUDGE_COEFFICIENT / kv_kat / Math.sqrt(kv_kat));

            if (pull >= 1) {
                matrixMoved[i + 0] = mRandomX;
                matrixMoved[i + 1] = mRandomY;
            } else {
                matrixMoved[i + 0] = xOriginal + dist_random_to_origin_x * pull;
                matrixMoved[i + 1] = yOriginal + dist_random_to_origin_y * pull;
            }
        }
    }

    /**
     * 旋转图片
     *
     * @param degree
     * @param bitmap
     * @return
     */
    public Bitmap rotateBitmap(int degree, Bitmap bitmap) {
        Matrix matrix = new Matrix();
        matrix.postRotate(degree);
        Bitmap bm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
                bitmap.getHeight(), matrix, true);
        return bm;
    }
}

这里用随机的XY坐标来代替点击的坐标,把pull进行处理,可以自由的去改变扭曲度,加入了一个旋转图像方法,当然如果需要,还可以去添加和修改里边的内容.


三、完整代码实及现

GridView的内容就不再写了.唯一注意的一点就是,需要重新测量一下GridView的高度,并适当添加一些高度,不然会出现GridView有滚动条的现象.
最后结合前两篇文章,贴一下完整的代码实现:
Android仿斗鱼领取鱼丸文字验证(一)
Android仿斗鱼领取鱼丸文字验证(二)

attr:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--AnswerLayout相关-->
    <!--删除按钮图像-->
    <attr name="deleteBtnSrc" format="reference"/>
    <!--汉字格边框颜色-->
    <attr name="hanziBorderColor" format="color"/>
    <!--汉字格边框的粗度-->
    <attr name="hanziBorderStrokeWidth" format="float"/>
    <!--汉字格的个数-->
    <attr name="hanziTestNum" format="integer"/>
    <!--文字的颜色-->
    <attr name="hanziTextColor" format="color"/>
    <!--文字大小-->
    <attr name="hanziTextSize" format="dimension"/>
    <declare-styleable name="AnswerLayout">
        <attr name="deleteBtnSrc"/>
        <attr name="hanziBorderColor"/>
        <attr name="hanziBorderStrokeWidth"/>
        <attr name="hanziTestNum"/>
        <attr name="hanziTextColor"/>
        <attr name="hanziTextSize"/>
    </declare-styleable>
    <!--HanZiView相关-->
    <!--左右宽度留白-->
    <attr name="borderWidthSize" format="dimension"/>
    <!--上下高度留白-->
    <attr name="borderHeightSize" format="dimension"/>
    <!--旋转的度数-->
    <attr name="rotateSize" format="integer"/>
    <!--扭曲的系数-->
    <attr name="smudgeCoefficieng" format="float"/>
    <!--文字的颜色-->
    <attr name="textColor" format="color"/>
    <!--文字大小-->
    <attr name="textSize" format="dimension"/>
    <declare-styleable name="HanZiView">
        <attr name="borderWidthSize"/>
        <attr name="borderHeightSize"/>
        <attr name="rotateSize"/>
        <attr name="smudgeCoefficieng"/>
        <attr name="textColor"/>
        <attr name="textSize"/>
    </declare-styleable>
    <!--CodeView相关-->
    <!--扰乱项的个数-->
    <attr name="disturbSize" format="integer"/>
    <!--干扰项文字的大小-->
    <attr name="disturbTextSize" format="dimension"/>
    <!--干扰项文字颜色-->
    <attr name="disturbTextColor" format="color"/>
    <!--验证码文字大小-->
    <attr name="codeTextSize" format="dimension"/>
    <!--验证码文字颜色-->
    <attr name="codeTextColor" format="color"/>
    <!--画布旋转的度数-->
    <attr name="rotate" format="integer"/>
    <declare-styleable name="CodeView">
        <attr name="disturbSize"/>
        <attr name="disturbTextSize"/>
        <attr name="disturbTextColor"/>
        <attr name="codeTextSize"/>
        <attr name="codeTextColor"/>
        <attr name="rotate"/>
    </declare-styleable>
</resources>

xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:hz="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/ll_all"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context="com.example.junweiliu.hanzicode.MainActivity">

    <LinearLayout
        android:layout_width="280dp"
        android:layout_height="wrap_content"
        android:background="@mipmap/hz_nor_ng"
        android:orientation="vertical"
        android:padding="20dp"
        >

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="horizontal"
            >

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="10dp"
                android:text="验证码:"
                android:textSize="14sp"
                />

            <com.example.junweiliu.hanzicode.AnswerLayout
                android:id="@+id/al_mal"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:orientation="horizontal"
                />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:gravity="center"
            android:orientation="horizontal"
            >

            <com.example.junweiliu.hanzicode.CodeView
                android:id="@+id/cv_hz"
                android:layout_width="120dp"
                android:layout_height="42dp"
                hz:rotate="4"
                />

            <Button
                android:id="@+id/btn_reset"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                android:background="@null"
                android:text="看不清?"
                android:textColor="#4791FF"
                android:textSize="14sp"
                />
        </LinearLayout>

        <GridView
            android:id="@+id/gv_hz"
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="10dp"
            android:gravity="center"
            android:horizontalSpacing="10dp"
            android:listSelector="@android:color/transparent"
            android:numColumns="3"
            android:scrollbars="none"
            android:stretchMode="columnWidth"
            android:verticalSpacing="10dp"
            />
    </LinearLayout>
</LinearLayout>

HanZiView:

package com.example.junweiliu.hanzicode;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ImageView;

import java.util.Random;

/**
 * Created by junweiliu on 16/5/3.
 */
public class HanZiView extends ImageView {
    /**
     * 分割数
     */
    private static final int WIDTH = 9, HEIGHT = 9;
    /**
     * 焦点数
     */
    private static final int COUNT = (WIDTH + 1) * (HEIGHT + 1);
    /**
     * 位图对象
     */
    private Bitmap mBitmap;
    /**
     * 基准点坐标数组
     */
    private float[] matrixOriganal = new float[COUNT * 2];
    /**
     * 变换后点坐标数组
     */
    private float[] matrixMoved = new float[COUNT * 2];
    /**
     * 避免过多的重绘
     */
    private boolean needDraw = true;
    /**
     * 画笔
     */
    private Paint mPaint;
    /**
     * 文字大小
     */
    private int mTextSize;
    /**
     * 文字
     */
    private String mText = "逗";
    /**
     * 文字颜色
     */
    private int mTextColor;
    /**
     * 矩形区域
     */
    private Rect mBounds;
    /**
     * 四周的留白宽度值
     */
    private int mBorderWidthSize;
    /**
     * 四周的留白高度值
     */
    private int mBorderHeightSize;
    /**
     * 获取扭曲点的随机位置
     */
    private float mRandomX, mRandomY;
    /**
     * 字体旋转的角度
     */
    private int DEFAULT_ROTATE_SIZE;
    /**
     * 扭曲的系数
     */
    private float SMUDGE_COEFFICIENT;
    /**
     * 旋转的角度
     */
    private int rotate;
    /**
     * 随机
     */
    private Random r = new Random();

    public HanZiView(Context context) {
        this(context, null);
    }

    public HanZiView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public HanZiView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 获取自定义属性
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.HanZiView);
        mBorderWidthSize = ta.getDimensionPixelSize(R.styleable.HanZiView_borderWidthSize, DisplayUtil.dip2px(context, 30));
        mBorderHeightSize = ta.getDimensionPixelSize(R.styleable.HanZiView_borderHeightSize, DisplayUtil.dip2px(context, 20));
        DEFAULT_ROTATE_SIZE = ta.getInteger(R.styleable.HanZiView_rotateSize, 40);
        SMUDGE_COEFFICIENT = ta.getFloat(R.styleable.HanZiView_smudgeCoefficieng, 1.0f);
        mTextSize = ta.getDimensionPixelSize(R.styleable.HanZiView_textSize, DisplayUtil.sp2px(context, 20));
        mTextColor = ta.getColor(R.styleable.HanZiView_textColor, 0);
        ta.recycle();
        init();

    }

    /**
     * 初始化数据
     */
    private void init() {
        mPaint = new Paint();
//        mPaint.setAntiAlias(true);
        mPaint.setTextSize(mTextSize);
        if (0 != mTextColor) {
            mPaint.setColor(mTextColor);
        } else {
            mPaint.setColor(Util.randomDarkColor());
        }
        // 随机使用粗体
        mPaint.setFakeBoldText(r.nextBoolean());
        mBounds = new Rect();
        mPaint.getTextBounds(mText, 0, mText.length(), mBounds);

    }

    /**
     * 随机生成扭曲位置的XY坐标
     */
    private void initRandomXY() {
        mRandomX = (float) Math.random() * getWidth();
        mRandomY = (float) Math.random() * getHeight();
    }

    /**
     * 设置显示的文字
     *
     * @param text
     */
    public void setText(String text) {
        this.mText = text;
        needDraw = true;
        invalidate();

    }

    /**
     * 设置显示的文字的颜色
     *
     * @param color
     */
    public void setTextColor(int color) {
        mPaint.setColor(color);
        needDraw = true;
        invalidate();

    }

    /**
     * 设置显示文字的颜色的ARGB
     *
     * @param A
     * @param R
     * @param G
     * @param B
     */
    public void setTextColor(int A, int R, int G, int B) {
        mPaint.setARGB(A, R, G, B);
        needDraw = true;
        invalidate();

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        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 + mBorderWidthSize;
                break;
            case MeasureSpec.AT_MOST:
                width = getPaddingLeft() + getPaddingRight() + mBounds.width() + mBorderWidthSize;
                break;
            case MeasureSpec.UNSPECIFIED:
                width = getPaddingLeft() + getPaddingRight() + mBounds.width() + mBorderWidthSize;
                break;
        }
        specMode = MeasureSpec.getMode(heightMeasureSpec);
        specSize = MeasureSpec.getSize(heightMeasureSpec);
        switch (specMode) {
            case MeasureSpec.EXACTLY:
                height = getPaddingTop() + getPaddingBottom() + specSize + mBorderHeightSize;
                break;
            case MeasureSpec.AT_MOST:
                height = getPaddingTop() + getPaddingBottom() + mBounds.height() + mBorderHeightSize;
                break;
            case MeasureSpec.UNSPECIFIED:
                height = getPaddingTop() + getPaddingBottom() + mBounds.height() + mBorderHeightSize;
                break;

        }

        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (needDraw) {
            // 创建带有文字的图片
            mBitmap = Bitmap.createBitmap(getWidth(), getHeight(),
                    Bitmap.Config.ARGB_8888);
            Canvas c = new Canvas(mBitmap);
            c.drawText(mText, getWidth() / 2 - mBounds.width() / 2, mBounds.height() / 2 + getHeight() / 2, mPaint);
            // 初始化坐标数组
            int index = 0;
            for (int y = 0; y <= HEIGHT; y++) {
                float fy = mBitmap.getHeight() * y / HEIGHT;

                for (int x = 0; x <= WIDTH; x++) {
                    float fx = mBitmap.getWidth() * x / WIDTH;
                    setXY(matrixMoved, index, fx, fy);
                    setXY(matrixOriganal, index, fx, fy);
                    index += 1;
                }
            }
            initRandomXY();
            smudgeMatrix();
            // 随机旋转角度
            rotate = (int) (Math.floor(Math.random() * DEFAULT_ROTATE_SIZE + 1)
                    - Math.floor(Math.random() * DEFAULT_ROTATE_SIZE * 2 + 1));
        }
        needDraw = false;
        // 绘制网格位图
        canvas.drawBitmapMesh(rotateBitmap(rotate, mBitmap), WIDTH, HEIGHT, matrixMoved, 0, null, 0, null);
    }

    /**
     * 设置坐标数组
     *
     * @param array 坐标数组
     * @param index 标识值
     * @param x     x坐标
     * @param y     y坐标
     */
    private void setXY(float[] array, int index, float x, float y) {
        array[index * 2 + 0] = x;
        array[index * 2 + 1] = y;
    }

    /**
     * 计算变换数组坐标
     */
    private void smudgeMatrix() {
        for (int i = 0; i < COUNT * 2; i += 2) {

            float xOriginal = matrixOriganal[i + 0];
            float yOriginal = matrixOriganal[i + 1];

            float dist_random_to_origin_x = mRandomX - xOriginal;
            float dist_random_to_origin_y = mRandomY - yOriginal;

            float kv_kat = dist_random_to_origin_x * dist_random_to_origin_x + dist_random_to_origin_y * dist_random_to_origin_y;

            float pull = (float) (1000 * SMUDGE_COEFFICIENT / kv_kat / Math.sqrt(kv_kat));

            if (pull >= 1) {
                matrixMoved[i + 0] = mRandomX;
                matrixMoved[i + 1] = mRandomY;
            } else {
                matrixMoved[i + 0] = xOriginal + dist_random_to_origin_x * pull;
                matrixMoved[i + 1] = yOriginal + dist_random_to_origin_y * pull;
            }
        }
    }


    /**
     * 旋转图片
     *
     * @param degree
     * @param bitmap
     * @return
     */
    public Bitmap rotateBitmap(int degree, Bitmap bitmap) {
        Matrix matrix = new Matrix();
        matrix.postRotate(degree);
        Bitmap bm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
                bitmap.getHeight(), matrix, true);
        return bm;
    }
}

BorderTextView:

package com.example.junweiliu.hanzicode;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * Created by junweiliu on 16/5/4.
 */
public class BorderTextView extends TextView {
    /**
     * 画笔
     */
    private Paint mPaint;

    public BorderTextView(Context context) {
        this(context, null);
    }

    public BorderTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        mPaint = new Paint();
        //  将边框设为灰色
        mPaint.setColor(Color.GRAY);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth((float) 3.0);
    }

    /**
     * 设置边框颜色
     *
     * @param color
     */
    public void setBorderColor(int color) {
        mPaint.setColor(color);
        invalidate();
    }

    /**
     * 设置边框宽度
     *
     * @param size
     */
    public void setBorderStrokeWidth(float size) {
        mPaint.setStrokeWidth(size);
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //  画TextView的4个边
        canvas.drawLine(0, 0, this.getWidth(), 0, mPaint);
        canvas.drawLine(0, 0, 0, this.getHeight(), mPaint);
        //  右边线
        //  canvas.drawLine(this.getWidth(), 0, this.getWidth(), this.getHeight(), mPaint);
        canvas.drawLine(0, this.getHeight(), this.getWidth(), this.getHeight(), mPaint);
        super.onDraw(canvas);
    }

}

AnswerLayout:

package com.example.junweiliu.hanzicode;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Created by junweiliu on 16/5/4.
 */
public class AnswerLayout extends LinearLayout {
    /**
     * 整体的宽度
     */
    private int pWidth;
    /**
     * 需要验证字的个数(多少个框,默认为4个)
     */
    private int HANZI_TEST_SIZE;
    /**
     * 需要创建的子View个数
     */
    private int childViewCount;
    /**
     * 回答的答案列表
     */
    private List<String> mAnswers;
    /**
     * 正确答案
     */
    private List<String> mCorrectAnswer;
    /**
     * 删除按钮资源
     */
    private int deleteBtnSrc;
    /**
     * 汉子格边框颜色
     */
    private int hanziBorderColor;
    /**
     * 汉子格边框宽度
     */
    private float hanziBorderStrokeWidth;
    /**
     * 汉字格文字的颜色
     */
    private int hanziTextColor;
    /**
     * 汉字格文字的大小
     */
    private int hanziTextSize;

    /**
     * 回调接口
     */
    interface CheckAnswerListener {
        // 成功回调
        public void onSuccess();

        // 失败回调
        public void onFail();

    }

    /**
     * 回调
     */
    private CheckAnswerListener mListener;

    /**
     * 设置回调
     *
     * @param listener
     */
    public void setCheckAnswerListener(CheckAnswerListener listener) {
        this.mListener = listener;
    }

    public AnswerLayout(Context context) {
        this(context, null);
    }

    public AnswerLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AnswerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 获取自定义属性
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.AnswerLayout);
        deleteBtnSrc = ta.getResourceId(R.styleable.AnswerLayout_deleteBtnSrc, R.drawable.delete_btn_selector);
        hanziBorderColor = ta.getColor(R.styleable.AnswerLayout_hanziBorderColor, Color.GRAY);
        hanziBorderStrokeWidth = ta.getFloat(R.styleable.AnswerLayout_hanziBorderStrokeWidth, 3.0f);
        HANZI_TEST_SIZE = ta.getInt(R.styleable.AnswerLayout_hanziTestNum, 4);
        hanziTextColor = ta.getColor(R.styleable.AnswerLayout_hanziTextColor, Color.BLACK);
        hanziTextSize = ta.getDimensionPixelSize(R.styleable.AnswerLayout_hanziTextSize, 16);
        ta.recycle();
        init();
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        if (mAnswers.size() > HANZI_TEST_SIZE) {
            return;
        }
        if (mAnswers.size() == 0) {
            for (int i = 0; i < HANZI_TEST_SIZE; i++) {
                TextView tv = (TextView) getChildAt(i);
                tv.setText("");
            }
        } else {
            for (int i = 0; i < mAnswers.size(); i++) {
                TextView tv = (TextView) getChildAt(i);
                tv.setText(mAnswers.get(i));
            }
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        pWidth = MeasureSpec.getSize(widthMeasureSpec);
        if (HANZI_TEST_SIZE <= 0) {
            return;
        }
        // 需要加入一个删除按钮,所以子view的总数需要加1
        childViewCount = HANZI_TEST_SIZE + 1;
        for (int i = 0; i < HANZI_TEST_SIZE; i++) {
            addView(makeItemView(i));
        }
        addView(makeDeleteView());
    }


    /**
     * 初始化数据
     */
    public void init() {
        mAnswers = new ArrayList<String>();
        mCorrectAnswer = new ArrayList<String>();
    }

    /**
     * 填写的答案
     *
     * @param answers
     */
    public void setAnswers(List<String> answers) {
        this.mAnswers = answers;
        invalidate();
        if (compare(mAnswers, mCorrectAnswer) && mAnswers.size() == HANZI_TEST_SIZE) {
            if (null != mListener)
                mListener.onSuccess();
        } else if (mAnswers.size() == HANZI_TEST_SIZE) {
            if (null != mListener)
                mListener.onFail();
        }

    }

    /**
     * 判断两个list是否完全相同
     *
     * @param a
     * @param b
     * @param <T>
     * @return
     */
    private <T extends Comparable<T>> boolean compare(List<T> a, List<T> b) {
        if (a.size() != b.size())
            return false;
        for (int i = 0; i < a.size(); i++) {
            if (!a.get(i).equals(b.get(i)))
                return false;
        }
        return true;
    }

    /**
     * 正确答案
     *
     * @param correctAnswers
     */
    public void setCorrectAnswers(List<String> correctAnswers) {
        this.mCorrectAnswer = correctAnswers;
    }

    /**
     * 重置
     */
    public void reSet(List<String> correctAnswers) {
        mAnswers.clear();
        mCorrectAnswer = correctAnswers;
        invalidate();
    }

    /**
     * 创建删除按钮
     *
     * @return
     */
    private View makeDeleteView() {
        ImageView deleteView = new ImageView(getContext());
        LinearLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        lp.width = pWidth / childViewCount;
        lp.height = pWidth / childViewCount;
        deleteView.setImageResource(deleteBtnSrc);
        deleteView.setScaleType(ImageView.ScaleType.FIT_XY);
        deleteView.setLayoutParams(lp);
        deleteView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                for (int i = HANZI_TEST_SIZE; i > 0; i--) {
                    TextView tv = (TextView) getChildAt(i - 1);
                    if (!"".equals(tv.getText())) {
                        mAnswers.remove(i - 1);
                        tv.setText("");
                        break;
                    }

                }
            }
        });
        return deleteView;
    }


    /**
     * 创建汉子验证格
     *
     * @return
     */
    private View makeItemView(final int i) {
        final BorderTextView bv = new BorderTextView(getContext());
        LinearLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        lp.width = pWidth / childViewCount;
        lp.height = pWidth / childViewCount;
        bv.setBorderColor(hanziBorderColor);
        bv.setTextSize(hanziTextSize);
        bv.setGravity(Gravity.CENTER);
        bv.setText("");
        bv.setLayoutParams(lp);
        bv.setTextColor(hanziTextColor);
        bv.setBorderStrokeWidth(hanziBorderStrokeWidth);
        return bv;
    }
}

CodeView:

package com.example.junweiliu.hanzicode;

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.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Created by junweiliu on 16/5/9.
 */
public class CodeView extends ImageView {
    /**
     * 干扰项
     */
    private final char[] CHARS = {
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
            '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'
    };
    /**
     * 汉字画笔
     */
    private Paint hzPaint;
    /**
     * 干扰项画笔
     */
    private Paint dbPaint;
    /**
     * 汉字画笔颜色
     */
    private int hzColor;
    /**
     * 干扰项画笔颜色
     */
    private int dbColor;
    /**
     * 干扰项的个数,默认30个
     */
    private int DEFAULT_DBSIZE;
    /**
     * 默认画笔颜色
     */
    private int DEFAULT_DBCOLOR, DEFAULT_HZCOLOR;
    /**
     * 干扰项随机生成的位置
     */
    private float dbRandomX, dbRandomY;
    /**
     * 验证码文字
     */
    private List<String> codeList;
    /**
     * 随机
     */
    private Random random = new Random();
    /**
     * 矩形区域
     */
    private Rect mBounds;
    /**
     * 干扰文字大小
     */
    private int dbTextSize;
    /**
     * 验证码文字大小
     */
    private int hzTextSize;
    /**
     * 画布旋转度数,默认为6
     */
    private int rotate;


    public CodeView(Context context) {
        this(context, null);
    }

    public CodeView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    ;

    public CodeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 获取自定义属性
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CodeView);
        DEFAULT_DBSIZE = ta.getInteger(R.styleable.CodeView_disturbSize, 30);
        dbTextSize = DisplayUtil.sp2px(context, ta.getDimensionPixelSize(R.styleable.CodeView_disturbTextSize, 10));
        hzTextSize = DisplayUtil.sp2px(context, ta.getDimensionPixelSize(R.styleable.CodeView_codeTextSize, 20));
        DEFAULT_DBCOLOR = ta.getColor(R.styleable.CodeView_disturbTextColor, 0);
        DEFAULT_HZCOLOR = ta.getColor(R.styleable.CodeView_codeTextColor, 0);
        rotate = ta.getInteger(R.styleable.CodeView_rotate, 6);
        init();

    }

    /**
     * 初始化
     */
    private void init() {
        // 验证的文字颜色一致,只需要生成一次
        if (0 == DEFAULT_HZCOLOR) {
            hzColor = Util.randomDarkColor();
        } else {
            hzColor = DEFAULT_HZCOLOR;
        }
        mBounds = new Rect();
        codeList = new ArrayList<String>();

    }

    @Override
    protected void onDraw(Canvas canvas) {

        Bitmap bp = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(bp);
        String dbCode = createDBCode();
        // 绘制干扰项
        for (int i = 0; i < dbCode.length(); i++) {
            randomDBStyele();
            randomDBPosition();
            c.drawText(dbCode.charAt(i) + "", dbRandomX, dbRandomY, dbPaint);
        }
        canvas.drawBitmap(bp, 0, 0, dbPaint);
        // 绘制验证文字
        if (null != codeList && codeList.size() > 0) {
            for (int i = 0; i < codeList.size(); i++) {
                randomHZStyle();
                canvas.save();
                canvas.rotate((int) (Math.floor(Math.random() * rotate + 1)
                        - Math.floor(Math.random() * rotate * 2 + 1)));
                canvas.drawText(codeList.get(i), mBounds.width() * (i + 1), getHeight() / 2 + mBounds.height() / 2 + rotate, hzPaint);
                canvas.restore();
            }
        }
//        super.onDraw(canvas);
    }

    /**
     * 重置
     */
    public void reSet(List<String> codes) {
        // 重置汉字画笔颜色
        if (0 == DEFAULT_HZCOLOR) {
            hzColor = Util.randomDarkColor();
        } else {
            hzColor = DEFAULT_HZCOLOR;
        }
        this.codeList.clear();
        this.codeList = codes;
        invalidate();
    }

    /**
     * 设置验证码文字
     *
     * @param codes
     */
    public void setCode(List<String> codes) {
        this.codeList = codes;
        invalidate();
    }

    /**
     * 随机生成汉字画笔样式
     */
    private void randomHZStyle() {
        hzPaint = new Paint();
//        hzPaint.setAntiAlias(true);
        hzPaint.setColor(hzColor);
        hzPaint.setFakeBoldText(random.nextBoolean());
        hzPaint.setTextSize(hzTextSize);
        hzPaint.getTextBounds("一", 0, "一".length(), mBounds);
    }


    /**
     * 随机生成干扰项画笔样式
     */
    private void randomDBStyele() {
        if (0 == DEFAULT_DBCOLOR) {
            dbColor = Util.randomDarkColor();
        } else {
            dbColor = DEFAULT_DBCOLOR;
        }
        dbPaint = new Paint();
//        dbPaint.setAntiAlias(true);
        dbPaint.setColor(dbColor);
        dbPaint.setTextSize(dbTextSize);
    }

    /**
     * 随机生成干扰项的显示位置
     */
    private void randomDBPosition() {
        dbRandomX = (float) Math.random() * getWidth();
        dbRandomY = (float) Math.random() * getHeight();
    }

    /**
     * 创建干扰项
     *
     * @return
     */
    private String createDBCode() {
        StringBuilder buffer = new StringBuilder();
        for (int i = 0; i < DEFAULT_DBSIZE; i++) {
            buffer.append(CHARS[random.nextInt(CHARS.length)]);
        }
        return buffer.toString();
    }

}

Util:

package com.example.junweiliu.hanzicode;

import android.graphics.Color;
import android.view.View;
import android.view.ViewGroup;
import android.widget.GridView;
import android.widget.ListAdapter;

import java.io.UnsupportedEncodingException;
import java.util.Random;

/**
 * Created by junweiliu on 16/5/9.
 */
public class Util {


    /**
     * 获取随机的颜色
     *
     * @return
     */
    public static int randomColor() {
        int red = (int) (Math.random() * 256);
        int green = (int) (Math.random() * 256);
        int blue = (int) (Math.random() * 256);
        return Color.argb(255, red, green, blue);
    }

    /**
     * 获取随机的暗色
     *
     * @return
     */
    public static int randomDarkColor() {
        int red = (int) (Math.random() * 100 + 56);
        int green = (int) (Math.random() * 100 + 56);
        int blue = (int) (Math.random() * 100 + 56);
        return Color.argb(255, red, green, blue);
    }

    /**
     * 获取指定长度随机简体中文
     *
     * @param len int
     * @return String
     */
    public static String getRandomJianHan(int len) {
        String ret = "";
        for (int i = 0; i < len; i++) {
            String str = null;
            int hightPos, lowPos; // 定义高低位
            Random random = new Random();
            hightPos = (176 + Math.abs(random.nextInt(39))); //获取高位值
            lowPos = (161 + Math.abs(random.nextInt(93))); //获取低位值
            byte[] b = new byte[2];
            b[0] = (new Integer(hightPos).byteValue());
            b[1] = (new Integer(lowPos).byteValue());
            try {
                str = new String(b, "GBk"); //转成中文
            } catch (UnsupportedEncodingException ex) {
                ex.printStackTrace();
            }
            ret += str;
        }
        return ret;
    }

    /**
     * 计算GridView宽高
     *
     * @param gridView
     */
    public static void calGridViewWidthAndHeigh(int numColumns, GridView gridView) {

        // 获取GridView对应的Adapter
        ListAdapter listAdapter = gridView.getAdapter();
        if (listAdapter == null) {
            return;
        }

        int totalHeight = 0;
        for (int i = 0, len = listAdapter.getCount(); i < len; i++) {
            // listAdapter.getCount()返回数据项的数目
            View listItem = listAdapter.getView(i, null, gridView);
            // 计算子项View 的宽高
            listItem.measure(0, 0);

            if ((i + 1) % numColumns == 0) {
                // 统计所有子项的总高度
                totalHeight += listItem.getMeasuredHeight();
            }

            if ((i + 1) == len && (i + 1) % numColumns != 0) {
                // 统计所有子项的总高度
                totalHeight += listItem.getMeasuredHeight();
            }
        }
        // 适当添加一些高度,防止gridview高度不够,出现滚动条
        totalHeight += 60;

        ViewGroup.LayoutParams params = gridView.getLayoutParams();
        params.height = totalHeight;
        // params.height最后得到整个ListView完整显示需要的高度
        gridView.setLayoutParams(params);
    }
}

DisplayUtil:

package com.example.junweiliu.hanzicode;

import android.content.Context;

/**
 * dp、sp 转换为 px 的工具类
 */
public class DisplayUtil {
    /**
     * 将px值转换为dip或dp值,保证尺寸大小不变
     *
     * @param pxValue
     * @return
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

    /**
     * 将dip或dp值转换为px值,保证尺寸大小不变
     *
     * @param dipValue
     * @return
     */
    public static int dip2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    /**
     * 将px值转换为sp值,保证文字大小不变
     *
     * @param pxValue
     * @return
     */
    public static int px2sp(Context context, float pxValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (pxValue / fontScale + 0.5f);
    }

    /**
     * 将sp值转换为px值,保证文字大小不变
     *
     * @param spValue
     * @return
     */
    public static int sp2px(Context context, float spValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }
}

MainActivity:

package com.example.junweiliu.hanzicode;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.graphics.ColorUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ListAdapter;
import android.widget.Toast;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class MainActivity extends Activity {
    /**
     * 验证答案框
     */
    private AnswerLayout mAnswerLayout;
    /**
     * 验证文字框
     */
    private GridView mGridView;
    /**
     * 验证码
     */
    private CodeView mCodeView;
    /**
     * 重置按钮
     */
    private Button mResetBtn;
    /**
     * 适配器
     */
    private HanZiCodeAdapter hzAdapter;
    /**
     * 获取到的文字备份
     */
    private List<String> cHanZiList;
    /**
     * 获取到的文字
     */
    private List<String> mHanZiList;
    /**
     * 选择的文字
     */
    private List<String> mChooseHZList;
    /**
     * 正确答案
     */
    private List<String> mCorrectList;
    /**
     * 验证框汉字颜色
     */
    private int mColor;
    /**
     * 测试计数
     */
    private int num = 0;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initData();
        initView();
        initGridView();
    }

    /**
     * 初始化gridview
     */
    private void initGridView() {
        mGridView = (GridView) findViewById(R.id.gv_hz);
        hzAdapter = new HanZiCodeAdapter();
        mGridView.setAdapter(hzAdapter);
        Util.calGridViewWidthAndHeigh(3, mGridView);
    }

    /**
     * 初始化数据
     */
    private void initData() {
        mHanZiList = new ArrayList<String>();
        cHanZiList = new ArrayList<String>();
        mChooseHZList = new ArrayList<String>();
        mCorrectList = new ArrayList<String>();
        for (int i = 0; i < 9; i++) {
            mHanZiList.add(Util.getRandomJianHan(1));
        }
        cHanZiList.addAll(mHanZiList);
        // 添加不重复的文字
        for (int i = 0; i < 4; i++) {
            String hz = cHanZiList.get((int) (Math.random() * cHanZiList.size()));
            cHanZiList.remove(hz);
            mCorrectList.add(hz);
        }
        mColor = Util.randomColor();
//        Log.e("main", "颜色值为" + mColor + "\n获取到的文字为\n" + mCorrectList.get(0) + "\n" + mCorrectList.get(1) + "\n" + mCorrectList.get(2) + "\n" + mCorrectList.get(3));
    }


    /**
     * 初始化控件
     */
    private void initView() {
        mAnswerLayout = (AnswerLayout) findViewById(R.id.al_mal);
        mAnswerLayout.setCorrectAnswers(mCorrectList);
        mAnswerLayout.setCheckAnswerListener(new AnswerLayout.CheckAnswerListener() {
            @Override
            public void onSuccess() {
                Toast.makeText(MainActivity.this, "成功", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onFail() {
                Toast.makeText(MainActivity.this, "失败", Toast.LENGTH_SHORT).show();
            }
        });
        mCodeView = (CodeView) findViewById(R.id.cv_hz);
        mCodeView.setCode(mCorrectList);
        mResetBtn = (Button) findViewById(R.id.btn_reset);
        mResetBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                initData();
                mAnswerLayout.reSet(mCorrectList);
                mCodeView.reSet(mCorrectList);
                hzAdapter.notifyDataSetChanged();
            }
        });

    }


    /**
     * 汉子展示框适配器
     */
    class HanZiCodeAdapter extends BaseAdapter {

        @Override
        public int getCount() {
            return mHanZiList.size();
        }

        @Override
        public Object getItem(int i) {
            return mHanZiList.get(i);
        }

        @Override
        public long getItemId(int i) {
            return 0;
        }

        @Override
        public View getView(final int i, View view, ViewGroup viewGroup) {
            ViewHolder holder = new ViewHolder();
            if (null == view) {
                view = LayoutInflater.from(MainActivity.this).inflate(R.layout.hanzi_item, null, false);
                holder.hzImg = (HanZiView) view.findViewById(R.id.hz_item);
                view.setTag(holder);
            } else {
                holder = (ViewHolder) view.getTag();
            }
            //设置holder
            holder.hzImg.setText(mHanZiList.get(i));
            holder.hzImg.setTextColor(mColor);
            holder.hzImg.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (mChooseHZList.size() < 4) {
                        mChooseHZList.add(mHanZiList.get(i));
                        mAnswerLayout.setAnswers(mChooseHZList);
                    }
//                    Toast.makeText(MainActivity.this, "点的是" + mHanZiList.get(i), Toast.LENGTH_SHORT).show();
                }
            });
            return view;
        }

        class ViewHolder {
            HanZiView hzImg;
        }

    }
}

源码地址:

下载地址


四、总结

实现这个功能,用了好几天,期间也遇到过很多坑,不过还是坚持写完了,当然还有很多可以完善的地方,最重要的是自己学习了不少新知识.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值