简介
TabLayout继承自 HorizontalScrollView
TabLayout 提供了一个水平布局来显示标签。
所有的 Tab 选项卡实例化都是通过 TabLayout.Tab 完成的。你可以通过 TabLayout.newTab()来创建 Tab 对象。你可以通过更改Tab 的setText()、setIcon()分别设置选项卡的文字和 Icon。要显示选项卡 Tab,你必须通过一个方法 addTab(tab)方法将其添加到布局。例如:
TabLayout tabLayout = ...;
tabLayout.addTab(tabLayout.newTab().setText("Tab 1"));
tabLayout.addTab(tabLayout.newTab().setText("Tab 2"));
tabLayout.addTab(tabLayout.newTab().setText("Tab 3"));
你可以设一个监听setOnTabSelectedListener(OnTabSelectedListener),当任何表情的选择状态改变的时候回调。你也可以在 xml 布局中使用TabItem添加 tab 到TabLayout 里面,例如
<android.support.design.widget.TabLayout
android:layout_height="wrap_content"
android:layout_width="match_parent">
<android.support.design.widget.TabItem
android:text="@string/tab_text"/>
<android.support.design.widget.TabItem
android:icon="@drawable/ic_android"/>
</android.support.design.widget.TabLayout>
结合ViewPager
如果你的 ViewPager 和这个布局用在一起,你可以调用 setupWithVIewPager(ViewPager)两个链接在一起,这种布局将会自动填充 PagerAdapter 的页面标题
你也可以把这种用法当成 ViewPager 的装饰,并且可以这样写布局资源直接添加到 ViewPager 当中:
<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.TabLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top" />
</android.support.v4.view.ViewPager>
哈哈哈哈哈,有木有感觉读起来很尴尬,上面这段文字翻译自Google 官方文档,是用我三级没过的英语翻译的,官方文档真的讲的很清楚,大家真的不要恐惧看官方文档。but,上文中的两种 xml写法,我用了这么久 TabLayout,真的是第一次知道。
XML attributes
<declare-styleable name="TabLayout">
<attr format="color" name="tabIndicatorColor"/>
<attr format="dimension" name="tabIndicatorHeight"/>
<attr format="dimension" name="tabContentStart"/>
<attr format="reference" name="tabBackground"/>
<attr name="tabMode">
<enum name="scrollable" value="0"/>
<enum name="fixed" value="1"/>
</attr>
<attr name="tabGravity">
<enum name="fill" value="0"/>
<enum name="center" value="1"/>
</attr>
<attr format="dimension" name="tabMinWidth"/>
<attr format="dimension" name="tabMaxWidth"/>
<attr format="reference" name="tabTextAppearance"/>
<attr format="color" name="tabTextColor"/>
<attr format="color" name="tabSelectedTextColor"/>
<attr format="dimension" name="tabPaddingStart"/>
<attr format="dimension" name="tabPaddingTop"/>
<attr format="dimension" name="tabPaddingEnd"/>
<attr format="dimension" name="tabPaddingBottom"/>
<attr format="dimension" name="tabPadding"/>
</declare-styleable>
- tabIndicatorColor 下标颜色
- tabIndicatorHeight 下标高度
- tabContentStart 设置左边的 padding
- tabBackground 背景颜色
- tabGravity fill:tabs 平均填充整个宽度 center:tabs 居中显示
- tabMode scrollable:可滑动;fixed:不能滑动,平分tabLayout宽度
- tabMinWidth tab 的最新宽度
- tabMaxWidth tab 的最大宽度
- tabTextAppearance tab 的文字style
- tabTextColor tab 文字颜色
- tabSelectedTextColor tab 选中文字颜色
- tabPadding***** tab 的 padding 值
Public methods
方法名 | 作用 |
---|---|
addOnTabSelectedListener() | 添加 tab 选中监听 |
addTab() | 添加一个 tab |
addView | 添加一个 View。注意只能是TabItem,然后最终转换成 tab |
clearOnTabSelectedListener() | 移除条目选中监听 |
generateLayoutparams() | 获取 layoutParams |
getSelectedTabPositing() | 获取当前所选标签 position |
getTabAt(int index) | 获取指定索引的 Tab |
gettabCount() | 获取 tab 数 |
getTabGravity() | 获取tabGravity |
getTabMode() | 获取 TabMode |
getTabTextColors() | 获取选项卡中不同状态颜色 |
newTab() | 创建并返回一个新的 TabLayout.Tab |
removeAllTabs() | 删除所有选项卡 |
removeOnTabSelectedListener() | 删除所有 OnTabSelectedListener |
removeTab(Tab) | 移除指定 tab |
removetabAt(position) | 移除指定 position 的 tab |
setOnTabSelectedListener() | 等同 addOnTabSelectedListener() |
setScrollPosition() | 设置选项卡滚动位置 |
setSelectedTabIndicatorColor() | 设置选中下标颜色 |
setTabGravity() | 设置 TabGravity |
setTabMode() | 设置 TabMode |
setTabTextCloros() | 设置 tab 的文字颜色 |
setTabsFromPagerAdapter() | 已过期,使用 setupWithViewPager() |
setupViewPager() | 绑定 ViewPager |
shouldDelayChildPressedState() | 如果tab可以滚动,只延迟按下状态 |
其实没什么好写的,基本上看到方法名就能知道是干嘛的,初入 android 开发的同学切记不要死记硬背这些 api,有个大概的印象就行。老司机权当查漏补缺吧。
一张图看懂 TabLayout 的类层次
可能有些同学没看懂SlidingTabStrip是什么。
TabLayout继承自 HorizontalScrollView,ScrollView 只能添加一个子 View,所以 SlidingTabStrip 就是那个用来添加子View 的HorizontalLinearLayout。
花式玩法
1.tab 之间分割线
Ui 说要在 tab 条目之间添加分割线,很操蛋有木有。拿到需求之后研究了一遍 Api,然而发现并没有提供添加分割线的方法,然后自己去手撸一个 TabLayout。
其实不用手撸,没有Api 我们可以曲线救国。鲁迅当年觉得学医救不了中国人,不也选择了 DJ 来曲线救国么。
用过 ScrollView 的童鞋都知道,ScrollView 只能有一个子 View,因此,ScrollView 都会有一个 LinearLayout。然后 LinearLayout 有个方法setShowDividers()可以设置分割线,而 TabLayout 就算继承自HorizontalScrollView,那么我们是不是可以去找一下,TabLayout 里面的 ScrollView。通过阅读源码,我们找到了这样几行代码
mTabStrip = new SlidingTabStrip(context);
super.addView(mTabStrip, 0, new HorizontalScrollView.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
...
private class SlidingTabStrip extends LinearLayout {
这里的mTabStrip就是我们要找的LinearLayout。but,mTabStrip是一个 private 变量。
当然,获取一个 private 变量拦不到我们牛逼的 java 程序员,tabLayout.getClass()...分分钟获取到mTabStrip对象。
stop,我们这里有个优雅的方法获取mTabStrip对象。我们都知道TabLayout 继承自HorizontalScrollView,HorizontalScrollView只能有一个子类!!!tabLayout.getChildAt(0)是不是就获取到了 mTabStrip对象。
然后调用以下方法给 LinearLayout 设置分割线即可
LinearLayout linearLayout = (LinearLayout) toolbar_tab.getChildAt(0);
linearLayout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
linearLayout.setDividerDrawable(ContextCompat.getDrawable(this,R.drawable.divider)); //设置分割线的样式linearLayout.setDividerPadding(20); //设置分割线间隔
这里贴上 R.drawable.divider 的代码
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#c0c0c0" />
<size android:width="1px"/>
</shape>
绑定 ViewPager
哈哈,其实这个只是基本功能。两个步骤
1.如果你的 TabLayout 的节点不是在 ViewPager 节点内部,需要把TabLayout 和 ViewPager 绑定起来。否则可以跳过这一步
mTabLayout.setupWithViewPager(mViewPager);
2.重写 PagerAdapter 的 getPageTitle()方法。
自定义指示器的长度
UI 说,指示器的长度不要充满屏幕~~~~~
这里有个办法通过反射的方式修改指示器长度,如果需要指示器宽度等于文字宽度需要自己微调。原理就是通过反射的方式获取 TabLayout 的字段 mTabStrip,然后再去遍历修改每一个子 View 的 padding 值。代码如下:
/**
* 通过反射设置TabLayout每一个的长度
* @param left 左边 Padding 单位 dp
* @param right 右边 Padding 单位 dp
*/
public void setIndicator(TabLayout tabLayout, int left, int right) {
Class<?> tabLayoutClass = tabLayout.getClass();
Field tabStrip = null;
try {
tabStrip = tabLayoutClass.getDeclaredField("mTabStrip");
tabStrip.setAccessible(true);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
LinearLayout llTab = null;
try {
llTab = (LinearLayout) tabStrip.get(tabLayout);
} catch (Exception e) {
e.printStackTrace();
}
int l = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, left, Resources.getSystem().getDisplayMetrics());
int r = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, right, Resources.getSystem().getDisplayMetrics());
if (llTab != null) {
for (int i = 0; i < llTab.getChildCount(); i++) {
View child = llTab.getChildAt(i);
child.setPadding(0, 0, 0, 0);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
params.leftMargin = l;
params.rightMargin = r;
child.setLayoutParams(params);
child.invalidate();
}
}
}
自定义 Tab
可能有些童鞋不满足于TabLayout 当前的定制,想要完全自定义。可以的~很有想法
大家对这种方式添加一个 Tab 条目肯定不陌生吧
tabLayout.addTab(tabLayout.newTab());
tabLayout.newTab()的返回值是一个TabLayout.Tab。既然 tabLayout.addTab(Tab)就能添加一个条目,那么可以大胆的断定 Tab 就是代表一个条目,然后我们通过查看源码可以知道 tab.getCustomView()可以获得这个 View,这时就很简单了,你可以直接设置这个 mCustomView ,然后自己处理 mCustomView 的显示。
能干什么?比如说首页底部导航,选中条目放大等等。。。。自由发挥
源码分析
这个源码好像比较简单,我们先从构造方法开始
public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//检查当前主题是否是 AppCompat 系列的,否则报错,里面代码就只有几行
ThemeUtils.checkAppCompatTheme(context);
// Disable the Scroll Bar 禁用滚动条
setHorizontalScrollBarEnabled(false);
// Add the TabStrip 创建SlidingTabStrip,
// 以后 tabView 就是添加到这里面
mTabStrip = new SlidingTabStrip(context);
super.addView(mTabStrip, 0, new HorizontalScrollView.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabLayout,
defStyleAttr, R.style.Widget_Design_TabLayout);
...读取attributes属性代码,省略
final Resources res = getResources();
//设置默认文字大小12sp
mTabTextMultiLineSize = res.getDimensionPixelSize(R.dimen.design_tab_text_size_2line);
//设置默认最小宽度72dp
mScrollableTabMinWidth = res.getDimensionPixelSize(R.dimen.design_tab_scrollable_min_width);
// Now apply the tab mode and gravity
//设置 mode 和 gravity,不明白这两个属性的请回头看attributes
applyModeAndGravity();
}
//这个应该能看懂吧~~~
private void applyModeAndGravity() {
int paddingStart = 0;
if (mMode == MODE_SCROLLABLE) {
// If we're scrollable, or fixed at start, inset using padding
paddingStart = Math.max(0, mContentInsetStart - mTabPaddingStart);
}
ViewCompat.setPaddingRelative(mTabStrip, paddingStart, 0, 0, 0);
switch (mMode) {
case MODE_FIXED:
mTabStrip.setGravity(Gravity.CENTER_HORIZONTAL);
break;
case MODE_SCROLLABLE:
mTabStrip.setGravity(GravityCompat.START);
break;
}
updateTabViews(true);
}
//遍历所有子 View,并更新 LayoutParams
void updateTabViews(final boolean requestLayout) {
for (int i = 0; i < mTabStrip.getChildCount(); i++) {
View child = mTabStrip.getChildAt(i);
child.setMinimumWidth(getTabMinWidth());
updateTabViewLayoutParams((LinearLayout.LayoutParams) child.getLayoutParams());
if (requestLayout) {
child.requestLayout();
}
}
}
//layoutParams 属性请参照 mode 和 gravity 的属性看
private void updateTabViewLayoutParams(LinearLayout.LayoutParams lp) {
if (mMode == MODE_FIXED && mTabGravity == GRAVITY_FILL) {
lp.width = 0;
lp.weight = 1;
} else {
lp.width = LinearLayout.LayoutParams.WRAP_CONTENT;
lp.weight = 0;
}
}
-----------构造方法结束-----
-----如何添加一个 tab------
//创建了一个Tab对象,并持有对 TabLayout 的引用
//这里的 sTabPool 继承自 pool,一个可以设置最大创建个数的工具类,如果
//超过最大创建个数则不再创建返回 null,这里还加了一个非空判断,表示没看懂
//为什么要用Pools.SynchronizedPool来创建Tab
public Tab newTab() {
Tab tab = sTabPool.acquire();
if (tab == null) {
tab = new Tab();
}
tab.mParent = this;
//创建一个 TabView,TabView 就是真正的每个条目View
//Tab 只是一个简单的 View Model
tab.mView = createTabView(tab);
return tab;
}
//创建 TabView
private TabView createTabView(@NonNull final Tab tab) {
TabView tabView = mTabViewPool != null ? mTabViewPool.acquire() : null;
if (tabView == null) {
tabView = new TabView(getContext());
}
tabView.setTab(tab);
tabView.setFocusable(true);
tabView.setMinimumWidth(getTabMinWidth());
return tabView;
}
//添加到
public void addTab(@NonNull Tab tab, int position, boolean setSelected) {
if (tab.mParent != this) {
throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
}
//配置这个方法不重要,就几行代码
configureTab(tab, position);
//调用方法添加 TabView 到mTabStrip里面
addTabView(tab);
if (setSelected) {
//设置条目选中
tab.select();
}
}
//添加TabView到mTabStrip里的执行方法
private void addTabView(Tab tab) {
final TabView tabView = tab.mView;
mTabStrip.addView(tabView, tab.getPosition(), createLayoutParamsForTabs());
}
//设置 Tab 选中,并且将之前选中的 Tab 设为未选中状态
//然后更新下标 updateIndicator
void selectTab(Tab tab) {
selectTab(tab, true);
}
----------------------------
--------ViewPager 绑定-------
-----------------------------
//上文说过,绑定 ViewPager 只需要一行代码mTabLayout.setupWithViewPager(mViewPager)
//那么我们就从这个方法开始看
private void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh,
boolean implicitSetup) {
if (mViewPager != null) {
// If we've already been setup with a ViewPager, remove us from it
if (mPageChangeListener != null) {
mViewPager.removeOnPageChangeListener(mPageChangeListener);
}
if (mAdapterChangeListener != null) {
mViewPager.removeOnAdapterChangeListener(mAdapterChangeListener);
}
}
if (mCurrentVpSelectedListener != null) {
// If we already have a tab selected listener for the ViewPager, remove it
removeOnTabSelectedListener(mCurrentVpSelectedListener);
mCurrentVpSelectedListener = null;
}
if (viewPager != null) {
mViewPager = viewPager;
// Add our custom OnPageChangeListener to the ViewPager
if (mPageChangeListener == null) {
mPageChangeListener = new TabLayoutOnPageChangeListener(this);
}
mPageChangeListener.reset();
viewPager.addOnPageChangeListener(mPageChangeListener);
// Now we'll add a tab selected listener to set ViewPager's current item
mCurrentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager);
addOnTabSelectedListener(mCurrentVpSelectedListener);
final PagerAdapter adapter = viewPager.getAdapter();
if (adapter != null) {
// Now we'll populate ourselves from the pager adapter, adding an observer if
// autoRefresh is enabled
setPagerAdapter(adapter, autoRefresh);
}
// Add a listener so that we're notified of any adapter changes
if (mAdapterChangeListener == null) {
mAdapterChangeListener = new AdapterChangeListener();
}
mAdapterChangeListener.setAutoRefresh(autoRefresh);
viewPager.addOnAdapterChangeListener(mAdapterChangeListener);
// Now update the scroll position to match the ViewPager's current item
setScrollPosition(viewPager.getCurrentItem(), 0f, true);
} else {
// We've been given a null ViewPager so we need to clear out the internal state,
// listeners and observers
mViewPager = null;
setPagerAdapter(null, false);
}
//这个变量我没看懂有什么用,private,没有提供修改方法,
//几个赋值的地方都是被赋值为 false。
mSetupViewPagerImplicitly = implicitSetup;
}
好了,看完了,踏马源码里面都有写代码注释,我三级的英语水平都看得懂,这里为了给大家原值原味的感觉,我就不再翻译了,希望大家阅读愉快。
好了,TabLayout 分析到此结束。本来还想写 SearchView 和 CardView 的,但是我觉得从MaterialDesign(1)开始看过来的朋友现在都已经学会了自己去看源码,所以下一篇不准备写 View 了,没意思。
明天一起来学沉浸式设计以及沉浸式设计里面的那些坑。
加油Coder。加油Android Developer。