原生TabLayout依舊很香砸彬,信不信由你颠毙,反正我是信了

TabLayout是項(xiàng)目開發(fā)中常用的一個(gè)控件,常和ViewPager結(jié)合使用砂碉。本文基于Androidx com.google.android.material:material:1.0.0版本根據(jù)TabLayout源碼對(duì)其進(jìn)行分析并對(duì)其深度定制蛀蜜。TabLayout源碼不足2000行,短小精悍增蹭,小巧精致涵防,非常適合我們閱讀研究。其實(shí)大部分場(chǎng)景下沪铭,TabLayout原有的功能或?qū)?code>TabLayout修改定制一下壮池,便可滿足我們的需求。原生的往往是最好的杀怠,抱緊Google大腿就對(duì)了椰憋。

TabLayout整體結(jié)構(gòu)

TabLayout類關(guān)系圖

默認(rèn)Style

TabLayout默認(rèn)styleWidget.Design.TabLayout,定義如下:

  <dimen name="design_tab_max_width">264dp</dimen>
  <dimen name="design_tab_scrollable_min_width">72dp</dimen>
  <dimen name="design_tab_text_size">14sp</dimen>
  <dimen name="design_tab_text_size_2line">12sp</dimen>
  <integer name="design_tab_indicator_anim_duration_ms">300</integer>
  <style name="Base.Widget.Design.TabLayout" parent="android:Widget">
    <item name="android:background">@null</item>
    <item name="tabIconTint">@null</item>
    <item name="tabMaxWidth">@dimen/design_tab_max_width</item>
    <item name="tabIndicatorAnimationDuration">@integer/design_tab_indicator_anim_duration_ms</item>
    <item name="tabIndicatorColor">?attr/colorAccent</item>
    <item name="tabIndicatorGravity">bottom</item>
    <item name="tabIndicator">@drawable/mtrl_tabs_default_indicator</item>
    <item name="tabPaddingStart">12dp</item>
    <item name="tabPaddingEnd">12dp</item>
    <item name="tabTextAppearance">@style/TextAppearance.Design.Tab</item>
    <item name="tabRippleColor">?attr/colorControlHighlight</item>
    <item name="tabUnboundedRipple">false</item>
  </style>

  <style name="Widget.Design.TabLayout" parent="Base.Widget.Design.TabLayout">
    <item name="tabGravity">fill</item>
    <item name="tabMode">fixed</item>
    <item name="tabIndicatorFullWidth">true</item>
  </style>

tabIndicator默認(rèn)為高度為2dp赔退,顏色為white的矩形橙依,對(duì)應(yīng)的文件為mtrl_tabs_default_indicator,定義如下:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item>
    <shape android:shape="rectangle">
      <solid android:color="@android:color/white"/>
      <size android:height="2dp"/>
    </shape>
  </item>
</selector>

TextAppearance.Design.Tab對(duì)應(yīng)的定義為:

 <style name="TextAppearance.Design.Tab" parent="TextAppearance.AppCompat.Button">
    <item name="android:textSize">@dimen/design_tab_text_size</item>
    <item name="android:textColor">@color/mtrl_tabs_legacy_text_color_selector</item>
    <item name="textAllCaps">true</item>
  </style>

mtrl_tabs_legacy_text_color_selector對(duì)應(yīng)的定義為:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:color="?attr/android:textColorPrimary" android:state_selected="true"/>
  <item android:color="?attr/android:textColorSecondary"/>
</selector>

相關(guān)屬性

  • tabMode
    取值為scrollablefixed硕旗,默認(rèn)為fixed窗骑。scrollable表示TabLayout是可以滑動(dòng)的,適用于Tab數(shù)量較多的情況漆枚,如新聞資訊類APP首頁创译。fixed表示寬度固定,適用于Tab數(shù)量固定的情況墙基。
<attr name="tabMode">
   <enum name="scrollable" value="0"/>
   <enum name="fixed" value="1"/>
</attr>
tabMode為scrollable時(shí)顯示效果
tabMode為fixed時(shí)顯示效果
  • tabGravity
    取值為fillcenter软族,默認(rèn)值為fill。此屬性只在app:tabMode="fixed"時(shí)起作用残制,fill表示平分寬度模式立砸,center表示居中顯示模式
<attr name="tabGravity">
   <enum name="fill" value="0"/>
   <enum name="center" value="1"/>
 </attr>
private void updateTabViewLayoutParams(LinearLayout.LayoutParams lp) {
     if (this.mode == MODE_FIXED && this.tabGravity == GRAVITY_FILL) {
          lp.width = 0;
          lp.weight = 1.0F;
     } else {
          lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
          lp.weight = 0.0F;
     }
 }
tabGravity為fill時(shí)顯示效果
tabGravity為center時(shí)顯示效果
  • tabPaddingStart|tabPaddingTop|tabPaddingEnd|tabPaddingBottom
    tabPaddingStart表示每個(gè)Tab的左邊距,默認(rèn)值為12dp初茶,tabPaddingTop表示每個(gè)Tab的上邊距颗祝,默認(rèn)值為0dptabPaddingEnd表示每個(gè)Tab的右邊距,默認(rèn)值為12dp螺戳,tabPaddingBottom表示每個(gè)Tab的下邊距规揪,默認(rèn)值為0dp
  public TabView(Context context) {
     super(context);
     this.updateBackgroundDrawable(context);
     ViewCompat.setPaddingRelative(this, TabLayout.this.tabPaddingStart,TabLayout.this.tabPaddingTop,TabLayout.this.tabPaddingEnd, TabLayout.this.tabPaddingBottom);
     this.setGravity(Gravity.CENTER);
     this.setOrientation(TabLayout.this.inlineLabel ? HORIZONTAL : VERTICAL);
     this.setClickable(true);
     ViewCompat.setPointerIcon(this, PointerIconCompat.getSystemIcon(this.getContext(), 1002));
   }   
  • tabMinWidth
    此屬性表示每個(gè)Tab的最小寬度,默認(rèn)值為-1温峭。如果tabMinWidth有設(shè)置猛铅,則Tab的最小寬度為設(shè)定的值,否則如果 app:tabMode="scrollable"Tab最小寬度為scrollableTabMinWidth72dp凤藏,其余情況最小寬度為0
 private int getTabMinWidth() {
     if (this.requestedTabMinWidth != -1) {
         return this.requestedTabMinWidth;
     } else {
         return this.mode == MODE_SCROLLABLE ? this.scrollableTabMinWidth : 0;
     }
 }
  • tabMaxWidth
    此屬性表示每個(gè)Tab的最大寬度奸忽,默認(rèn)值為264dp此屬性在TabLayout適配平板電腦時(shí)有用揖庄,需要同時(shí)設(shè)置app:tabGravity="fill"app:tabMaxWidth="0dp"才能平分屏幕寬度
 <com.google.android.material.tabs.TabLayout
     app:tabMode="fixed"
     app:tabGravity="fill"
     app:tabMaxWidth="0dp"
   />

分析一下原因栗菜,主要有以下兩個(gè)方面的原因:

  1. 針對(duì)平板電腦,TabLayout的默認(rèn)styletabGravity屬性值被修改了蹄梢,由fill改為了center疙筹,使用的stylecom.google.android.material:material:1.0.0包下的res\values-sw600dp-v13\Widget.Design.TabLayout,定義如下:
 <style name="Widget.Design.TabLayout" parent="Base.Widget.Design.TabLayout">
    <item name="tabGravity">center</item>
    <item name="tabMode">fixed</item>
 </style>

可根據(jù)以下代碼判斷當(dāng)前機(jī)器是否為平板電腦:

 fun isPad(context: Context): Boolean {
    return context.resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK >= Configuration.SCREENLAYOUT_SIZE_LARGE
 }

2.TabLayout源碼中對(duì)tabMaxWidth屬性的處理

 int tabMaxWidth;
 private final int requestedTabMaxWidth;
 public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
   super(context, attrs, defStyleAttr);
   this.tabMaxWidth = 2147483647;
   this.requestedTabMaxWidth = a.getDimensionPixelSize(styleable.TabLayout_tabMaxWidth, -1);
 }

TabLayoutonMeasure方法里禁炒,如果requestedTabMaxWidth>0而咆,則tabMaxWidth 值為requestedTabMaxWidth,默認(rèn)為264dp幕袱,否則tabMaxWidth值為specWidth - this.dpToPx(56)暴备,即其值為屏幕寬度 - 56dp

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   ......省略部分代碼
   int specWidth = MeasureSpec.getSize(widthMeasureSpec);
   if (MeasureSpec.getMode(widthMeasureSpec) != 0) {
       this.tabMaxWidth = this.requestedTabMaxWidth > 0 ? this.requestedTabMaxWidth : specWidth - this.dpToPx(56);
   }
   super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   ......省略部分代碼
 }

TabViewonMeasure方法里,根據(jù)tabMaxWidth值計(jì)算測(cè)量自身的寬度

 int getTabMaxWidth() {
    return this.tabMaxWidth;
 }

 public void onMeasure(int origWidthMeasureSpec, int origHeightMeasureSpec) {
    int specWidthSize = MeasureSpec.getSize(origWidthMeasureSpec);
    int specWidthMode = MeasureSpec.getMode(origWidthMeasureSpec);
    int maxWidth = TabLayout.this.getTabMaxWidth();
    int widthMeasureSpec;
    if (maxWidth <= 0 || specWidthMode != 0 && specWidthSize <= maxWidth) {
       widthMeasureSpec = origWidthMeasureSpec;
    } else {
       widthMeasureSpec = MeasureSpec.makeMeasureSpec(TabLayout.this.tabMaxWidth, MeasureSpec.AT_MOST);
    }
   super.onMeasure(widthMeasureSpec, origHeightMeasureSpec);
   ......省略部分代碼
 }

TabLayoutEx

基于com.google.android.material:material:1.0.0中TabLayout源碼修改而來

亮點(diǎn)

  • 基于原生TabLayout源碼修改而來们豌,支持原TabLayout所有功能涯捻,用法也基本保持一致
  • 取消原生TabLayout默認(rèn)將文字轉(zhuǎn)換為大寫的屬性
  • 添加選中字體變大和加粗效果
  • 添加Tab圓角背景動(dòng)畫,支持背景越界回彈效果
  • 添加指示符跳躍動(dòng)畫

截圖

基于原生TabLayout的TabLayoutEx

相關(guān)屬性

TabLayout原有的屬性基本都支持,此處僅列出新添加的屬性

屬性名稱 類型 說明
tabUnSelectedTextSize dimension 未選中字體大小
tabSelectedTextSize dimension 選中字體大小
tabBoldWhenSelected boolean 選中字體是否加粗
tabBackgroundIsCorner boolean 是否使用圓角背景
tabSlideAnimType enum 跳躍動(dòng)畫樣式,none表示不啟用跳躍動(dòng)畫,half_glue表示啟用跳躍動(dòng)畫1,glue表示啟用跳躍動(dòng)畫2

TabLayoutEx和原生TabLayout功能相同但名字有修改的屬性

  • tabMode改為tabModeEx
  • tabGravity改為tabGravityEx
  • tabIconTintMode改為tabIconTintModeEx
  • tabIndicatorGravity改為tabIndicatorGravityEx

用法

<com.github.kongpf8848.viewworld.views.TabLayoutEx
    android:id="@+id/tab_layout"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:layout_marginTop="10dp"
    android:background="@color/white"
    <!--每個(gè)TabView的左邊距-->
    app:tabPaddingStart="10dp"
    <!--每個(gè)TabView的右邊距-->
    app:tabPaddingEnd="10dp"
    <!--SlidingTabIndicator的左邊距望迎,其值=app:tabPaddingStart+實(shí)際的左邊距-->
    app:tabContentStart="25dp"
    <!--tab模式障癌,scrollable或fixed-->
    app:tabModeEx="scrollable"
    <!--指示符和TabView寬度是否相同-->
    app:tabIndicatorFullWidth="true"
    <!--指示符高度-->
    app:tabIndicatorHeight="32dp"
    <!--未選中文字顏色-->
    app:tabTextColor="#999999"
    <!--選中文字顏色-->
    app:tabSelectedTextColor="@color/black"
    <!--點(diǎn)擊波紋顏色,透明即去除波紋-->
    app:tabRippleColor="@color/transparent"
    <!--未選中文字大小-->
    app:tabUnSelectedTextSize="14sp"
    <!--選中文字大小-->
    app:tabSelectedTextSize="16sp"
    <!--是否為圓角背景-->
    app:tabBackgroundIsCorner="true"
    <!--選中字體是否加粗-->
    app:tabBoldWhenSelected="true"
    />

GitHub

https://github.com/kongpf8848/ViewWorld

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末辩尊,一起剝皮案震驚了整個(gè)濱河市涛浙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌对省,老刑警劉巖蝗拿,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晾捏,死亡現(xiàn)場(chǎng)離奇詭異蒿涎,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)惦辛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門劳秋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事玻淑∷悦埃” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵补履,是天一觀的道長(zhǎng)添坊。 經(jīng)常有香客問我,道長(zhǎng)箫锤,這世上最難降的妖魔是什么贬蛙? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮谚攒,結(jié)果婚禮上阳准,老公的妹妹穿的比我還像新娘。我一直安慰自己馏臭,他們只是感情好野蝇,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著括儒,像睡著了一般绕沈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上帮寻,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天七冲,我揣著相機(jī)與錄音,去河邊找鬼规婆。 笑死澜躺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抒蚜。 我是一名探鬼主播掘鄙,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼嗡髓!你這毒婦竟也來了操漠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤饿这,失蹤者是張志新(化名)和其女友劉穎浊伙,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體长捧,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嚣鄙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了串结。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哑子。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡舅列,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出卧蜓,到底是詐尸還是另有隱情帐要,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布弥奸,位于F島的核電站榨惠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏盛霎。R本人自食惡果不足惜冒冬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望摩渺。 院中可真熱鬧简烤,春花似錦、人聲如沸摇幻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绰姻。三九已至枉侧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間狂芋,已是汗流浹背榨馁。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帜矾,地道東北人翼虫。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像屡萤,于是被迫代替她去往敵國(guó)和親珍剑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容