Android三层拼图代码,Android自定义拼图验证码

2019.7.5更新,Android自定义点选验证码已完成。

先上效果图,没图说个蛋蛋:

a6a7a1ea85d1

ezgif-3-a89a55a08fab.gif

从效果图开始"临摹"

分析

从上面的效果图中,我们可以很直观的看出一共包含三个元素:背景图、空缺部分、填充部分,需要注意的是:

1. 空缺部分缺失的图片刚好是填充部分

2. 我们把填充部分位置固定在左侧,而随机生成空缺部分在右侧,增加验证难度

思路

准备背景图片,通过canvas.drawBitmap()方法画出背景图

计算View宽高,随机生成空缺部分的x坐标在(width/3, width)范围,固定填充部分的x左边在(0,width/3)范围内,保证填充部分和空缺部分在初始化时没有重叠。(不严谨,具体数值还要结合空缺部分/填充部分尺寸详细计算,仅提供思路)。

先随机生成空缺部分,然后根据空缺部分在原来Bitmap上的左边生成一样大小一样形状的图片,用于填充部分。

然后重写onTouchEvent方法,处理拖动时填充部分的位移,在MotionEvent.ACTION_UP条件下,计算填充部分和空缺部分在画布中的x坐标差值,判断当差值小于阙值 dx 时,则认为通过验证,否则调用 invalidate() 方法重新生成验证码。

主要代码分析

这里重写了onMeasure方法,根据我们准备的原图片尺寸设置View宽高,并且重新生成和View一样尺寸的背景图newBgBitmap,统一尺寸以便后面我们对左边的转化。(这里曾经有些地方参照画布尺寸计算,有些地方参照背景图bitmap尺寸计算,导致填充部分和空缺部分没有吻合)。

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int minimumWidth = getSuggestedMinimumWidth();

/*根据原背景图宽高比设置画布尺寸*/

width = measureSize(minimumWidth, widthMeasureSpec);

float scale = width / (float) bgBitmap.getWidth();

height = (int) (bgBitmap.getHeight() * scale);

setMeasuredDimension(width, height);

/*根据画布尺寸生成相同尺寸的背景图*/

newBgBitmap = clipBitmap(bgBitmap, width, height);

/*根据新的背景图生成填充部分*/

srcBitmap = createSmallBitmap(newBgBitmap);

}

设置画笔的混合模式,生成一张自定义形状的图片供填充部分使用

public Bitmap createSmallBitmap(Bitmap var) {

Bitmap bitmap = Bitmap.createBitmap(shadowSize, shadowSize, Bitmap.Config.ARGB_8888);

Canvas canvas1 = new Canvas(bitmap);

canvas1.drawCircle(shadowSize / 2, shadowSize / 2, shadowSize / 2, paintSrc);

/*设置混合模式*/

paintSrc.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

/*在指定范围随机生成空缺部分坐标,保证空缺部分出现在View右侧*/

int min = width / 3;

int max = width - shadowSize / 2 - padding;

Random random = new Random();

shadowLeft = random.nextInt(max) % (max - min + 1) + min;

Rect rect = new Rect(shadowLeft, (height - shadowSize) / 2, shadowSize + shadowLeft, (height + shadowSize) / 2);

RectF rectF = new RectF(0, 0, shadowSize, shadowSize);

canvas1.drawBitmap(var, rect, rectF, paintSrc);

paintSrc.setXfermode(null);

return bitmap;

}

在onDraw()方法中依次画出背景图、空缺部分、填充部分,注意先后顺序(具体细节自行处理,例如阴影、凹凸感等等)

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

RectF rectF = new RectF(0, 0, width, height);

/*画背景图*/

canvas.drawBitmap(newBgBitmap, null, rectF, paintSrc);

bgPaint.setColor(Color.parseColor("#000000"));

/*画空缺部分周围阴影*/

canvas.drawCircle(shadowLeft + shadowSize / 2, height / 2, shadowSize / 2, bgPaint);

/*画空缺部分*/

canvas.drawCircle(shadowLeft + shadowSize / 2, height / 2, shadowSize / 2, paintShadow);

Rect rect = new Rect(srcLeft, (height - shadowSize) / 2, shadowSize + srcLeft, (height + shadowSize) / 2);

bgPaint.setColor(Color.parseColor("#FFFFFF"));

/*画填充部分周围阴影*/

canvas.drawCircle(srcLeft + shadowSize / 2, height / 2, shadowSize / 2, bgPaint);

/*画填充部分*/

canvas.drawBitmap(srcBitmap, null, rect, paintSrc);

}

草纸代码参考

随写随发布😛

package com.example.qingfengwei.myapplication;

import android.content.Context;

import android.content.res.Resources;

import android.graphics.Bitmap;

import android.graphics.BitmapFactory;

import android.graphics.BlurMaskFilter;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Matrix;

import android.graphics.Paint;

import android.graphics.PorterDuff;

import android.graphics.PorterDuffXfermode;

import android.graphics.Rect;

import android.graphics.RectF;

import android.util.AttributeSet;

import android.util.Log;

import android.view.MotionEvent;

import android.view.View;

import android.widget.Toast;

import java.util.Random;

public class SlidingVerificationView extends View {

private Bitmap bgBitmap;

private Bitmap newBgBitmap;

private Bitmap srcBitmap;

private Paint paintShadow;

private Paint paintSrc;

private float curX;

private float lastX;

private int dx;

private int shadowSize = dp2px(60);

private int padding = dp2px(40);

private int shadowLeft;

private int srcLeft = padding;

private int width, height;

private Paint bgPaint;

private OnVerifyListener listener;

public SlidingVerificationView(Context context) {

this(context, null);

}

public SlidingVerificationView(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public SlidingVerificationView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

paintShadow = new Paint();

paintShadow.setAntiAlias(true);

paintShadow.setColor(Color.parseColor("#AA000000"));

paintSrc = new Paint();

paintSrc.setAntiAlias(true);

paintSrc.setFilterBitmap(true);

paintSrc.setStyle(Paint.Style.FILL_AND_STROKE);

paintSrc.setColor(Color.WHITE);

bgPaint = new Paint();

bgPaint.setMaskFilter(new BlurMaskFilter(5, BlurMaskFilter.Blur.OUTER));

bgPaint.setAntiAlias(true);

bgPaint.setStyle(Paint.Style.FILL);

bgBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.syzt);

}

public void setVerifyListener(OnVerifyListener listener) {

this.listener = listener;

}

public Bitmap clipBitmap(Bitmap bm, int newWidth, int newHeight) {

int width = bm.getWidth();

int height = bm.getHeight();

float scaleWidth = ((float) newWidth) / width;

float scaleHeight = ((float) newHeight) / height;

Matrix matrix = new Matrix();

matrix.postScale(scaleWidth, scaleHeight);

return Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);

}

public Bitmap createSmallBitmap(Bitmap var) {

Bitmap bitmap = Bitmap.createBitmap(shadowSize, shadowSize, Bitmap.Config.ARGB_8888);

Canvas canvas1 = new Canvas(bitmap);

canvas1.drawCircle(shadowSize / 2, shadowSize / 2, shadowSize / 2, paintSrc);

/*设置混合模式*/

paintSrc.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

/*在指定范围随机生成空缺部分坐标,保证空缺部分出现在View右侧*/

int min = width / 3;

int max = width - shadowSize / 2 - padding;

Random random = new Random();

shadowLeft = random.nextInt(max) % (max - min + 1) + min;

Rect rect = new Rect(shadowLeft, (height - shadowSize) / 2, shadowSize + shadowLeft, (height + shadowSize) / 2);

RectF rectF = new RectF(0, 0, shadowSize, shadowSize);

canvas1.drawBitmap(var, rect, rectF, paintSrc);

paintSrc.setXfermode(null);

return bitmap;

}

@Override

public boolean onTouchEvent(MotionEvent event) {

curX = event.getRawX();

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

lastX = event.getRawX();

break;

case MotionEvent.ACTION_MOVE:

dx = (int) (curX - lastX);

srcLeft = dx + padding;

invalidate();

break;

case MotionEvent.ACTION_UP:

boolean isSuccess = Math.abs(srcLeft - shadowLeft) < 8;

if (isSuccess) {

Toast.makeText(getContext(), "验证成功!", Toast.LENGTH_SHORT).show();

Log.d("w", "check success!");

} else {

Toast.makeText(getContext(), "验证失败!", Toast.LENGTH_SHORT).show();

Log.d("w", "check fail!");

srcBitmap = createSmallBitmap(newBgBitmap);

srcLeft = padding;

invalidate();

}

if (listener != null) {

listener.onResult(isSuccess);

}

break;

}

return true;

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int minimumWidth = getSuggestedMinimumWidth();

/*根据原背景图宽高比设置画布尺寸*/

width = measureSize(minimumWidth, widthMeasureSpec);

float scale = width / (float) bgBitmap.getWidth();

height = (int) (bgBitmap.getHeight() * scale);

setMeasuredDimension(width, height);

/*根据画布尺寸生成相同尺寸的背景图*/

newBgBitmap = clipBitmap(bgBitmap, width, height);

/*根据新的背景图生成填充部分*/

srcBitmap = createSmallBitmap(newBgBitmap);

}

private int measureSize(int defaultSize, int measureSpec) {

int mode = MeasureSpec.getMode(measureSpec);

int size = MeasureSpec.getSize(measureSpec);

int result = defaultSize;

switch (mode) {

case MeasureSpec.UNSPECIFIED:

result = defaultSize;

break;

case MeasureSpec.AT_MOST:

case MeasureSpec.EXACTLY:

result = size;

break;

}

return result;

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

RectF rectF = new RectF(0, 0, width, height);

/*画背景图*/

canvas.drawBitmap(newBgBitmap, null, rectF, paintSrc);

bgPaint.setColor(Color.parseColor("#000000"));

/*画空缺部分周围阴影*/

canvas.drawCircle(shadowLeft + shadowSize / 2, height / 2, shadowSize / 2, bgPaint);

/*画空缺部分*/

canvas.drawCircle(shadowLeft + shadowSize / 2, height / 2, shadowSize / 2, paintShadow);

Rect rect = new Rect(srcLeft, (height - shadowSize) / 2, shadowSize + srcLeft, (height + shadowSize) / 2);

bgPaint.setColor(Color.parseColor("#FFFFFF"));

/*画填充部分周围阴影*/

canvas.drawCircle(srcLeft + shadowSize / 2, height / 2, shadowSize / 2, bgPaint);

/*画填充部分*/

canvas.drawBitmap(srcBitmap, null, rect, paintSrc);

}

public static int dp2px(float dp) {

float density = Resources.getSystem().getDisplayMetrics().density;

return (int) (density * dp + 0.5f);

}

}

下节预告:自定义点选验证码,效果图在文章开头已经放出了,就跟验证码死磕上了,哈哈。。。

2019.7.5更新,Android自定义点选验证码已完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值