TabLayout:另一种Tab的实现方式

来源:互联网 发布:我知女人心南宫寒正序 编辑:程序博客网 时间:2024/05/19 18:37

尊重原创转载请注明:From AigeStudio(http://blog.csdn.net/aigestudio)Power by Aige 侵权必究!

炮兵 镇楼

在5.0以前我们想要实现像网易新闻客户端那样的的Tab可以有很多种选择:

比如古老的TabHost,3.0后ActionBar所提供的Tab,以及各种成熟的Tab开源控件等,都可以直接或间接地实现Tab的效果。然而,对于这样一种使用极多的控件,Android是不会放弃将它纳入麾下的打算的,于是乎在5.0后放出的design包中Android就厚颜无耻地推出了自家官方的TabLayout控件来方便地实现类似的效果。其实这玩意跟ActionBar的Tab没什么卵的区别,只是说它更符合谷歌自家的MD设计理念,不过在实际使用中这玩意略显蛋疼,废话不多说,我们还是直接切入主题,要想使用TabLayout你就得导入Android的design包:

dependencies {    compile 'com.android.support:design:22.2.0'}
  • 1
  • 2
  • 3

然后在你的布局里加入TabLayout就行了:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">    <android.support.design.widget.TabLayout        android:id="@+id/ac_tab_layout"        android:layout_width="match_parent"        android:layout_height="wrap_content" /></RelativeLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这里需要注意的是design包中有些资源依赖于5.0,这是不是说我们就不能使用了呢,不是的,我们可以从AppCompat获得这些资源,将当前Activity的主题设置为Theme.AppCompat或其子类即可:

<activity    android:name="tablayout.TabLayoutActivity"    android:theme="@style/Theme.AppCompat" />
  • 1
  • 2
  • 3

当然现在你运行不会看到任何东西,因为我们没往里面家东西嘛,与ActionBar类似的是TabLayout添加Tab也很方便,只需要调用其addTab方法即可:

public class TabLayoutActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.ac_tab_layout);        TabLayout tabLayout = (TabLayout) findViewById(R.id.ac_tab_layout);        TabLayout.Tab tab1 = tabLayout.newTab().setText("Tab1");        tabLayout.addTab(tab1);        TabLayout.Tab tab2 = tabLayout.newTab().setText("Tab2");        tabLayout.addTab(tab2);        TabLayout.Tab tab3 = tabLayout.newTab().setText("Tab3");        tabLayout.addTab(tab3);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

运行效果如下:

非常简单,不过需要注意的是,这里的Tab类是TabLayout的一个静态内部类,并且其构造方法是包内可访问的,也就是说你无法在外部构造其实例,只能通过TabLayout的newTab方法:

public class TabLayout extends HorizontalScrollView {    // ......省去很多代码......    public static final class Tab {        // ......省去很多代码......        Tab(TabLayout parent) {            mPosition = -1;            mParent = parent;        }    }    public Tab newTab() {        return new Tab(this);    }    // ......省去很多代码......}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

addTab方法有四个不同的实现,拿参数最多的addTab(Tab tab, int position, boolean setSelected)方法来说,第一个参数tab就不说了,第二个参数position表示当前Tab需要插入的位置,其值必须小于当前TabLayout所拥有的Tab总数减一,如果大于则会报出数组下标溢出异常,这点很好理解,最后的boolean参数setSelected与其名意义一样,表示当前添加的Tab是否为选中状态,拿上面代码中的第三个tab3来说,我们修改其代码如下:

TabLayout.Tab tab3 = tabLayout.newTab().setText("Tab3");tabLayout.addTab(tab3, 2, true);
  • 1
  • 2

运行后tab3默认就会是被选中状态:

TabLayout中的公共方法不算多,大多都很好理解,不过有一个方法爱哥表示对其极度不爽,setScrollPosition从名字上来看它是用来设置滚动位置的,大家注意我们的TabLayout中被选中的Tab下方不是有根线么,我们可以通过这个方法来设置它的位置。首先我们在布局中添加一个按钮:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">    <android.support.design.widget.TabLayout        android:id="@+id/ac_tab_layout"        android:layout_width="match_parent"        android:layout_height="wrap_content" />    <Button        android:id="@+id/ac_tab_btn"        android:layout_width="wrap_content"        android:layout_centerInParent="true"        android:layout_height="wrap_content"        android:text="Move" /></RelativeLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

然后呢为这个按钮绑定一个点击事件监听并在其中调用setScrollPosition方法:

Button btnAdd = (Button) findViewById(R.id.ac_tab_btn);btnAdd.setOnClickListener(new View.OnClickListener() {    @Override    public void onClick(View v) {        tabLayout.setScrollPosition(0, 0.5F, false);    }});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

setScrollPosition方法的三个参数根据官方文档给出的解释如下:

第一个参数表示当前Tab的位置,第二个参数是偏移值,从文档中看到该值的取值范围是0到1的一个半开区间,最后一个参数很好理解表示是否置移动后位置所对应的Tab为选中状态,打个比方,如果我从0移动到1的位置,如果updateSelectedText为true,那么1这个位置上的文本就会是一个选中状态。上面的代码中我们只是简单地让下方的横条从0的Tab位置移动到一个0.5F的位置,效果如下:

可能大家会觉得纳闷,这个0.5F是个吊毛意思,其实positionOffset这个参数是一个倍数值,它的计算逻辑有些复杂,从源码中看setScrollPosition的逻辑,大致分为两步:

public void setScrollPosition(int position, float positionOffset, boolean updateSelectedText) {    if (isAnimationRunning(getAnimation()))        return;    if (position < 0 || position >= mTabStrip.getChildCount())        return;    mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset);    scrollTo(calculateScrollXForTab(position, positionOffset), 0);    if (updateSelectedText)        setSelectedTabView(Math.round((float) position + positionOffset));}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

首先是

mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset);
  • 1

这段的调用,其目的很简单,就是设置Tab下方的那个横条的位置,先是调用SlidingTabStrip的setIndicatorPositionFromTabPosition方法赋值:

void setIndicatorPositionFromTabPosition(int position, float positionOffset) {    if (TabLayout.isAnimationRunning(getAnimation())) {        return;    } else {        mSelectedPosition = position;        mSelectionOffset = positionOffset;        updateIndicatorPosition();        return;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

然后又调用了updateIndicatorPosition来做具体的位置更新操作:

private void updateIndicatorPosition() {    View selectedTitle = getChildAt(mSelectedPosition);    int left;    int right;    if (selectedTitle != null && selectedTitle.getWidth() > 0) {        left = selectedTitle.getLeft();        right = selectedTitle.getRight();        if (mSelectionOffset > 0.0F && mSelectedPosition < getChildCount() - 1) {            View nextTitle = getChildAt(mSelectedPosition + 1);            left = (int) (mSelectionOffset * (float) nextTitle.getLeft() + (1.0F - mSelectionOffset) * (float) left);            right = (int) (mSelectionOffset * (float) nextTitle.getRight() + (1.0F - mSelectionOffset) * (float) right);        }    } else {        left = right = -1;    }    setIndicatorPosition(left, right);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

首先是得到当前Tab左右的坐标值,然后尝试获取下一个Tab,如果有,那么根据相对位置计算最终的左右坐标,没有则不改变使用当前的左右坐标,这很好理解,最后通过setIndicatorPosition放啊分设置最终的横条位置:

private void setIndicatorPosition(int left, int right) {    if (left != mIndicatorLeft || right != mIndicatorRight) {        mIndicatorLeft = left;        mIndicatorRight = right;        ViewCompat.postInvalidateOnAnimation(this);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

回到setScrollPosition方法中再看看第二步滚动TabLayout到指定位置:

scrollTo(calculateScrollXForTab(position, positionOffset), 0);
  • 1

这一步在默认模式下不会被执行,TabLayout提供两种模式,一种是填充模式,也就是默认模式,表示TabLayout不可滚动,其宽度有一个最大值,所有的Tab则会挤在这个宽度中:

该模式的常量值为MODE_FIXED = 1,而我们可以通过TabLayout的setTabMode方法来设置其模式,这里我们将TabLayout的模式改为可滚动:

tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
  • 1

TabLayout.MODE_SCROLLABLE的常量值为0,这时候再看看效果:

这时候TabLayout变为可滚动而所有的Tab均显示完整。好我们现在回过头来看看

scrollTo(calculateScrollXForTab(position, positionOffset), 0);
  • 1

这段代码,scrollTo这个方法相信大家做过滑动都不会太陌生,其会将当前视图滚动至指定的坐标位置,TabLayout是一个水平滚动视图,因此其Y坐标滚动至恒为0,而X坐标则通过calculateScrollXForTab这个方法来计算:

private int calculateScrollXForTab(int position, float positionOffset) {    if (mMode == 0) {        View selectedChild = mTabStrip.getChildAt(position);        View nextChild = position + 1 >= mTabStrip.getChildCount() ? null : mTabStrip.getChildAt(position + 1);        int selectedWidth = selectedChild == null ? 0 : selectedChild.getWidth();        int nextWidth = nextChild == null ? 0 : nextChild.getWidth();        return (int) (((float) selectedChild.getLeft() + (float) (selectedWidth + nextWidth) * positionOffset * 0.5F + (float) selectedChild.getWidth() * 0.5F) - (float) getWidth() * 0.5F);    } else {        return 0;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这里可以看到先做了一个判断,如果模式为填充也就是MODE_FIXED的情况下是不需要滚动的,只有当模式为MODE_SCROLLABLE时才会去滚动,X坐标的计算逻辑与updateIndicatorPosition方法中的计算逻辑类似,都是通过当前Tab与下一个Tab的相对关系来计算的。那么为什么爱哥会对setScrollPosition这个方法表示极度不爽呢?首先,这个方法调用的前提根据官方文档和源码来看是需要相邻的两个Tab共同计算的,打个比方,如果我想从第0个Tab跳转至第1个Tab那么我就可以这么干:

public class TabLayoutActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.ac_tab_layout);        final TabLayout tabLayout = (TabLayout) findViewById(R.id.ac_tab_layout);        TabLayout.Tab tab1 = tabLayout.newTab().setText("Tab1");        tabLayout.addTab(tab1);        TabLayout.Tab tab2 = tabLayout.newTab().setText("Tab2");        tabLayout.addTab(tab2);        TabLayout.Tab tab3 = tabLayout.newTab().setText("Tab3");        tabLayout.addTab(tab3);        tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);        Button btnAdd = (Button) findViewById(R.id.ac_tab_btn);        btnAdd.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                tabLayout.setScrollPosition(0, 1F, true);        });    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

从第0个Tab开始,偏移值为一个单位,效果如下:

注意到这里的一个BUG没有,淡定,好戏在后头,大家想想虽说官方文档建议第二个参数的取值区间在0到1,但是我们完全可以考虑将该值设置大于1对吧,假设,仅仅是个假设,我们将其设为2,那么是否可以表示我们能直接从第0个Tab直接跳转到第2个Tab呢?答案好像是肯定的:

btnAdd.setOnClickListener(new View.OnClickListener() {    @Override    public void onClick(View v) {        tabLayout.setScrollPosition(0, 2F, true);});
  • 1
  • 2
  • 3
  • 4
  • 5

运行效果如下:

是不是有点意外?但是注意,这种想法是错误的!!!这里之所以能够直接跳转到第2个Tab是因为在这个例子中我们的每个Tab宽度都是一样的,如果我们将Tab稍作修改:

TabLayout.Tab tab1 = tabLayout.newTab().setText("Aige");tabLayout.addTab(tab1);TabLayout.Tab tab2 = tabLayout.newTab().setText("AigeStudio");tabLayout.addTab(tab2);TabLayout.Tab tab3 = tabLayout.newTab().setText("Studio");tabLayout.addTab(tab3);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这时候你就会看到错误的计算显示了:

当然如果仅仅是相邻的滚动,这样做是没问题的,比如我们从第1个Tab滚动至第2个Tab:

public class TabLayoutActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.ac_tab_layout);        final TabLayout tabLayout = (TabLayout) findViewById(R.id.ac_tab_layout);        TabLayout.Tab tab1 = tabLayout.newTab().setText("Aige");        tabLayout.addTab(tab1);        TabLayout.Tab tab2 = tabLayout.newTab().setText("AigeStudio");        tabLayout.addTab(tab2, true);        TabLayout.Tab tab3 = tabLayout.newTab().setText("Studio");        tabLayout.addTab(tab3);        tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);        Button btnAdd = (Button) findViewById(R.id.ac_tab_btn);        btnAdd.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                tabLayout.setScrollPosition(1, 1F, true);            }        });    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

这样就没问题:

使用setScrollPosition的另一个问题是,滚动时Tab底部那个横条切换生硬,不知道大家注意到没,没注意到往上看之前的一些切换动图,非常生硬,然而如果我们用另外一种方式来做这种切换就显得很带感了,Tab类中提供了一个select方法来设置当前Tab被选中:

public static final class Tab {    // ......省去很多代码......    public void select() {        mParent.selectTab(this);    }    // ......省去很多代码......}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这个方法所带来的效果就很不一样了,比如我们点击设置第三个Tab被选中:

public class TabLayoutActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.ac_tab_layout);        final TabLayout tabLayout = (TabLayout) findViewById(R.id.ac_tab_layout);        TabLayout.Tab tab1 = tabLayout.newTab().setText("Aige");        tabLayout.addTab(tab1);        TabLayout.Tab tab2 = tabLayout.newTab().setText("AigeStudio");        tabLayout.addTab(tab2);        final TabLayout.Tab tab3 = tabLayout.newTab().setText("Studio");        tabLayout.addTab(tab3);        tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);        Button btnAdd = (Button) findViewById(R.id.ac_tab_btn);        btnAdd.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                tab3.select();            }        });    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

效果是不是要动感的多呢:

从源码里可以看到select方法本质是调用了TabLayout中的selectTab方法:

void selectTab(Tab tab) {    if (mSelectedTab == tab) {        if (mSelectedTab != null) {            if (mOnTabSelectedListener != null)                mOnTabSelectedListener.onTabReselected(mSelectedTab);            animateToTab(tab.getPosition());        }    } else {        int newPosition = tab == null ? -1 : tab.getPosition();        setSelectedTabView(newPosition);        if ((mSelectedTab == null || mSelectedTab.getPosition() == -1) && newPosition != -1)            setScrollPosition(newPosition, 0.0F, true);        else            animateToTab(newPosition);        if (mSelectedTab != null && mOnTabSelectedListener != null)            mOnTabSelectedListener.onTabUnselected(mSelectedTab);        mSelectedTab = tab;        if (mSelectedTab != null && mOnTabSelectedListener != null)            mOnTabSelectedListener.onTabSelected(mSelectedTab);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

在该方法中比较重要的就是一个animateToTab方法的调用,该方法是产生动画效果的关键:

private void animateToTab(int newPosition) {    clearAnimation();    if (newPosition == -1)        return;    if (getWindowToken() == null || !ViewCompat.isLaidOut(this)) {        setScrollPosition(newPosition, 0.0F, true);        return;    }    int startScrollX = getScrollX();    int targetScrollX = calculateScrollXForTab(newPosition, 0.0F);    int duration = 300;    if (startScrollX != targetScrollX) {        ValueAnimatorCompat animator = ViewUtils.createAnimator();        animator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);        animator.setDuration(300);        animator.setIntValues(startScrollX, targetScrollX);        animator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {                                       public void onAnimationUpdate(ValueAnimatorCompat animator) {                                           scrollTo(animator.getAnimatedIntValue(), 0);                                       }                                       final TabLayout this$0;                                       {                                           this$0 = TabLayout.this;                                           super();                                       }                                   }        );        animator.start();    }    mTabStrip.animateIndicatorToPosition(newPosition, 300);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

如果你不需要设置Tab的一些监听的话那么完全可以通过反射的方式调用该方法滚动Tab至指定位置:

public class TabLayoutActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.ac_tab_layout);        final TabLayout tabLayout = (TabLayout) findViewById(R.id.ac_tab_layout);        TabLayout.Tab tab1 = tabLayout.newTab().setText("Aige");        tabLayout.addTab(tab1);        TabLayout.Tab tab2 = tabLayout.newTab().setText("AigeStudio");        tabLayout.addTab(tab2);        TabLayout.Tab tab3 = tabLayout.newTab().setText("Studio");        tabLayout.addTab(tab3);        TabLayout.Tab tab4 = tabLayout.newTab().setText("Fuck You");        tabLayout.addTab(tab4);        TabLayout.Tab tab5 = tabLayout.newTab().setText("Some of bitch");        tabLayout.addTab(tab5);        TabLayout.Tab tab6 = tabLayout.newTab().setText("Android");        tabLayout.addTab(tab6);        tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);        Button btnAdd = (Button) findViewById(R.id.ac_tab_btn);        btnAdd.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Class clz = tabLayout.getClass();                try {                    Method animateToTab = clz.getDeclaredMethod("animateToTab", new Class[]{int.class});                    animateToTab.setAccessible(true);                    animateToTab.invoke(tabLayout, new Object[]{5});                } catch (Exception e) {                    e.printStackTrace();                }            }        });    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

运行效果如下:

Tab中还有些其它的方法比如setIcon设置图标、setTag设置一个Tag标签还有setCustomView设置一个自定义视图作为一个Tab等,这些都比较简单不多说了:

TabLayout还有个setTabGravity方法可以用来设置TabLayout的布局方式,该方法只有两个可选参数GRAVITY_FILL = 0和GRAVITY_CENTER = 1,默认值是GRAVITY_FILL,setTabGravity方法只有在Mode为MODE_FIXED的情况下才有用,比如现在我们有两个Tab且这两个Tab的宽度比TabLayout小,默认情况下Tab的显示如下:

如果我们将Gravity设置为GRAVITY_CENTER:

tabLayout.setTabMode(TabLayout.MODE_FIXED);tabLayout.setTabGravity(TabLayout.GRAVITY_CENTER);
  • 1
  • 2

那么它就会是这个样子:

不难理解吧。我们还可以通过setOnTabSelectedListener方法为Tab切换设置监听器,OnTabSelectedListener接口中有三个方法onTabSelected、onTabUnselected和onTabReselected,前两个都很好理解不多说,不过要注意的是onTabUnselected总会在onTabSelected前被触发,而onTabReselected则会在你重复点击Tab的时候会被触发。你也可以调用setTabTextColors方法来更改显示文本的颜色,setTabTextColors接收两个颜色值分别表示普通状态的颜色和选中状态的颜色,当然你也可以提供一个ColorStateList对象:

tabLayout.setTabTextColors(Color.RED, Color.GREEN);
  • 1

效果如下:

这里要注意的是setTabTextColors方法必须在你添加Tab也就是调用addTab前调用,否则无效,这也是一个bug。还有一点是你无法通过代码的方式修改指示器也就是Tab下方的那根横线,因为那玩意由TabLayout的一个私有内部类SlidingTabStrip控制,其中的方法全TM是non-public的,不过值得欣慰的是你可以在xml中通过属性来改变它,当然上面我们说到的文本颜色也可以通过这种方式修改:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout 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.support.design.widget.TabLayout        android:id="@+id/ac_tab_layout"        android:layout_width="match_parent"        android:layout_height="wrap_content"        app:tabIndicatorColor="#FFFFFF"        app:tabSelectedTextColor="#00FF00"        app:tabTextColor="#FF0000" /></RelativeLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

效果如下:

大部分情况下TabLayout都不会单独使用,一般我们会配合ViewPager来实现一个完整的Tab切换效果,而TabLayout也提供了几个很简便的方法来与ViewPager进行关联交互,先在我们的布局里加一个ViewPager:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <android.support.design.widget.TabLayout        android:id="@+id/ac_tab_layout"        android:layout_width="match_parent"        android:layout_height="wrap_content" />    <android.support.v4.view.ViewPager        android:id="@+id/ac_tab_vp"        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="1" /></LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

然后,然后,然后还是看代码吧,TabLayout与ViewPager结合的逻辑有点绕:

public class TabLayoutActivity extends Activity {    /**     * 数据源     */    private static final String[] DATA = {"AigeStudio", "Aige", "Studio", "Android", "Java", "Design"};    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.ac_tab_layout);        // 获取ViewPager        final ViewPager viewPager = (ViewPager) findViewById(R.id.ac_tab_vp);        // 构造一个TabPagerAdapter对象        TabPagerAdapter adapter = new TabPagerAdapter();        // 获取ViewPager        TabLayout tabLayout = (TabLayout) findViewById(R.id.ac_tab_layout);        // 设置TabLayout模式        tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);        // 设置从PagerAdapter中获取Tab        tabLayout.setTabsFromPagerAdapter(adapter);        // 设置Tab的选择监听        tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {            @Override            public void onTabSelected(TabLayout.Tab tab) {                // 每当我们选择了一个Tab就将ViewPager滚动至对应的Page                viewPager.setCurrentItem(tab.getPosition(), true);            }            @Override            public void onTabUnselected(TabLayout.Tab tab) {            }            @Override            public void onTabReselected(TabLayout.Tab tab) {            }        });        // 构造一个TabLayoutOnPageChangeListener对象        TabLayout.TabLayoutOnPageChangeListener listener =                new TabLayout.TabLayoutOnPageChangeListener(tabLayout);        // 设置ViewPager页面改变后的监听        viewPager.addOnPageChangeListener(listener);        // 设置ViewPager的适配器        viewPager.setAdapter(adapter);    }    private class TabPagerAdapter extends PagerAdapter {        @Override        public int getCount() {            return DATA.length;        }        @Override        public boolean isViewFromObject(View view, Object object) {            return view == object;        }        @Override        public Object instantiateItem(ViewGroup container, int position) {            TextView tvContent = new TextView(TabLayoutActivity.this);            tvContent.setText(DATA[position]);            tvContent.setGravity(Gravity.CENTER);            container.addView(tvContent,                    ViewPager.LayoutParams.MATCH_PARENT, ViewPager.LayoutParams.WRAP_CONTENT);            return tvContent;        }        @Override        public void destroyItem(ViewGroup container, int position, Object object) {            container.removeView((View) object);        }        @Override        public CharSequence getPageTitle(int position) {            return DATA[position];        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

运行效果如下:

其它的不多说,大家注意到我们的代码中并没有添加Tab的具体逻辑,但是从运行的结果来看却实实在在地把我们的Tab标题显示上去了,这里主要还是要归功于TabLayout的setTabsFromPagerAdapter方法,在该方法中TabLayout会通过PagerAdapter的getPageTitle方法获得Page中的标题并将其封装成一个Tab添加至TabLayout中:

public void setTabsFromPagerAdapter(PagerAdapter adapter){    removeAllTabs();    int i = 0;    for(int count = adapter.getCount(); i < count; i++)        addTab(newTab().setText(adapter.getPageTitle(i)));}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这也是为什么我们会在PagerAdapter中重写getPageTitle方法返回数据的原因。同时我们为TabLayout设置了一个Tab选择监听,在Tab被选择后可以让滚动至对应的Page,这一步就完成了Tab到Page的事件绑定,那么当Page改变后又如何影响Tab呢?这时候我们就得为ViewPager添加一个PageChangeListener,而这个PageChangeListener当然你可以自己去实现,但是TabLayout提供了一个方便的实现类TabLayoutOnPageChangeListener,我们只需构造一个添加到ViewPager即可。是不是觉得逻辑有点乱?一会TabLayout一会ViewPager的,确实,我也觉的,不过TabLayout提供了一个更超级简便的方法setupWithViewPager来完成与ViewPager的结合,使用方法很简单,先把ViewPager的相关参数什么的设置好,比如Adapter什么的,然后直接调用TabLayout的setupWithViewPager方法设置即可:

@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.ac_tab_layout);    // 获取ViewPager    ViewPager viewPager = (ViewPager) findViewById(R.id.ac_tab_vp);    // 构造一个TabPagerAdapter对象    TabPagerAdapter adapter = new TabPagerAdapter();    // 获取ViewPager    TabLayout tabLayout = (TabLayout) findViewById(R.id.ac_tab_layout);    // 设置TabLayout模式    tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);    // 设置ViewPager的适配器    viewPager.setAdapter(adapter);    // 设置ViewPager    tabLayout.setupWithViewPager(viewPager);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

是不是感觉瞬间就简单了很多…………Fuck。好了TabLayout就啰嗦到这里,总结就一句话,这货一定是程序猿心情不好的时候写的,很多逻辑好奇葩啊有木有~~~~

Demo项目源码在Github可下载:AndroidViewDemo

 
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可

原创粉丝点击