Android 原生应用验证码糊化展示

大概长这样~
Android 应用验证码模糊效果Android 应用验证码模糊效果Android 应用验证码模糊效果Android 应用验证码模糊效果

需求背景

维护老版本应用,里面有自生成的验证码(非短信码,类似网页上的那个,让你输入几个字符的玩意儿),原来的就是个TextView里面展示几个数字,现在客户说不安全,容易被识破 ,需要增加复杂程度,于是就有了现在的这个东西。

分析思路

看了Web的一些案例,多是以文字糊化、文字扭曲、增加噪点、干扰线等方式,决定采用噪点、干扰线作为主要的糊化策略。搜了下前辈们的文章,其中这篇文章的内容比较容易上手,于是拿这里的代码进行修改。

实现的功能

看下图吧,大概就是这些,另外还有个自定义字符组的功能,参照后续源码。
Android 应用验证码模糊效果

工具类代码

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

相关文章: