在开发中为了防止恶意破解、恶意提交等行为,所以我们在提交数据时,都会使用验证功能,去尽量区别人机。在Android应用中我们同样需要这一功能,去有效的避开恶意注册,恶意攻击。
上篇文章:Android-来填写一个验证码吧!(一)中,介绍了一种,数字英文随机生成的图片验证码,使用很简单,一行代码,调用一个工具类,就能完美的实现图片验证码的显示。
另外一种常见的,可以实现简单人机区别检测的方法,滑动验证条,效果如下:
那么该如何实现呢?具体思路如下:
使用拓展自定义的SeekBar实现滑动条,根据触摸事件过滤错误操作,根据进度回调设置不同状态效果,再根据状态设置上不同的操作提示。
理清思路,接下来看下代码吧!
首先制作我们的拓展自定义SeekBar实现滑动条,然后根据dispatchTouchEvent
进行事件的拦截,过滤非起始点的点击事件,避免SeekBar自带的点击跳转进度的效果,不然的话还叫什么滑动进度条。
这里的计算,是根据ValidationSeekBar
的Left
点坐标位为起始,根据thumb
的大概大小计算出有效触摸范围,而其余的,通过 return true;
来进行拦截。
public class ValidationSeekBar extends AppCompatSeekBar {
//验证条的宽度
private int index = 0;
//点击状态
private boolean k = true;
private int xDOWN;
class measureRunnable
implements Runnable {
@Override
public void run() {
//重新测绘获取验证条宽度
ValidationSeekBar.this.measure(0, 0);
index = ValidationSeekBar.this.getLeft();
Log.e("ValidationSeekBar", "index:" + index);
}
}
public ValidationSeekBar(Context context) {
super(context);
this.post(new measureRunnable());
}
public ValidationSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
this.post(new measureRunnable());
}
public ValidationSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.post(new measureRunnable());
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
//过滤SeekBar的点击事件
if (event.getAction() == MotionEvent.ACTION_DOWN) {
xDOWN = (int) event.getX();
k = true;
if (xDOWN - index > 200) {
k = false;
return true;
}
}
//过滤SeekBar的点击事件
if (event.getAction() == MotionEvent.ACTION_UP) {
k = true;
if (xDOWN - index > 200) {
k = false;
return true;
}
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (!k) {
return true;
}
}
return super.dispatchTouchEvent(event);
}
}
以上,我的ValidationSeekBar就完成了。接下来去搭建我们的xml布局吧。以下是滑动条的布局:使用我们的ValidationSeekBar以及TextView组合出相关的提示效果。
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="25dp"
android:layout_marginRight="25dp"
android:layout_marginTop="30dp">
<sakura.backdemo.ValidationSeekBar
android:id="@+id/sb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress="0"
android:progressDrawable="@drawable/seekbar_bg"
android:thumb="@drawable/thumb"
android:thumbOffset="-3px" />
<TextView
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:text="请按住滑块,拖动到最右边"
android:textColor="#888888"
android:textSize="14dp" />
</RelativeLayout>
来看一下整体的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@android:color/holo_red_light">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="设置登录密码"
android:textColor="#fff"
android:textSize="20sp" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="45dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginTop="30dp"
android:background="@drawable/bg"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:text="中国+86"
android:textColor="#A2CD5A"
android:textSize="16sp" />
<View
android:layout_width="0.1dp"
android:layout_height="match_parent"
android:background="#FF7F00" />
<EditText
android:id="@+id/et_forgetPass_PhoneNum"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:background="@null"
android:digits="0123456789"
android:hint="请填入您的手机号"
android:inputType="number"
android:maxLength="11"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="25dp"
android:layout_marginRight="25dp"
android:layout_marginTop="30dp">
<sakura.backdemo.ValidationSeekBar
android:id="@+id/sb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress="0"
android:progressDrawable="@drawable/seekbar_bg"
android:thumb="@drawable/thumb"
android:thumbOffset="-3px" />
<TextView
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:text="请按住滑块,拖动到最右边"
android:textColor="#888888"
android:textSize="14dp" />
</RelativeLayout>
<Button
android:id="@+id/getCode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="35dp"
android:background="#ccc"
android:text="获取验证码"
android:textColor="#fff"
android:textSize="18sp" />
</LinearLayout>
这样整个界面的布局就出来了,但是还不够,我们还希望为我们的ValidationSeekBar增加更多的状态反馈效果。那么我们需要制作一些progressDrawable
和thumb
材料一:默认状态下的progressDrawable
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!--seekBar背景-->
<item android:id="@android:id/background">
<!--形状-->
<shape android:shape="rectangle">
<!--大小-->
<size android:height="42dp" />
<!--圆角-->
<corners android:radius="2dp" />
<!--背景-->
<solid android:color="#E7EAE9" />
<!--边框-->
<stroke
android:width="1dp"
android:color="#C3C5C4" />
</shape>
</item>
<!--seekBar的进度条-->
<item android:id="@android:id/progress">
<clip>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:innerRadius="120dp"
android:shape="rectangle"
android:thickness="1dp"
android:useLevel="false">
<solid android:color="#281991fa" />
<stroke
android:width="1dp"
android:color="#1991FA" />
<corners
android:bottomLeftRadius="2dp"
android:bottomRightRadius="2dp"
android:topLeftRadius="2dp"
android:topRightRadius="2dp" />
</shape>
</clip>
</item>
</layer-list>
材料二:错误状态下的progressDrawable
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!--seekBar背景-->
<item android:id="@android:id/background">
<!--形状-->
<shape android:shape="rectangle">
<!--大小-->
<size android:height="42dp" />
<!--圆角-->
<corners android:radius="2dp" />
<!--背景-->
<solid android:color="#E7EAE9" />
<!--边框-->
<stroke
android:width="1dp"
android:color="#C3C5C4" />
</shape>
</item>
<!--seekBar的进度条-->
<item android:id="@android:id/progress">
<clip>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:innerRadius="120dp"
android:shape="rectangle"
android:thickness="1dp"
android:useLevel="false">
<solid android:color="#28f57a7a" />
<stroke
android:width="1dp"
android:color="#F57A7A" />
<corners
android:bottomLeftRadius="2dp"
android:bottomRightRadius="2dp"
android:topLeftRadius="2dp"
android:topRightRadius="2dp" />
</shape>
</clip>
</item>
</layer-list>
材料三:完成状态下的progressDrawable
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!--seekBar背景-->
<item android:id="@android:id/background">
<!--形状-->
<shape android:shape="rectangle">
<!--大小-->
<size android:height="42dp" />
<!--圆角-->
<corners android:radius="2dp" />
<!--背景-->
<solid android:color="#E7EAE9" />
<!--边框-->
<stroke
android:width="1dp"
android:color="#C3C5C4" />
</shape>
</item>
<!--seekBar的进度条-->
<item android:id="@android:id/progress">
<clip>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:innerRadius="120dp"
android:shape="rectangle"
android:thickness="1dp"
android:useLevel="false">
<solid android:color="#2829946b" />
<stroke
android:width="1dp"
android:color="#29946b" />
<corners
android:bottomLeftRadius="2dp"
android:bottomRightRadius="2dp"
android:topLeftRadius="2dp"
android:topRightRadius="2dp" />
</shape>
</clip>
</item>
</layer-list>
材料四:各状态下的thumb
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 按下状态-->
<item android:drawable="@drawable/dw" android:state_pressed="true" />
<!-- 有焦点状态 -->
<item android:drawable="@drawable/de" android:state_focused="true" />
<!-- 普通状态 -->
<item android:drawable="@drawable/de" />
</selector>
到这里,我们的界面以及材料都已经准备好了,接下里我们去Activity
中看一下操作流程吧
public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener {
private TextView tv;
private ValidationSeekBar seekBar;
Handler handler = new Handler();
private Button getCode;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getCode = (Button) findViewById(R.id.getCode);
tv = (TextView) findViewById(R.id.tv);
seekBar = (ValidationSeekBar) findViewById(R.id.sb);
seekBar.setOnSeekBarChangeListener(this);
}
/**
* seekBar进度变化时回调
*
* @param seekBar
* @param progress
* @param fromUser
*/
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (seekBar.getProgress() == seekBar.getMax()) {
} else {
tv.setVisibility(View.INVISIBLE);
}
}
/**
* seekBar开始触摸时回调
*
* @param seekBar
*/
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
/**
* seekBar停止触摸时回调
*
* @param seekBar
*/
@Override
public void onStopTrackingTouch(final SeekBar seekBar) {
//验证错误时的滑动条样式
if (seekBar.getProgress() != seekBar.getMax()) {
seekBar.setProgressDrawable(getResources().getDrawable(R.drawable.seekbar_error_bg));
seekBar.setThumb(getResources().getDrawable(R.drawable.er));
seekBar.setThumbOffset(0);
int progress = seekBar.getProgress();
seekBar.setProgress(0);
seekBar.setProgress(progress);
//错误状态延时警示
handler.postDelayed(new Runnable() {
@Override
public void run() {
//错误回退动画效果
runFloat(seekBar.getProgress());
}
}, 400);
}
//设置验证成功的滑动条样式
if (seekBar.getProgress() == seekBar.getMax()) {
seekBar.setThumb(getResources().getDrawable(R.drawable.gd));
seekBar.setProgressDrawable(getResources().getDrawable(R.drawable.seekbar_good_bg));
seekBar.setProgress(0);
seekBar.setThumbOffset(-3);
seekBar.setProgress(100);
//成功时的提示信息
tv.setVisibility(View.VISIBLE);
tv.setTextColor(Color.rgb(0x29, 0x94, 0x6b));
tv.setText("完成验证");
//按钮状态
getCode.setBackgroundColor(getResources().getColor(android.R.color.holo_red_light));
}
}
private void runFloat(int Progress) {
//使用ValueAnimator进行进度的偏移。
ValueAnimator valueAnimator = ValueAnimator.ofInt(Progress, 0);
//动画时间,可根据需要调整
valueAnimator.setDuration(200);
//动画更新监听
valueAnimator
.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//设置进度
seekBar.setProgress(Integer.parseInt(valueAnimator
.getAnimatedValue().toString()));
}
});
//动画状态监听
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
//重置默认的滑动条样式
seekBar.setThumb(getResources().getDrawable(R.drawable.thumb));
seekBar.setThumbOffset(-1);
seekBar.setProgressDrawable(getResources().getDrawable(R.drawable.seekbar_bg));
//重置提示信息
tv.setVisibility(View.VISIBLE);
tv.setTextColor(Color.GRAY);
tv.setText("请按住滑块,拖动到最右边");
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
//开启valueAnimator
valueAnimator.start();
}
}
至此,我们的整个滑动验证码的制作流程就结束了,以上代码基本实现文章开头效果图以及常规的滑动验证码的需求逻辑,整体代码逻辑还是比较清晰了然的,使用起来也很方便。
**当然一定还有更多方法和更优化的逻辑,还请大家提出,共同完善,
有了需求才有了功能,有了想法才有了创作,你的反馈会是使我进步的最大动力。**
觉得还不够方便?还想要什么功能?告诉我!欢迎反馈,欢迎Star。