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ì)了椰憋。
默認(rèn)Style
TabLayout
默認(rèn)style
為Widget.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
取值為scrollable
或fixed
硕旗,默認(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>
-
tabGravity
取值為fill
或center
软族,默認(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;
}
}
-
tabPaddingStart|tabPaddingTop|tabPaddingEnd|tabPaddingBottom
tabPaddingStart
表示每個(gè)Tab
的左邊距,默認(rèn)值為12dp
初茶,tabPaddingTop
表示每個(gè)Tab
的上邊距颗祝,默認(rèn)值為0dp
,tabPaddingEnd
表示每個(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
最小寬度為scrollableTabMinWidth
即72dp
凤藏,其余情況最小寬度為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è)方面的原因:
- 針對(duì)平板電腦,
TabLayout
的默認(rèn)style
的tabGravity
屬性值被修改了蹄梢,由fill
改為了center
疙筹,使用的style
為com.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);
}
在TabLayout
的onMeasure
方法里禁炒,如果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);
......省略部分代碼
}
在TabView
的onMeasure
方法里,根據(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)畫
截圖
相關(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"
/>