UIAndroid 知识Android技术进阶

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

2021-04-25  本文已影响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>

相关属性

<attr name="tabMode">
   <enum name="scrollable" value="0"/>
   <enum name="fixed" value="1"/>
</attr>
tabMode为scrollable时显示效果 tabMode为fixed时显示效果
<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时显示效果
  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));
   }   
 private int getTabMinWidth() {
     if (this.requestedTabMinWidth != -1) {
         return this.requestedTabMinWidth;
     } else {
         return this.mode == MODE_SCROLLABLE ? this.scrollableTabMinWidth : 0;
     }
 }
 <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的TabLayoutEx

相关属性

TabLayout原有的属性基本都支持,此处仅列出新添加的属性

属性名称 类型 说明
tabUnSelectedTextSize dimension 未选中字体大小
tabSelectedTextSize dimension 选中字体大小
tabBoldWhenSelected boolean 选中字体是否加粗
tabBackgroundIsCorner boolean 是否使用圆角背景
tabSlideAnimType enum 跳跃动画样式,none表示不启用跳跃动画,half_glue表示启用跳跃动画1,glue表示启用跳跃动画2

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

用法

<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

上一篇 下一篇

猜你喜欢

热点阅读