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.