AndroidX TabLayout使用、擴展及解析All In One

前言

TabLayout仍舊是移動端比較經常使用的一個控件,這裏分析一下TabLayout,分別從下面幾個方面進行解析:html

  1. 基本構成及使用TabLayout
  2. 原理解析
  3. 開發擴展

1.TabLayout的引用變化

先看下支持庫指南。以前的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

2.基本功能及使用

TabLayout繼承自 HorizontalScrollView

控件的基本層次關係

2.1 代碼添加tab

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)以在任何選項卡的選擇狀態更改時獲得通知。

2.2 xml配置tab

還能夠經過使用將項目添加到佈局中的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>複製代碼

2.3 tab配置viewpager

若是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>複製代碼

3.使用詳解

這裏不少人都使用的都是design 28,主工程的gradle的配置根據不一樣狀況改。

工程庫引用

上面三種使用方法,咱們使用新的庫androidx看下使用的效果圖:

基礎庫使用效果

3.1 使用擴展

這裏列舉一下主要使用到到屬性,只列舉幾個,具體可看官方文檔。

屬性

說明

tabMode

scrollable/fixed

tab是水平可滾動的仍是固定寬,個數較少的時候可使用fixed,若是標籤超出了屏幕範圍,設置爲scrollable比較好

tabGravity

fill/center

tab的佈局是佈滿空間仍是居中

tabIndicatorHeight

(dp/pix)

底部滑動線條的高度

tabMaxWidth

(dp/pix)

Tab的最大寬度

tabTextColor

顏色值

默認樣式未選中顏色

app:tabSelectedTextColor

顏色值

選中顏色

3.2 典型的使用場景

(1)代碼添加tab

//TabLayout的基本使用
        for(int i=0;i<mTitles.length;i++){
            TabLayout.Tab tab=mTabLayout.newTab();
            tab.setTag(i);
            tab.setText(mTitles[i]);
            mTabLayout.addTab(tab);
        }複製代碼

(2)不須要指示器

屬性設置
    app:tabIndicatorHeight="0dp" 複製代碼

有時候想指示器的寬度小一些,能夠參考文章Tablayout使用全解,一篇就夠了 修改指示線長度(利用的反射,感受不如本身基於源碼封裝一個,能夠自定義長度)。

(3)添加圖標

TabItem有個上下結構的默認佈局,這裏使用默認的

tabLayout1.addTab(tabLayout1.newTab().setText("Tab 4").setIcon(R.mipmap.ic_launcher));複製代碼

(4)加入Padding

設置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"複製代碼

(5)Tab的寬度限制

設置最大的tab寬度:

app:tabMaxWidth="xxdp"複製代碼

設置最小的tab寬度:

app:tabMinWidth="xxdp"複製代碼

(6)Tab的「Margin」

TabLayout開始位置的偏移量:

app:tabContentStart="50dp"複製代碼

(7)Tab默認選中

若是你要設置默認選中第三項,你能夠這樣:

mTabLayout.getTabAt(2).select();複製代碼

(8)監聽事件

初始化進入的時候,監聽事件的三個方法都不會執行

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再次點擊會走到。

(9)判讀是否選中

mTabLayout.getTabAt(position).isSelected()複製代碼

有時候要監聽某個Tab的點擊事件,能夠參考TabLayout基本屬性全解 或者 tablayout增長選擇tab 的事件和重寫tab點擊事件

(10)自定義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;
}複製代碼

4.源碼分析

最新的TabLayout源碼能夠經過下面的地址中看到,看到Google是由專門的material設計和工程團隊負責此庫。

github.com/material-co…

先看下總體的類關係圖

結構圖
  • (1)總體的構成

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.

  • (2)Tab的建立

這裏調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;
}複製代碼
  • (3)TabLayout的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;
}複製代碼
  • (4)ViewPager的綁定是經過一些監聽器實現,這裏代碼不復雜,不詳細說明。可參見引用文章。

5.引用文章

[1] 官方介紹

[2] MaterialDesign--(7)TabLayout的使用及其源碼分析

[3] TabLayout基本屬性全解

[4] Tablayout使用全解,一篇就夠了

[5] 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

相關文章
相關標籤/搜索