記得有一次宿舍兄弟要開直播,儘管他的直播間只有幾個人(宿舍幾兄弟- - ),我還是把我攢了許久的魚丸給他走了一波,還惦記着他以後成了網紅佔佔光,結果……哈哈,滿滿都是回憶啊.看過鬥魚直播的應該都知道,每次領取魚丸的時候都會有一個文字驗證,今天就用安卓來仿一下.
先看一下效果圖:
一、分析功能
首先我們拆分一下,分爲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;
}
}
}
});
}
只做一個簡單的小測試
測試效果如下: