安卓图片滑动验证模块

本文介绍了如何实现一个Android滑动拼图验证码控件,包括思路、具体步骤和代码实现。作者从网上找到一个示例,并根据UI需求进行了修改,如处理阴影显示、空余部分白边、左右范围限制和适配问题。此外,还分享了如何将该控件与Glide库结合使用,并提供了详细的代码示例和注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近想把工作这两年的东西好好写一写,一直觉得自己好像没做什么东西一样,写一写也能给自己一点自信,当然更像是一次总结。

安卓滑动验证模块是去年一个需求做的,当时也只是从网上找了一个不错的博客 cv 大法了一把,但是人家写的很详细,也是让我搞懂了一些东西,后面按 UI 要求改动了一些,就更加深刻了,从里面也学到了很多东西,下面好好说说。

具体效果

在这里插入图片描述

原版博客

前面先贴出原版博文,我做了一些修改但是不多,文章末尾贴代码吧

【Android】仿斗鱼滑动拼图验证码控件

https://www.jianshu.com/p/9bf982da6e96

实现思路

原作者写的挺清楚了,我这也是说一下我的理解吧

  1. 继承 ImageView,初始化笔触,绘制验证码滑块路径
  2. 以背景图和路径擦出一个和背景图尺寸一样,但只包含滑块部分的 bitmap
  3. 以滑块 bitmap 提取一个同等大小只含滑块形状的底层阴影的 bitmap
  4. 通过外部 seekbar 更新滑动比例,触发invalidate 函数,重绘图形
  5. 先绘制空出部分的阴影,再绘制滑块,包含滑块的阴影和滑块的图形
  6. 在外部滑块停止后,进行验证,显示动画,触发里面验证的回调接口

其他细节就不详述了,代码里面注释很多,我也加了挺多注释,看文末代码吧

我的修改

因为需要,我也按着上面的代码做了一定修改,下面说说

阴影显示问题

直接使用原版的代码发现没有阴影,我还自己弄了一番,后面发现原博主的代码可以,只是使用的时候没有关闭硬件加速,把硬件加速关了,滑块阴影就可以显示了

//关闭硬件加速
setLayerType(LAYER_TYPE_SOFTWARE, null);
空余部分白边

按设计说的上面博客的空余部分不太容易发现,希望加一层白边,这里使用画笔 stroke 模式多描了一层白边,但是记得画完后恢复 fill 模式,因为画笔还需要绘制空余部分的阴影

//首先绘制验证码阴影,即待填充部分
if (mCaptchaPath != null) {
    //待填充阴影,此处为填充模式
    canvas.drawPath(mCaptchaPath, mPaint);
    
    //描绘白边
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setColor(Color.WHITE);
    mPaint.setStrokeWidth(1);
    canvas.drawPath(mCaptchaPath, mPaint);

	//重置填充模式
	mPaint.setStyle(Paint.Style.FILL);
	mPaint.setColor(0xaa000000);
}
左右范围限制

由于和 seekbar 一起使用,seekbar 的左右还有边距,而且不好修改,加上被设计师狂喷说这个左右还能滑到头,不行之类的话,就加了点限制,看下面几点代码

	//初始偏移值,右边空余值,我这里代码里面写9和外部的seekbar比较适配
	public final int offset = 9;
	public static int dp2px(Context context, float dpValue) {
   
		final float scale = context.getResources().getDisplayMetrics().density;
    	return (int) (dpValue * scale + 0.5f);
	}

    public int getMaxSwipeValue() {
   
        //返回控件宽度
        return mWidth - mCaptchaWidth - 2 * dp2px(getContext(), offset);
    }

    public void setCurrentSwipeValue(int value) {
   
        mDragerOffset = dp2px(getContext(), offset) + value;
        invalidate();
    }


主要就是设置默认的滑动位置,和外部滑块能够滑动的最大位置

适配问题

这里因为我使用了头条的适配方案,将屏幕宽度写死了为360dp,在某些大屏手机上会有问题,而且修改系统显示大小(非字体大小)也会造成问题,具体问题是滑块和底部图片的缩放不一致,验证的实际位置是准确的,但是显示的滑块却不准确,影响验证。

实际问题是在 onDraw 方法中的 canvas 的 density 和适配的 density 不一致问题,解决办法我摸索了很长一段时间,才发现很简单,把 onDraw 方法中的 canvas 改成图片的 density 就可以了。

//修改canvas的density,屏幕适配会导致density不一致
canvas.setDensity(mMaskBitmap.getDensity());

这里直接使用了 mMaskBitmap 的 density,完美解决,很OK!

其他

其他的就是简化了一下代码什么的,可以忽略。

结合Glide使用

这里也写一下用法吧,原来的 demo 不好用了,下面贴代码吧

  • Activity
public class SwipeCheckActivity extends BaseActivity {
   

    //随便给一个必应的吧
    private static final List<String> URLS = Arrays.asList(
            "https://api.dujin.org/bing/1920.php",
    );

    SwipeCaptchaView mSwipeCaptchaView;
    SeekBar mSeekBar;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
   
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_swipe_check);
        setTitle("验证码");
        mSwipeCaptchaView = findViewById(R.id.swipeCaptchaView);
        mSeekBar = findViewById(R.id.dragBar);
        mSwipeCaptchaView.setCurrentSwipeValue(0);
        mSwipeCaptchaView.setOnCaptchaMatchCallback(new SwipeCaptchaView.OnCaptchaMatchCallback() {
   
            @Override
            public void matchSuccess(SwipeCaptchaView swipeCaptchaView) {
   
                mSeekBar.setEnabled(false);
                setResult(RESULT_OK);
                finish();
            }

            @Override
            public void matchFailed(SwipeCaptchaView swipeCaptchaView) {
   
                Toast.makeText(SwipeCheckActivity.this,
                        "验证失败!", Toast.LENGTH_SHORT).show();
                swipeCaptchaView.resetCaptcha();
                mSeekBar.setProgress(0);
            }
        });
        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
   

            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
   
                mSwipeCaptchaView.setCurrentSwipeValue(progress);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
   
                //随便放这里是因为控件
                mSeekBar.setMax(mSwipeCaptchaView.getMaxSwipeValue());
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
   
                mSwipeCaptchaView.matchCaptcha();
            }
        });
        //这里很可惜,使用图片缓存的话会有问题
        Log.e("SwipeCaptchaView", " System: density:" + getResources().getDisplayMetrics().densityDpi);
        Glide.with(SwipeCheckActivity.this)
                .asBitmap()
                .load(URLS.get((int) (Math.random() * URLS.size())))
                .apply(new RequestOptions()
                        .diskCacheStrategy(DiskCacheStrategy.NONE)
                        .skipMemoryCache(true)
                )
                .addListener(new RequestListener<Bitmap>() {
   
                    @Override
                    public boolean onLoadFailed(@Nullable GlideException e, Object model,
                                                Target<Bitmap> target, boolean isFirstResource) {
   
                        return true;
                    }

                    @Override
                    public boolean onResourceReady(Bitmap resource, Object model,
                                                   Target<Bitmap> target,
                                                   DataSource dataSource, boolean isFirstResource) {
   
                        mSwipeCaptchaView.setImageBitmap(resource);
                        mSwipeCaptchaView.createCaptcha();
                        return true;
                    }
                })
                .into(mSwipeCaptchaView);
    }
}

注意

这里需要禁用一下缓存,不然获取不到图片的宽高,会闪退,我也没有什么好办法。

  • XML - layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingLeft="14dp"
    android:paddingRight="14dp">

    <TextView
        android:layout_width=
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值