TabLayout的指示器寬度問題

最近碰到一個需求,因爲是我比較感興趣的TabLayout的,所以記錄一下吧。

  • 產品需求:希望上部導航欄中的指示器寬度略大於文字寬度;
  • 技術方案:TabLayout配合ViewPager;
  • 問題分析: 原生TabLayout的指示器寬度等於每個tab的寬度,遠大於 tab內文字標題的寬度。
  • 原因分析:
    TabLayout(TL)繼承自HorizontalScrollView,其只能添加一個子控件,這個子控件便是TL內部私有類–SlidingTabStrip,其繼承自LinearLayout。指示器怎麼加上的呢?便是在該類的onDraw方法中:
 @Override
public void draw(Canvas canvas) {
   super.draw(canvas);
   // Thick colored underline below the current selection
  if (mIndicatorLeft >= 0 && 
  mIndicatorRight > mIndicatorLeft) {
     canvas.drawRect(mIndicatorLeft, 
 mIndicatorRight, getHeight(), 
 mSelectedIndicatorPaint);
  }
}

即指示器是與這個SlidingTabStrip容器伴生存在,其寬度,由mIndicatorLeft和mIndicatorRight確定,而這二者,由tab的寬度決定,因爲本文不會詳細描述TL的源碼,所以這裏直接報出結論:這個指示器的寬度設置由tab寬度決定,而tab在mode=fix情況下,是符合linearLayout中weight控制的,因此,也沒法通過tab的寬度來影響指示器的寬度,比較囧的是,源碼中也沒給tab設置個Margin什麼的,另外,源碼中也沒有暴露set等方法來改變指示器寬度,否則也不會有此問題了。

解決方案1:

百度的方案基本集中在反射,使用麻煩,有時還不好用。
這個的思路來自stackoverflow,大致過程是 由TL拿到其唯一的子類,即SlidingTabStrip,然後遍歷再拿到其各子View,然後爲每個子View設置Margin,這就相當於給每個tab設置margin,那麼指示器的寬度自然也就跟着改變了。show code:

public static void reduceMarginsInTabs(TabLayout tabLayout, int marginOffset) {
       View tabStrip = tabLayout.getChildAt(0);
       if (tabStrip instanceof ViewGroup) {
           ViewGroup tabStripGroup = (ViewGroup) tabStrip;
           for (int i = 0; i < ((ViewGroup) tabStrip).getChildCount(); i++) {
               View tabView = tabStripGroup.getChildAt(i);
              if (tabView.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
                  ((ViewGroup.MarginLayoutParams) tabView.getLayoutParams()).leftMargin = marginOffset;
                 ((ViewGroup.MarginLayoutParams) tabView.getLayoutParams()).rightMargin = marginOffset;
              }
         }
        tabLayout.requestLayout();
      }
 }

這種方式的限制是:最多隻能讓指示器與tab等寬,無法繼續減小指示器的寬度。

解決方案2:

本方案屬於自定製指示器顯示,相對比較靈活,能解決方案1的tab等寬問題,但是其缺點是不能保留指示器的動畫。具體過程:
TL中tab可以設置自定義view,方法是setCustomView(@Nullable View view) ,也就是說,完全可以不用TL中那套,直接自定義佈局即可。在tab源碼的update方法中有這麼一句:

 mCustomTextView = (TextView) custom.findViewById(android.R.id.text1);
  if (mCustomTextView != null) {
     mDefaultMaxLines = TextViewCompat.getMaxLines(mCustomTextView);
    }
  mCustomIconView = (ImageView) custom.findViewById(android.R.id.icon);

裏面的android.R.id.text1和android.R.id.icon就是TL中tab能跟隨滑動改變狀態的兩個view的id,我們自定義的時候可以讓對應的view也這麼設置id即可。而指示器,大可屏蔽原生TL的,直接在自定義佈局的合適位置,加個view就行。
這裏可以參考簡單優雅的使用tablayout和viewpager打造導航欄

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