Android:提供一个可以设置下划线离底部的距离和宽度的TabLayout控件

标题本来是设置下划线高度的,但是感觉有歧义,额,高度设置的话,在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代码

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    android:layout_width="80dp" android:layout_height="40dp"
    android:text=""
    android:gravity="center"
    android:textSize="14sp"
    android:textColor="@color/tab_select_color">

</TextView>

//注意他的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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章