介紹
TabLayout是support.design包中提供的一個控制元件,如果要使用需要在app下的build.gradle中加入依賴:
dependencies {
compile 'com.android.support:design:25.+'
}
複製程式碼
後面的版本根據自己的專案需要而定。
使用場景
TabLayout從命名上來看就跟Tab有關,所以它是用來做Tab選項卡的。先上一波圖:
掘金APP
360手機助手 慕課網 等等很多APP都使用了TabLayout這個控制元件來做Tab選項卡,而且通常都是配合ViewPager來使用,當然它也可以單獨作為Tab來使用。用法
在佈局檔案中加入:
<android.support.design.widget.TabLayout
android:id="@+id/TabLayout"
android:layout_width="match_parent"
android:layout_height="35dp" />
複製程式碼
給TabLayout新增Tab,有兩種方式:
- 在佈局檔案中新增
<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="35dp">
<android.support.design.widget.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="C" />
<android.support.design.widget.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="C++" />
<android.support.design.widget.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Java" />
<android.support.design.widget.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Kotlin" />
</android.support.design.widget.TabLayout>
複製程式碼
- 在Java程式碼中動態新增
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabLayout);
ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
tabLayout.addTab(tabLayout.newTab().setText("C"));
tabLayout.addTab(tabLayout.newTab().setText("C++"));
tabLayout.addTab(tabLayout.newTab().setText("Java"));
tabLayout.addTab(tabLayout.newTab().setText("Kotlin"));
複製程式碼
執行效果:
- Tab文字英文變成大寫問題
不知道大家發現沒有,我們新增的英文Java和Kotlin怎麼變成大寫了?解決方案如下:
<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="35dp"
app:tabTextAppearance="@android:style/TextAppearance.Widget.TabWidget"/>
複製程式碼
重新設定TabLayout的tabTextApperance屬性給它指定文字顯示樣式。重新執行就不會出現英文變成大寫:
造成這個原因主要是因為"textAllCaps"=true這個屬性,TabLayout給tabTextAppearance設定了預設的樣式: 引用了下面的這個樣式:- 修改Tab文字樣式
<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="35dp"
app:tabSelectedTextColor="#000070"
app:tabTextAppearance="@android:style/TextAppearance.Widget.TabWidget"
app:tabTextColor="#000000"/>
複製程式碼
app:tabTextColor是Tab文字正常顯示顏色,app:tabSelectedTextColor是Tab選中後文字顯示顏色,app:tabTextAppearance是Tab文字顯示外觀樣式,可以給它設定更多的text相關屬性。
- 自定義Tab顯示樣式
TabLayout的Tab不僅僅只能顯示文字還可以顯示圖示等,完全自定義佈局也是可以的。 還是360手機助手的【熱點】模組,可以看到上面的Tab選項卡"房產"那一個欄右邊有個小紅點,那這個怎麼做呢?TabLayout是否能適應我們的需求?先上個圖:
先要自定義一個佈局,用來替代TabLayout預設的Tab:<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center"
android:maxLines="2"
tools:text="aa" />
<View
android:id="@+id/dot"
android:layout_width="3dp"
android:layout_height="3dp"
android:layout_gravity="top"
android:background="@drawable/tab_dot" />
</LinearLayout>
複製程式碼
小紅點樣式:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/colorAccent" />
<corners android:radius="3dp" />
</shape>
複製程式碼
然後在Java程式碼中改動TabLayout新增Tab的方式:
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabLayout);
ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
tabLayout.addTab(tabLayout.newTab().setText("C"));
tabLayout.addTab(tabLayout.newTab().setText("C++"));
tabLayout.addTab(tabLayout.newTab().setText("Java"));
TabLayout.Tab tab = tabLayout.newTab().setCustomView(R.layout.tab_custom);
final View customTabView = tab.getCustomView();
final TextView tabText = (TextView) customTabView.findViewById(R.id.text);
tabText.setText("Kotlin");
tabLayout.addTab(tab);
複製程式碼
執行結果:
可以看到Tab上已經顯示了小紅點,但是有個問題不知道大家注意到了沒有?Kotlin的文字顏色好像跟前面幾個不一樣,而且Tab選中後的文字顏色也不一樣。其實也很簡單,估計你們都想到了,沒錯,就是給自定義的Tab佈局中的TextView的textColor設定selector:<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#000070" android:state_selected="true" />
<item android:color="#000000" />
</selector>
複製程式碼
為了保持TabLayout整體Tab效果一致,我們需要參照定義TabLayout佈局中的app:tabSelectedTextColor和 app:tabTextColor設定的顏色值。再次執行效果就都一樣了:
Kotlin未選中時效果
Kotlin選中時效果 我在想有沒有更高階的或更簡單點的方法呢?外部只需要通過:tabLayout.addTab(tabLayout.newTab().setText("Kotlin"));
複製程式碼
就行了,至於Tab文字選中和未選中文字顏色和TabLayout保持一致就行了,上面那樣做好麻煩啊!我想應該是有的。當然這種情況只適用像這種顯示個小紅點什麼的,如果小紅點裡還要顯示數字,那你還是老老實實的按照上面這種方法去寫吧!
- 監聽TabLayout切換事件
小紅點操作升級!我觀察了一下360手機助手的【熱點】模組,當切換到"房產"這一欄時,小紅點會顯示。那這個又該怎麼做呢?這個時候我們需要監聽TabLayout的滑動切換事件。TabLayout提供了一個addOnTabSelectedListener方法用來監聽Tab選中事件:
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
final View customTabView = tab.getCustomView();
if (customTabView != null) {
final View dotView = customTabView.findViewById(R.id.dot);
dotView.setVisibility(View.INVISIBLE);
}
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
複製程式碼
注意:在onTabSelected回撥方法中tab.getCustomView需要非空判斷,因為我們只給Kotlin這個Tab設定了自定義佈局。 如果我想Kotlin欄未選中時又顯示小紅點呢?簡單,在onTabUnselected回撥方法中給它設為可見就行了:
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
final View customTabView = tab.getCustomView();
if (customTabView != null) {
final View dotView = customTabView.findViewById(R.id.dot);
dotView.setVisibility(View.INVISIBLE);
}
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
final View customTabView = tab.getCustomView();
if (customTabView != null) {
final View dotView = customTabView.findViewById(R.id.dot);
dotView.setVisibility(View.VISIBLE);
}
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
複製程式碼
案例
1、使用TabLayout做引導頁指示器
上效果圖:
底部的指示器不是用的第三方的庫,也不是自己擺的幾個View,而是用TabLayout實現!按照上面的講解,這種Tab效果系統沒有,所以我們要自定義Tab佈局來替換預設的Tab。自定義Tab佈局R.layout.tab_custom_dot:<?xml version="1.0" encoding="utf-8"?>
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dot"
android:layout_width="8dp"
android:layout_height="8dp"
android:background="@drawable/selector_tab_dot" />
複製程式碼
Tab選中和未選中背景樣式:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/tab_dot_selected" android:state_selected="true" />
<item android:drawable="@drawable/tab_dot_normal" />
</selector>
複製程式碼
R.drawable.tab_dot_selected:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#efef00" />
<corners android:radius="8dp" />
</shape>
複製程式碼
R.drawable.tab_dot_normal:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#efefef" />
<corners android:radius="8dp" />
</shape>
複製程式碼
Java程式碼:
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabLayout);
tabLayout.addTab(tabLayout.newTab().setCustomView(R.layout.tab_custom_dot));
tabLayout.addTab(tabLayout.newTab().setCustomView(R.layout.tab_custom_dot));
tabLayout.addTab(tabLayout.newTab().setCustomView(R.layout.tab_custom_dot));
tabLayout.addTab(tabLayout.newTab().setCustomView(R.layout.tab_custom_dot));
tabLayout.addTab(tabLayout.newTab().setCustomView(R.layout.tab_custom_dot));
複製程式碼
佈局檔案:
<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="20dp"
app:tabIndicatorHeight="0dp"
app:tabGravity="center"
app:tabMode="fixed" />
複製程式碼
這裡要注意的是,需要加上app:tabIndicatorHeight="0dp"表示隱藏Tab指示器,預設的TabLayout會在底部顯示指示條,或者加上app:tabIndicatorColor="@android:color/transparent"給它顏色設定為透明。不然是整個樣子的:
還有要加上app:tabGravity="center"讓它水平居中顯示,否則Tab會填充整個螢幕。2、配合ViewPager一起滑動切換page並指示
TabLayou還有一個比較強大的地方就是可以配合ViewPager這個控制元件一起使用,可以隨著ViewPager的滑動一起滑動切換Tab,並且點選TabLayout的Tab也可以讓ViewPager切換到指定的page,主要是因為TabLayout有一個setupWithViewPager方法可以關聯ViewPager。不多說上程式碼,activity佈局:
<?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/tabLayout"
android:layout_width="match_parent"
android:layout_height="35dp"
app:tabIndicatorColor="@color/colorPrimary"
app:tabMode="fixed"
app:tabSelectedTextColor="#000070"
app:tabTextAppearance="@android:style/TextAppearance.Widget.TabWidget"
app:tabTextColor="#000000" />
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
複製程式碼
Java程式碼:
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabLayout);
ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
tabLayout.addTab(tabLayout.newTab().setText("C"));
tabLayout.addTab(tabLayout.newTab().setText("C++"));
tabLayout.addTab(tabLayout.newTab().setText("Java"));
tabLayout.addTab(tabLayout.newTab().setText("Swift"));
tabLayout.addTab(tabLayout.newTab().setText("C#"));
tabLayout.addTab(tabLayout.newTab().setText("Kotlin"));
TestViewPagerAdapter adapter = new TestViewPagerAdapter(this, tabs);
viewPager.setAdapter(adapter);
tabLayout.setupWithViewPager(viewPager);
複製程式碼
自定義ViewPager的PagerAdapter:
private class TestViewPagerAdapter extends PagerAdapter {
private final LayoutInflater layoutInflater;
TestViewPagerAdapter(Context context) {
layoutInflater = LayoutInflater.from(context);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
final View view = layoutInflater.inflate(R.layout.tab_1, container, false);
container.addView(view);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public int getCount() {
return 6;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
複製程式碼
然後執行:
WTF??Tab一片白??正確姿勢:private class TestViewPagerAdapter extends PagerAdapter {
private final LayoutInflater layoutInflater;
private List<String> data;
TestViewPagerAdapter(Context context, List<String> data) {
layoutInflater = LayoutInflater.from(context);
this.data = data;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
final View view = layoutInflater.inflate(R.layout.tab_1, container, false);
container.addView(view);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public int getCount() {
return data.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public CharSequence getPageTitle(int position) {
return data.get(position);
}
}
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabLayout);
ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
List<String> tabs = new ArrayList<>();
tabs.add("C");
tabs.add("C++");
tabs.add("Java");
tabs.add("Swift");
tabs.add("C#");
tabs.add("Kotlin");
TestViewPagerAdapter adapter = new TestViewPagerAdapter(this, tabs);
viewPager.setAdapter(adapter);
tabLayout.setupWithViewPager(viewPager);
複製程式碼
再次執行,就正常顯示:
PagerAdapter有一個getPageTitle方法,下面是它的定義:/**
* This method may be called by the ViewPager to obtain a title string
* to describe the specified page. This method may return null
* indicating no title for this page. The default implementation returns
* null.
*
* @param position The position of the title requested
* @return A title for the requested page
*/
public CharSequence getPageTitle(int position) {
return null;
}
複製程式碼
看到這裡我們不禁有個疑問,為什麼覆蓋PagerAdapter的getPageTitle方法返回指定的Page標題就可以顯示在TabLayout的Tab上呢?想要知道答案,我們得從原始碼入手,從TabLayout關聯ViewPager的setupWithViewPager方法開始,我在下面找到了核心程式碼:
private void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh,
boolean implicitSetup) {
// 省略部分程式碼
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);
}
// 省略部分程式碼
}
複製程式碼
setPagerAdapter方法邏輯:
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
populateFromPagerAdapter();
}
複製程式碼
最終的邏輯是在內部方法populateFromPagerAdapter方法中將PagerAdapter的getPageTitle返回的標題新增到TabLayout中:
void populateFromPagerAdapter() {
removeAllTabs();
if (mPagerAdapter != null) {
final int adapterCount = mPagerAdapter.getCount();
for (int i = 0; i < adapterCount; i++) {
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));
}
}
}
}
複製程式碼