TabLayout高端用法(一)

TabLayout提供了一个水平的布局用来展示Tabs,很多应用都有这样的设计,典型的有网易新闻,简书,知乎等。TabLayout就可以很好的完成这一职责,当然也或许各家应用的实现方式不尽相同,这里介绍下TabLayout的用法。

首先TabLayout一般都是配合Viewpager使用的,Viewpager里的Fragment随着顶部的Tab一起联动,这种场景再熟悉不过了。在没有TabLayout的日子里关于这种设计一般都是自己实现的。

  • 先来个简单通俗的代码:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.design.widget.TabLayout
        android:id="@+id/toolbar_tab"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:layout_gravity="bottom"
        android:background="#ffffff"
        android:fillViewport="false"
        app:tabMode="fixed"
        app:layout_scrollFlags="scroll"
        app:tabIndicatorColor="#057523"
        app:tabIndicatorHeight="2.0dp"
        app:tabSelectedTextColor="#057523"
        app:tabTextColor="#ced0d3">

        <android.support.design.widget.TabItem
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="a" />

        <android.support.design.widget.TabItem
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="b" />

        <android.support.design.widget.TabItem
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="c" />

    </android.support.design.widget.TabLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

上面代码的运行效果如下:

E7C96D0A-4692-40B3-B1D7-08361A7793E7-75356-000684F41CA2CA41.gif
  • 为了使用TabLayout,我们要让Activity继承自AppCompatActivity,但有时候你项目里的BaseActivity却是继承自FragmentActivity的,这就尴尬了。其实没关系的, AppCompatActivity 也是extends FragmentActivity的。可以把BaseActivity extends AppCompatActivity。如果不想这么做也可以,可以指定当前Activity的theme为

android:theme="@style/Theme.AppCompat"

然后build.gradle文件在dependencies里加上

compile 'com.android.support:design:25.0.0'

然后基本上就不会有什么问题了。

下面来解析下TabLayout的一些基本属性:

app:tabIndicatorColor :指示条的颜色
app:tabIndicatorHeight :指示条的高度
app:tabSelectedTextColor : tab被选中时的字体颜色
app:tabTextColor : tab未被选中时的字体颜色
app:tabMode="scrollable" : 默认是fixed:固定的,标签很多时候会被挤压,不能滑动。

重要的属性基本就这些,其他简单的属性可以自己去摸索,这里选中和未选中的字体颜色,可以根据自己的设计自行修改,同样指示条的高度颜色也可以随意修改。

  • 但假如我的设计里不需要指示条怎么办,好像没发现隐藏的API,那也很简单。有两个思路:
    1:把指示条高度设为0:

app:tabIndicatorHeight="0dp"

2:把指示条的颜色设为透明:

app:tabIndicatorColor="@color/transparent"

效果如下:

40FAEFFF-FF76-43D1-8DD9-98177B630AE4-75356-00068525B87F337E.gif
  • TabItem

    在高版本的design库里已经有了TabItem,TabItem是作为TabLayout的子View而配合使用的,点进去发现其实代码很简单,就是个自定义View。

public final class TabItem extends View {
    final CharSequence mText;
    final Drawable mIcon;
    final int mCustomLayout;

    public TabItem(Context context) {
        this(context, null);
    }

    public TabItem(Context context, AttributeSet attrs) {
        super(context, attrs);

        final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
                R.styleable.TabItem);
        mText = a.getText(R.styleable.TabItem_android_text);
        mIcon = a.getDrawable(R.styleable.TabItem_android_icon);
        mCustomLayout = a.getResourceId(R.styleable.TabItem_android_layout, 0);
        a.recycle();
    }
}

所以当我们的需求能够明确知道Tab的个数时,可以在xml里直接添加TabItem。但是但是,心细的你不知道有没有发现问题,我在上面的代码中,tab明明设置的小写,但是运行出来确是大写:

 <android.support.design.widget.TabItem
       android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="a" />

事先申明我可没在代码里重新设置文本,就是这么操蛋。好在天无绝人之路,找到了一个属性叫app:tabTextAppearance,这是Tablayout的属性。TabItem代码简单到几乎没有什么属性可供设置,什么字体大小,颜色貌似都设置不了。
所以我们自己写了个样式,然后酱写:

app:tabTextAppearance="@style/MyTabLayoutTextAppearance"

MyTabLayoutTextAppearance里的代码如下:

<style name="MyTabLayoutTextAppearance"parent="TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse">
<item name="android:textSize">16sp</item>
<item name="android:textAllCaps">false</item>
</style>

这里的android:textAllCaps属性就是控制字体大小写的,TabLayout里默认是true,我们手动改成false即可,我们顺便设置了下字体。

但是但是,问题又来了,我设置的字体大小貌似没什么卵用,无论我怎么调节字体大小就是不变。呵呵,还是要从tabTextAppearance这个属性来着手。
下面我们把代码改成这样:

app:tabTextAppearance="@android:style/TextAppearance.Holo.Large"

这下好了,字体的大小写解决了,字体大小也解决了。这样的属性我们找到了3组,

<style name="TextAppearance.Holo.Large" parent="TextAppearance.Large" />
<style name="TextAppearance.Holo.Medium"parent="TextAppearance.Medium"/>
<style name="TextAppearance.Holo.Small" parent="TextAppearance.Small" />

大.jpg

分别设置字体为大中小,说实话,这玩意真的不太好用,跟其他控件比起来,这个属性设置有点绕。

  • 不要用文本了,改成icon吧,wtf,TabItem根本没有这样的属性啊,TabLayout貌似也没有啊。怎么搞?TabLayout没有明确地提供向Tab中设置图标的途径,但是很多事情总可以另辟蹊径。我们知道,Tab是使用adapter中的getPageTitle()方法做其显示的内容,这个方法返回类型为CharSequence。于是,我们可以在PagerAdapter中重写getPageTitle()方法,创建一个SpannableString,而将图标放置在ImageSpan中,设置在SpannableString中:
    private int[] imageResId = {       
           R.mipmap.ic_0,       
           R.mipmap.ic_1,       
           R.mipmap.ic_2
        };

    @Override
        public CharSequence getPageTitle(int position){
            Drawable image = ContextCompat.getDrawable(MainActivity.this, imageResId[position]);
            image.setBounds(0, 0, image.getIntrinsicWidth(), image.getIntrinsicHeight());
            SpannableString sb = new SpannableString(" ");
            ImageSpan imageSpan = new ImageSpan(image, ImageSpan.ALIGN_BOTTOM);
            sb.setSpan(imageSpan, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            return sb;

        }

好了,运行起来,效果有了。

1B73B273D717C65EA0D26E63FEBB9C40.jpg
  • 要不改成icon+文本吧?呵呵。。。又改???
    还好还好,还是上面的方案,稍微修改下代码。在SpannableString中添加文本就可以了:
        @Override
        public CharSequence getPageTitle(int position){
            Drawable drawable = null;
            String title=null;
            switch (position) {
                case 0:
                    drawable = ContextCompat.getDrawable(MainActivity.this,R.mipmap.ic_qq_pre);
                    title = "a";
                    break;
                case 1:
                    drawable = ContextCompat.getDrawable(MainActivity.this, R.mipmap.ic_weibo_pre);
                    title = "b";
                    break;
                case 2:
                    drawable = ContextCompat.getDrawable(MainActivity.this, R.mipmap.login_weixin_icon);
                    title = "c";
                    break;
                default:
                    break;
            }
            drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());

            ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM);
            SpannableString spannableString = new SpannableString("   " + title);
            spannableString.setSpan(imageSpan, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

            return spannableString;

        }

还好还好,至于图片的select效果应该很easy了,就不演示了,效果如下。

C9E196F7ABD7AED92B5047DFA767E1E2.jpg
  • 图片在左边?要不放右边吧,不不不,放上面,算了算了,放下面吧。到底放哪???
    如果需求太奇葩,常规手段或者奇技淫巧都无法满足需求的话,就只有最后一招了:自定义。前面说过了TabItem本质上也是View,我们可以根据自己的实际需求来重写这个View。

icon在右边:


右.jpg

icon在上边:


上.jpg

可以发现通过自定义View的方式我们可以随意摆放文本和icon的位置,无所谓上下左右,处理起来都是一样的。甚至一个tab想放两个icon或者两个文本什么的都不在话下。一不下心展开讲,说的有点多了,这里就不再介绍如何自定义TabItem了,放在下篇讲,说了这么多好像也没上Tablayout和ViewPager的代码,也放在下一篇。总体来讲Tablayout的坑还是蛮多的,很多API都没提供,或者提供了但留了很多坑,这很google,一方面给你一个很常用的控件,一方面这个控件又留了很多坑,最后这个控件带给你无限想象和发挥,根据自己的想法,动手去实现吧。

完整代码:GitHub

推荐阅读更多精彩内容