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,設定上去就行了(看看誰有更好的辦法,希望提出來如何解決)