这两天手头上的项目需要给Tablayout指示器设置一个固定的宽度,但谷歌并没有提供api,网上搜索的各种方式试了之后也没什么效果,而且调用比较麻烦,于是自己翻了源码,发现只要在tablayout中修改指示器左右的值就可以达到目的。源码如下:
public void draw(Canvas canvas) {
....
if (this.indicatorLeft >= 0 && this.indicatorRight > this.indicatorLeft) {
Drawable selectedIndicator = DrawableCompat.wrap((Drawable)(TabLayout.this.tabSelectedIndicator != null ? TabLayout.this.tabSelectedIndicator : this.defaultSelectionIndicator));
selectedIndicator.setBounds(this.indicatorLeft, indicatorTop, this.indicatorRight, indicatorBottom);
if (this.selectedIndicatorPaint != null) {
if (VERSION.SDK_INT == 21) {
selectedIndicator.setColorFilter(this.selectedIndicatorPaint.getColor(), android.graphics.PorterDuff.Mode.SRC_IN);
} else {
DrawableCompat.setTint(selectedIndicator, this.selectedIndicatorPaint.getColor());
}
}
selectedIndicator.draw(canvas);
}
super.draw(canvas);
}
}
实现功能后的效果如下:
调用也非常方便,直接设置一个dp值:
setIndicatorWidth(70);
原理:
1、既然我们的目的是要在draw时修改指定的数值,那首先想到用反射修改相应的field值就好;
private class SlidingTabIndicator extends LinearLayout {
private int indicatorLeft = -1;
private int indicatorRight = -1
}
2、找到SlidingTabIndicator对象,发现在Tablayout的构造函数中进行了初始化:
this.slidingTabIndicator = new TabLayout.SlidingTabIndicator(context);
super.addView(this.slidingTabIndicator, 0, new LayoutParams(-2, -1));
TypedArray a = ThemeEnforcement.obtainStyledAttributes(context, attrs, styleable.TabLayout, defStyleAttr, style.Widget_Design_TabLayout, new int[]{styleable.TabLayout_tabTextAppearance});
this.slidingTabIndicator.setSelectedIndicatorHeight(a.getDimensionPixelSize(styleable.TabLayout_tabIndicatorHeight, -1));
this.slidingTabIndicator.setSelectedIndicatorColor(a.getColor(styleable.TabLayout_tabIndicatorColor, 0));
3、反射拿到该对象及两个field,然后结合android view绘制的原理,想到使用OnPreDrawListener,并且为了避免多次调用重复添加listener和方便写修改数值的代码,写了一个内部类实现OnPreDrawListener,并且每次添加前删除已有的listener;
完整代码如下:
import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.google.android.material.tabs.TabLayout;
import java.lang.reflect.Field;
import androidx.annotation.NonNull;
/**
* 描述:可设置tab indicator的宽度
*
* @author renpeng
* @since 2019/10/10
*/
public class TabLayoutEx extends TabLayout {
public TabLayoutEx(Context context) {
this(context, null);
}
public TabLayoutEx(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TabLayoutEx(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(AttributeSet attrs) {
setTabIndicatorFullWidth(false);
setIndicatorWidth(70);
}
private class DefPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
private LinearLayout tabStrip = null;
private int tabWidth;
private Field fieldLeft;
private Field fieldRight;
public void setTabStrip(LinearLayout tabStrip, int width) {
try {
this.tabStrip = tabStrip;
this.tabWidth = width;
Class cls = tabStrip.getClass();
fieldLeft = cls.getDeclaredField("indicatorLeft");
fieldLeft.setAccessible(true);
fieldRight = cls.getDeclaredField("indicatorRight");
fieldRight.setAccessible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public boolean onPreDraw() {
try {
if (tabWidth > 0) {
int left = fieldLeft.getInt(this.tabStrip);
int right = fieldRight.getInt(this.tabStrip);
//根据目标宽度及现在的宽度调整为合适的left和right
int diff = right - left - tabWidth;
left = left + diff / 2;
right = right - diff / 2;
fieldLeft.setInt(this.tabStrip, left);
fieldRight.setInt(this.tabStrip, right);
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
}
private DefPreDrawListener defPreDrawListener = new DefPreDrawListener();
public void setIndicatorWidth(int widthDp) {
Class> tabLayout = TabLayout.class;
Field tabStrip = null;
try {
tabStrip = tabLayout.getDeclaredField("slidingTabIndicator");
tabStrip.setAccessible(true);
LinearLayout tabIndicator = (LinearLayout) tabStrip.get(this);
int width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, widthDp, Resources.getSystem().getDisplayMetrics());
//avoid add preDrawListener multi times
tabIndicator.getViewTreeObserver().removeOnPreDrawListener(defPreDrawListener);
tabIndicator.getViewTreeObserver().addOnPreDrawListener(defPreDrawListener);
defPreDrawListener.setTabStrip(tabIndicator, width);
} catch (Exception e) {
e.printStackTrace();
}
}
}
其他:
Tablayout使用中几个要注意的点:
setSelectedTabIndicator可以自定义指示器样式,比如使用渐变等,但要注意setSelectedTabIndicatorColor的着色影响;
app:tabRippleColor="@null"可以去掉默认的点击波纹效果