作者简介 原创微信公众号郭霖 WeChat ID: guolin_blog
本篇来自 老老司机(第六篇了)张旭童的投稿。一直给大家带来实战文章的他,今天也毫无例外,分享了如何实现滑动验证码功能。希望能帮助有需要的朋友。
张旭童的博客地址:
http://blog.csdn.net/zxt0601
概述
上周一总监让我研究一波滑动验证码,说项目可能会上。我想了一下好像在斗鱼、淘宝都见过,结果下了这两个app,发现怎么点也出不来滑动验证码。于是,我就去web端斗鱼看了一下,果然,每次登陆都会出现验证码。
好吧,那我们这次的目标就定为 在 Android端 app上,自定义View,仿一个web端滑动验证码吧。
(后话,做到后面发现我有点蠢了,我应该直接模仿app端的,很多效果在web端应该很好实现 ,但是在Android端就不那么好整了。,例如验证成功的白光扫过动画,如下图。在Android上实现起来就不太容易,有些效果还是不如web端酷炫。)
(图很渣,也忽略底下的SeekBar,这不是重点)
一些动画,效果录不出来了,大家可以去斗鱼web端看一下,然后下载Demo看一下,效果还是可以的。
我们的Demo和web端基本上一样。那么本控件包含不仅包含以下功能:
随机区域起点(左上角x,y)生成一个验证码阴影。
验证码拼图 凹凸图形会随机变换。
验证码区域宽高可自定义。
抠图验证码区域,绘制一个用于联动滑动的验证码滑块。
验证失败,会闪烁几下然后回到原点。
验证成功,会有白光扫过的动画。
分解一下验证码核心实现思路:
控件继承自 ImageView。理由:
1. 如果放在项目中用,验证码图片希望可以是接口返回。ImageView 以及其子类支持花式加载图片。
2. 继承自 ImageView,绘制图片本身不用我们干预,也不用我们操心 scaleType,节省很多工作。
在 onSizeChanged() 方法中生成 和 控件宽高相关的属性值:
1. 初始化时随机生成验证码区域起点
2. 生成验证码区域Path
3. 生成滑块Bitmap
onDraw() 时,依次绘制:
1. 验证码阴影
2. 滑块
核心工作是以上,可是实现起来还是有很多坑的,下面一步一步来吧。
验证码区域的生成
这里我省略自定义View的几个基础步骤:
在 attrs.xml 定义属性
在View的构造函数里获取 attrs 属性
一些 Paint,Path 的初始化工作
首先思考,验证码区域包含:
绘制在图片上的验证码阴影
可移动的验证码滑块
生成验证码阴影
我们用Path存储验证码区域,所以这一步最重要是生成验证码区域的Path。查看竞品(斗鱼web端)如下:
so,我们这里要绘制一个矩形+四边可能会有随机的凹凸,凹凸可以用半圆来替代。我们如下编写:
代码配有注释,gap 是指凹凸的起点和顶点的距离。
关于drawPartCircle(),它的功能是传入起点、终点坐标,以及需要凹还是凸,和绘制的Path。它会在Path上绘制一个凹、凸的半圆。代码如下:
这里用的是推导之后的公式,没推导前的也在注释里。
简单说,先计算出中点和半径,利用三次贝塞尔曲线绘制一个圆(c和gap1都是和三次贝塞尔曲线相关)。关于三次贝塞尔曲线就不展开了,网上很多资料,我也是现学的。
这里关于绘制验证码阴影 Path,还有一段曲折心路历程,绘制出来的效果如下:
心路历程(可以不看):
验证码 Path,猛的一看,似乎很简单,不就是 一个矩形 +上 四个边可能出现的凹凸嘛。
凹凸的话,我们就是绘制一个半圆好了。利用Path的lineTo()+addCircle()似乎可以很轻松的实现?
最开始我是这么做的,结果发现画出来的 Path 是 多段的Path,闭合后,无法形成一个完整阴影区域。更无法用于下一步验证码滑块bitmap的生成。
好,看来是addCircle()的锅,导致了 Path 被分割成多段。那我用arcTo()好了,结果发现arcTo()不像addCircle()那样可以设置绘图的方向(顺时针,逆时针),这当时可把我难住了,因为不能逆时针的话,上、右边的凹就画不出来。所以我放弃了,我转用贝塞尔曲线绘制这个凹凸。
文章写到这里,我突然发现自己智障了,sweepAngle传入负值不就可以逆时针了吗。如:arcTo(oval, 180, -180);
所以说写博客是有很大好处的,写博客时大脑也是高速旋转,因为生怕写出错误,一是误导别人,二是丢人。大脑高速运转说不定就想通了以前想不通的问题。
于是我就脑残的用sin+二阶贝尔赛曲线去绘制这个半圆了,为什么用它们呢?因为当初我绘制波浪滚动的时候用的 sin函数+二阶贝塞尔 模拟波浪,于是我就惯性思维的也这么解决了。结果呢?绘制出来的凹凸不够圆啊,sin函数还是比不过圆是不是。于是我就走上了用三阶贝塞尔曲线模拟圆的路。
看来我当初写这一块代码的时候,脑子确实不太清醒,不过也有收获。又复习了一遍 Path 的几个函数和贝塞尔曲线。
抠图:验证码滑块的生成
验证码 Path 生成好了后,我要根据 Path 去生成验证码滑块。那么第一步就是要抠图了。代码如下:
其实这里我也走了一些曲折的路,我先是用canvas.clipPath(path)抠的图,结果发现有锯齿,搜了很多资料也没搞定。于是我又回到了Xfermode的路上,将其设置为mPorterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
先绘制 dst,即遮罩验证码 Path,然后再绘制 src:Bitmap,取交集即可完成抠图。这里有一些需要注意的地方:
src 的 Bitmap 是 取ImageView本身的bitmap。
创建的新Bitmap的宽高取控件的宽高。
它们两者的宽高很大可能是不同的,这就是ImageView参数 scaleType 的作用。所以我们取出 ImageView 的 Matrix 用于绘制 src 的 Bitmap。这样抠出来的Bitmap区域就和第1步遮盖住的区域是一样的了。
mMaskShadowBitmap = mMaskBitmap.extractAlpha();这句话是为了在绘制出的滑块周围也绘制一圈阴影,加强立体效果。仔细看下图效果,周边又一圈立体阴影的效果:
绘制
onDraw()方法其实比较简单,只不过在其中加入了一些布尔类型的flag,都是和动画相关的,代码如下:
mPaint如下定义: 所以绘制出阴影也有一些阴影效果。
值得说的就是,配合滑块滑动,是利用mDragerOffset,默认是0,滑动时mDragerOffset增加,滑块右移,反之亦然。
验证成功的白光扫过动画,是利用canvas.translate()做的,mSuccessPath和mSuccessPaint如下:
滑动、验证、动画
上一节完成后,我们的滑动验证码View已经可以正常绘制出来了,现在我们为它增加一些方法,让它可以联动滑动、验证功能和动画。
上一节也提到,滑动主要是改变mDragerOffset的值,然后重绘自己->ondraw(),根据mDragerOffset偏移滑块Bitmap的绘制。
校验
校验的话,需要引入一个回调接口:
成功、失败的回调是在动画结束时通知的。
动画
动画里要用到宽高,所以它是在onSizeChanged()方法里被调用的。
代码很简单,修改的一些 布尔值 flag,在 onDraw() 方法里会用到,结合 onDraw() 一看便懂。
Demo
这一节,我们联动SeekBar滑动起来。xml如下:
UI就是文首那张图的样子,完整Activity代码:
总结
代码传送门,喜欢的话,随手点个star:
https://github.com/mcxtzhang/SwipeCaptcha
包含完整的Demo源码和SwipeCaptchaView。
利用一些工具发现web端斗鱼,验证码图片和滑块图片都是接口返回的。推测前端其实只返回后台:用户移动的距离或者距离的百分比。
本例完全由前端实现验证码生成、验证功能,是因为:
1.练习自定义VIew,自己全部实现抠图 验证 绘制,感觉很酷。
2.我不会做后台,手动微笑。
核心点:
1.不规则图形Path的生成。
2.指定Path对Bitmap抠图,抗锯齿。
3.适配ImageView的ScaleType。
4.成功、失败的动画
完。。。。。。。。。。。。。。。。。。。。。
文章原创作者GuoLin 书籍推荐
郭林大神原创android 书籍:《第一行代码 android》
网友评论