TabLayout仍舊是移動端比較經常使用的一個控件,這裏分析一下TabLayout,分別從下面幾個方面進行解析:html
先看下支持庫指南。以前的TabLayout是在support中使用,新的引用所有放到老AndroidX中java
使用老的庫須要用android
implementation 'com.android.support:design:28.0.0'複製代碼
Android 支持庫的最新版本是28.0.0,這是最後一個google發佈的支持庫版本,如今google已將全部support包下的庫都遷移至androidx包下面,之後的更新都只會在androidx包中進行。具體對照表點擊進入遷移說明。git
這些變更是因爲android的jetpack項目,意在幫助開發者快速實現應用開發,將一些經常使用的框架都整合進來了。github
官網詳細介紹。segmentfault
使用新的庫須要引用:緩存
implementation 'androidx.appcompat:appcompat:1.0.2'複製代碼
TabLayout類的繼承關係:bash
java.lang.Objectapp |
|||||
---|---|---|---|---|---|
↳框架 |
android.view.View |
||||
|
↳ |
android.view.ViewGroup |
|||
|
|
↳ |
android.widget.FrameLayout |
||
|
|
|
↳ |
android.widget.HorizontalScrollView |
|
|
|
|
|
↳ |
com.google.android.material.tabs.TabLayout |
TabLayout繼承自 HorizontalScrollView
TabLayout提供了用於顯示選項卡的水平佈局。要顯示的選項卡的填充是經過TabLayout.Tab
實例完成的。能夠經過建立標籤 newTab()
。在此處,您能夠分別經過setText(int)
和更改選項卡的標籤或圖標setIcon(int)
。要顯示選項卡,須要經過一種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」));複製代碼
應該添加一個監聽器,addOnTabSelectedListener(OnTabSelectedListener)
以在任何選項卡的選擇狀態更改時獲得通知。
還能夠經過使用將項目添加到佈局中的TabLayout TabItem
。用法示例以下:
<com.google.android.material.tabs.TabLayout
android:layout_height =「 wrap_content」
android:layout_width =「 match_parent」>
<com.google.android.material.tabs.TabItem
android:text =「 @ string / tab_text」 />
<com.google.android.material.tabs.TabItem
android:icon =「 @ drawable / ic_android」 />
</com.google.android.material.tabs.TabLayout>複製代碼
若是ViewPager
將此佈局與一塊兒使用,則能夠調用setupWithViewPager(ViewPager)
將二者連接在一塊兒。該版式將從PagerAdapter
的頁面標題中自動填充。
此視圖還支持用做ViewPager裝飾的一部分,而且能夠像這樣在佈局資源文件中直接添加到ViewPager:
<androidx.viewpager.widget.ViewPager
android:layout_width =「 match_parent」
android:layout_height =「 match_parent」>
<com.google.android.material.tabs.TabLayout
android:layout_width =「 match_parent」
android:layout_height =「 wrap_content」
android:layout_gravity =「 top」 />
</androidx.viewpager.widget.ViewPager>複製代碼
這裏不少人都使用的都是design 28,主工程的gradle的配置根據不一樣狀況改。
上面三種使用方法,咱們使用新的庫androidx看下使用的效果圖:
這裏列舉一下主要使用到到屬性,只列舉幾個,具體可看官方文檔。
屬性 |
值 |
說明 |
---|---|---|
tabMode |
scrollable/fixed |
tab是水平可滾動的仍是固定寬,個數較少的時候可使用fixed,若是標籤超出了屏幕範圍,設置爲scrollable比較好 |
tabGravity |
fill/center |
tab的佈局是佈滿空間仍是居中 |
tabIndicatorHeight |
(dp/pix) |
底部滑動線條的高度 |
tabMaxWidth |
(dp/pix) |
Tab的最大寬度 |
tabTextColor |
顏色值 |
默認樣式未選中顏色 |
app:tabSelectedTextColor |
顏色值 |
選中顏色 |
//TabLayout的基本使用
for(int i=0;i<mTitles.length;i++){
TabLayout.Tab tab=mTabLayout.newTab();
tab.setTag(i);
tab.setText(mTitles[i]);
mTabLayout.addTab(tab);
}複製代碼
屬性設置
app:tabIndicatorHeight="0dp" 複製代碼
有時候想指示器的寬度小一些,能夠參考文章Tablayout使用全解,一篇就夠了 修改指示線長度(利用的反射,感受不如本身基於源碼封裝一個,能夠自定義長度)。
TabItem有個上下結構的默認佈局,這裏使用默認的
tabLayout1.addTab(tabLayout1.newTab().setText("Tab 4").setIcon(R.mipmap.ic_launcher));複製代碼
設置Tab內部的子控件的Padding:
app:tabPadding="xxdp"
app:tabPaddingTop="xxdp"
app:tabPaddingStart="xxdp"
app:tabPaddingEnd="xxdp"
app:tabPaddingBottom="xxdp"複製代碼
設置整個TabLayout的Padding:
app:paddingEnd="xxdp"
app:paddingStart="xxdp"複製代碼
設置最大的tab寬度:
app:tabMaxWidth="xxdp"複製代碼
設置最小的tab寬度:
app:tabMinWidth="xxdp"複製代碼
TabLayout開始位置的偏移量:
app:tabContentStart="50dp"複製代碼
若是你要設置默認選中第三項,你能夠這樣:
mTabLayout.getTabAt(2).select();複製代碼
初始化進入的時候,監聽事件的三個方法都不會執行
tabLayout1.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
//選中了tab的邏輯
Log.i(TAG, "======我選中了===="+tab.getTag());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
//未選中tab的邏輯
Log.i(TAG, "======我未被選中===="+tab.getTag());
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
//再次選中tab的邏輯
Log.i(TAG, "======我再次被選中===="+tab.getTag());
}
});複製代碼
onTabReselected爲已經選中的tab再次點擊會走到。
mTabLayout.getTabAt(position).isSelected()複製代碼
有時候要監聽某個Tab的點擊事件,能夠參考TabLayout基本屬性全解 或者 tablayout增長選擇tab 的事件和重寫tab點擊事件
這裏有兩種方式添加TabItem的自定義佈局,其一種方式是在TabItem的xml中定義
<com.google.android.material.tabs.TabItem
android:layout_width="match_parent"
android:layout_height="match_parent"
android:icon="@drawable/two"
android:layout="@layout/custom_indicator1"
android:text="娛樂" />複製代碼
custom_indicator1.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
/>
<ImageView
android:id="@android:id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
/>
</LinearLayout>複製代碼
custom_indicator1.xml文件內容,值得注意的是這裏的TextView的id必須是「@android:id/text1」,ImageView的id必須是「@android:id/icon」,緣由來自於與TabLayout的源碼中TabView的update方法。
這種方式只能事先肯定有幾個Tab的時候用到,當這個Tab個數須要動態的建立的時候不能使用此方法。
另一種方式經過代碼動態設置佈局,佈局的選中和未選中態的更新採用監聽器動態修改的方式。
/**
* 設置Tab的樣式
*/
private void setTabView() {
holder = null;
for (int i = 0; i < tabs.size(); i++) {
//依次獲取標籤
TabLayout.Tab tab = tabLayout.getTabAt(i);
//爲每一個標籤設置佈局
tab.setCustomView(R.layout.tab_item);
holder = new ViewHolder(tab.getCustomView());
//爲標籤填充數據
holder.tvTabName.setText(tabs.get(i));
holder.tvTabNumber.setText(String.valueOf(tabNumbers.get(i)));
//默認選擇第一項
if (i == 0){
holder.tvTabName.setSelected(true);
holder.tvTabNumber.setSelected(true);
holder.tvTabName.setTextSize(18);
holder.tvTabNumber.setTextSize(18);
}
}
//tab選中的監聽事件
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
holder = new ViewHolder(tab.getCustomView());
holder.tvTabName.setSelected(true);
holder.tvTabNumber.setSelected(true);
//選中後字體變大
holder.tvTabName.setTextSize(18);
holder.tvTabNumber.setTextSize(18);
//讓Viewpager跟隨TabLayout的標籤切換
viewPager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
holder = new ViewHolder(tab.getCustomView());
holder.tvTabName.setSelected(false);
holder.tvTabNumber.setSelected(false);
//恢復爲默認字體大小
holder.tvTabName.setTextSize(16);
holder.tvTabNumber.setTextSize(16);
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
class ViewHolder{
TextView tvTabName;
TextView tvTabNumber;
public ViewHolder(View tabView) {
tvTabName = (TextView) tabView.findViewById(R.id.tv_tab_name);
tvTabNumber = (TextView) tabView.findViewById(R.id.tv_tab_number);
}
}複製代碼
有一些寫的比較好的文章分享了一些比較高級的功能。如,TabLayout的簡單運用和若干問題的解決。
這篇中介紹了怎麼加分割線,設置原有字體大小,自定義標籤等。
在源碼中能夠看到再newTab中,customView的的建立。
@NonNull
public Tab newTab() {
Tab tab = sTabPool.acquire();
if (tab == null) {
tab = new Tab();
}
tab.mParent = this;
tab.mView = createTabView(tab);
if (mViewPagerTabEventListener != null) {
tab.setCustomView(mViewPagerTabEventListener.onCreateTab(tab.mView));
}
return tab;
}複製代碼
最新的TabLayout源碼能夠經過下面的地址中看到,看到Google是由專門的material設計和工程團隊負責此庫。
先看下總體的類關係圖
TabLayout繼承HorizontalScrollView是一個橫向滾動的ViewGroup,他包含一個子View(只能包含一個)SlidingTabStrip。
SlidingTabStrip爲一個LinearLayout爲TabLayout的內部類。全部的TabView都是它的子View.
TabView繼承於LinearLayout,以Tab爲數據源,來展現Tab的樣式。最終用for循環被add進SlidingTabStrip
Tab是一個簡單的View Model實體類,控制TabView的title, icon, custom layout id等屬性。
TabItem繼承於View. 用於在layout xml中來描述Tab. 須要注意的是,它不會add到SlidingTabStrip中去。它的做用是從xml中獲取到text,icon,custom layout id等屬性。TabLayout inflate到TabItem並獲取屬性到裝配到Tab中,最終add到SlidingTabStrip中的仍是TabView.
這裏調newTab()
方法建立了一個tab對象,而且用對象池把建立的tab對象緩存起來。而後將TabItem
對象的屬性都賦值給tab對象。在createTabView(Tab tab)
這個方法中,首先從TabView
池中獲取TabView
對象,若是不存在,則實例化一個對象,並調用tabView.setTab(tab)
方法來進行了數據綁定。
private static final Pools.Pool<Tab> sTabPool = new Pools.SynchronizedPool<Tab>(16);複製代碼
public Tab newTab() {
Tab tab = sTabPool.acquire();
if (tab == null) {
tab = new Tab();
}
tab.mParent = this;
tab.mView = createTabView(tab);
if (mViewPagerTabEventListener != null) {
tab.setCustomView(mViewPagerTabEventListener.onCreateTab(tab.mView));
}
return tab;
}複製代碼
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;
}複製代碼
mTabStrip自己在HorizonScrollView中,因此直接經過scrollTo方法便可實現滾動的操做,這裏只須要計算位置便可。
private int calculateScrollXForTab(int position, float positionOffset) {
if (mMode == MODE_SCROLLABLE) {
final View selectedChild = mTabStrip.getChildAt(position);
final View nextChild = position + 1 < mTabStrip.getChildCount()
? mTabStrip.getChildAt(position + 1)
: null;
final int selectedWidth = selectedChild != null ? selectedChild.getWidth() : 0;
final int selectedLeft = selectedChild != null ? selectedChild.getLeft() : 0;
final int nextWidth = nextChild != null ? nextChild.getWidth() : 0;
// base scroll amount: places center of tab in center of parent
int scrollBase = selectedLeft + (selectedWidth / 2) - (getWidth() / 2);
// offset amount: fraction of the distance between centers of tabs
int scrollOffset = (int) ((selectedWidth + nextWidth) * 0.5f * positionOffset);
return (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_LTR)
? scrollBase + scrollOffset
: scrollBase - scrollOffset;
}
return 0;
}複製代碼
[1] 官方介紹
[2] MaterialDesign--(7)TabLayout的使用及其源碼分析
[3] TabLayout基本屬性全解
[6] MaterialDesign之對TabLayout的探索
[7] https://github.com/itgoyo/AndroidSource-Analysis/blob/master/chapter1/TabLayout%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.md