簡介
TabLayout繼承自 HorizontalScrollView
TabLayout 提供了一個水平布局來顯示標簽妙色。
所有的 Tab 選項卡實例化都是通過 TabLayout.Tab 完成的。你可以通過 TabLayout.newTab()來創(chuàng)建 Tab 對象盆顾。你可以通過更改Tab 的setText()诬像、setIcon()分別設(shè)置選項卡的文字和 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"));
你可以設(shè)一個監(jiān)聽setOnTabSelectedListener(OnTabSelectedListener),當任何表情的選擇狀態(tài)改變的時候回調(diào)坏挠。你也可以在 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>
結(jié)合ViewPager
如果你的 ViewPager 和這個布局用在一起,你可以調(diào)用 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 設(shè)置左邊的 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 選中監(jiān)聽 |
addTab() | 添加一個 tab |
addView | 添加一個 View。注意只能是TabItem溃论,然后最終轉(zhuǎn)換成 tab |
clearOnTabSelectedListener() | 移除條目選中監(jiān)聽 |
generateLayoutparams() | 獲取 layoutParams |
getSelectedTabPositing() | 獲取當前所選標簽 position |
getTabAt(int index) | 獲取指定索引的 Tab |
gettabCount() | 獲取 tab 數(shù) |
getTabGravity() | 獲取tabGravity |
getTabMode() | 獲取 TabMode |
getTabTextColors() | 獲取選項卡中不同狀態(tài)顏色 |
newTab() | 創(chuàng)建并返回一個新的 TabLayout.Tab |
removeAllTabs() | 刪除所有選項卡 |
removeOnTabSelectedListener() | 刪除所有 OnTabSelectedListener |
removeTab(Tab) | 移除指定 tab |
removetabAt(position) | 移除指定 position 的 tab |
setOnTabSelectedListener() | 等同 addOnTabSelectedListener() |
setScrollPosition() | 設(shè)置選項卡滾動位置 |
setSelectedTabIndicatorColor() | 設(shè)置選中下標顏色 |
setTabGravity() | 設(shè)置 TabGravity |
setTabMode() | 設(shè)置 TabMode |
setTabTextCloros() | 設(shè)置 tab 的文字顏色 |
setTabsFromPagerAdapter() | 已過期,使用 setupWithViewPager() |
setupViewPager() | 綁定 ViewPager |
shouldDelayChildPressedState() | 如果tab可以滾動屎蜓,只延遲按下狀態(tài) |
其實沒什么好寫的,基本上看到方法名就能知道是干嘛的钥勋,初入 android 開發(fā)的同學切記不要死記硬背這些 api炬转,有個大概的印象就行。老司機權(quán)當查漏補缺吧算灸。
一張圖看懂 TabLayout 的類層次
可能有些同學沒看懂SlidingTabStrip是什么扼劈。
TabLayout繼承自 HorizontalScrollView,ScrollView 只能添加一個子 View菲驴,所以 SlidingTabStrip 就是那個用來添加子View 的HorizontalLinearLayout荐吵。
花式玩法
1.tab 之間分割線
Ui 說要在 tab 條目之間添加分割線,很操蛋有木有赊瞬。拿到需求之后研究了一遍 Api捍靠,然而發(fā)現(xiàn)并沒有提供添加分割線的方法,然后自己去手擼一個 TabLayout森逮。
其實不用手擼,沒有Api 我們可以曲線救國磁携。魯迅當年覺得學醫(yī)救不了中國人褒侧,不也選擇了 DJ 來曲線救國么。
用過 ScrollView 的童鞋都知道谊迄,ScrollView 只能有一個子 View闷供,因此,ScrollView 都會有一個 LinearLayout统诺。然后 LinearLayout 有個方法setShowDividers()可以設(shè)置分割線歪脏,而 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识藤,我們這里有個優(yōu)雅的方法獲取mTabStrip對象砚著。我們都知道TabLayout 繼承自HorizontalScrollView次伶,HorizontalScrollView只能有一個子類!;隆冠王!tabLayout.getChildAt(0)是不是就獲取到了 mTabStrip對象。
然后調(diào)用以下方法給 LinearLayout 設(shè)置分割線即可
LinearLayout linearLayout = (LinearLayout) toolbar_tab.getChildAt(0);
linearLayout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
linearLayout.setDividerDrawable(ContextCompat.getDrawable(this,R.drawable.divider)); //設(shè)置分割線的樣式linearLayout.setDividerPadding(20); //設(shè)置分割線間隔
這里貼上 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 的節(jié)點不是在 ViewPager 節(jié)點內(nèi)部,需要把TabLayout 和 ViewPager 綁定起來乎折。否則可以跳過這一步
mTabLayout.setupWithViewPager(mViewPager);
2.重寫 PagerAdapter 的 getPageTitle()方法绒疗。
自定義指示器的長度
UI 說,指示器的長度不要充滿屏幕~~~~~
這里有個辦法通過反射的方式修改指示器長度骂澄,如果需要指示器寬度等于文字寬度需要自己微調(diào)吓蘑。原理就是通過反射的方式獲取 TabLayout 的字段 mTabStrip,然后再去遍歷修改每一個子 View 的 padding 值。代碼如下:
/**
* 通過反射設(shè)置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私痹,這時就很簡單了脐嫂,你可以直接設(shè)置這個 mCustomView ,然后自己處理 mCustomView 的顯示。
能干什么紊遵?比如說首頁底部導(dǎo)航账千,選中條目放大等等。暗膜。匀奏。。自由發(fā)揮
源碼分析
這個源碼好像比較簡單学搜,我們先從構(gòu)造方法開始
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 創(chuàng)建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();
//設(shè)置默認文字大小12sp
mTabTextMultiLineSize = res.getDimensionPixelSize(R.dimen.design_tab_text_size_2line);
//設(shè)置默認最小寬度72dp
mScrollableTabMinWidth = res.getDimensionPixelSize(R.dimen.design_tab_scrollable_min_width);
// Now apply the tab mode and gravity
//設(shè)置 mode 和 gravity,不明白這兩個屬性的請回頭看attributes
applyModeAndGravity();
}
//這個應(yīng)該能看懂吧~~~
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;
}
}
-----------構(gòu)造方法結(jié)束-----
-----如何添加一個 tab------
//創(chuàng)建了一個Tab對象咧最,并持有對 TabLayout 的引用
//這里的 sTabPool 繼承自 pool,一個可以設(shè)置最大創(chuàng)建個數(shù)的工具類,如果
//超過最大創(chuàng)建個數(shù)則不再創(chuàng)建返回 null矢沿,這里還加了一個非空判斷滥搭,表示沒看懂
//為什么要用Pools.SynchronizedPool來創(chuàng)建Tab
public Tab newTab() {
Tab tab = sTabPool.acquire();
if (tab == null) {
tab = new Tab();
}
tab.mParent = this;
//創(chuàng)建一個 TabView,TabView 就是真正的每個條目View
//Tab 只是一個簡單的 View Model
tab.mView = createTabView(tab);
return tab;
}
//創(chuàng)建 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);
//調(diào)用方法添加 TabView 到mTabStrip里面
addTabView(tab);
if (setSelected) {
//設(shè)置條目選中
tab.select();
}
}
//添加TabView到mTabStrip里的執(zhí)行方法
private void addTabView(Tab tab) {
final TabView tabView = tab.mView;
mTabStrip.addView(tabView, tab.getPosition(), createLayoutParamsForTabs());
}
//設(shè)置 Tab 選中瑟匆,并且將之前選中的 Tab 設(shè)為未選中狀態(tài)
//然后更新下標 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 分析到此結(jié)束幔亥。本來還想寫 SearchView 和 CardView 的耻讽,但是我覺得從MaterialDesign(1)開始看過來的朋友現(xiàn)在都已經(jīng)學會了自己去看源碼,所以下一篇不準備寫 View 了帕棉,沒意思针肥。
明天一起來學沉浸式設(shè)計以及沉浸式設(shè)計里面的那些坑。
加油Coder笤昨。加油Android Developer。