Android实现动态验证码的技术调研与实现
来源:互联网 发布:切片软件哪个好 编辑:程序博客网 时间:2024/05/22 14:38
前言
本文链接:http://blog.csdn.net/dreamsever/article/details/53467708
前一段时间看到干货集中营 推荐的一个开源项目验证码CaptchaImageView,可用于动态生成验证码,项目地址:https://github.com/jineshfrancs/CaptchaImageView。我就忽然联想到陆金所App的动态验证码效果挺赞的,因为它不仅有文字倾斜,文字上下错位间距,中间黑曲线遮挡,还有文字背景阴影和文字变形。
下面是陆金所验证码效果,奈何这个app禁止了系统截屏,我只有手动拍照了。。
当时就很想实现一下这样的效果,经过网上的查找我找到两篇博客:
android自定义view(一),打造绚丽的验证码
Android仿斗鱼领取鱼丸文字验证(三)
其实看效果第一篇博客的效果就挺好的,如果不需要那么花哨,干货集中营推荐的CaptchaImageView也是可以用的,但是从效果和体验上我感觉陆金所的更好,更像网页版的动态验证码而且又不难看清楚文字,点击一下刷新验证码,最主要的还是这里有前面几位都没有的效果:文字变形。我就苦思冥想自定义View绘制文字的时候,哪个属性可以让文字变形,没有想出来,我就查了半天试了n次,最后找到了一篇爱哥的博客:http://blog.csdn.net/aigestudio/article/details/41960507,里面大胸美女的那段代码你会发现一个方法:
// 绘制网格位图
canvas.drawBitmapMesh(mBitmap, WIDTH, HEIGHT, matrixMoved, 0, null, 0, null);
这个方法能够实现图片变形,我们可以先生成文字的图片,然后对这个图片进行变形,这样就可以实现文字变形的动态验证码
最终效果:
实现
一、思路
关于思路我就大致的说说,其实除了文字变形基本都是参考的android自定义view(一),打造绚丽的验证码 这篇博客。感兴趣可以具体去看看。
首先,你要会基本的自定义View,市面上关于自定义View的博客太多了,如果对自定义View不了解的可以先去补补课。看到效果我们首先想到的是在onDraw()方法里面画一个背景,生成一个验证码,然后生成不同方位的文字,绘制上去。现在有了文字变形,所以大致步骤需要调节一下,现在是:
1、根据:
mbitmap = Bitmap.createBitmap(mWidth,mHeight, Bitmap.Config.ARGB_8888);Canvas myCanvas=new Canvas(mbitmap);
得到一个画布,在这个画布上绘制上下倾斜错位不同颜色的文字,同时也得到了bitmap
2、对上面的到的bitmap进行变形,使用canvas.drawBitmapMesh(。。)方法,可以对图片进行变形扭曲等等,非常强大。用爱哥的话是:它可以对Bitmap做几乎任何改变。前提只要你算法够强
3、使用path绘制干扰线,要有一定的随机性
二、代码
先生成两个空画布,其中一个画布得到变形前的形状的bitmap,后一个画布的到最终的bitmap,最终显示出来
mbitmap = Bitmap.createBitmap(mWidth,mHeight, Bitmap.Config.ARGB_8888); codebitmap = Bitmap.createBitmap(mWidth,mHeight, Bitmap.Config.ARGB_8888); Canvas myCanvas=new Canvas(mbitmap); Canvas canvas=new Canvas(codebitmap);
生成随机验证码:
/** * java生成随机数字和字母组合 * @return 随机验证码 */ public String getCharAndNumr() { String val = ""; Random random = new Random(); for (int i = 0; i < codeNum; i++) { // 输出字母还是数字 String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num"; // 字符串 if ("char".equalsIgnoreCase(charOrNum)) { // 取得大写字母还是小写字母 int choice = random.nextInt(2) % 2 == 0 ? 65 : 97; val += (char) (choice + random.nextInt(26)); } else if ("num".equalsIgnoreCase(charOrNum)) { // 数字 val += String.valueOf(random.nextInt(10)); } } vCode=val; return val; }
已经画了背景,得到了验证码,现在把验证码绘制成上下倾斜错位且不同颜色的文字效果
for (int i=0;i<codeNum;i++){ int offsetDegree=mRandom.nextInt(15); // 这里只会产生0和1,如果是1那么正旋转正角度,否则旋转负角度 offsetDegree = mRandom.nextInt(2) == 1?offsetDegree:-offsetDegree; myCanvas.save(); myCanvas.rotate(offsetDegree,mWidth/2,mHeight/2); // 给画笔设置随机颜色,+20是为了去除一些边界值 textPaint.setARGB(255, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20); myCanvas.drawText(String.valueOf(tempCode.charAt(i)), i * charLength * 1.6f+30, mHeight * 2 / 3f, textPaint); myCanvas.restore(); }
关于图像扭曲变形这块建议先去看看爱哥的那一篇博客,先看看drawBitmapMesh到底是怎么工作的,我推荐这两篇博客:
自定义控件其实很简单5/12
Android简单实现界面扭曲与简单的物理效果实现
这两篇博客应该就可以看懂drawBitmapMesh是怎么通过网格对图像进行扭曲的,如果还没有看懂还可以再找找其他的博客看看,对这个方法的原理理解透彻,并且你的算法能力比较强,也许你能够实现更加炫酷的效果。由于我的算法能力不是很强,这里我的实现扭曲方法是将验证码bitmap分成4*3个网格,共(4+1)*(3+1)=20个点
不要笑我的这太简单了,主要是这样实现的效果基本上也挺好的,如果你想要更好的效果可以去按照这个套路去优化,反正原理你已经知道了
代码:
int index=0; float bitmapwidth= mbitmap.getWidth(); float bitmapheight= mbitmap.getHeight(); for(int i=0;i<HEIGHT+1;i++){ float fy=bitmapheight/HEIGHT*i; for(int j=0;j<WIDTH+1;j++){ float fx=bitmapwidth/WIDTH*j; //偶数位记录x坐标 奇数位记录Y坐标 origs[index*2+0]=verts[index*2+0]=fx; origs[index*2+1]=verts[index*2+1]=fy; index++; } } //设置变形点,这些点将会影响变形的效果 offset=bitmapheight/HEIGHT/3; verts[12]=verts[12]-offset; verts[13]=verts[13]+offset; verts[16]=verts[16]+offset; verts[17]=verts[17]-offset; verts[24]=verts[24]+offset; verts[25]=verts[25]+offset;
最后扭曲,加干扰线
// 对验证码图片进行扭曲变形 canvas.drawBitmapMesh(mbitmap, WIDTH, HEIGHT, verts, 0, null, 0, null); // 产生干扰效果2 -- 干扰线 for(Path path : mPaths){ linePaint.setARGB(255, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20); canvas.drawPath(path, linePaint); }
三、完整代码
package sgffsg.com.verifycodeview;import android.content.Context;import android.graphics.Bitmap;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.graphics.Rect;import android.graphics.RectF;import android.graphics.Typeface;import android.util.AttributeSet;import android.view.View;import java.util.ArrayList;import java.util.Random;/** * Verification Code View * Created by sgffsg on 16/11/30. */public class VerificationCodeView extends View { //将图片划分成4*3个小格 private static final int WIDTH=4; private static final int HEIGHT=3; //小格相交的总的点数 private int COUNT=(WIDTH+1)*(HEIGHT+1); private float[] verts=new float[COUNT*2]; private float[] origs=new float[COUNT*2]; //黄背景颜色 private int YELLOW_BG_COLOR = 0xfff9dec1; //蓝背景颜色 private int BLUE_BG_COLOR = 0xffdcdef8; private RectF mBounds;//用于获取控件宽高 private Rect textBound;//用于计算文本的宽高 private Paint bgPaint;//背景画笔 private Paint textPaint; private Paint linePaint; private String tempCode;//当前生成的验证码 private int codeNum = 4;//验证码位数 4或6。。 private Random mRandom; //控件总宽度 private int mWidth; //控件高度 private int mHeight; private Bitmap mbitmap; private Bitmap codebitmap; /** * 绘制贝塞尔曲线的路径集合 */ private ArrayList<Path> mPaths = new ArrayList<Path>(); private float offset=5;//扭曲偏移 private String vCode; public VerificationCodeView(Context context) { this(context,null); } public VerificationCodeView(Context context, AttributeSet attrs) { this(context,attrs,0); } public VerificationCodeView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //获取宽和高的SpecMode和SpecSize int wSpecMode = MeasureSpec.getMode(widthMeasureSpec); int wSpecSize = MeasureSpec.getSize(widthMeasureSpec); int hSpecMode = MeasureSpec.getMode(heightMeasureSpec); int hSpecSize = MeasureSpec.getSize(heightMeasureSpec); //分别判断宽高是否设置为wrap_content if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) { //宽高都为wrap_content,直接指定为400 setMeasuredDimension(mWidth, mWidth); } else if (wSpecMode == MeasureSpec.AT_MOST) { //只有宽为wrap_content,宽直接指定为400,高为获取的SpecSize setMeasuredDimension(mWidth, hSpecSize); } else if (hSpecMode == MeasureSpec.AT_MOST) { //只有高为wrap_content,高直接指定为400,宽为获取的SpecSize setMeasuredDimension(wSpecSize, mWidth); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mBounds=new RectF(getLeft(),getTop(),getRight(),getBottom()); mWidth= (int) (mBounds.right-mBounds.left); mHeight= (int) (mBounds.bottom-mBounds.top); createCodeBitmap(); } /** * 初始化 */ private void initView() { mRandom=new Random(); bgPaint=new Paint(); bgPaint.setAntiAlias(true); bgPaint.setColor(YELLOW_BG_COLOR); linePaint=new Paint(); linePaint.setAntiAlias(true); linePaint.setStyle(Paint.Style.STROKE); linePaint.setColor(Color.BLACK); linePaint.setStrokeWidth(5); linePaint.setStrokeCap(Paint.Cap.ROUND); textPaint=new Paint(); textPaint.setAntiAlias(true); textPaint.setTextSize(DisplayUtils.spToPx(getContext(),30)); textPaint.setShadowLayer(5,3,3,0xFF999999); textPaint.setTypeface(Typeface.DEFAULT_BOLD); textPaint.setTextScaleX(0.8F); textPaint.setColor(Color.GREEN); textBound=new Rect(); } @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(codebitmap,0,0,null); } /** * 生成验证码图片 */ private void createCodeBitmap() { mPaths.clear(); // 生成干扰线坐标 for(int i=0;i<2;i++){ Path path = new Path(); int startX = mRandom.nextInt(mWidth/3)+10; int startY = mRandom.nextInt(mHeight/3)+10; int endX = mRandom.nextInt(mWidth/2)+mWidth/2-10; int endY = mRandom.nextInt(mHeight/2)+mHeight/2-10; path.moveTo(startX,startY); path.quadTo(Math.abs(endX-startX)/2, Math.abs(endY-startY)/2,endX,endY); mPaths.add(path); } mbitmap = Bitmap.createBitmap(mWidth,mHeight, Bitmap.Config.ARGB_8888); codebitmap = Bitmap.createBitmap(mWidth,mHeight, Bitmap.Config.ARGB_8888); Canvas myCanvas=new Canvas(mbitmap); Canvas canvas=new Canvas(codebitmap); tempCode=getCharAndNumr(); //画背景 myCanvas.drawColor(YELLOW_BG_COLOR); textPaint.getTextBounds(tempCode,0,codeNum,textBound); float charLength=(textBound.width())/codeNum; for (int i=0;i<codeNum;i++){ int offsetDegree=mRandom.nextInt(15); // 这里只会产生0和1,如果是1那么正旋转正角度,否则旋转负角度 offsetDegree = mRandom.nextInt(2) == 1?offsetDegree:-offsetDegree; myCanvas.save(); myCanvas.rotate(offsetDegree,mWidth/2,mHeight/2); // 给画笔设置随机颜色,+20是为了去除一些边界值 textPaint.setARGB(255, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20); myCanvas.drawText(String.valueOf(tempCode.charAt(i)), i * charLength * 1.6f+30, mHeight * 2 / 3f, textPaint); myCanvas.restore(); } int index=0; float bitmapwidth= mbitmap.getWidth(); float bitmapheight= mbitmap.getHeight(); for(int i=0;i<HEIGHT+1;i++){ float fy=bitmapheight/HEIGHT*i; for(int j=0;j<WIDTH+1;j++){ float fx=bitmapwidth/WIDTH*j; //偶数位记录x坐标 奇数位记录Y坐标 origs[index*2+0]=verts[index*2+0]=fx; origs[index*2+1]=verts[index*2+1]=fy; index++; } } //设置变形点,这些点将会影响变形的效果 offset=bitmapheight/HEIGHT/3; verts[12]=verts[12]-offset; verts[13]=verts[13]+offset; verts[16]=verts[16]+offset; verts[17]=verts[17]-offset; verts[24]=verts[24]+offset; verts[25]=verts[25]+offset; // 对验证码图片进行扭曲变形 canvas.drawBitmapMesh(mbitmap, WIDTH, HEIGHT, verts, 0, null, 0, null); // 产生干扰效果2 -- 干扰线 for(Path path : mPaths){ linePaint.setARGB(255, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20, mRandom.nextInt(200) + 20); canvas.drawPath(path, linePaint); } } /** * java生成随机数字和字母组合 * @return 随机验证码 */ public String getCharAndNumr() { String val = ""; Random random = new Random(); for (int i = 0; i < codeNum; i++) { // 输出字母还是数字 String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num"; // 字符串 if ("char".equalsIgnoreCase(charOrNum)) { // 取得大写字母还是小写字母 int choice = random.nextInt(2) % 2 == 0 ? 65 : 97; val += (char) (choice + random.nextInt(26)); } else if ("num".equalsIgnoreCase(charOrNum)) { // 数字 val += String.valueOf(random.nextInt(10)); } } vCode=val; return val; } /** * refresh verification Code */ public void refreshCode(){ createCodeBitmap(); invalidate(); } /** * get verification code * @return verification code */ public String getvCode() { return vCode; }}
具体项目已经上传至github欢迎star
github项目地址
参考文献
android自定义view(一),打造绚丽的验证码
Android仿斗鱼领取鱼丸文字验证(三)
Android简单实现界面扭曲与简单的物理效果实现
自定义控件其实很简单5/12
- Android实现动态验证码的技术调研与实现
- 关于Android打印技术的调研—如何实现PrintAdapter?
- 动态验证码的实现
- servlet与jsp实现动态验证码
- Servlet技术实现动态图片验证码(Java)
- 实现动态验证码的思路
- Struts2实现动态验证码的生成和验证
- Struts2实现动态验证码的生成和验证
- Struts2实现动态验证码的生成和验证
- 简单实现验证码技术
- 动态验证码实现思路
- springMVC--动态验证码实现
- iOS动态验证码实现
- 民意调研工具的实现
- 验证码的学习与实现
- 验证码的操作与实现
- 验证码的操作与实现
- 验证码的实现与刷新
- 从前世看今生,从JavaEE到微服务
- js 监听浏览器后退事件
- javascript学习笔记之apply()和call()
- SequoiaDB Spark Yarn部署及案例演示
- iOS通讯录,蓝牙,内购等开发系列
- Android实现动态验证码的技术调研与实现
- Latex排版全解
- SDL_LockSurface和SDL_UnlockSurface函数
- 第一节 PHP微信开发入口文件解读
- CodeForces 255C. Almost Arithmetical Progression (DP)
- 新篇伊始
- php请求接口
- 【OpenCV学习笔记 017】图像颜色分布直方图
- 关于apk 反编译的使用和注意事项