Android開(kāi)發(fā)中經(jīng)常碰到一個(gè)頁(yè)面上有多個(gè)可滑動(dòng)tab的場(chǎng)景,經(jīng)常是tabLayout和viewpager配合實(shí)現(xiàn)
如果碰到的場(chǎng)景并不需要viewpager柠衍,只是想要一個(gè)輕量的指示器,可以用很少的代碼自己實(shí)現(xiàn)欲主。效果如下
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.Scroller;
public class TabStrip extends View {
private Paint mPaint;
private Scroller mScroller;
private int mIndicatorCenterX;
private int mIndicatorWidth = 25;
private int mIndicatorHeight = 2;
private RectF mIndicatorRect;
private int ANIM_DURATION = 500;
private int mRadius = 5;
public TabStrip(Context context) {
this(context, null);
}
public TabStrip(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public TabStrip(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStrokeWidth(5);
mPaint.setColor(0xFFFF5777);
mScroller = new Scroller(getContext());
mIndicatorRect = new RectF();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(dp2px(mIndicatorHeight), MeasureSpec.EXACTLY));
}
public void smoothScrollTo(int destX) {
smoothScrollTo(destX, ANIM_DURATION);
}
public void smoothScrollTo(int destX, int duration) {
int finalX = mScroller.getFinalX();
int deltaX = destX - finalX;
mScroller.startScroll(finalX, 0, deltaX, 0, duration);
invalidate();
}
public void setIndicatorPosition(int x) {
smoothScrollTo(x, 1);
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
mIndicatorCenterX = mScroller.getCurrX();
invalidate();
}
}
private int dp2px(int dp) {
return (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics()
);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 畫(huà)指示器
mIndicatorRect.left = mIndicatorCenterX - dp2px(mIndicatorWidth) / 2;
mIndicatorRect.right = mIndicatorCenterX + dp2px(mIndicatorWidth) / 2;
mIndicatorRect.top = 0;
mIndicatorRect.bottom = dp2px(mIndicatorHeight);
canvas.drawRoundRect(mIndicatorRect, dp2px(mRadius), dp2px(mRadius), mPaint);
}
}
其實(shí)只需要看smoothScrollTo
computeScroll
onDraw
三個(gè)方法
要實(shí)現(xiàn)Indicator的效果募疮,首先當(dāng)然是要畫(huà)出指示條,那么需要在ondraw
方法里畫(huà)出指示條昔园。(這里要注意ondraw方法絕不能new任何東西蔓榄,因?yàn)閛ndraw是十分頻繁調(diào)用的,要保證不讓無(wú)謂的操作影響性能)
然后需要思考怎么讓指示條動(dòng)起來(lái)默刚∩#可能很容易想到可以用動(dòng)畫(huà)Animation
或者插值器Interpolator
實(shí)現(xiàn)。不過(guò)這類(lèi)滑動(dòng)的場(chǎng)景荤西,系統(tǒng)提供了一個(gè)很好用的工具類(lèi)Scroller
澜搅,可以十分方便的實(shí)現(xiàn)滑動(dòng)場(chǎng)景伍俘。
既然指示條是我們自己在onDraw里畫(huà)出來(lái)的,那么只需要保證不斷進(jìn)入onDraw重繪并且每次都改變畫(huà)指示條的位置勉躺,滑動(dòng)效果就出來(lái)了癌瘾。
Scroller跟插值器有點(diǎn)像,可以想象它是一個(gè)特定時(shí)間特定距離的滑動(dòng)的數(shù)據(jù)表示饵溅,可以通過(guò)它獲取某個(gè)時(shí)間點(diǎn)的滑動(dòng)信息妨退。
這里的調(diào)用關(guān)系是
smoothScrollTo -> invalidate->onDraw->computeScroll-> invalidate-> onDraw->computeScroll->invalidate-> onDraw->....
關(guān)鍵在于onDraw會(huì)調(diào)用computeScroll
,computeScroll
又會(huì)用invalidate
刷新導(dǎo)致onDraw被調(diào)用蜕企,而每次onDraw中畫(huà)指示條的時(shí)候的位置由scroller.getcurrentX()
提供咬荷,整個(gè)循環(huán)調(diào)用由mScroller.computeScrollOffset()
控制退出。當(dāng)設(shè)定的滑動(dòng)完成后轻掩,computeScrollOffset
返回true退出不斷重繪的循環(huán)幸乒。
由于使用scroller可以實(shí)時(shí)得到當(dāng)前的滑動(dòng)位置,哪怕是在某次指示條還在滑向某個(gè)指定tab的滑動(dòng)過(guò)程又點(diǎn)了另一個(gè)tab唇牧,smoothScrollTo
設(shè)定了新的滑動(dòng)動(dòng)作之后指示條也可以從半路開(kāi)始滑向新的tab罕扎,動(dòng)畫(huà)不會(huì)中斷。
這里實(shí)現(xiàn)了一個(gè)可以滑動(dòng)的Indicator奋构,只需要在適當(dāng)?shù)臅r(shí)候調(diào)用smoothScrollTo
壳影,就可以跟其他內(nèi)容組合起來(lái),起到指示器的作用