一、簡述
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=""複製程式碼
1)設定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"/>複製程式碼
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進行原始碼分析,然後通過反射等手段拿到其中的控制元件來設定?不!有時候解決問題不要循規蹈矩,應該適當轉變下思路,或許解決問題的方法並不需要去看原始碼那麼困難(如果你是大神,就當我沒說),下面看我操作:
1、分析TabLayout的結構
將APP執行起來,然後回到AS,在選單欄中依次找到Tools-->Android-->Android Device Monitor,用過Eclipse開發的Android程式設計師應該都知道這久違的老夥計———Android 裝置監測儀。
選中正在執行的APP,點選Dump View Hierarchy for UI Automator。
可能會卡一下,然後它會自動把當前介面的控制元件結構展示出來。
從這個結構上我們可以知道TabLayout(就是HorizontalScrollView)並不是直接就包裹這些Tab的,而是包裹了一個LinearLayout,然後這些Tab放在這個LinearLayout中,此外,可以發現Tab裡包含了一個TextView,到這裡對前面2個需求是不是有點想法了呢?
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設定分割線了。
3、為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的方法去修改,且只能統一設定選中和未選中的文字大小,故,這個需求沒法完成。
4、自定義指示器長度
其實這有點標題黨的意思了,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);
}複製程式碼
好,到這裡我對TabLayout的探索之旅就結束了,本文通過直接查詢TabLayout中控制元件的方式,來自定義TabLayout本身沒法直接設定的樣式效果,從而來滿足我們專案的需求。這僅僅是我個人對TabLayout的理解,可能存在些瑕疵,請多包涵,如果對“為TabLayout設定不同狀態下的字型大小”和“自定義指示器長度”其他確實可行的方法,請留言告訴我一下吧,多多指教,謝謝。