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

来源:互联网 发布:linux打包压缩命令tar 编辑:程序博客网 时间:2024/04/28 02:14

记得有一次宿舍兄弟要开直播,尽管他的直播间只有几个人(宿舍几兄弟- - ),我还是把我攒了许久的鱼丸给他走了一波,还惦记着他以后成了网红占占光,结果……哈哈,满满都是回忆啊.看过斗鱼直播的应该都知道,每次领取鱼丸的时候都会有一个文字验证,今天就用安卓来仿一下.

先看一下效果图:

效果图


一、分析功能

首先我们拆分一下,分为3个大部分,分别是验证答案框,验证码,还有底下的九宫格文字.

验证框用来验证在九宫格中选择的文字及顺序是不是和验证码中的一致,从而来确定是不是验证成功.

验证码当然就是用来提供答案的.

最后一部分九宫格是用来提供备选答案,供用户选择,以便完成验证.

需要完成的工作还是很多的,所以打算分篇来写,第一篇先来做一下验证框的部分.


二、验证框的实现

先看一下验证框,其实做这个验证框很简单,几个TextView加一个Button就可以轻松搞定是吧.不过为了扩展性和实用性,我打算自定义一个ViewGroup,之前都是看大神的文章,没有实践过,这次练习一下.

看一下验证框,有几个控件横向排列组成,没有什么特殊的.所以这里写一个AnswerLayout继承自LinearLayout即可.
在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></resources>

再定义一下成员变量及获取自定义属性:

    /**     * 整体的宽度     */    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;    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();    }

没有什么特殊的,在开始写之前,我们先看一下左边的4个框,这4个框都有边框且最后一个框右边是没有黑色边框的.简单分析一下.其实就是每个View除了右边没有边框,其他的三条边都有,这个很简单,自定义一个带边框的View就可以了,代码如下:

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(0, this.getHeight(), this.getWidth(), this.getHeight(), mPaint);        super.onDraw(canvas);    }}

右边的删除按钮很简单,就不说了,接着我们需要创建这几个View,代码如下:

    @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());    }    /**     * 创建删除按钮     *     * @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;    }

我们创建了验证框和删除按钮,并且在删除按钮中做了处理,来对填写的验证答案进行删减.
之后需要提供一些方法,来完成填写答案以及验证答案是否成功的功能.
具体实现如下:

    /**     * 回调接口     */    interface CheckAnswerListener {        // 成功回调        public void onSuccess();        // 失败回调        public void onFail();    }    /**     * 回调     */    private CheckAnswerListener mListener;    /**     * 正确答案     *     * @param correctAnswers     */    public void setCorrectAnswers(List<String> correctAnswers) {        this.mCorrectAnswer = correctAnswers;    }    /**     * 填写的答案     *     * @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;    }    /**     * 重置     */    public void reSet(List<String> correctAnswers) {        mAnswers.clear();        mCorrectAnswer = correctAnswers;        invalidate();    }    @Override    protected void dispatchDraw(Canvas canvas) {        super.dispatchDraw(canvas);        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));            }        }    }

设置正确答案的方法来核对填写的答案是否正确,设置填写答案的方法,提供判断是否验证正确的回调接口及重置方法.在dispatchDraw中来完成填写答案的显示功能(在ViewGroup重绘走的方法是dispathDraw而不是onDraw).


三、完整代码及实现

写完之后来使用一下:

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;    }}

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="wrap_content"            android:layout_height="wrap_content">            <Button                android:id="@+id/btn_test"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_marginTop="10dp"                android:text="验证"                />        </LinearLayout></LinearLayout>

MainActivity:

public class MainActivity extends Activity {    /**     * 验证答案框     */    private AnswerLayout mAnswerLayout;    /**     * 测试按钮     */    private Button mTestBtn;    /**     * 选择的文字     */    private List<String> mChooseHZList;    /**     * 正确答案     */    private List<String> mCorrectList;    /**     * 测试计数     */    private int num = 0;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initData();        initView();    }    /**     * 初始化数据     */    private void initData() {        mChooseHZList = new ArrayList<String>();        mCorrectList = new ArrayList<String>();        // 初始化答案,为0,1,2,3        for(int i=0;i<4;i++){        mCorrectList.add(""+i);        }    }    /**     * 初始化控件     */    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();            }        });        mTestBtn = (Button) findViewById(R.id.btn_test);        mTestBtn.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                if (mChooseHZList.size() < 4) {                    // 防止异常                    if (num >= mCorrectList.size()) {                        num = 0;                    }                    mChooseHZList.add(mCorrectList.get(num));                    mAnswerLayout.setAnswers(mChooseHZList);                    if (num < 4) {                        num++;                    } else {                        num = 0;                    }                }            }        });}

只做一个简单的小测试

测试效果如下:

测试效果

0 0