一、说明
笔记主要是记录一些本人在开发当中的学习和使用笔记。笔记内容包含一些本人觉得重要的知识点、本人易犯的错误等。
由于本人水平有限,其中出现的错误或者不合理的地方望各位读者多多包含,并指出其中不合理和错误的地方,以便我来修改正。谢谢!
二、笔记时间
2019年06月27日:首次编辑
2019年10月23日:第一次修改
三、简述
本文主要讲述如何把 SeekBar 变成滑动验证的控件。
四、详情
由于工作和生活的原因,很长一段时间没有写博客了,最近稍闲一点点,决定把这段时间工作中遇到的一些问题和解决方案记录下来。供自己和有需要的朋友借鉴。
SeekBar 是我们工作当中经常使用到的控件,SeekBar 在画板中可以用他来调节画笔、画布的大小;可以用来调节图片颜色、显示下载进度;还可以制作滑动验证条等等。总的来说有很多场景我们都可以用SeekBar来实现。今天主要讲我的用 SeekBar 做滑动验证条的实现方式。
1、自定义 SeekBar 样式
要实现美工小姐姐设计图的效果,以便与美工小姐姐产生共鸣,我们无法避免对SeekBar的样子进行自定义。下面介绍几个 SeekBar 的常用属性:
- android:thumb //滑块样式
- android:progressDrawable //进度条样式
- android:max //拖动条的最大值
- android:min //拖动条的最小值
- android:progress //当前的进度值
- android:thumbOffset //滑块偏移量
- android:splitTrack //是否拆分绘制(滑块透明区域透明)
- android:paddingStart //盒子内左边距
- android:paddingEnd //盒子内右边距
上面对几个重要属性进行了简短说明,下面一一说明怎么通过设置以上属性实现滑动验证效果。
1.1 android:thumb
设置 thumb 可以实现自定义滑块样式。我们可以使用美工提供的图片,也可以用 shape 等来自定义滑块样式。我这里为了在不同机型上的通用性,是使用 shape 来自定义的样式,如下:
<!-- seekbar_thumb.xml -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size
android:width="11dp"
android:height="11dp" />
<corners android:radius="3dp" />
<gradient
android:endColor="#FF44A3FF"
android:startColor="#FF9CDCFF" />
</shape>
样式效果是四角为圆角、颜色渐变、固定大小的正方形滑块。效果如下图:
1.2 android:progressDrawable
设置 progressDrawable 可以实现自定义进度条样式。和 thumb 不一样的是,我们可以使用美工提供的图片,但是还是要用 layer-list 来进行组合,因为进度条有选中未选择等状态;当然也可以使用 shape 自定义。同样我这里也是使用 shape 自定义的,具体代码如下:
<!-- seekbar_progress.xml -->
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@android:id/background"
android:height="11dp"
android:gravity="center_vertical">
<shape>
<corners android:radius="3dp" />
<solid android:color="@android:color/transparent" />
</shape>
</item>
<item
android:id="@android:id/secondaryProgress"
android:height="11dp"
android:gravity="center_vertical">
<clip>
<shape>
<corners android:radius="3dp" />
<solid android:color="#FF9CDCFF" />
</shape>
</clip>
</item>
<item
android:id="@android:id/progress"
android:height="11dp"
android:gravity="center_vertical">
<clip>
<shape>
<corners android:radius="3dp" />
<solid android:color="#FF9CDCFF" />
</shape>
</clip>
</item>
</layer-list>
先简要说明一下我自定义的效果,我实现的效果是选中状态为:未选中(滑块右侧)透明,选中和过渡区域为固定色(滑块渐变的开始颜色)。效果如下图:
细心的同学回发现里面有三个 @android 的 ID,那个这几个 ID到底是做什么用的勒,下面对他们说明一下。
- @android:id/background //这个不难理解,从命名上我们就可以看出来,他是设置进度条的背景
- @android:id/secondaryProgress //这个咋一看难以从命名上直接看出来,其实他是第二进度选中样式,和 android:secondaryProgress 一起看就不难理解了。
- @android:id/progress //这个自然就是第一进度选中样式了。
1.3 android:max、android:min
这两个属性就不需要多讲了,相信大家都明白,他就是设置进度条的最大、最小进度值。
1.4 android:progress
这个属性应该大家也都明白,就是设置当前选中的进度值。当然还有 android:secondaryProgress 这个属性,这个属性就是设置第二进度的角度值。
1.5 android:thumbOffset
这个属性从命名可以看出是设置滑块的偏移量,但是设置不同的值是什么效果勒?下面我们来看看设置为10dp、0dp、-10dp 的效果:
- android:thumbOffset="10dp" //向左偏移
- android:thumbOffset="0dp" //向右偏移一个滑块宽度
- android:thumbOffset="-10dp" //向右偏移
1.6 android:splitTrack
这个属性还是很有用的,尤其是自定义滑块的时候。我自定义的滑块是圆角,当我没设置这个属性的时候(默认 android:splitTrack="true"),发现滑动的时候圆角处不是透明的,而是有一个白色的角,这明显不是我们想要的效果。
出现这种不透明的现象我们就可以设置 android:splitTrack="false" ,这样就是透明的了。
1.6 android:paddingStart、android:paddingEnd
我们知道固定 SeekBar 宽高之后,滑块左右都默认会偏移半个滑块的宽度,以便滑动到最左和最右的时候能够完整的显示滑块。其实这个偏移位置我们是可以自己设定的,就是通过 paddingStart、paddingEnd 来设置两边的偏移。当然 paddingLeft、paddingRight 也可以达到一样的效果。
1.7 滑动验证最终配置
虽然上面介绍了那么多属性,不过这里实现滑动验证效果,我并没有全部使用,最终的配置如下:
<SeekBar
android:id="@+id/seekbar"
android:layout_width="50dp"
android:layout_height="11dp"
android:paddingStart="6dp"
android:paddingEnd="6dp"
android:progressDrawable="@drawable/seekbar_progress"
android:splitTrack="false"
android:thumb="@drawable/seekbar_thumb" />
以上配置的效果如下图:
2、滑动验证功能实现
第一大点主要是自定义滑动条的样式,也就是实现我们美工小姐姐的奇思异想。剩下的滑动验证功能就需要我们这些攻城狮一句一句代码敲出来了!下面我介绍一下我的实现吧。
2.1 滑动停止回到初始位置
这并不是什么复杂的功能,我们快速过。只需要给 SeekBar 设置 setOnSeekBarChangeListener 监听,然后实现监听,在监听中的 onStopTrackingTouch 方法里出现设置 progres 为 0 就好了。还是贴一下代码,如下:
SeekBar seekBar = findViewById(R.id.seekbar);
seekBar.setOnSeekBarChangeListener(this);
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
seekBar.setProgress(0);
}
2.1 屏蔽滑动条滑块外的点击事件
原本以为做完以上工作,滑动验证就算完成了。可偏偏又出现了另外一个问题,说好的滑动验证,居然点击滑块的其它位置滑块也会设置过去。不过这是 SeekBar 的基本功能,我们不能说啥,现在我们看看怎么屏蔽滑块外的点击事件。我自定义了 SeekBar ,对触摸事件进行了处理,代码如下:
public class VerificationSeekBar extends AppCompatSeekBar {
private boolean isInterception = true;
public VerificationSeekBar(Context context) {
super(context);
}
public VerificationSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public VerificationSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
isInterception = true;
if (event.getX() > getThumb().getMinimumWidth()) {
isInterception = false;
return true;
}
}
if (event.getAction() == MotionEvent.ACTION_MOVE && !isInterception) {
return true;
}
return super.dispatchTouchEvent(event);
}
}
上面代码的逻辑也很简单,就是判断点击位置是否在滑块外,在滑块外就屏蔽啥也不做,但是消费掉滑动事件。
现在替换 SeekBar 为我们自定义的 VerificationSeekBar ,就能够达到滑动验证的效果了。
2.2 解决点击滑动条尾部直接验证成功的问题
我在做画板的时候,利用该方式来做滑动清屏,结果发现了一个隐藏bug。我们程序员往往最怕这种隐藏bug了,但是怕归怕,问题我们还是要解决了。当然了,这个bug并不是很难解。
下面说一下这个具体的bug:在白板当中,我设置的是滑动到90%以上,松手即触发清屏。结果当我们点击滑动条末尾位置时,同样触发了清屏。说好的滑动清屏,可是实际使用结果却可以点击清屏,这很明显和我们的设计方案不符合。因此我做了以下小修改,来完善该功能。
我把该问题记录下来,一个是给自己做笔记,另外一个我相信滑动验证也会有类似的问题,希望读者能够直接解决好问题。以下就是我的解决方案。
2.2.1 把 isInterception 的值提供出来
把 isInterception 的值提供出来其实就是在 VerificationSeekBar 中添加一个获取该值的方法,具体如下:
// VerificationSeekBar.java
/**
* 获取是否滑动清屏
*
* @return true:滑动验证 false:非滑动验证
*/
public boolean isInterception() {
return isInterception;
}
2.2.2 判断是否是滑动验证
这个就需要我们在实现SeekBar的监听事件时,利用 isInterception 方法来判断是否触发验证成功。具体如下:
VerificationSeekBar mySeekBar;
mySeekBar = findViewById(R.id.seekbar);
mySeekBar.setOnSeekBarChangeListener(this);
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (mySeekBar.isInterception()) {
succeed(); //滑动验证成功
} else {
failed(); //滑动验证失败
}
seekBar.setProgress(0);
}
2.2.3 其它解决方案
我以上的解决方案并不是唯一的解决方案。我再提供两种解决思路,因为项目时间紧张我就没一一验证了。读者可以验证一下,把验证结果加到评论区,让所有读者共勉。
第一种解决方案和我上面提供的解决方案一样,只是我们可以把 setOnSeekBarChangeListener 监听的逻辑在 VerificationSeekBar 中实现,并向外提供一个 succeed、failed 接口,来保证封装的完整性。本人推荐该方法。
第二中解决方案,我们可以利用 onStartTrackingTouch ,通过开始点击的值(位置)来判断是否是滑动验证,非点击滑动条末尾,但是要增加变量,不建议使用。用我画板90%触发清屏为例,我们可以修改为如下:
int startProgress = 0;
SeekBar seekBar = findViewById(R.id.seekbar);
seekBar.setOnSeekBarChangeListener(this);
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
startProgress = seekBar.getProgress();
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (startProgress < 90 && seekBar.getProgress() >= 90) {
succeed(); //滑动验证成功
} else {
failed(); //滑动验证失败
}
seekBar.setProgress(0);
}
注:第二中方案要着重验证,从滑动条中间拖到满足条件,是否会被判断为 succeed?若满足验证成功,建议和第一种犯案一样封装一下;若验证失败,建议把 <90 的条件改为,<= getThumb().getMinimumWidth()