Android 原生应用验证码糊化展示
大概长这样~
需求背景
维护老版本应用,里面有自生成的验证码(非短信码,类似网页上的那个,让你输入几个字符的玩意儿),原来的就是个TextView里面展示几个数字,现在客户说不安全,容易被识破 ,需要增加复杂程度,于是就有了现在的这个东西。
分析思路
看了Web的一些案例,多是以文字糊化、文字扭曲、增加噪点、干扰线等方式,决定采用噪点、干扰线作为主要的糊化策略。搜了下前辈们的文章,其中这篇文章的内容比较容易上手,于是拿这里的代码进行修改。
实现的功能
看下图吧,大概就是这些,另外还有个自定义字符组的功能,参照后续源码。
工具类代码
package fun.chice.verificationcode;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.widget.ImageView;
import java.util.Random;
/**
* Verification code creator.
* Created by Chice on 2018/9/27.
* Email:[email protected]
* CSDN:http://blog.csdn.net/chicet
*/
public class VerificationCodeCreator {
private final char[] CODE_CHARS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm',
'n', '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', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
};
//可根据需求决定是否移除容易混淆的1、i、I、l和0、o、O等字符
private int mBmpWidth = 200;
private int mBmpHeight = 100;
private int mVerificationCodeLength = 4;
private int mCornerRadius = 20;
private int mBgColor = Color.BLUE;
private boolean isRandomBgColor = false;
private int mLineCount = 10;
private int mPointCount = 10;
private boolean isRandomObliqueLetter = true;
private boolean isDrawLetterShader = true;
private boolean isFloatVerticalPos = true;
private boolean isDifferentLetterColor = true;
private char[] userCodeChars = null;
private Random mRandom;
//======================inner_variables=================//
private int mTextSize = 12;
private int firstCharStartX = 0;
public VerificationCodeCreator(int width, int height, int codeLength) {
if (width > 0) this.mBmpWidth = width;
if (height > 0) this.mBmpHeight = height;
if (codeLength > 0) this.mVerificationCodeLength = codeLength;
mRandom = new Random();
}
public void setBackgroundProperty(int cornerRadius, @ColorInt int bgColor, boolean isRandomBgColor) {
int maxRadius = Math.min(mBmpWidth, mBmpHeight) / 4;
this.mCornerRadius = cornerRadius > 0 ? (cornerRadius < maxRadius ? cornerRadius : maxRadius) : 0;
this.mBgColor = bgColor;
this.isRandomBgColor = isRandomBgColor;
}
public void setDisturbanceProperty(int lineCount, int pointCount) {
this.mLineCount = lineCount > 0 ? lineCount : 0;
this.mPointCount = pointCount > 0 ? pointCount : 0;
}
public void setCodeShowStyle(boolean isOblique, boolean isDrawShader, boolean isFloatVerticalPos, boolean isDifferentLetterColor) {
this.isRandomObliqueLetter = isOblique;
this.isDrawLetterShader = isDrawShader;
this.isFloatVerticalPos = isFloatVerticalPos;
this.isDifferentLetterColor = isDifferentLetterColor;
}
public void setUserCodeChars(char[] chars) {
this.userCodeChars = chars;
}
//验证码图片
public String refreshCode(@NonNull ImageView imageView) {
Bitmap bp = Bitmap.createBitmap(mBmpWidth, mBmpHeight, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bp);
String code = createCode();
if (mCornerRadius > 0) {
// 圆角背景
Path path = new Path();
int doubleRadius = 2 * mCornerRadius;
path.arcTo(new RectF(0, 0, doubleRadius, doubleRadius), 180, 90, true);
path.lineTo(mBmpWidth - mCornerRadius, 0);
path.arcTo(new RectF(mBmpWidth - doubleRadius, 0, mBmpWidth, doubleRadius), 270, 90, false);
path.lineTo(mBmpWidth, mBmpHeight - mCornerRadius);
path.arcTo(new RectF(mBmpWidth - doubleRadius, mBmpHeight - doubleRadius, mBmpWidth, mBmpHeight), 0, 90, false);
path.lineTo(mCornerRadius, mBmpHeight);
path.arcTo(new RectF(0, mBmpHeight - doubleRadius, doubleRadius, mBmpHeight), 90, 90, false);
path.close();
c.clipPath(path);
}
c.drawColor(isRandomBgColor ? randomColor() : mBgColor);
Paint paint = new Paint();
paint.setAntiAlias(true);
initTextSize(paint, code);
int xPos = firstCharStartX;
int textLineHeight = getTextLineHeight(paint);
int extraVerticalSpace = (mBmpHeight - textLineHeight) / 2;
paint.setColor(randomColor());
//画验证码
for (int i = 0; i < code.length(); i++) {
randomTextStyle(paint);
int yPos = mBmpHeight / 2 - getTextBaseLineOffset(paint);
if (isFloatVerticalPos && extraVerticalSpace > 0) {
yPos += (mRandom.nextBoolean() ? -1 : 1) * mRandom.nextInt(extraVerticalSpace);
}
c.drawText(code.charAt(i) + "", xPos, yPos, paint);
xPos = (int) (xPos + 1.5f * paint.measureText(code.charAt(i) + ""));
}
paint.clearShadowLayer();
//画线条
for (int i = 0; i < mLineCount; i++) {
drawLine(c, paint);
}
drawRandomPoint(c, paint);
c.save();
c.restore();
imageView.setImageBitmap(bp);
c = null;
return code;
}
private void initTextSize(Paint paint, String codeString) {
boolean isSizeOk = false;
int textSize = 0;
while (!isSizeOk) {
paint.setTextSize(textSize + 1);
int lineHeight = getTextLineHeight(paint);
int textWidth = (int) (1.5 * paint.measureText(codeString));
if (lineHeight < mBmpHeight && textWidth < mBmpWidth) {
isSizeOk = false;
textSize++;
} else {
isSizeOk = true;
}
}
mTextSize = textSize;
paint.setTextSize(mTextSize);
firstCharStartX = (int) (paint.measureText(codeString.substring(0, 1)) * 0.25f);
double textWidth = (1.5 * paint.measureText(codeString));
if (textWidth < mBmpWidth) {
firstCharStartX += (int) (mBmpWidth - textWidth) / 2;
}
}
private int getTextLineHeight(Paint paint) {
return paint.getFontMetricsInt().bottom - paint.getFontMetricsInt().top;
}
private int getTextBaseLineOffset(Paint paint) {
return (paint.getFontMetricsInt().bottom + paint.getFontMetricsInt().top) / 2;
}
//生成验证码
private String createCode() {
StringBuilder buffer = new StringBuilder();
char[] codeChars = (userCodeChars == null || userCodeChars.length < 1) ? CODE_CHARS : userCodeChars;
for (int i = 0; i < mVerificationCodeLength; i++) {
buffer.append(codeChars[mRandom.nextInt(codeChars.length)]);
}
return buffer.toString();
}
//画干扰点
private void drawRandomPoint(Canvas canvas, Paint paint) {
paint.setStrokeCap(Paint.Cap.ROUND);
int minDim = Math.min(mBmpWidth, mBmpHeight);
for (int i = 0; i < mPointCount; i++) {
paint.setColor(randomColor());
paint.setStrokeWidth(mRandom.nextInt(minDim / 20) + 6);
canvas.drawPoint(mRandom.nextInt(mBmpWidth), mRandom.nextInt(mBmpHeight), paint);
}
}
//画干扰线
private void drawLine(Canvas canvas, Paint paint) {
int color = randomColor();
int startX = mRandom.nextInt(mBmpWidth);
int startY = mRandom.nextInt(mBmpHeight);
int stopX = mRandom.nextInt(mBmpWidth);
int stopY = mRandom.nextInt(mBmpHeight);
paint.setStrokeWidth(1 + mRandom.nextInt(5));
paint.setColor(color);
canvas.drawLine(startX, startY, stopX, stopY, paint);
}
private int randomColor() {
int red = mRandom.nextInt(256);
int green = mRandom.nextInt(256);
int blue = mRandom.nextInt(256);
return Color.rgb(red, green, blue);
}
//随机生成文字样式,颜色,粗细,倾斜度
private void randomTextStyle(Paint paint) {
if (isDifferentLetterColor) {
int color = randomColor();
paint.setColor(color);
}
paint.setFakeBoldText(mRandom.nextBoolean()); //true为粗体,false为非粗体
if (isDrawLetterShader) {
paint.setShadowLayer(0.2f * mTextSize, 0, 0, randomColor());
}
if (isRandomObliqueLetter) {
float skewX = (float) (mRandom.nextFloat() * Math.PI / 6);
skewX = mRandom.nextBoolean() ? skewX : -skewX;
paint.setTextSkewX(skewX); //float类型参数,负数表示右斜,整数左斜
}
//paint.setUnderlineText(true); //true为下划线,false为非下划线
//paint.setStrikeThruText(true); //true为删除线,false为非删除线
}
}
方法调用
VerificationCodeCreator mVerificationCodeCreator = new VerificationCodeCreator(300, 120, codeCount);
mVerificationCodeCreator.setBackgroundProperty(20, 0xFFF0F0F0, isRadomBgColor);
mVerificationCodeCreator.setDisturbanceProperty(lineCount, pointCount);
mVerificationCodeCreator.setCodeShowStyle(isRadomOblique, isLetterShader, isFloatYPos, isDifferentColor);
// mVerificationCodeCreator.setUserCodeChars(chars); // 这个就是设置自定义字符组的
tvCode.setText(mVerificationCodeCreator.refreshCode(imgVerificationCode));
后记
代码相对比较简短,逻辑也比较明确,就没有去写过多的注释。整体代码结构略有欠缺,以后需要注意细节规整。Demo
参考文章:[1]: https://blog.csdn.net/wk843620202/article/details/50960904