1. 程式人生 > >TabLayout設定下劃線(Indicator)寬

TabLayout設定下劃線(Indicator)寬

因為TabLayout中系統是強制設定所有TabView的寬度為最寬那個TabView的寬度,而下劃線寬度即為TabView寬度,所以需要自定義下劃線寬度時,解決方法如下:

//瞭解原始碼得知:下劃線的寬度是根據TabView的寬度來設定的  
tabLayout.post(() -> {  
  
    try {  
        //拿到tabLayout的mTabStrip屬性  
        Field mTabStripField = tabLayout.getClass().getDeclaredField("mTabStrip");  
        mTabStripField.setAccessible(true);  
  
        LinearLayout mTabStrip = (LinearLayout) mTabStripField.get(tabLayout);  
  
        int dp10 = SM.dip2px(getContext(), 10);  
  
        for (int i = 0; i < mTabStrip.getChildCount(); i++) {  
            View tabView = mTabStrip.getChildAt(i);  
  
            //拿到tabView的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 = 0;  
            width = mTextView.getWidth();  
            if (width == 0) {  
                mTextView.measure(0, 0);  
                width = mTextView.getMeasuredWidth();  
            }  
  
            //設定tab左右間距為10dp  注意這裡不能使用Padding 因為原始碼中下劃線的寬度是根據 tabView的寬度來設定的  
            LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) tabView.getLayoutParams();  
            params.width = width ;  
            params.leftMargin = dp10;  
            params.rightMargin = dp10;  
            tabView.setLayoutParams(params);  
  
            tabView.invalidate();  
        }  
  
    } catch (NoSuchFieldException e) {  
        e.printStackTrace();  
    } catch (IllegalAccessException e) {  
        e.printStackTrace();  
    }  
  
});  

問題解決思路

第一反應是找系統的方法和屬性,發現只有設定tabIndicatorHeight的屬性 並沒有寬度的屬性;接著百度,一百度就看到幾個部落格,宣稱可以解決這個問題,我們先看看他們的解決方案:傳送門

這種解決方案僅限於所有的tabView的text字數都是相同字數,比如所有的圖中所有的tab字數都是2個。其實思路是錯的,沒有研究原始碼詳細實現。

他的思路是設定tabView的padding為0,並且設定了margin。這種方案錯誤的原因是,tablayout會強制設定tabView的寬度為  幾個tabView中最寬的寬度,比如4個字的tabview和2個字的tabview的組合,兩個tabview的寬度強制為4個字的tabview的寬度。

下面會證實這一點:


那只有查原始碼了唄,tab的建立是 tablayout.addTab();方法構造的 具體程式碼如下

tabLayout.addTab(tabLayout.newTab().setText("生鮮食品"));  

直接查這個方法,通過幾個過載方法(addTab(Tab tab)->addTab(Tab tab,boolean setSelected)->addTab( Tab tab, int position, boolean setSelecte);  跳轉如下程式碼


從註釋看就是新增一個tab到這個layout上  具體實現是在addTabView(Tab tab)裡面,繼續看這個方法



可以看到最後新增到mTabStrip中,我們再來看看TabView裡面有什麼東西


從屬性可以看出TabView可以自定義的,而且並沒有發現Indicator線的痕跡,猜測他可能放在layout(mTabStrip)裡面,那就來看mTabStrip


檢視類中,發現mSelectedIndicatorHeight,眼睛一亮,下劃線高度!!,就是畫線的地方。追蹤mIndicatorLeft和mIndicatorRight的來路,幾經追蹤,發現如下程式碼


如圖,selectedTitle就是TabView,直接獲取了左邊座標和右邊座標,也就說是線的寬度就是tabview的寬度,那疑問又來了,為什麼我們兩個字的tabView和4個字的tabView是一樣寬度,先去看看SlidingTabStrip的onMeasure方法,如下圖

第一個for迴圈乾的事就是記錄下來所有tabView中的最大寬度,第二個迴圈就是把所有的tabView的寬度設定為第一個迴圈得到的最大寬!!!

罪魁禍首是找到了,這時候能動態代理一個重寫onMeasure方法的SlidingTabStrip物件塞進去,也可以解決這個問題,你會發現SlidingTabStrip是private的!!!!!!!

思路一轉,系統是強制設定所有tabview的寬度為 最寬那個tabview的寬度,那重新設定一遍tabView的寬度即可,解決問題(其實中間還嘗試過呼叫setIndicatorPosition方法,但是系統原始碼,在多個時期呼叫這個方法,所以斃掉了)

那最上面的解決方案就來了:

1、通過反射拿到SlidingTabStrip,通過遍歷拿到tabview,繼續通過反射拿到textview,然後設定Tabview的寬度為textview的寬度

2、為了美觀我們可以設定一下tabview的margin,不設定會連在一起

PS:需要注意的事

1.因為用到了反射,所以混淆的時候要注意

2.如果app:tabMode="fixed",每個TabView的weight都為1,設定width是沒用的,目前的解決辦法是可以先拿到TabView的寬度減去TextView寬度除以2,得到TabView的左右margin,設定上去就行了(看看誰有更好的辦法,希望提出來如何解決)