TabLayout
是项目开发中常用的一个控件,常和ViewPager结合使用。本文基于Androidx com.google.android.material:material:1.0.0
版本根据TabLayout
源码对其进行分析并对其深度定制。TabLayout
源码不足2000
行,短小精悍,小巧精致,非常适合我们阅读研究。其实大部分场景下,TabLayout
原有的功能或对TabLayout
修改定制一下,便可满足我们的需求。原生的往往是最好的,抱紧Google
大腿就对了。
TabLayout类关系图
默认Style
TabLayout
默认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
默认为高度为2dp
,颜色为white
的矩形,对应的文件为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
对应的定义为:
<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
对应的定义为:
<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>
相关属性
-
tabMode
取值为scrollable
或fixed
,默认为fixed
。scrollable
表示TabLayout
是可以滑动的,适用于Tab数量较多的情况,如新闻资讯类APP首页。fixed
表示宽度固定,适用于Tab数量固定的情况。
<attr name="tabMode">
<enum name="scrollable" value="0"/>
<enum name="fixed" value="1"/>
</attr>
tabMode为scrollable时显示效果
tabMode为fixed时显示效果
-
tabGravity
取值为fill
或center
,默认值为fill
。此属性只在app:tabMode="fixed"
时起作用,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时显示效果
tabGravity为center时显示效果
-
tabPaddingStart|tabPaddingTop|tabPaddingEnd|tabPaddingBottom
tabPaddingStart
表示每个Tab
的左边距,默认值为12dp
,tabPaddingTop
表示每个Tab
的上边距,默认值为0dp
,tabPaddingEnd
表示每个Tab
的右边距,默认值为12dp
,tabPaddingBottom
表示每个Tab
的下边距,默认值为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
此属性表示每个Tab
的最小宽度,默认值为-1
。如果tabMinWidth
有设置,则Tab
的最小宽度为设定的值,否则如果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
此属性表示每个Tab
的最大宽度,默认值为264dp
,此属性在TabLayout适配平板电脑时有用,需要同时设置app:tabGravity="fill"
和app:tabMaxWidth="0dp"
才能平分屏幕宽度
<com.google.android.material.tabs.TabLayout
app:tabMode="fixed"
app:tabGravity="fill"
app:tabMaxWidth="0dp"
/>
分析一下原因,主要有以下两个方面的原因:
- 针对平板电脑,
TabLayout
的默认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>
可根据以下代码判断当前机器是否为平板电脑:
fun isPad(context: Context): Boolean {
return context.resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK >= Configuration.SCREENLAYOUT_SIZE_LARGE
}
2.TabLayout
源码中对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
,默认为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
方法里,根据tabMaxWidth
值计算测量自身的宽度
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源码修改而来
亮点
- 基于原生TabLayout源码修改而来,支持原TabLayout所有功能,用法也基本保持一致
- 取消原生TabLayout默认将文字转换为大写的属性
- 添加选中字体变大和加粗效果
- 添加Tab圆角背景动画,支持背景越界回弹效果
- 添加指示符跳跃动画
截图
基于原生TabLayout的TabLayoutEx相关属性
TabLayout原有的属性基本都支持,此处仅列出新添加的属性
属性名称 | 类型 | 说明 |
---|---|---|
tabUnSelectedTextSize | dimension | 未选中字体大小 |
tabSelectedTextSize | dimension | 选中字体大小 |
tabBoldWhenSelected | boolean | 选中字体是否加粗 |
tabBackgroundIsCorner | boolean | 是否使用圆角背景 |
tabSlideAnimType | enum | 跳跃动画样式,none表示不启用跳跃动画,half_glue表示启用跳跃动画1,glue表示启用跳跃动画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"
<!--每个TabView的左边距-->
app:tabPaddingStart="10dp"
<!--每个TabView的右边距-->
app:tabPaddingEnd="10dp"
<!--SlidingTabIndicator的左边距,其值=app:tabPaddingStart+实际的左边距-->
app:tabContentStart="25dp"
<!--tab模式,scrollable或fixed-->
app:tabModeEx="scrollable"
<!--指示符和TabView宽度是否相同-->
app:tabIndicatorFullWidth="true"
<!--指示符高度-->
app:tabIndicatorHeight="32dp"
<!--未选中文字颜色-->
app:tabTextColor="#999999"
<!--选中文字颜色-->
app:tabSelectedTextColor="@color/black"
<!--点击波纹颜色,透明即去除波纹-->
app:tabRippleColor="@color/transparent"
<!--未选中文字大小-->
app:tabUnSelectedTextSize="14sp"
<!--选中文字大小-->
app:tabSelectedTextSize="16sp"
<!--是否为圆角背景-->
app:tabBackgroundIsCorner="true"
<!--选中字体是否加粗-->
app:tabBoldWhenSelected="true"
/>
网友评论