android系統(tǒng)提供的原生seekbar使用起來并不是那么好艰躺,例如可拖拽區(qū)域很小廊勃、沒有刻度。
最近項(xiàng)目里面設(shè)計(jì)提供了的設(shè)計(jì)圖就有一個(gè)seekbar犀忱,seekbar上有刻度,當(dāng)手指移動(dòng)到刻度附近時(shí)郊供,刻度條就會(huì)移動(dòng)到指定的刻度附近峡碉。,同時(shí)驮审,會(huì)有一個(gè)回調(diào)通知進(jìn)度的改變鲫寄。
接到這個(gè)需求后,在github上找了一圈疯淫,發(fā)現(xiàn)現(xiàn)有的seekbar提供的功能太多地来,與需求不符合,故自己寫了一個(gè)熙掺。
項(xiàng)目地址:scaleseekbar
首先看在布局文件里面怎么使用:
<com.cheng.scaleseekbar.ScaleSeekBar
android:layout_width="match_parent"
android:layout_height="40dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:id="@+id/seek_bar"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp"
app:layout_constraintTop_toBottomOf="@+id/text"
tools:layout_editor_absoluteX="8dp"/>
由于進(jìn)度條需要一定的寬度來展示,在這里直接寫死了高度和padding值币绩,實(shí)際可以在自定義view里面做特殊處理蜡秽。
init()函數(shù) 初始化
//定義畫筆
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintWidth = Util.dipToPixel(2);
mPaint.setStrokeWidth(mPaintWidth);
// 左右邊距
mSpace = Util.dipToPixel(12);
//一共畫5個(gè)點(diǎn)
mPointNum = 5;
//獲得小圓的半徑
mSmallCircleRadius = Util.dipToPixel(4);
//獲得當(dāng)前指示條的內(nèi)半徑(比較明顯的顏色)
mBigCircleInnerRadius = Util.dipToPixel(8);
//獲得當(dāng)前指示條的外半徑(比較淡的顏色)
mBigCircleOuterRadius = Util.dipToPixel(10);
//因?yàn)橐A(yù)留空位給外圈的大圓
mPaddingTop = getPaddingTop() + mBigCircleOuterRadius;
定義了上述屬性后,接著需要獲得各個(gè)點(diǎn)的坐標(biāo)缆镣。我們在OnMeasure后獲取控件的寬高芽突,同時(shí)確定各個(gè)點(diǎn)的位置。
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//控件寬度
mWidth = getMeasuredWidth();
//去除左右邊距后留給seekbar的繪制區(qū)間
mLength = mWidth - mSpace * 2;//
mSegmentLength = mLength / (mPointNum - 1);// 5個(gè)點(diǎn)分割成4段 每段的長度
mEndPoint = mSpace + (mSegmentLength * (mPointNum - 1));//終止點(diǎn)的位置.
提供接口給外部調(diào)用設(shè)置當(dāng)前的進(jìn)度條董瞻。(總進(jìn)度為5寞蚌, 這里progress的取值為0~4,這里可以假設(shè)默認(rèn)值為2)
public void setProgress(int progress) {
this.mNewProgress = progress;
}
獲取完每個(gè)點(diǎn)的坐標(biāo)位置、線段的起止值钠糊,圓的半徑以及當(dāng)前的進(jìn)度后挟秤,我們就可以在ondraw()里面進(jìn)行繪制了。
super.onDraw(canvas);
this.mPaint.setColor(this.mColors[1]);
//根據(jù)線段起止點(diǎn)繪制一條線
canvas.drawLine(this.mSpace, this.mSpace + this.mPaddingTop, this.mEndPoint, this.mSpace + this.mPaddingTop, this.mPaint);// 整體繪制一條灰色線貫穿整體
this.mPaint.setColor(this.mColors[0]);
//在起始點(diǎn)話了一個(gè)小圓抄伍。
canvas.drawCircle(this.mSpace, this.mSpace + this.mPaddingTop, mSmallCircleRadius, this.mPaint);//畫了一個(gè)小圓環(huán)
//根據(jù)mNewProgress的值確定當(dāng)前的進(jìn)度條所在的終點(diǎn)艘刚,繪制一條從起點(diǎn)到進(jìn)度終點(diǎn)的線
canvas.drawLine(this.mSpace, this.mSpace + this.mPaddingTop, this.mSpace + this.mSegmentLength * this.mNewProgress, this.mSpace + this.mPaddingTop, this.mPaint); // 根據(jù)進(jìn)度條畫了新的顏色線
int index = 1;
//根據(jù)mNowProgress的值,小于mNowProgress的值繪制藍(lán)色圓圈截珍,大于mNowProgress的繪制灰色圓圈昔脯。
if (index < this.mPointNum) {
for (; index < this.mPointNum; index++) {
if (this.mNewProgress > index) {
this.mPaint.setColor(this.mColors[0]);
} else {
this.mPaint.setColor(this.mColors[1]);
}
canvas.drawCircle(this.mSpace + index * mSegmentLength, this.mSpace + this.mPaddingTop, mSmallCircleRadius, this.mPaint);//每個(gè)位置畫一個(gè)小圓 顏色不一樣
}
}
this.mPaint.setColor(this.mCircleColor[1]);
//在mNewProgress的地方繪制一個(gè)顏色比較淺的大圓啄糙,再繪制一個(gè)顏色比較深的大圓
canvas.drawCircle(this.mSpace + this.mSegmentLength * this.mNewProgress, this.mSpace + this.mPaddingTop, this.mBigCircleOuterRadius, this.mPaint);
this.mPaint.setColor(this.mCircleColor[0]);
canvas.drawCircle(this.mSpace + this.mSegmentLength * this.mNewProgress, this.mSpace + this.mPaddingTop, this.mBigCircleInnerRadius, this.mPaint);
//通過這樣的方式,所有我們需要繪制的內(nèi)容已經(jīng)繪制完畢
前面已經(jīng)做到了根據(jù)當(dāng)前進(jìn)度分別繪制不同的內(nèi)容到canvas上云稚。
接下來需要響應(yīng)用戶的滑動(dòng)手勢,根據(jù)手勢滑動(dòng)的地方判斷當(dāng)前進(jìn)度值沈堡。
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
int x = (int) event.getX();
//滑動(dòng)事件開始的時(shí)候静陈,傳遞x的坐標(biāo)值,后面的notifydatachanged方法根據(jù)該值判斷是否滑動(dòng)到了臨界點(diǎn)诞丽,如果到了鲸拥,給外部的監(jiān)聽事件發(fā)通知。
notifyDataChanged(x);
return true;
}
//這里根據(jù)x值判斷當(dāng)前滑動(dòng)到了哪個(gè)點(diǎn)僧免,更新mNewProgress的值,同時(shí)通知view進(jìn)行重繪(這里應(yīng)該保留以前的值刑赶,避免每次都重繪)
private void notifyDataChanged(int x) {
this.mNewProgress = (this.mSegmentLength / 3 + x - mSpace) / this.mSegmentLength;
if ((this.mListener != null)) {
this.mListener.notifyProgressChanged(this.mNewProgress);
}
invalidate();
}
public interface ProgressListener {
void notifyProgressChanged(int progress);
}
這里就將seekbar的坐標(biāo)點(diǎn)確定、進(jìn)度條繪制懂衩、響應(yīng)touch事件完成撞叨,并完成了事件改變的通知。