Android 滑动验证码实践

oberober 8年前
   <p>一大早起床就看到推送一篇文章,关于仿斗鱼web端的滑动验证码,看了一下实现,挺有趣的,便自己顺着思路撸一遍,改了一点实现和动画什么的,顺带巩固一下绘制的代码。</p>    <p>先看一下效果图</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/08dece50fe95bf58e635042dc0257ddf.gif"></p>    <p style="text-align:center">效果图</p>    <p>效果还是不错的。</p>    <p>用法,SlideValidationView 是继承自ImageView,所以验证码图片直接set就行。</p>    <pre>  <code class="language-java">SeekBar seekBar;  SlideValidationView slideValidationView;  slideValidationView = (SlideValidationView) findViewById(R.id.yzm);  // 设置监听器,判断验证成功失败时回调  slideValidationView.setListener(new SlideListener() {          @Override          public void onSuccess() {                  Toast.makeText(MainActivity.this, "验证成功", Toast.LENGTH_SHORT).show();          seekBar.setProgress(0);      }      @Override      public void onFail() {          Toast.makeText(MainActivity.this, "验证失败", Toast.LENGTH_SHORT).show();          seekBar.setProgress(0);      }  });  seekBar = (SeekBar) findViewById(R.id.seekBar);  seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {      @Override      public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {          // 更新验证滑块的位置          slideValidationView.setOffsetX(progress);      }      @Override      public void onStartTrackingTouch(SeekBar seekBar) {    }      @Override      public void onStopTrackingTouch(SeekBar seekBar) {          // 进行验证码的判断          slideValidationView.deal();      }  });</code></pre>    <p>下面说一下实现,也可以选择去看代码,代码里面注释应该很全了。代码链接: <a href="/misc/goto?guid=4959733342010207755" rel="nofollow,noindex">代码</a> 。看得喜欢关系点个star。</p>    <p>第一步:画拼图的path,上下左右四个半圆随机凹凸</p>    <pre>  <code class="language-java">/**   * 创建验证区域path   */  private void creatValidationPath() {      validationPath = new Path();      if (validationSize == 0) {          validationSize = width/6;      }      circleSize = validationSize / 3;      startX = new Random().nextInt(width - validationSize * 2 - circleSize * 2 - 10) + circleSize + validationSize + 10;      startY = new Random().nextInt(height - validationSize - circleSize * 2) + circleSize;      // 从左上画path到右上      validationPath.moveTo(startX, startY);      validationPath.lineTo(startX + circleSize, startY);      creatRandomArc(validationPath, startX + circleSize, startY, false, 0);      validationPath.lineTo(startX + validationSize, startY);      // 从右上画path到右下      validationPath.lineTo(startX + validationSize, startY + circleSize);      creatRandomArc(validationPath, startX + validationSize, startY + circleSize, true, 0);      validationPath.lineTo(startX + validationSize, startY + validationSize);      // 从右下画path到左下      validationPath.lineTo(startX + circleSize * 2, startY + validationSize);      creatRandomArc(validationPath, startX + circleSize, startY + validationSize, false, 1);      validationPath.lineTo(startX, startY + validationSize);      // 从左下画path到左上      validationPath.lineTo(startX, startY + circleSize * 2);      creatRandomArc(validationPath, startX, startY + circleSize, true, 1);      validationPath.lineTo(startX, startY);  }  /**   * 验证区域path四条边的半圆弧度   * @param validationPath 要操作的path   * @param beginX         弧度的起始x坐标(取弧度的左边坐标,即弧度的两点,位于左边的那个坐标)   * @param beginY         弧度的起始y坐标(取弧度的上边坐标,即弧度的两点,位于上边的那个坐标)   * @param isleftRight    是否左右边   * @param type           右上边为0,左下边为1   */  private void creatRandomArc(Path validationPath, int beginX, int beginY, boolean isleftRight, int type) {      RectF rectF;      // 是左右边还是上下边      if (isleftRight) {          rectF = new RectF(beginX - circleSize / 2, beginY, beginX + circleSize / 2, beginY + circleSize);      } else {          rectF = new RectF(beginX, beginY - circleSize / 2, beginX + circleSize, beginY + circleSize / 2);      }      // 随机得到是突出还是凹入半圆,针对角度问题,用type来解决      if (new Random().nextInt(10) > 5) {          // 突出半圆          if (isleftRight) {              validationPath.arcTo(rectF, -90 + type * 180, 180);          } else {              validationPath.arcTo(rectF, -180 + type * 180, 180);          }      } else {          // 凹入半圆          if (isleftRight) {              validationPath.arcTo(rectF, -90 + type * 180, -180);          } else {              validationPath.arcTo(rectF, -180 + type * 180, -180);          }      }  }</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/fcd89ff4ebd663cff31924c55e03dfc3.png"></p>    <p style="text-align:center">绘制拼图path</p>    <p>第二步:绘制阴影(设置画笔的setMaskFilter,应该要为这个view关闭硬件加速,否则阴影没作用)</p>    <pre>  <code class="language-java">// 单独为这个view关闭硬件加速  setLayerType(LAYER_TYPE_SOFTWARE, null);</code></pre>    <p>阴影</p>    <pre>  <code class="language-java">// 验证块的阴影画笔  Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);  mPaint.setColor(0x99000000);  // 设置画笔遮罩滤镜mPaint.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.SOLID));</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/badbb499c646fea1f8b27cc11c321b44.png"></p>    <p style="text-align:center">绘制阴影</p>    <p>第三步:绘制滑块</p>    <p>因为上面我们得到了拼图的path,我们就创建一个bitmap,在里面分别绘制验证码原图和这个path,通过setXfermode(不了解的可以去搜搜),取得他们的交集,即为我们的滑块,由此我们通过一个变量来控制滑块的绘制x轴就行了</p>    <pre>  <code class="language-java">// 以控件宽高 create一块bitmap  Bitmap tempBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);  // 把创建的bitmap作为画板  Canvas mCanvas = new Canvas(tempBitmap);  // 抗锯齿  mCanvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));  // 绘制用于遮罩的圆形  mCanvas.drawPath(mask, mMaskPaint);  // 设置遮罩模式(图像混合模式)  mMaskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));  // 考虑到scaleType等因素,要用Matrix对Bitmap进行缩放  mCanvas.drawBitmap(mBitmap, getImageMatrix(), mMaskPaint);</code></pre>    <p>第四步</p>    <p>绘制滑块的阴影,这里面有个api我也是第一次接触,bitmap.extractAlpha()拿到该bitmap的图片大小等信息,但只有透明度没有颜色,返回一张新的bitmap。我们通过设置画笔的阴影来绘制新的bitmap即可绘制出滑块的阴影</p>    <pre>  <code class="language-java">// extractAlpha拿到原bitmap的区域,只有透明度Bitmap   mMaskShadowBitmap = mMaskBitmap.extractAlpha();</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f0f6d4ff940cc52260b2e7313c884e97.png"></p>    <p style="text-align:center">绘制滑块和阴影</p>    <p>一些方法</p>    <table>     <thead>      <tr>       <th>方法名</th>       <th>用处</th>      </tr>     </thead>     <tbody>      <tr>       <td>setOffsetX(float howMuch)</td>       <td>设置滑块移动距离(@param howMuch 0-100内数字,表示百分比)</td>      </tr>      <tr>       <td>restore()</td>       <td>重置验证区域位置(重新生成拼图path)</td>      </tr>      <tr>       <td>deal()</td>       <td>判断是否成功</td>      </tr>      <tr>       <td>setListener(SlideListener listener)</td>       <td>设置监听器</td>      </tr>     </tbody>    </table>    <p> </p>    <p> </p>