今天来写最后一部分,九宫格部分,先来看一下最终的效果图:
一、分析功能
直接看下边的九宫格,九宫格里边的文字随机,文字颜色随机,并且每个文字都进行了不同程度的扭曲变形,点击看不清的时候,可以对九宫格的内容进行重置,要解决的问题差不多就这么多.
(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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
然后在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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
看一下效果图:
很不错,达到了想要的效果,简单分析一下这段代码,这段代码其实就是通过点击的位置,去改变原始图片的各个原始点的坐标,然后使用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;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
可能算法会有很多,可以实现不同的效果,有兴趣的同学可以去研究一下,这里只看一下这部分,其实不难理解,这部分就是去生成新的扭曲坐标,我们找到一个关键变量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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
这里用随机的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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
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();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
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;
}
}
}
源代码云盘自取:链接:http://pan.baidu.com/s/1hr7fp4o 密码:folt