标题本来是设置下划线高度的,但是感觉有歧义,额,高度设置的话,在layout xml文件里tabIndicatorHeight=“xdp”就行了,本文后面讲的是设置该下划线距离底部的高度。为什么会有这种奇怪的需求呢?因为设计稿就是那样的,附上成品截图:
----------------------先啰嗦一下,不太会写博客----------------------
现在网上主流的设置tabLayout下划线宽度的方法是通过改变它子控件TabView的宽度来改变下划线宽度,因为下划线宽度是充满tabLayout的,假如是想和文字一样宽的话,则需要通过反射获取TabView里面mTextView然后测量宽度,将tabView的宽度设置成TextView的宽度。附上相关代码:
//TabLayout源码中的mTextView
class TabView extends LinearLayout {
private Tab mTab;
private TextView mTextView;
private ImageView mIconView;
private View mCustomView;
private TextView mCustomTextView;
private ImageView mCustomIconView;
private int mDefaultMaxLines = 2;
}
可以看出来,假如你是自定义每个TabView的界面,使用了setCustomView,那么反射获取的时候应该是mCustomView,而不是mTextView了。附上自定义customView的代码,我是继承了TabLayout使用的,一些方法,如果要使用的话,加上你的tabLayout.xxx()就行了
public void initTabList() {
int tabCount = getTabCount();
for (int i = 0; i < tabCount; i++) {
Tab tab = getTabAt(i);
//假如是居中显示,高度自适应,把gravity去掉
tab.setCustomView(R.layout.tab_layout_text);
tabList.add(tab);
}
}
//tab_layout_text代码
//注意他的id得是@android:id/text1 不然设置title无效
需要某一项特殊定制界面的,也可以通过这个方法设置某些条件,达到不同界面。TextView界面的话,诸如宽高和位置字体大小,选中效果,背景,都是可以按照自己的需求更改。附上改下划线宽度为字宽度的代码:
private void setTabLayout() {
tabLayout.post(new Runnable() {
@Override
public void run() {
try {
//拿到tabLayout的mTabStrip属性
LinearLayout mTabStrip = (LinearLayout) tabLayout.getChildAt(0);
for (int i = 0; i < mTabStrip.getChildCount(); i++) {
View tabView = mTabStrip.getChildAt(i);
//拿到tabView的mTextView属性 tab的字数不固定用反射取mTextView
Field mTextViewField = tabView.getClass().getDeclaredField("mTextView");
mTextViewField.setAccessible(true);
TextView mTextView = (TextView) mTextViewField.get(tabView);
tabView.setPadding(0, 0, 0, 0);
//效果是字多宽线就多宽,所以测量mTextView的宽度
int width;
width = mTextView.getWidth();
if (width == 0) {
mTextView.measure(0, 0);
width = mTextView.getMeasuredWidth();
}
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) tabView.getLayoutParams();
params.width = width;
params.leftMargin = 20;
params.rightMargin = 20;
tabView.setLayoutParams(params);
tabView.invalidate();
}
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
});
}
回到主题,想要改变下划线和底部的间距,那么需要知道它是怎么出现的,假如也是一个属性值在TabLayout里,通过反射获取它直接设置是不是可以达到我们的目的呢?
那么先来简单分析一下TabLayout
public class TabLayout extends HorizontalScrollView {}
可以看出,它继承自横向滚动控件,应该都不陌生吧,这个控件里只能放一个ViewGroup,其他控件填充ViewGroup允许其延伸到两侧屏幕外。
那么继续找这个ViewGroup:
public class TabLayout extends HorizontalScrollView {
private final SlidingTabStrip mTabStrip;
public TabLayout(Context context) {
this(context, null);
}
public TabLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ThemeUtils.checkAppCompatTheme(context);
// Disable the Scroll Bar
setHorizontalScrollBarEnabled(false);
// Add the TabStrip
mTabStrip = new SlidingTabStrip(context);
super.addView(mTabStrip, 0, new HorizontalScrollView.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
}
private class SlidingTabStrip extends LinearLayout {
private int mSelectedIndicatorHeight;
private final Paint mSelectedIndicatorPaint;
int mSelectedPosition = -1;
float mSelectionOffset;
private int mLayoutDirection = -1;
private int mIndicatorLeft = -1;
private int mIndicatorRight = -1;
private ValueAnimator mIndicatorAnimator;
SlidingTabStrip(Context context) {
super(context);
setWillNotDraw(false);
mSelectedIndicatorPaint = new Paint();
}
}
}
从上部分源码可以看出,在这个TabLayout里被添加了一个SlidingTabStrip的横向线性布局。这和我们上面设置下划线宽度的时候取得的子控件是一致的,那么它里面是放置TabView的,很容易分析出,在这里面,和TabLayout都不可能放置一个下划线控件,分析其应该是被画出来的,可以搜索onDraw,draw,或者直接搜索Indicator下划线相关,可以搜到:
private class SlidingTabStrip extends LinearLayout{
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
// Thick colored underline below the current selection
if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
}
}
}
这段源码算是简单的,可以看出,它在刚刚那个横向线性布局里画了一个长方形,绘制坐标是
上方:getHeight() - mSelectedIndicatorHeight(布局高度减去设置的下划线的高度)
左边:mIndicatorLeft 右边:mIndicatorRight
下方:getHeight()
之前我一篇博客讲过,屏幕坐标是,横为X轴,越往右越大;竖为Y轴,越往下越大。所以draw代码的意思是,在线性布局下方绘制一个贴着底部的长方形,长方形的高度是mSelectedIndicatorHeight。那么需要下划线往上平移,将上方坐标和下方坐标,都加上一个偏移量就行了,需要缩窄下划线宽度则左边加上一个偏移量,右边坐标减去一个偏移量。
当然,能修改的话,非常简单,直接tabLayout中添加一个int字段叫做indictBottomOffset,添加一个int字段indictHorizonOffset;
写出这两个字段的set方法,然后修改draw的代码为:
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
// Thick colored underline below the current selection
if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
canvas.drawRect(mIndicatorLeft + indictHorizonOffset, getHeight() - mSelectedIndicatorHeight - indictBottomOffset,
mIndicatorRight - indictHorizonOffset, getHeight() - indictBottomOffset, mSelectedIndicatorPaint);
}
}
就大功告成了。
那么我提供一个已经提取出来的TabLayout文件,现在就是用的这个,成品大家也都看到了,只做了里面draw的修改,以及某些包私有文件,是通过反射获取的属性,因为他导入的很多包,是有只允许在design包里。用法和正常的tabLayout没有任何区别。
github:https://github.com/CNzhu/TabLayout
作者:咳咳涯