滑块验证码
首先是布局文件
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="15dp" android:background="@drawable/container_backgroud" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="5dp" android:layout_marginLeft="20dp" android:layout_marginTop="15dp" android:text="滑动下方滑块完成拼图" android:textColor="@color/color333333" android:textSize="18sp"/> <Captcha android:id="@+id/captCha" android:layout_width="match_parent" android:layout_height="wrap_content" app:blockSize="50dp" app:max_fail_count="50" app:mode="mode_bar"/> </LinearLayout>
调用的滑块验证码的界面
public class CaptchaActivity extends BaseActivity { @Bind(R.id.captCha) Captcha mCaptCha; private int[] image = {R.drawable.captcha1,R.drawable.captcha2,R.drawable.captcha3,R.drawable.captcha4,R.drawable.captcha5,R.drawable.captcha6}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_captcha); ButterKnife.bind(this); mCaptCha.setCaptchaListener(new Captcha.CaptchaListener() { @Override public String onAccess(long time) { // Toast.makeText(CaptchaActivity.this, "验证成功", Toast.LENGTH_SHORT).show(); setResult(RESULT_OK); finish(); mCaptCha.setSeekBarStyle(R.drawable.huitiao, R.drawable.btn_chenggong); return "验证通过"; } @Override public String onFailed(int count) { // Toast.makeText(CaptchaActivity.this, "验证失败", Toast.LENGTH_SHORT).show(); Bitmap bmp = BitmapFactory.decodeResource(getResources(), image[ new Random().nextInt(6)]); mCaptCha.setBitmap(bmp); mCaptCha.setSeekBarStyle(R.drawable.huitiao, R.drawable.btn_moren); return "验证失败"; } @Override public String onMaxFailed() { return "可以走了"; } }); } }
自定义滑块布局
public class Captcha extends LinearLayout { private PictureVertifyView vertifyView; private TextSeekbar seekbar; private View accessSuccess, accessFailed; private TextView accessText, accessFailedText; private int drawableId; private int progressDrawableId; private int thumbDrawableId; private int mMode; private int maxFailedCount; private int failCount; private int blockSize; //处理滑动条逻辑 private boolean isResponse; private boolean isDown; private CaptchaListener mListener; /** * 带滑动条验证模式 */ public static final int MODE_BAR = 1; /** * 不带滑动条验证,手触模式 */ public static final int MODE_NONBAR = 2; private ImageView mIv_ok; @IntDef(value = {MODE_BAR, MODE_NONBAR}) public @interface Mode { } public interface CaptchaListener { /** * Called when captcha access. * * @param time cost of access time * @return text to show,show default when return null */ String onAccess(long time); /** * Called when captcha failed. * * @param failCount fail count * @return text to show,show default when return null */ String onFailed(int failCount); /** * Called when captcha failed * * @return text to show,show default when return null */ String onMaxFailed(); } public Captcha(@NonNull Context context) { super(context); } public Captcha(@NonNull Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public Captcha(@NonNull final Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Captcha); drawableId = typedArray.getResourceId(R.styleable.Captcha_src, R.drawable.captcha1); progressDrawableId = typedArray.getResourceId(R.styleable.Captcha_progressDrawable, R.drawable.huitiao); thumbDrawableId = typedArray.getResourceId(R.styleable.Captcha_thumbDrawable, R.drawable.btn_moren); mMode = typedArray.getInteger(R.styleable.Captcha_mode, MODE_BAR); maxFailedCount = typedArray.getInteger(R.styleable.Captcha_max_fail_count, 3); blockSize = typedArray.getDimensionPixelSize(R.styleable.Captcha_blockSize, Utils.dp2px(getContext(), 50)); typedArray.recycle(); init(); } private void init() { View parentView = LayoutInflater.from(getContext()).inflate(R.layout.container, this, true); vertifyView = (PictureVertifyView) parentView.findViewById(R.id.vertifyView); seekbar = (TextSeekbar) parentView.findViewById(R.id.seekbar); accessSuccess = parentView.findViewById(R.id.accessRight); accessFailed = parentView.findViewById(R.id.accessFailed); accessText = (TextView) parentView.findViewById(R.id.accessText); accessFailedText = (TextView) parentView.findViewById(R.id.accessFailedText); mIv_ok = (ImageView) parentView.findViewById(R.id.iv_ok); final LinearLayout ll_success = (LinearLayout) parentView.findViewById(R.id.ll_success); setMode(mMode); vertifyView.setImageResource(drawableId); setBlockSize(blockSize); vertifyView.callback(new PictureVertifyView.Callback() { @Override public void onSuccess(final long time) { ll_success.setVisibility(VISIBLE); new Handler().postDelayed(new Runnable() { public void run() { if (mListener != null) { String s = mListener.onAccess(time); if (s != null) { accessText.setText(s); } else { accessText.setText(String.format(getResources().getString(R.string.vertify_access), time)); } } accessSuccess.setVisibility(GONE); accessFailed.setVisibility(GONE); } }, 500); } @Override public void onFailed() { setSeekBarStyle(R.drawable.huitiao, R.drawable.btn_shibai); new Handler().postDelayed(new Runnable() { public void run() { //execute the task reset(false); failCount++; accessFailed.setVisibility(GONE); accessSuccess.setVisibility(GONE); if (mListener != null) { if (failCount == maxFailedCount) { String s = mListener.onMaxFailed(); if (s != null) { accessFailedText.setText(s); } else { accessFailedText.setText(String.format(getResources().getString(R.string.vertify_failed), maxFailedCount - failCount)); } } else { String s = mListener.onFailed(failCount); if (s != null) { accessFailedText.setText(s); } else { accessFailedText.setText(String.format(getResources().getString(R.string.vertify_failed), maxFailedCount - failCount)); } } } } }, 500); } }); setSeekBarStyle(progressDrawableId, thumbDrawableId); seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (isDown) { //手指按下 isDown = false; if (progress > 15) { //按下位置不正确 isResponse = false; } else { isResponse = true; accessFailed.setVisibility(GONE); vertifyView.down(0); } } if (isResponse) { vertifyView.move(progress); } else { seekBar.setProgress(0); } } @Override public void onStartTrackingTouch(SeekBar seekBar) { isDown = true; } @Override public void onStopTrackingTouch(SeekBar seekBar) { if (isResponse) { vertifyView.loose(); } } }); } public void setCaptchaListener(CaptchaListener listener) { this.mListener = listener; } public void setCaptchaStrategy(CaptchaStrategy strategy) { if (strategy != null) { vertifyView.setCaptchaStrategy(strategy); } } public void setSeekBarStyle(@DrawableRes int progressDrawable, @DrawableRes int thumbDrawable) { seekbar.setProgressDrawable(getResources().getDrawable(progressDrawable)); seekbar.setThumb(getResources().getDrawable(thumbDrawable)); seekbar.setThumbOffset(0); } /** * 设置滑块图片大小,单位px */ public void setBlockSize(int blockSize) { vertifyView.setBlockSize(blockSize); } /** * 设置滑块验证模式 */ public void setMode(@Mode int mode) { this.mMode = mode; vertifyView.setMode(mode); if (mMode == MODE_NONBAR) { seekbar.setVisibility(GONE); } else { seekbar.setVisibility(VISIBLE); } } public int getMode() { return this.mMode; } public void setMaxFailedCount(int count) { this.maxFailedCount = count; } public int getMaxFailedCount() { return this.maxFailedCount; } public void setBitmap(Bitmap bitmap) { vertifyView.setBitmap(bitmap); } public void setSuccessfulState(int gone) { mIv_ok.setVisibility(gone); } /** * 复位 */ public void reset(boolean clearFailed) { vertifyView.reset(); seekbar.setProgress(0); if (clearFailed) { failCount = 0; } } }
以下是滑块布局相关的工厂类
public abstract class CaptchaStrategy { protected Context mContext; public CaptchaStrategy(Context ctx) { this.mContext = ctx; } protected Context getContext() { return mContext; } /** * 定义缺块的形状 * * @param blockSize 单位dp,注意转化为px * @return path of the shape */ public abstract Path getBlockShape(int blockSize); /** * 定义缺块的位置信息 * * @param width picture width * @param height picture height * @return position info of the block */ public abstract PositionInfo getBlockPostionInfo(int width, int height, int blockSize); /** * 定义滑块图片的位置信息(只有设置为无滑动条模式有用) * * @param width picture width * @param height picture height * @return position info of the block */ public PositionInfo getPositionInfoForSwipeBlock(int width, int height, int blockSize){ return getBlockPostionInfo(width,height,blockSize); } /** * 获得缺块阴影的Paint */ public abstract Paint getBlockShadowPaint(); /** * 获得滑块图片的Paint */ public abstract Paint getBlockBitmapPaint(); /** * 装饰滑块图片,在绘制图片后执行,即绘制滑块前景 */ public void decoreateSwipeBlockBitmap(Canvas canvas, Path shape) { } }
public class DefaultCaptchaStrategy extends CaptchaStrategy { public DefaultCaptchaStrategy(Context ctx) { super(ctx); } @Override public Path getBlockShape(int blockSize) { int gap = (int) (blockSize/5f); Path path = new Path(); path.moveTo(0, gap); path.rLineTo(blockSize/2.5f, 0); path.rLineTo(0, -gap); path.rLineTo(gap, 0); path.rLineTo(0, gap); path.rLineTo(2 * gap, 0); path.rLineTo(0, 4 * gap); path.rLineTo(-5 * gap, 0); path.rLineTo(0, -1.5f * gap); path.rLineTo(gap, 0); path.rLineTo(0, -gap); path.rLineTo(-gap, 0); path.close(); return path; } @Override public @NonNull PositionInfo getBlockPostionInfo(int width, int height, int blockSize) { Random random = new Random(); int left = random.nextInt(width - blockSize +1); //Avoid robot frequently and quickly click the start point to access the captcha. if (left < blockSize) { left = blockSize; } int top = random.nextInt(height - blockSize +1); if (top < 0) { top = 0; } return new PositionInfo(left, top); } @Override public @NonNull PositionInfo getPositionInfoForSwipeBlock(int width, int height, int blockSize) { Random random = new Random(); int left = random.nextInt(width - blockSize+1); int top = random.nextInt(height - blockSize+1); if (top < 0) { top = 0; } return new PositionInfo(left, top); } @Override public Paint getBlockShadowPaint() { Paint shadowPaint = new Paint(); shadowPaint.setColor(Color.parseColor("#000000")); shadowPaint.setAlpha(165); return shadowPaint; } @Override public Paint getBlockBitmapPaint() { Paint paint = new Paint(); return paint; } @Override public void decoreateSwipeBlockBitmap(Canvas canvas, Path shape) { Paint paint = new Paint(); paint.setColor(Color.parseColor("#7f000000")); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(10); // paint.setPathEffect(new DashPathEffect(new float[]{20,20},10)); Path path = new Path(shape); canvas.drawPath(path,paint); } }
class PictureVertifyView extends AppCompatImageView { private static final int STATE_DOWN = 1; private static final int STATE_MOVE = 2; private static final int STATE_LOOSEN = 3; private static final int STATE_IDEL = 4; private static final int STATE_ACCESS = 5; private static final int STATE_UNACCESS = 6; private static final int TOLERANCE = 10; private int mState = STATE_IDEL; private PositionInfo shadowInfo; private PositionInfo blockInfo; private Bitmap verfityBlock; private Path blockShape; private Paint bitmapPaint; private Paint shadowPaint; private long startTouchTime; private long looseTime; private int blockSize = 50; private Callback callback; private CaptchaStrategy mStrategy; private int mMode; interface Callback{ void onSuccess(long time); void onFailed(); } public PictureVertifyView(Context context) { this(context, null); } public PictureVertifyView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public PictureVertifyView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mStrategy = new DefaultCaptchaStrategy(context); shadowPaint = mStrategy.getBlockShadowPaint(); bitmapPaint = mStrategy.getBlockBitmapPaint(); setLayerType(View.LAYER_TYPE_SOFTWARE, bitmapPaint); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (shadowInfo == null) { shadowInfo = mStrategy.getBlockPostionInfo(getWidth(), getHeight(), blockSize); if(mMode==Captcha.MODE_BAR){ blockInfo = new PositionInfo(0, shadowInfo.top); }else{ blockInfo = mStrategy.getPositionInfoForSwipeBlock(getWidth(), getHeight(), blockSize); } } if (blockShape == null) { blockShape = mStrategy.getBlockShape(blockSize); blockShape.offset(shadowInfo.left, shadowInfo.top); } if (verfityBlock == null) { verfityBlock = createBlockBitmap(); } if (mState != STATE_ACCESS) { canvas.drawPath(blockShape, shadowPaint); } if (mState == STATE_MOVE || mState == STATE_IDEL || mState == STATE_DOWN) { canvas.drawBitmap(verfityBlock, blockInfo.left, blockInfo.top, bitmapPaint); } } void down(int progress) { startTouchTime = System.currentTimeMillis(); mState = STATE_DOWN; blockInfo.left = (int) (progress / 100f * (getWidth() - blockSize)); invalidate(); } void downByTouch(float x, float y) { mState = STATE_DOWN; blockInfo.left = (int) (x - blockSize / 2f); blockInfo.top = (int) (y - blockSize / 2f); startTouchTime = System.currentTimeMillis(); invalidate(); } void move(int progress) { mState = STATE_MOVE; blockInfo.left = (int) (progress / 100f * (getWidth() - blockSize)); invalidate(); } void moveByTouch(float offsetX, float offsetY) { mState = STATE_MOVE; blockInfo.left += offsetX; blockInfo.top += offsetY; invalidate(); } void loose() { mState = STATE_LOOSEN; looseTime = System.currentTimeMillis(); checkAccess(); invalidate(); } void reset() { mState = STATE_IDEL; verfityBlock.recycle(); verfityBlock = null; shadowInfo = null; blockShape = null; invalidate(); } void unAccess() { mState = STATE_UNACCESS; invalidate(); } void access() { mState = STATE_ACCESS; invalidate(); } void callback(Callback callback) { this.callback = callback; } void setCaptchaStrategy(CaptchaStrategy strategy) { this.mStrategy = strategy; } void setBlockSize(int size) { this.blockSize = size; this.blockShape = null; this.blockInfo = null; this.shadowInfo =null; this.verfityBlock = null; invalidate(); } public void setBitmap(Bitmap bitmap) { this.blockShape = null; this.blockInfo = null; this.shadowInfo =null; this.verfityBlock = null; setImageBitmap(bitmap); } void setMode(@Captcha.Mode int mode) { this.mMode = mode; this.blockShape = null; this.blockInfo = null; this.shadowInfo =null; this.verfityBlock = null; invalidate(); } private Bitmap createBlockBitmap() { Bitmap tempBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(tempBitmap); getDrawable().setBounds(0, 0, getWidth(), getHeight()); canvas.clipPath(blockShape); getDrawable().draw(canvas); mStrategy.decoreateSwipeBlockBitmap(canvas, blockShape); return cropBitmap(tempBitmap); } private Bitmap cropBitmap(Bitmap bmp) { Bitmap result = null; result = Bitmap.createBitmap(bmp, shadowInfo.left, shadowInfo.top, blockSize, blockSize); bmp.recycle(); return result; } private void checkAccess() { if (Math.abs(blockInfo.left - shadowInfo.left) < TOLERANCE && Math.abs(blockInfo.top - shadowInfo.top) < TOLERANCE) { access(); if (callback != null) { long deltaTime = looseTime - startTouchTime; callback.onSuccess(deltaTime); } } else { unAccess(); if (callback != null) { callback.onFailed(); } } } private float tempX, tempY, downX, downY; @Override public boolean dispatchTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN && mMode == Captcha.MODE_NONBAR) { if (event.getX() < blockInfo.left || event.getX() > blockInfo.left + blockSize || event.getY() < blockInfo.top || event.getY() > blockInfo.top + blockSize) { return false; } } return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { if (mMode == Captcha.MODE_NONBAR && verfityBlock != null) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downX = x; downY = y; downByTouch(x, y); break; case MotionEvent.ACTION_UP: loose(); break; case MotionEvent.ACTION_MOVE: float offsetX = x - tempX; float offsetY = y - tempY; moveByTouch(offsetX, offsetY); break; } tempX = x; tempY = y; } return true; } }
public class PositionInfo { int left; int top; public PositionInfo(int left, int top) { this.left = left; this.top = top; } }
class TextSeekbar extends android.support.v7.widget.AppCompatSeekBar { private Paint textPaint; public TextSeekbar(Context context) { super(context); } public TextSeekbar(Context context, AttributeSet attrs) { this(context, attrs, R.style.MySeekbarSytle); } public TextSeekbar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); textPaint = new Paint(); textPaint.setTextAlign(Paint.Align.CENTER); int textSize = Utils.dp2px(context, 14); textPaint.setTextSize(textSize); textPaint.setAntiAlias(true); textPaint.setColor(Color.parseColor("#545454")); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Paint.FontMetrics fontMetrics = textPaint.getFontMetrics(); // float top = fontMetrics.top;//为基线到字体上边框的距离,即上图中的top // float bottom = fontMetrics.bottom;//为基线到字体下边框的距离,即上图中的bottom // int baseLineY = (int) (getHeight() / 2 - top / 2 - bottom / 2);//基线中间点的y轴计算公式 // canvas.drawText("向右滑动滑块完成拼图", getWidth() / 2, baseLineY, textPaint); } }
以上为全部代码 仅供参考学习
安卓开发交流群 : 595856941