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

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

先看一下效果图:

效果图


一、分析功能

首先我们拆分一下,分为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
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值