美文网首页UIAndroid 知识Android技术进阶
原生TabLayout依旧很香,信不信由你,反正我是信了

原生TabLayout依旧很香,信不信由你,反正我是信了

作者: 孔鹏飞 | 来源:发表于2021-04-25 12:57 被阅读0次

TabLayout是项目开发中常用的一个控件,常和ViewPager结合使用。本文基于Androidx com.google.android.material:material:1.0.0版本根据TabLayout源码对其进行分析并对其深度定制。TabLayout源码不足2000行,短小精悍,小巧精致,非常适合我们阅读研究。其实大部分场景下,TabLayout原有的功能或对TabLayout修改定制一下,便可满足我们的需求。原生的往往是最好的,抱紧Google大腿就对了。

TabLayout整体结构
TabLayout类关系图

默认Style

TabLayout默认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默认为高度为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
    取值为scrollablefixed,默认为fixedscrollable表示TabLayout是可以滑动的,适用于Tab数量较多的情况,如新闻资讯类APP首页。fixed表示宽度固定,适用于Tab数量固定的情况。
<attr name="tabMode">
   <enum name="scrollable" value="0"/>
   <enum name="fixed" value="1"/>
</attr>
tabMode为scrollable时显示效果 tabMode为fixed时显示效果
  • tabGravity
    取值为fillcenter,默认值为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的左边距,默认值为12dptabPaddingTop表示每个Tab的上边距,默认值为0dptabPaddingEnd表示每个Tab的右边距,默认值为12dptabPaddingBottom表示每个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最小宽度为scrollableTabMinWidth72dp,其余情况最小宽度为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"
   />

分析一下原因,主要有以下两个方面的原因:

  1. 针对平板电脑,TabLayout的默认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>

可根据以下代码判断当前机器是否为平板电脑:

 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);
 }

TabLayoutonMeasure方法里,如果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);
   ......省略部分代码
 }

TabViewonMeasure方法里,根据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"
    />

GitHub

https://github.com/kongpf8848/ViewWorld

相关文章

  • 原生TabLayout依旧很香,信不信由你,反正我是信了

    TabLayout是项目开发中常用的一个控件,常和ViewPager结合使用。本文基于Androidx com.g...

  • 信不信由你,反正我信了

    最近看见很多人在网上再度讨论是否依然相信“读书改变命运”的话题,尽管我从来没有公开发表意见,但是我却每次都会在心里...

  • 其实,你完全可以掌握自己的命运

    看我的截图,全球最顶尖的思维是这样说的! 区别在于下面! 信不信由你,反正,我是信了!

  • 【生活随笔】与一只蚊子的战争

    与一只蚊子的战争 如果我告诉你,立冬以后,还有蚊子来家里作祟,你信吗? 信不信由你,反正,我信...

  • 我的信仰:阴阳五行

    “信不信由你,我反正是信了”。扭曲的信仰,就是这么霸蛮荒谬,无言以对。 由相信升华到信仰,是在深深信任的基座之上,...

  • 青春志3:奇迹可以创造

    谁都可以创造奇迹。 这话你信吗?哎,信不信由你,反正我是有点信。 我有过一段当新兵排长的经历,时间不长,三个多月。...

  • 好人嘴贱,坏人嘴甜‖信不信由你,反正我是信了

    你遇到困难了, 去找人帮忙。 嘴上,客客气气,欣然答应的, 不一定真的会帮你。 而伶牙俐齿,尖酸刻薄的那个, 说不...

  • 未来五年最吃香的行业

    信不信由你,反正我是信的。 第一个是医疗行业。如今时代在发展,生活条件好了,人们饮食也繁杂,随着医疗越发达...

  • 我说这是一篇凑字数的文章,你信吗? 信不信由你,反正我告诉你了。 没到双休就会想心思出去走走,想出去吃大餐,晚上总...

  • 美宝案例分享(随感日志66)

    呆在我群里的一个我不认识的朋友分享服用美宝2个月后的效果! 信不信由你决定哦!反正我信了! 大家好!我是黄明心...

网友评论

    本文标题:原生TabLayout依旧很香,信不信由你,反正我是信了

    本文链接:https://www.haomeiwen.com/subject/eepxrltx.html