seekbar的样式层出不穷,下面这篇文章也只是讲述了其中的一种,自定义view实现从中间向左右滑动的seekbar。如图
从上面的图片看得出该seekbar的构成分为底部默认的背景条,开始滑动的红色背景条和中间的圆形thumb。那么就按照该结构开始编写代码。
首先我们来看一下所需要的全局变量,以便后面代码的阅读。
// 中间的拖动bar
private Drawable mThumb;
// 默认的背景
private Drawable mDefaultBack;
// 滑动后的背景
private Drawable mSlideBack;
private int mSeekBarWidth;
private int mSeekBarHeight;
private int mThumbWidth;
private int mThumbHeight;
// thumb的中心位置
private int mThumbCenterPosition = 0;
// 能滑动的总长度
private int mSlideTotalDistance = 0;
代码中也都注释了,不多说明。我们接着往下看。进行初始化操作。
private void init() {
mThumb = getResources().getDrawable(R.drawable.thumb);
mDefaultBack = getResources().getDrawable(R.drawable.back_default);
mSlideBack = getResources().getDrawable(R.drawable.back_slide);
mSeekBarHeight = mDefaultBack.getIntrinsicHeight();
mSeekBarWidth = mDefaultBack.getIntrinsicWidth();
mThumbHeight = mThumb.getIntrinsicHeight();
mThumbWidth = mThumb.getIntrinsicWidth();
}
上述代码中,将中间圆形mThumb,默认背景mDefaultBack,滑动红色背景mSlideBack进行相应的初始化。为了后面mThumb的滑动以及位置的变化,这里需要以默认背景条来作为整条seekbar的宽度和高度,以及获取mThumb的宽度和高度。
对于view的绘制一般分为onMeasure,onLayout,onDraw三个步骤,
-onMeasure():测量自己的大小,为正式布局提供建议;
-onLayout():使用layout()函数对所有子控件布局;
-onDraw():根据布局的位置绘图;
那么先进入第一步对view进行测量。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = measureWidth(widthMeasureSpec);
mSeekBarWidth = width;
mThumbCenterPosition = width / 2;
mSlideTotalDistance = width - mThumbWidth;
setMeasuredDimension(width, mThumbHeight);
}
private int measureWidth(int measureSpec) {
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.AT_MOST) {
} else if (specMode == MeasureSpec.EXACTLY) {
}
return specSize;
}
上述代码中mThumbCenterPosition指的Thumb的圆心坐标,mSlideTotalDistance为能滑动的总长度。这里最为重要的地方为圆心坐标的值mThumbCenterPosition = width / 2,一般thumb的位置位于seekbar的起始位置,那么这里就是将thumb设置到seekbar的中点,从这里开始向左向右滑动。
上述是做好了view 的测量,一般来说测量只是你建议的值,真正布局是在onLayout中,在这里呢我把onLayout省略掉了,在onMeasure中做好了处理。那么就接着看看onDraw的重头戏。
按照思路,把绘制的过程分为三个部分,第一部分:绘制默认的背景条,这个很简单
mDefaultBack.setBounds(mThumbWidth / 2, 0, mSeekBarWidth - mThumbWidth / 2, mSeekBarHeight);
mDefaultBack.draw(canvas);
从第一个thumb的中心点到最后一个thumb的中心点绘制默认的背景条。
第二部分:绘制thumb,按照thumb的宽度进行绘制即可。
mThumb.setBounds(mThumbCenterPosition - mThumbWidth / 2, 0, mThumbCenterPosition + mThumbWidth / 2, mThumbHeight);
mThumb.draw(canvas);
第三部分:开始随着手势绘制滑动的进度条
if (mThumbCenterPosition > mSeekBarWidth / 2) {
mSlideBack.setBounds(mSeekBarWidth / 2, 0, mThumbCenterPosition, mSeekBarHeight);
} else if (mThumbCenterPosition < mSeekBarWidth / 2) {
mSlideBack.setBounds(mThumbCenterPosition, 0, mSeekBarWidth / 2, mSeekBarHeight);
} else {
mSlideBack.setBounds(mThumbCenterPosition, 0, mSeekBarWidth / 2, mSeekBarHeight);
}
mSlideBack.draw(canvas);
咱们来分析下这个代码,因为今天所做的seekbar分为左右两部分,所以根据不同情况做不同处理。首先如果小圆球的中心位于有半部分,那么将绘制的范围设置为(mSeekBarWidth / 2到mThumbCenterPosition)即seekbar中心点到小圆球的中心位置,小圆球的中心位置是随着手势变化的,所以就就形成了动态绘制的效果。
理解了上面的第一段代码,那么接下来的两段就很好理解了。和上面一样,如果小圆球处于左半部分,做相同处理,只不过范围变了罢了。最后一种情况是位于中心位置,就不多说了。最后就将上述不同的情况进行绘制了。
从上面的分析,我们知道了图形是怎么绘制的,我们也知道能动态绘制的重点在于小圆球的中心点,也就是mThumbCenterPosition,那么mThumbCenterPosition是如何随手势获取的呢?
我们带着这个疑问,来看下面的代码:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mSeekBarChangeListener != null) {
}
mFlag = getAreaFlag(event);
if (mFlag == CLICK_ON_PRESS) {
mThumb.setState(STATE_PRESSED);
mSeekBarChangeListener.onProgressBefore();
}
break;
case MotionEvent.ACTION_MOVE:
if (mFlag == CLICK_ON_PRESS) {
if (event.getX() < 0 || event.getX() <= mThumbWidth / 2) {
mThumbCenterPosition = mThumbWidth / 2;
} else if (event.getX() >= mSeekBarWidth - mThumbWidth / 2) {
mThumbCenterPosition = mSlideTotalDistance + mThumbWidth / 2;
} else {
mThumbCenterPosition = (int) event.getX();
}
}
invalidate();
break;
case MotionEvent.ACTION_UP:
mThumb.setState(STATE_NORMAL);
if (mSeekBarChangeListener != null) {
mSeekBarChangeListener.onProgressAfter();
}
break;
default:
break;
}
return true;
}
从知道随手势变化来动态的获取小圆球的中心值,就能想到要用Touch事件了,上面代码分为down,move和up,我们现在只看move部分,首先获取手势在x方向的值,然后判断是否超出了seekbar的范围,超过左边就将设置为小圆球的半径的长度位置,如果超过了右边就设置为seekbar长度减小圆球半径的位置,这里如果有不清楚的可以在纸上自己画一下图。最后就是处于seekbar范围内,直接就随着手势获取手势的位置,即 mThumbCenterPosition = (int) event.getX();到这里,基本的布局和绘制也就结束了。
如果你想使用seekbar的change事件,可以自己定义一个changeListener,如下:
public interface OnSeekBarChangeListener {
void onProgressBefore();
void onProgressChanged(MidThumbSeekBar seekBar, double progress);
void onProgressAfter();
}
好了,从中间向左右滑动的seekbar全部绘制完了,最后我们来调用 并运行
xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.fusy.midthumbseekbar.MidThumbSeekBar
android:id="@+id/seekBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
/>
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity implements MidThumbSeekBar.OnSeekBarChangeListener {
private MidThumbSeekBar mBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mBar = findViewById(R.id.seekBar);
mBar.setOnSeekBarChangeListener(this);
}
@Override
public void onProgressBefore() {
}
@Override
public void onProgressChanged(MidThumbSeekBar seekBar, double progress) {
}
@Override
public void onProgressAfter() {
}
}
End.