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

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