一.滑动带
什么是Android滑动带,我们举个栗子
image.png
就是图中的黑色长条,最典型的就是用在和viewpager或者多个fragment相关的地方,因此也有人称这个东西为Indicator(指示器)。
那我为什么称它为滑动带呢?有个使用和典型场景,有个控件叫TabLayout,它经常和viewpager一起使用,TabLayout的内部会自带这个横条指示器,看看内部的定义。
image.png
它的官方给它命名为SlidingTabStrip,我翻译过来就是滑动带、滑动条。Tab是和TabLayout相关的命名,我可以再接下来都叫它SlidingStrip
二.自定义滑动带
1. 为什么要自定义SlidingStrip
既然系统的控件已经帮我封装好了,为什么还要重复造轮子。有时候可能某种特殊情况不适用TabLayout,需要自定义Tab或者其它一些SlidingStrip和Tab不连用的状态,那时候就只能自己写个SlidingStrip。
2.怎么自定义SlidingStrip
怎么去自定义,当然每个人都有每个人的做法,或者你脑洞能想出实现这个功能的方法,但这里既然官方都写了,我个人肯定是会按照官方的做法去做。至于官方怎么做的,我们只能看看源码,看TabLayout内部的SlidingTabStrip类
private class SlidingTabStrip extends LinearLayout {
private int mSelectedIndicatorHeight;
private final Paint mSelectedIndicatorPaint;
private int mSelectedPosition = -1;
private float mSelectionOffset;
private int mIndicatorLeft = -1;
private int mIndicatorRight = -1;
private ValueAnimatorCompat mIndicatorAnimator;
SlidingTabStrip(Context context) {
super(context);
setWillNotDraw(false);
mSelectedIndicatorPaint = new Paint();
}
void setSelectedIndicatorColor(int color) {
if (mSelectedIndicatorPaint.getColor() != color) {
mSelectedIndicatorPaint.setColor(color);
ViewCompat.postInvalidateOnAnimation(this);
}
}
void setSelectedIndicatorHeight(int height) {
if (mSelectedIndicatorHeight != height) {
mSelectedIndicatorHeight = height;
ViewCompat.postInvalidateOnAnimation(this);
}
}
boolean childrenNeedLayout() {
for (int i = 0, z = getChildCount(); i < z; i++) {
final View child = getChildAt(i);
if (child.getWidth() <= 0) {
return true;
}
}
return false;
}
void setIndicatorPositionFromTabPosition(int position, float positionOffset) {
if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
mIndicatorAnimator.cancel();
}
mSelectedPosition = position;
mSelectionOffset = positionOffset;
updateIndicatorPosition();
}
float getIndicatorPosition() {
return mSelectedPosition + mSelectionOffset;
}
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
......
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
......
}
private void setIndicatorPosition(int left, int right) {
if (left != mIndicatorLeft || right != mIndicatorRight) {
// If the indicator's left/right has changed, invalidate
mIndicatorLeft = left;
mIndicatorRight = right;
ViewCompat.postInvalidateOnAnimation(this);
}
}
void animateIndicatorToPosition(final int position, int duration) {
if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
mIndicatorAnimator.cancel();
}
final boolean isRtl = ViewCompat.getLayoutDirection(this)
== ViewCompat.LAYOUT_DIRECTION_RTL;
final View targetView = getChildAt(position);
if (targetView == null) {
// If we don't have a view, just update the position now and return
updateIndicatorPosition();
return;
}
final int targetLeft = targetView.getLeft();
final int targetRight = targetView.getRight();
final int startLeft;
final int startRight;
if (Math.abs(position - mSelectedPosition) <= 1) {
// If the views are adjacent, we'll animate from edge-to-edge
startLeft = mIndicatorLeft;
startRight = mIndicatorRight;
} else {
// Else, we'll just grow from the nearest edge
final int offset = dpToPx(MOTION_NON_ADJACENT_OFFSET);
if (position < mSelectedPosition) {
// We're going end-to-start
if (isRtl) {
startLeft = startRight = targetLeft - offset;
} else {
startLeft = startRight = targetRight + offset;
}
} else {
// We're going start-to-end