2019.7.5更新墓律,Android自定義點選驗證碼已完成寻狂。
先上效果圖弹沽,沒圖說個蛋蛋:
從效果圖開始"臨摹"
分析
從上面的效果圖中,我們可以很直觀的看出一共包含三個元素:背景圖螃宙、空缺部分托呕、填充部分差牛,需要注意的是:
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()方法中依次畫出背景圖昌渤、空缺部分、填充部分憔四,注意先后順序(具體細節(jié)自行處理膀息,例如陰影、凹凸感等等)
@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);
}
草紙代碼參考
隨寫隨發(fā)布??
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);
}
}