根据ui需求,需要显示一个奇葩的seekbar,滑块要在进度条上面,类似于刻度的显示。找了网上很多帖子,全都是教你怎样自定义seekbar,没有实现滑块在进度条上面的。于是捡起自定义view,自己写了一个。效果图:
首先说下整体思路,思路比较简单,但是实际写起来有不少坑。上面是一个imageview,下面是一个progressBar。重写onTouchEvent,控制ImageView的位置并控制进度条的进度。
1.定义一个MySeekBar的view继承RelaTiveLayout,为什么不继承线性布局,因为继承线性布局会出现坐标系混乱还有显示不全的问题,onMesure我学的不好,所以还是继承相对布局省事。然后就是init方法,这里开始添加滑块的图片和进度条,并设置位置。
/**
* view初始化
*/
private void init() {
ivPara = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
ivThumb = new ImageView(context);
ivThumb.setImageResource(R.mipmap.sbo);
ivThumb.setId(R.id.ivThumb);
addView(ivThumb);
progressBar = (ProgressBar) View.inflate(context, R.layout.view_seekbar, null);
params = new LayoutParams(LayoutParams.MATCH_PARENT, dip2px(progressbarHeight));
params.addRule(RelativeLayout.BELOW, R.id.ivThumb);
params.topMargin = dip2px(distance);
progressBar.setLayoutParams(params);
addView(progressBar);
progressBar.setMax(max);
}
iv
Para是在这里初始化,之后用来控制滑块也就是ivThumb的位置。
2.重写onTouchEvent方法
@Override
public boolean onTouchEvent(MotionEvent event) {
//不管是按下还是滑动,都要改变位置
float x = event.getX();
if (x <= 0) {
x = 0;
}
if (x >= range) {
x = range;
}
float rightedge = range - range * 1.0f / max / 2;
if (x >= rightedge) {
progress = max;
} else {
progress = (int) (x / (range * 1.0f) * max);
}
setProgress(progress);
return true;
}
这里
解释一下,前两个if判断是限制滑块的左右边界位置。rightedge是最后一个刻度的边界,比如我们设置progressBar的最大进度是10.那么这个rightedge对应的边界刻度就是9所在的位置。为什么要这么判断?因为在max比较大的时候还不明显,max比较小的时候你就会发现滑块是一跳一跳那样滑动的,中间很多的位置他是没法到达,他所在的位置只能是相应的进度所在的位置。举个例子:假设进度条的总长度是100px,总进度max是10,那么图片所滑动的位置只能是0,10,20,30,40...一直到100,所以滑动的时候通过event.getX获取的滑动坐标就要向下取到他所对应的数值,比如55就要取50,66要取60.这样进度条的进度和图片的位置才能对应。那么最后一个刻度就会有问题,直接运行的话你会发现我们很难滑到满,因为无论是91还是99都是90,正常情况下用户是很难滑到100或者一百以上。所以这里稍微加了一个判断,当位置>95的时候就置为100.这样就很容易能滑到100了。
3.上面是计算出了进度,下面就要根据进度计算位置
public void setProgress(int progress) {
this.progress = progress;
Log.i("wangyu", "progress:" + progress);
if (progress >= 0 && progress <= max) {
//计算进度对应的位置
if (range != 0) {
float fen = range / (max * 1.0f);
int left = (int) (progress * fen);
if (left > range - range / max / 2) {
left = range;
}
Log.i("wangyu", "left:" + left + " fen:" + fen + " range:" + range);
setThumbPosition(left);
setProgressBarProgress(progress);
} else {
shouldsetProgress = true;
}
}
}
这个原理上面已经解释过了,乘除法而已。唯一需要注意的一点是shouldsetProgress这个参数,如果range是0,这个参数置为true。这个涉及到view的生命周期和activity的生命周期,在oncreate方法执行的时候,view完成了inflate,这时候我们的view的init方法已经执行完毕,但是onMesure,onLayout,onDraw方法都还没有执行,如果在onCreate方法里面setProgress,这个时候我们还没测量完毕,没有得到range,因此我们就没法设置图片的位置,所以如果range是0的时候,标记一下,在测量完毕后设置一次滑块图片的位置。在下面的onMeasure方法里面可以看到
4.onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//这里滑块图片的宽度已经测量完毕
if (ivThumb.getWidth() > 0) {
padding = ivThumb.getWidth() / 2;
params.leftMargin = (int) padding;
params.rightMargin = (int) padding;
progressBar.setLayoutParams(params);
range = progressBar.getWidth();
if (shouldsetProgress) {
shouldsetProgress = false;
setProgress(progress);
}
}
}
在测量完毕之后,让进度条左右让出半个滑块的距离,保证滑块在两边的时候整好能对应进度条的边界。
然后就是上面说的shouldsetProgress参数,如果判断为true,说明在onCreate方法论里面设置了progres,那么就要在这里重新再设置一下进度,因为现在已经测量完毕,range已经有了正确的数值,所以会生效。
其他的方法就没有什么好介绍的了,一些属性设置的方法可以自行添加,样式也可以自行修改。至此,自定义seekBar完成。
源码:点击打开链接