什么是 TabLayout
TabLayout provides a horizontal layout to display tabs.
官方介紹有滑,TabLayout 是一個橫向標(biāo)簽顯示的布局娄琉,效果就是現(xiàn)在很多新聞客戶端的那種頂部標(biāo)簽展示效果往果,并支持指示器、 ViewPager 聯(lián)動
簡單使用
按照慣例颓鲜,我們先看一下效果圖:
從效果圖來看表窘,這是采用 TabLayout + ViewPager 滑動切換和點擊標(biāo)簽切換的一個效果。TabLayout 支持橫向滾動多標(biāo)簽設(shè)置甜滨,還可以支持指示器乐严,支持與 ViewPager 進(jìn)行聯(lián)動。下面看看具體實現(xiàn)
- 引入 com.android.support:design
TabLayout 是屬于 com.android.support:design 包的控件衣摩,所以需要依賴該包
compile 'com.android.support:design:26.1.0'
- xml 文件創(chuàng)建
<?xml version="1.0" encoding="utf-8"?>
<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/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabBackground="@color/colorPrimary"
app:tabIndicatorColor="#ffffff"
app:tabIndicatorHeight="3dp"
app:tabMode="scrollable"
app:tabPadding="2dp"
app:tabSelectedTextColor="#ffffff"
app:tabTextAppearance="@style/TabTextStyle"
app:tabTextColor="#333333"/>
<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
從 xml 布局文件看昂验,根布局為一個垂直的線性布局,包含 TabLayout 和 ViewPager昭娩。重點看一下 TabLayout 的幾個常用屬性值
- app:tabBackground 標(biāo)簽布局的背景色
- app:tabIndicatorColor 指示器的顏色
- app:tabIndicatorHeight 指示器的高度(如果不需要指示器可以設(shè)置為0dp)
- app:tabMode 顯示模式:默認(rèn) fixed(固定)凛篙,scrollable(可橫向滾動)
- app:tabPadding 標(biāo)簽內(nèi)邊距
- app:tabSelectedTextColor 標(biāo)簽選中的文本顏色
- app:tabTextAppearance 標(biāo)簽文本樣式
- app:tabTextColor 標(biāo)簽未選中的文本顏色
tab_custom_view.xml
<?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="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="5dp">
<ImageView
android:id="@+id/iv_tab_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/tab_iv_selector"/>
<TextView
android:id="@+id/tv_tab_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="2dp"
android:text="tab"
android:textColor="@color/tab_tv_selector"/>
</LinearLayout>
tab_iv_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_add_black_24dp" android:state_selected="false"/>
<item android:drawable="@drawable/ic_add_white_24dp" android:state_selected="true"/>
</selector>
tab_tv_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/black" android:state_selected="false"/>
<item android:color="@android:color/white" android:state_selected="true"/>
</selector>
以上是一個簡單的自定義標(biāo)簽布局文件黍匾,可以看到 ImageView 的 src 和 TextView 的 textColor 屬性值都使用了 selector栏渺,這樣寫的話就不需要再在代碼監(jiān)聽選中的 Tab 去改變狀態(tài)了,非常方便锐涯。該 xml 會在下面的代碼中應(yīng)用
- Activity 中獲取并設(shè)置相關(guān)屬性值磕诊,以及事件監(jiān)聽
arrays.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="tab">
<item>推薦</item>
<item>視頻</item>
<item>熱點</item>
<item>社會</item>
<item>娛樂</item>
<item>科技</item>
<item>汽車</item>
<item>體育</item>
</string-array>
</resources>
TabLayoutActivity.java
@BindView(R.id.tab_layout)
TabLayout mTabLayout;
@BindView(R.id.view_pager)
ViewPager mViewPager;
private ArrayList<MyFragment> mFragments = new ArrayList<>();
// 獲取標(biāo)簽數(shù)組
String[] tabName = getResources().getStringArray(R.array.tab);
for (String s : tabName) {
MyFragment fragment = new MyFragment();
Bundle bundle = new Bundle();
bundle.putString(MyFragment.TAB, s);
fragment.setArguments(bundle);
mFragments.add(fragment);
}
MyTabAdapter adapter = new MyTabAdapter(getSupportFragmentManager(), mFragments, tabName);
mViewPager.setAdapter(adapter);
// 關(guān)聯(lián) viewPager
mTabLayout.setupWithViewPager(mViewPager);
// FIXME 與 viewPager 關(guān)聯(lián)后會為我們添加標(biāo)題,所以可以通過 getTabAt 獲取到標(biāo)題
for (int i = 0; i < tabName.length; i++) {
TabLayout.Tab tab = mTabLayout.getTabAt(i);
if (null != tab) {
tab.setCustomView(R.layout.tab_custom_view);
if (null != tab.getCustomView()) {
ImageView imageView = tab.getCustomView().findViewById(R.id.iv_tab_icon);
TextView textView = tab.getCustomView().findViewById(R.id.tv_tab_text);
textView.setText(tabName[i]);
}
}
}
MyFragment.java
public class MyFragment extends Fragment {
public static final String TAB = "TAB";
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
String text = getArguments().getString(TAB);
TextView textView = new TextView(getContext());
textView.setGravity(Gravity.CENTER);
textView.setText(text);
return textView;
}
}
MyTabAdapter.java
public class MyTabAdapter extends FragmentStatePagerAdapter {
private ArrayList<MyFragment> mFragments;
private String[] mTabName;
public MyTabAdapter(FragmentManager fm, ArrayList<MyFragment> fragments, String[] tabName) {
super(fm);
mFragments = fragments;
mTabName = tabName;
}
@Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
@Override
public int getCount() {
return mFragments.size();
}
// /**
// * FIXME 調(diào)用 setupWithViewPager 方法后會為我們設(shè)置與 getCount 等量的空白標(biāo)題纹腌,如果標(biāo)題只有文字可以在此進(jìn)行設(shè)置
// *
// * @param position
// * @return
// */
// @Override
// public CharSequence getPageTitle(int position) {
// return mTabName[position];
// }
}
從代碼可以看出霎终,先是獲取到標(biāo)簽數(shù)組,創(chuàng)建 MyFragment 并將標(biāo)簽傳入升薯;創(chuàng)建 MyTabAdapter 并設(shè)置給 ViewPage莱褒。前門這幾步都是 ViewPager 使用的正常操作。調(diào)用 TabLayout 的 setupWithViewPager()
傳入 ViewPager 與之進(jìn)行關(guān)聯(lián)涎劈,關(guān)聯(lián)之后 TabLayout 會根據(jù) MyTabAdapter 的 getCount
方法生成對應(yīng)數(shù)量的 Tab广凸,最后我們通過 getTabAt
方法獲取到對應(yīng)的 Tab,并將 Tab 設(shè)置為我們的自定義視圖蛛枚。到這里已經(jīng)實現(xiàn)了效果圖所展示的效果
PS:雖然 TabLayout 已經(jīng)為我們提供了默認(rèn)標(biāo)簽的布局谅海,可以設(shè)置圖標(biāo)和文字,同時也支持我們的自定義視圖作為標(biāo)簽蹦浦。但是為了應(yīng)對需求的多變扭吁,我們最好還是使用自定義的視圖作為標(biāo)簽布局
如何避坑
調(diào)用 setupWithViewPager()
后 Tab 的視圖不顯示
這里貼一下我第一次寫的代碼
String[] tabName = getResources().getStringArray(R.array.tab);
// 給 TabLayout 添加新 Tab
for (int i = 0; i < tabName.length; i++) {
TabLayout.Tab tab = mTabLayout.newTab();
// 使用自定義tab視圖
tab.setCustomView(R.layout.tab_custom_view);
if (null != tab.getCustomView()) {
ImageView imageView = tab.getCustomView().findViewById(R.id.iv_tab_icon);
TextView textView = tab.getCustomView().findViewById(R.id.tv_tab_text);
textView.setText(tabName[i]);
}
mTabLayout.addTab(tab);
}
for (String s : tabName) {
MyFragment fragment = new MyFragment();
Bundle bundle = new Bundle();
bundle.putString(MyFragment.TAB, s);
fragment.setArguments(bundle);
mFragments.add(fragment);
}
MyTabAdapter adapter = new MyTabAdapter(getSupportFragmentManager(), mFragments, tabName);
mViewPager.setAdapter(adapter);
// 關(guān)聯(lián) viewPager
mTabLayout.setupWithViewPager(mViewPager);
看一下上面的代碼,先是為 TabLayout 添加自定義布局的 Tab,然后再設(shè)置 ViewPager侥袜,并調(diào)用 setupWithViewPager()
進(jìn)行關(guān)聯(lián)蝌诡。看起來沒有什么問題枫吧。運行之后送漠,發(fā)現(xiàn) Tab 都是空白的。因為一開始沒有關(guān)聯(lián) ViewPager 的時候由蘑,我運行是正常的闽寡,所以問題肯定是出在 setupWithViewPager()
這個方法。下面我們看一下這個方法的源碼
public void setupWithViewPager(@Nullable ViewPager viewPager) {
setupWithViewPager(viewPager, true);
}
public void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh) {
setupWithViewPager(viewPager, autoRefresh, false);
}
private void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh,
boolean implicitSetup) {
// ...省略了一些監(jiān)聽器設(shè)置
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
1. 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);
}
mSetupViewPagerImplicitly = implicitSetup;
}
從源碼可以看出尼酿,最后都是調(diào)用到 setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh, boolean implicitSetup)
方法爷狈,重點看 setPagerAdapter(adapter, autoRefresh);
方法,繼續(xù)進(jìn)入到該方法
void setPagerAdapter(@Nullable final PagerAdapter adapter, final boolean addObserver) {
if (mPagerAdapter != null && mPagerAdapterObserver != null) {
// If we already have a PagerAdapter, unregister our observer
mPagerAdapter.unregisterDataSetObserver(mPagerAdapterObserver);
}
mPagerAdapter = adapter;
if (addObserver && adapter != null) {
// Register our observer on the new adapter
if (mPagerAdapterObserver == null) {
mPagerAdapterObserver = new PagerAdapterObserver();
}
adapter.registerDataSetObserver(mPagerAdapterObserver);
}
// Finally make sure we reflect the new adapter
2. populateFromPagerAdapter();
}
void populateFromPagerAdapter() {
3. removeAllTabs();
if (mPagerAdapter != null) {
final int adapterCount = mPagerAdapter.getCount();
for (int i = 0; i < adapterCount; i++) {
4. addTab(newTab().setText(mPagerAdapter.getPageTitle(i)), false);
}
// Make sure we reflect the currently set ViewPager item
if (mViewPager != null && adapterCount > 0) {
final int curItem = mViewPager.getCurrentItem();
if (curItem != getSelectedTabPosition() && curItem < getTabCount()) {
selectTab(getTabAt(curItem));
}
}![TabLayout.gif](http://upload-images.jianshu.io/upload_images/1131117-d9a80453a1363fbd.gif?imageMogr2/auto-orient/strip)
}
}
最后調(diào)用到了 populateFromPagerAdapter()
方法裳擎,到了這里基本找到問題了涎永,populateFromPagerAdapter
第一行代碼 removeAllTabs()
移除了所有的 Tab 后又根據(jù) mPagerAdapter.getCount()
的數(shù)量添加新的 Tab,并為新的 Tab 設(shè)置了文本鹿响,文本內(nèi)容則是通過 mPagerAdapter.getPageTitle(int)
獲取羡微。分析到處,我們的問題很容易就可以解決了惶我。正確寫法就是在簡單使用的第三點所貼的代碼
關(guān)鍵知識點總結(jié)
- 如果要使 Tab 可橫向滑動妈倔,需在 TabLayout 的 app:tabMode 屬性設(shè)置為 scrollable
- 如果不需要 Tab 的指示器的時候,有兩種方法:方法一绸贡、設(shè)置
app:tabIndicatorColor="@android:color/transparent"
盯蝴,方法二、設(shè)置app:tabIndicatorHeight="0dp"
- 系統(tǒng)默認(rèn)的 Tab 支持可設(shè)置圖片(上)和文字(下)听怕,如果需要可定制的 Tab 可以使用
setCustomView()
設(shè)置自定義視圖 - 自定義 Tab 支持使用 selector捧挺,采用 selector 方式更方便的設(shè)置選中和未選中時的狀態(tài),而無需在代碼進(jìn)行狀態(tài)切換
- 與 ViewPager 關(guān)聯(lián)時尿瞭,如果 Tab 只有本文闽烙,則可以重寫 Adapter 的
getPageTitle()
方法即可 - 與 ViewPager 關(guān)聯(lián)時,如果需要自定義 Tab声搁,則通過調(diào)用
setupWithViewPager()
方法后再通過 TabLayout 的getTabAt(int)
方法獲取到對應(yīng)的 Tab 進(jìn)行相關(guān)設(shè)置即可
結(jié)語
本文主要介紹了 TabLayout 的使用以及如何關(guān)聯(lián) ViewPager黑竞。該控件實際開發(fā)也非常實用,目前市場上的很多新聞類客戶端等都有此效果酥艳。本文 demo 已上傳到 github
以下是官方API地址(自備梯子)