昨天朋友问我了解不了解SeekBar,他说想实现两个功能:
- 禁止SeekBar手动滑动thumb
- 给SeekBar的thumb设置点击事件,但是没有现成的方法
当时我还这没研究过SeekBar(说实话当时我还不了解SeekBar和thumb到底是什么。。。随手查了下才知道是SeekBar是进度条,thumb就是进度条上的滑块),所以决定动手实现下。
SeekBar的使用
由于没有使用过SeekBar所以先从网上查了下SeekBar的用法,用法还是比较简单的,在xml布局文件中使用方法如下:
<SeekBar
android:id="@+id/seek_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:thumb="@drawable/round"/>
然后可以调用setProgress方法动态设置进度
屏蔽点击和拖动滑块
重写SeekBar,让onTouchEvent方法返回true即可
@Override
public boolean onTouchEvent(MotionEvent event) {
return true;
}
给thumb设置点击事件
因为thumb不是View,所以不能直接给thumb设置点击事件,那么thumb是什么?
看源码:
从源码中可以看出thumb是Drawable
那么就好办了,因为我们可以通过getBounds方法得到Drawable上下左右四个位置坐标,所以可以通过判断手指落点是否在thumb上来判断点击事件是否发生
于是写出了如下代码:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
if (isTouchInThumb(event, getThumb().getBounds())){
if (listener != null)
listener.onClickThumb();
}
break;
}
return true;
}
/**
* 判断MotionEvent事件是否位于thumb上
* @param event
* @param thumbBounds
* @return
*/
private boolean isTouchInThumb(MotionEvent event, Rect thumbBounds){
float x = event.getX();
float y = event.getY();
if (x >= thumbBounds.left && x <= thumbBounds.right
&& y >= thumbBounds.top && y <= thumbBounds.bottom)
return true;
return false;
}
public void setOnClickThumbListener(OnClickThumbListener listener){
this.listener = listener;
}
interface OnClickThumbListener{
void onClickThumb();
}
然后美滋滋的去运行了,结果却不尽如人意
点击的时候发现手指没在thumb上而是向右偏移很多仍能触发点击事件。。。。
示意图如下:
这就让人很尴尬了!难道是我设置的图片有很宽透明边缘?
那就换张图片试试,结果证明并不是图片的原因,更换图片以后问题依旧复现
那问题到底在哪里?
迫不得已我绘制了一下drawable所在矩形的边缘,代码如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Rect rect = getThumb().getBounds();
canvas.drawRect(rect, new Paint());
}
结果发现了一个惊天大秘密
这是什么鬼???矩形和图片不是应该重合才对吗?
静下心来一想,这并非偶然,先看一张SeekBar的初始状态图
我们发现thumb会被遮住一部分,再对比一下绘制的矩形初始状态
原来drawable的左边界和SeekBar的左边界是重合的(有人可能会说,SeekBar的左边是没有贴到屏幕左侧的,而这个矩形已经贴到屏幕左侧了,怎么会是重合的,其实看到的进度条的左边界并不是SeekBar的左边界,通过给SeekBar设置一个背景颜色就能直观的看出来,我这里就不多做演示)
那么我可以做进一步猜想,SeekBar可能是以drawable的位置为基础进行了偏移,为了寻找依据,我不得不查看源码。
无奈之下又去查看源码,发现绘制thumb时的坐标会受到两个值的影响,分别是mPaddingLeft和的mThumbOffset,下面针对这两个变量进行分析。
mPaddingLeft控制进度条起始点的位置,mThumbOffset设置thumb相对于进度条起始点的相对位置。
public void setThumb(Drawable thumb) {
...
// Assuming the thumb drawable is symmetric, set the thumb offset
// such that the thumb will hang halfway off either edge of the
// progress bar.
mThumbOffset = thumb.getIntrinsicWidth() / 2;
...
}
从上述代码中可以看出在设置thumb时同时会设置一个初始偏移量,这个值是thumb宽度的二分之一,这个是为了保证自定义thumb水平方向的中间位置能正好位于SeekBar进度条的起始位置,如果不设置偏移量的话图片的左边缘会位于进度条的起始位置。
再看一下绘制thumb的部分
/**
* Draw the thumb.
*/
void drawThumb(Canvas canvas) {
if (mThumb != null) {
final int saveCount = canvas.save();
// Translate the padding. For the x, we need to allow the thumb to
// draw in its extra space
canvas.translate(mPaddingLeft - mThumbOffset, mPaddingTop);
mThumb.draw(canvas);
canvas.restoreToCount(saveCount);
}
}
在绘制thumb时把画布的原点坐标移动到了(mPaddingLeft - mThumbOffset, mPaddingTop)
于是修改如下代码:
/**
* 判断MotionEvent事件是否位于thumb上
* @param event
* @param thumbBounds
* @return
*/
private boolean isTouchInThumb(MotionEvent event, Rect thumbBounds){
float x = event.getX();
float y = event.getY();
//根据偏移量和左边距确定thumb位置
int left = thumbBounds.left - getThumbOffset() + getPaddingLeft();
int right = left + thumbBounds.width();
if (x >= left && x <= right
&& y >= thumbBounds.top && y <= thumbBounds.bottom)
return true;
return false;
}
再运行时发现问题已经解决,到此就完成了需要的两个功能
源码地址