一、簡述
TabLayout是Android Support Design庫的新控件,可以用來實現開源框架ViewPageIndicator的效果(在MaterialDesign沒出來之前基本都用這玩意兒吧~),TabLayout相比它使用上更加簡單,且不一定要跟ViewPager一起使用,畢竟谷歌做出來的,穩定性更是不用說啦,此外,本文還會仔細列出本人對該控件的探索過程,從而實現一些控件本身沒法實現的自定義效果,下面來看看它都有哪些操作吧。
二、使用
1、創建Tab及Tab的點擊事件
要使用TabLayout,一般會先在布局文件中放好,如:
<?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="wrap_content" />
</LinearLayout>
然后在Activity中找到它,對它進行設置,如果不跟ViewPager一起使用的話,可以對TabLayout手動添加多個tab,并設置其點擊事件,如:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tab_layout);
mTabLayout = (TabLayout) findViewById(R.id.tabLayout);
// 添加多個tab
for (int i = 0; i < title.length; i++) {
TabLayout.Tab tab = mTabLayout.newTab();
tab.setText(title[i]);
// tab.setIcon(R.mipmap.ic_launcher);//icon會顯示在文字上面
mTabLayout.addTab(tab);
}
// 給tab設置點擊事件
mTabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
Toast.makeText(getApplicationContext(), title[tab.getPosition()], Toast.LENGTH_SHORT).show();
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
這里比較有意思的是Tab的創建需要調用TabLayout對象的newTab()方法,而不是直接new出一個Tab。Tab除了可以設置文字外,還能設置Icon,甚至可以自定義View,分別調用的是setIcon()和setCustomView(),有興趣的可以試試看,上面代碼效果如下:
2、自定義TabLayout樣式
這個TabLayout還是挺好看的,但開發中難免會要定制TabLayout的樣式,如設置默認或選中文字的顏色和大小等,還好,TabLayout盡可能多的提供了這些自定義屬性,可以讓開發者很方便的修改樣式,下面來看看都有哪些控件屬性可以設置:
<!--設置Tab指示器-->
app:tabIndicatorColor=""
app:tabIndicatorHeight=""
<!--設置Tab位置及顯示模式-->
app:tabGravity=""
app:tabMode=""
<!--設置Tab文字樣式-->
app:tabSelectedTextColor=""
app:tabTextAppearance=""
app:tabTextColor=""
<!--設置Tab的寬度、背景、內間距-->
app:tabMaxWidth=""
app:tabMinWidth=""
app:tabBackground=""
app:tabPadding=""
- 設置Tab指示器
TabLayout的指示器默認顏色是color.xml中的colorAccent,通過TabLayout提供的自定義屬性,可以設置指示器的高度和顏色,如果不想顯示指示器(Indicator),可以將其高度設置為0dp或設置其顏色為透明,這里為演示,我就把顯示指示器(Indicator)的高度提高,顏色改為刺眼的紅眼,如下:
<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabIndicatorColor="@color/red"
app:tabIndicatorHeight="8dp"/>
設置Tab指示器樣式
2. 設置Tab位置及顯示模式
TabLayout的顯示模式(tabMode)默認是固定不可滾動(fixed),位置(tabGravity)默認填滿(fill)整個TabLayout,我們先保持app:tabMode="fixed",把tabGravity的值換成fill和center對比下,為了方便對比,我把背景也設置了。
<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabBackground="@color/colorPrimaryDark"
app:tabGravity="fill" // 再換成center
app:tabMode="fixed"/>
當tab比較多的時候,一個屏幕寬度容納不下,這時候就需要讓TabLayout可以橫向滾動了,只需要修改app:tabMode="scrollable"即可。注意,當app:tabMode="scrollable"時,app:tabGravity=""不管取什么值都不會生效。
<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabBackground="@color/colorPrimaryDark"
app:tabGravity="center"
app:tabMode="scrollable"/>
3. 設置Tab文字樣式
上面的效果不好看,我想讓它默認文字顏色為灰色,選中時文字為白色,文字大小為16sp,但TabLayout沒有提供直接設置文字大小的屬性,這時候就需要用到app:tabTextAppearance=""了。操作如下:
在Style.xml中聲明文字樣式
<style name="TabLayout.TabText" parent="TextAppearance.Design.Tab">
<item name="android:textSize">16sp</item>
<item name="textAllCaps">false</item>
</style>
其中除了可以設置字體大小外,還可以設置英文是否都全部大寫顯示。textAllCaps的默認值為true,即英文全部大寫。
在布局文件中設置TabLayout的文字相關屬性
<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabBackground="@color/colorPrimaryDark"
app:tabGravity="center"
app:tabMode="scrollable"
...
app:tabSelectedTextColor="@android:color/white"
app:tabTextAppearance="@style/TabLayout.TabText"
app:tabTextColor="@android:color/darker_gray"/>
好了,看看效果如何:
好了,關于Tab的寬度、內間距等設置比較簡單,自己需要的時候試試吧,這里就不演示了。
3、與ViewPager結合
上面通過對TabLayout的單獨使用學習了TabLayout的樣式自定義、創建Tab及設置Tab的點擊事件等,可以說常用的也就那些了,下面來看看TabLayout如何與ViewPager的結合使用。這種需求也是很常見的,界面頂部有一個標簽欄,中下部是與標簽對應的內容,可以左右滑動,同時標簽也跟隨其切換,相反的,在切換標簽時,內容部分也會跟著變化,不太明白的可以參考下“今日頭條”APP的首頁界面。這樣的效果就可以用TabLayout+ViewPager+Fragment來實現。
1. 先在布局文件中放好TabLayout和ViewPager:
<?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="wrap_content"
app:tabBackground="@color/colorPrimaryDark"
app:tabMode="scrollable"
app:tabSelectedTextColor="@android:color/white"
app:tabTextColor="@android:color/darker_gray"/>
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
2. 在代碼中設置TabLayout與ViewPager相互關聯:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tab_layout);
mTabLayout = (TabLayout) findViewById(R.id.tabLayout);
mViewPager = (ViewPager) findViewById(R.id.viewPager);
MyViewPagerAdapter adapter = new MyViewPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(adapter);
// 適配器必須重寫getPageTitle()方法
mTabLayout.setTabsFromPagerAdapter(adapter);
// 監聽TabLayout的標簽選擇,當標簽選中時ViewPager切換
mTabLayout.setOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(mViewPager));
// 監聽ViewPager的頁面切換,當頁面切換時TabLayout的標簽跟著切換
mViewPager.setOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout));
}
這三句代碼不難理解,就字面上的意思,但是這三句代碼都已經過時,因為要關聯TabLayout與ViewPager就得寫三句代碼似乎是麻煩了一點點(其實我覺得還好吧),所以TabLayout提供了可以通過一句代碼搞定兩者關聯的方法:setupWithViewPager(),因此,上面的代碼可以簡化如下:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tab_layout);
mTabLayout = (TabLayout) findViewById(R.id.tabLayout);
mViewPager = (ViewPager) findViewById(R.id.viewPager);
MyViewPagerAdapter adapter = new MyViewPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(adapter);
// 關聯TabLayout與ViewPager,且適配器必須重寫getPageTitle()方法
mTabLayout.setupWithViewPager(mViewPager);
}
來看下效果:
關聯TabLayout與ViewPager相當簡單,只要注意ViewPager適配器需重寫getPageTitle()方法,這里順便貼出Demo中適配器的代碼:
class MyViewPagerAdapter extends FragmentPagerAdapter {
private final String[] title = new String[]{
"推薦", "熱點", "視頻", "深圳", "通信",
"互聯網", "問答", "圖片", "電影",
"網絡安全", "軟件"};
public MyViewPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int i) {
Fragment fragment = new TextFragment();
Bundle bundle = new Bundle();
bundle.putString("title", title[i]);
fragment.setArguments(bundle);
return fragment;
}
@Override
public int getCount() {
return title.length;
}
@Override
public CharSequence getPageTitle(int position) {
return title[position];
}
}
拓展
上面部分是TabLayout的正規使用說明,而這部分是對TabLayout的進一步探索,同時將列出本人在這個過程中的探索思路,可以說是對TabLayout的進一步自定義吧。廢話不多說,下面就直接開車了。
假如,你手中的APP設計稿中有如下的三個需求那該怎么辦:
- 為TabLayout添加分割線,且分割線距離上下存在間距。
- 選中時tab字體變大,未選中時tab字體變小。
- 指示器(Indicator)不要充滿整個標簽(Tab)
簡單的說就是為TabLayout添加分割線、設置不同狀態下的字體大小和指示器的“長度”,這些在TabLayout中并沒有提供直接的修改方法,你可能會想,那我們對TabLayout進行源碼分析,然后通過反射等手段拿到其中的控件來設置?不!有時候解決問題不要循規蹈矩,應該適當轉變下思路,或許解決問題的方法并不需要去看源碼那么困難(如果你是大神,就當我沒說),下面看我操作:
從結構上我們可以知道TabLayout(就是HorizontalScrollView)并不是直接就包裹這些Tab的,而是包裹了一個LinearLayout,然后這些Tab放在這個LinearLayout中,此外,可以發現Tab里包含了一個TextView,到這里對前面2個需求是不是有點想法了呢?
為TabLayout添加分割線
LinearLayout自帶就有設置分割線的方法,我們可以通過它來添加分割線,也沒什么好說的,直接上代碼:
mLinearLayout = (LinearLayout) mTabLayout.getChildAt(0);
// 在所有子控件的中間顯示分割線(還可能只顯示頂部、尾部和不顯示分割線)
mLinearLayout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
// 設置分割線的距離本身(LinearLayout)的內間距
mLinearLayout.setDividerPadding(20);
// 設置分割線的樣式
mLinearLayout.setDividerDrawable(ContextCompat.getDrawable(this, R.drawable.divider_vertical));
divider_vertical.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#ccc"/>
<size android:width="1dp" />
</shape>
這樣,分割線就有了。
看起來有點怪是吧,這是因為我們前面設置的app:tabBackground="@color/colorPrimaryDark"只是給Tab設置了背景色,而不是給Tab的父級控件LinearLayout設置,這個LinearLayout默認的背景色是白色,所以才會是這個樣子,解決方法自然就是給LinearLayout設置跟Tab一樣的背景色就好了。
mLinearLayout = (LinearLayout) mTabLayout.getChildAt(0);
...
mLinearLayout.setBackgroundColor(getResources().getColor(R.color.colorPrimaryDark));
這樣就完美的為TabLayout設置分割線了。
為TabLayout設置不同狀態下的字體大小(并不能成功)
用同樣的方式拿到Tab中的文本控件,判斷當前是否被選中,再對該文本控件進行字體大小設置就歐了。借助上面拿到的用來包裹Tab的LinearLayout(mLinearLayout),遍歷LinearLayout中的子控件,拿到一個個的子view(即Tab),再從Tab中拿到文本控件設置文字大小。
// 默認讓所有沒有選中的Tab的文字設置為小字體
for (int i = 0; i < mTabLayout.getTabCount(); i++) {
((TextView) ((LinearLayout) mLinearLayout.getChildAt(i)).getChildAt(1)).setTextSize(10);
// 也可以這么寫,一樣的
// ((TextView) ((LinearLayout) ((LinearLayout) mTabLayout.getChildAt(0)).getChildAt(i)).getChildAt(0)).setTextSize(12);
}
// 再把當前被選中的Tab文字設置為大字體
((TextView) ((LinearLayout) mLinearLayout.getChildAt(mTabLayout.getSelectedTabPosition())).getChildAt(1)).setTextSize(30);
// 當選中的Tab切換時,再調整Tab的字體大小
mTabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
((TextView) ((LinearLayout) mLinearLayout.getChildAt(tab.getPosition())).getChildAt(1)).setTextSize(30);
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
((TextView) ((LinearLayout) mLinearLayout.getChildAt(tab.getPosition())).getChildAt(1)).setTextSize(12);
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
上面代碼中把得到的Tab強轉成LinearLayout,這是因為Tab實際上是TabView,而TabView繼承自LinearLayout,所以可以這樣轉換,我們可以看下TabLayout的newTab()方法:
然而事實并不如意,完全沒有效果,去看了下源碼,也不是很確定,我的猜想是這樣的,當我們對Tab中的文本控件設置字體大小后,TabView的onMeasuer()方法會被重新調用,而這個方法里就對文字大小重新進行賦值,導致文字大小沒法按上面的方式進行修改。
所以,文字的大小只能通過Style的方法去修改,且只能統一設置選中和未選中的文字大小,故,這個需求沒法完成。
自定義指示器長度
其實這有點標題黨的意思了,TabLayout的指示器長度沒法指定,它原本多長就是多長,但可以通過設置Tab外間距的方式,讓指示器看起來像是與Tab保持一定距離,這里我在網上找到了方法,方法如下:
// 設置TabLayout的“長度”
setIndicator(mTabLayout,10,10);
// 具體方法(通過反射的方式)
public void setIndicator(TabLayout tabs, int leftDip, int rightDip) {
Class<?> tabLayout = tabs.getClass();
Field tabStrip = null;
try {
tabStrip = tabLayout.getDeclaredField("mTabStrip");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
tabStrip.setAccessible(true);
LinearLayout llTab = null;
try {
llTab = (LinearLayout) tabStrip.get(tabs);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
int left = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, leftDip, Resources.getSystem().getDisplayMetrics());
int right = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, rightDip, Resources.getSystem().getDisplayMetrics());
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 = left;
params.rightMargin = right;
child.setLayoutParams(params);
child.invalidate();
}
}
這個setIndicator()方法主要是通過反射的方式,先拿到mTabStrip(其實就是TabLayout直接包裹的LinearLayout),再遍歷出mTabStrip中的子控件Tab,再設置Tab的外間距,為了證明就是設置了Tab的外間距,這里我分別對mTabStrip設置了背景色和不設置其背景色,來看看對比:
設置了背景色看起來效果還馬馬虎虎吧,但這樣的方式沒辦法讓指示器的長度比文字長度短(無奈~)。好了,不管這個了,既然我前面說了mTabStrip其實就是TabLayout直接包裹的LinearLayout,那通過這個LinearLayout來設置也是可以的,證明一下:
// 得到TabLayout包裹的LinearLayout并設置背景色
mLinearLayout = (LinearLayout) mTabLayout.getChildAt(0);
mLinearLayout.setBackgroundColor(getResources().getColor(R.color.colorPrimaryDark));
...
// 設置LinearLayout中子View(Tab)的外間距
int left = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, Resources.getSystem().getDisplayMetrics());
int right = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, Resources.getSystem().getDisplayMetrics());
for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
View tabView = mLinearLayout.getChildAt(0);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
params.leftMargin = left;
params.rightMargin = right;
tabView.setLayoutParams(params);
}
最后附上Demo鏈接
https://github.com/GitLqr/MaterialDesignDemo
作者:CSDN_LQR
鏈接:http://www.lxweimin.com/p/bbefb97cccdd