Android实现拼图解锁

1,228 阅读4分钟

前言

验证的设计主要处于安全考虑,防止机器直接进行某种操作,进而很多人大展思路设计出了很多不同种类的验证码,以下将是通过Android自定义View实现拼图解锁功能。

效果图

t.gif

从图中大致可以看出并没有什么特别难的点,最多也就是如何将这个验证的图给随机的抠出来,这里用到了Xfermode即图像混合模式来进行图片的裁剪。

核心思路

第一、首先要有一张图片作为一个锁子,以下为例。

第一步我们仅仅拿到了图片,紧接着我们要在锁子的一个位置中挖出一块作为锁芯。为了好看,我们的锁芯应该与上下都有一定的间隔。

image.png

第二、一张图片作为锁芯的形状,然后随机取一个X坐标作为锁芯的中心点,并生成锁芯

其中绿色点为这个图片的中心点,白色为整个锁芯的大小,黑色的则是锁芯的形状,剩下的因为是透明的所以显示出了锁子的内容。

image.png

第三、两次绘制中通过设置Paint的Xfermode来取得不同的内容,进而将锁子钥匙锁芯进行分离。

可以说这个应该算是整个拼图解锁功能中最核心的了,不过在Android的绘图中Paint为我们提供的Xfermode,以此可以轻松解决这个事情,由于Xfermode提供了很多模式并不能一下子都掌握并且熟练使用,只需要从常用的几种着手就可以了。 以下不会赘述,有一个绘制圆角图片案例可做参考,内容简单相信看完就会明白! blog.lost520.cn/study/show-…

如果你感兴趣可以参考这位大神的博客:blog.csdn.net/harvic88092…

绘制锁子和钥匙的核心代码:

/**
 * 获取锁子或者钥匙
 * @param lock        锁子
 * @param keyTemp     钥匙模板
 * @param keyLocation 钥匙所处锁子的位置
 * @param mode        1:锁子,2:钥匙
 * @return
 */
private Bitmap getKeyOrLock(Bitmap lock, Bitmap keyTemp, Rect keyLocation, int mode) {
    //根据mode来具体设置Bitmap的大小
    int width = lock.getWidth();
    int height = lock.getHeight();
    if (mode == 2) {
        width = keyTemp.getWidth();
        height = keyTemp.getHeight();
    }
    Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);//绘制结果
    Canvas canvas = new Canvas(result);//画板
    Paint paint = new Paint();//画笔
    //根据mode设置不同的PorterDuffXfermode达到不同的遮罩效果
    if (mode == 1) {//绘制锁子
        canvas.drawBitmap(keyTemp, keyLocation.left, keyLocation.top, paint);//绘制DST
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
        canvas.drawBitmap(lock, 0, 0, paint);//绘制SRC
    } else {//绘制钥匙
        canvas.drawBitmap(keyTemp, 0, 0, paint);//绘制DST
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(lock, -keyLocation.left, -keyLocation.top, paint);//绘制SRC
    }
    return result;
}

image.png

第四、将锁子(带锁芯的)和钥匙分别绘制到特定位置。

锁子直接平铺即可,锁芯的绘制可以随机进行取值,钥匙则绘制在最左侧让用户进行滑动。

image.png

第五、检测用户拖动,验证钥匙和锁芯中心点是否重合。

在View中重写onTouchEvent函数来检测用户的拖拽,当用户点中钥匙并锁子进行拖动时,计算钥匙锁芯的中心点是否合并(可加上特定的偏移量),用户释放后则直接回调处理结果。到此五部基本完成了拼图解锁的功能。

image.png

核心代码

拆分锁子和钥匙:

/**
 * 获取锁子或者钥匙
 *
 * @param lock        锁子
 * @param keyTemp     钥匙模板
 * @param keyLocation 钥匙所处锁子的位置
 * @param mode        1:锁子,2:钥匙
 * @return
 */
private Bitmap getKeyOrLock(Bitmap lock, Bitmap keyTemp, Rect keyLocation, int mode) {
    //根据mode来具体设置Bitmap的大小
    int width = lock.getWidth();
    int height = lock.getHeight();
    if (mode == 2) {
        width = keyTemp.getWidth();
        height = keyTemp.getHeight();
    }
    Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);//绘制结果
    Canvas canvas = new Canvas(result);//画板
    Paint paint = new Paint();//画笔
    //根据mode设置不同的PorterDuffXfermode达到不同的遮罩效果
    if (mode == 1) {//绘制锁子
        canvas.drawBitmap(keyTemp, keyLocation.left, keyLocation.top, paint);//绘制DST
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
        canvas.drawBitmap(lock, 0, 0, paint);//绘制SRC
    } else {//绘制钥匙
        canvas.drawBitmap(keyTemp, 0, 0, paint);//绘制DST
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(lock, -keyLocation.left, -keyLocation.top, paint);//绘制SRC
    }
    return result;
}

绘制特定大小图片:

/**
 * 将图片绘制为特定大小
 *
 * @param bitmap 图片
 * @param width  宽度
 * @param height 高度
 * @return
 */
private Bitmap resizeBitmap(Bitmap bitmap, int width, int height) {
    Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(result);
    Rect rect = new Rect(0, 0, width, height);
    canvas.drawBitmap(bitmap, null, rect, null);
    return result;
}

布局:

<?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"
    tools:context="com.demo.qylost.puzzleunlockdemo.MainActivity">
    <com.demo.qylost.puzzleunlockdemo.PuzzleUnlockView
        android:id="@+id/puzzleUnlockView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/btnUpdate"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?selectableItemBackground"
        android:text="更换图片"
        android:textColor="@color/colorAccent" />
    <Button
        android:id="@+id/btnRefresh"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?selectableItemBackground"
        android:text="刷新"
        android:textColor="@color/colorAccent" />
    <TextView
        android:id="@+id/txtStatus"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="状态:XXX"
        android:textColor="@color/colorAccent" />
</LinearLayout>

后台:

//拿到相关控件
Button btnUpdate = findViewById(R.id.btnUpdate);
Button btnRefresh = findViewById(R.id.btnRefresh);
//自定义的拼图解锁View
final PuzzleUnlockView puzzleUnlockView = findViewById(R.id.puzzleUnlockView);
//显示状态
final TextView txtStatus = findViewById(R.id.txtStatus);

//更换图片
btnUpdate.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        puzzleUnlockView
          .setLockBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.lock2));
    }
});
//刷新
btnRefresh.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        puzzleUnlockView.refreshLock();
    }
});
//验证回调
puzzleUnlockView.setOnLockResultListener(new PuzzleUnlockView.OnLockResultListener() {
    @Override
    public void onResult(boolean result) {
        if (result) {
            txtStatus.setText("状态:Success");
        } else {
            txtStatus.setText("状态:Failed");
            puzzleUnlockView.refreshLock();//刷新
        }
    }
});

源码+素材

源码+素材.zip