TabLayout的簡單運用和若干問題的解決

一、介紹和準備

咱們在使用手機App時不難會看到這樣的頁面上面是一組起導航做用的標籤,點擊標籤就會切換到相應的頁面;在不一樣的頁面中滑動時,標籤的樣式(文字大小或者顏色)也會發生變化。這樣你任什麼時候候都能一眼看出本身停留在哪一個頁面。這個佈局出鏡率實在過高了,我甚至敢說每一個學Android的人都寫過這樣的佈局(下面就是知乎中的頁面)。html

好了,廢話少說,咱們照例先來分析一下這個佈局的組成。標籤下面的頁面比較容易想到:總體是一個左右滑動的ViewPager,每一頁則能夠用Fragment填充,也就是ViewPager+Fragment。但上面的標籤部分就有點頭大了,以前咱們都是使用第三方的項目(如PagerSlidingTabStrip),高手的話也能夠自定義一個控件。可是這樣並不是長久之計,因此谷歌後來人性化地推出了自家的標籤控件TabLayout(注意可不要跟TableLayout搞混了,後者是Android的基本佈局之一,而前者是一個控件)。TabLayout顧名思義就是包含Tab的佈局,它包含在Design support library庫中,要使用它,你須要在先添加依賴庫:
這裏寫圖片描述java

我導入的是最新的26.0.0版本:android

compile 'com.android.support:design:26.0.0-alpha1'

準備完這些,咱們能夠開始寫代碼了。git

二、初步實現

以前在知乎上看到有人對微信的設計改動:將使用頻率高的朋友圈、消息提醒和公衆號這三個功能獨立出來放在首頁。我很贊同這樣的設計思路,因此今天就來弄一個簡陋版的吧。大致效果以下:
這裏寫圖片描述github

2.1 頁面佈局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.lindroid.tablayoutdemo.MainActivity">

    <android.support.design.widget.TabLayout
        android:id="@+id/tayLayout"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        app:tabIndicatorColor="#49dd12"
        app:tabSelectedTextColor="#49dd12"
        app:tabTextColor="@android:color/darker_gray"/>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

    </android.support.v4.view.ViewPager>
</LinearLayout>

上面的TabLayout的高度固定爲60dp,而後讓ViewPager佔據剩餘的空間便可。如今我來介紹一下用到的TabLayout的屬性:微信

  • app:tabIndicatorColor:標籤下面移動的橫線的顏色。
  • app:tabTextColor:標籤文字的顏色
  • app:tabSelectedTextColor :標籤被選中後的文字顏色

TabLayout還有不少其餘的屬性,好比你要是不想要下面的移動橫線的話,能夠調用屬性app:tabIndicatorHeight ,將高度設置爲0dp便可。關於TabLayout的其餘屬性,你們能夠看看這篇博客,動手練習一下:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0731/3247.htmlapp

2.2 建立Viewpager頁面(Fragment)

爲了可以識別咱們切換到的是ViewPager的哪一個頁面,咱們在Fragment中建立一個帶參數的構造函數,動態添加一個TextView,它的文本內容跟標籤的一致就好。ide

public class TabFragment extends Fragment {
    private Context context;
    private String content; //Fragment的顯示內容
    public TabFragment() {

    }

    public TabFragment(Context context,String content){
        this.context = context;
        this.content = content;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        TextView textView = new TextView(context);
        textView.setText(content);
        textView.setTextSize(30);
        textView.setGravity(Gravity.CENTER);
        return textView;
    }
}

2.3 MainActivity代碼

先來看代碼:函數

public class MainActivity extends AppCompatActivity {
    private ViewPager viewPager;
    private TabLayout tabLayout;
    private List<Fragment> fragments = new ArrayList<>();
    private List<String> tabs = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initData();
        initView();
    }

    private void initData() {
        tabs.add("新消息");
        tabs.add("朋友圈");
        tabs.add("公衆號");
        fragments.add(new TabFragment(this,tabs.get(0)));
        fragments.add(new TabFragment(this,tabs.get(1)));
        fragments.add(new TabFragment(this,tabs.get(2)));
    }

    private void initView() {
        tabLayout = (TabLayout) findViewById(R.id.tayLayout);
        viewPager = (ViewPager) findViewById(R.id.viewPager);
        //設置TabLayout的模式
        tabLayout.setTabMode(TabLayout.MODE_FIXED);
        viewPager.setAdapter(new TabAdapter(getSupportFragmentManager()));
        //關聯ViewPager和TabLayout
        tabLayout.setupWithViewPager(viewPager);
    }

    class TabAdapter extends FragmentPagerAdapter{
        public TabAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            return fragments.get(position);
        }

        @Override
        public int getCount() {
            return fragments.size();
        }
        
        //顯示標籤上的文字
        @Override
        public CharSequence getPageTitle(int position) {
            return tabs.get(position);
        }
    }
}

代碼不長,initData方法中添加數據,initView方法初始化控件,跟咱們平時使用ViewPager的寫法差異不大。要注意的是TabLayout須要設置模式(即setTabMode方法),一共有兩種:佈局

  • TabLayout.MODE_FIXED :當Tab較少,且佔滿整個屏幕時可使用這種模式;
  • TabLayout.MODE_SCROLLABLE :當Tab數量較多,屏幕寬度不夠時使用該模式,整個TabLayout是能夠左右滑動的。

除此以外,咱們須要讓Tab顯示文字,要重寫FragmentPagerAdapter的getPageTitle方法,返回每個Tab的文字內容。最後可別忘了最關鍵的一步:使用setupWithViewPager方法關聯Viewpager和TabLayout,這樣二者纔會聯動。

運行一下,就能夠看到動態圖中的效果了。

三、進階

3.1 修改標籤字體大小

默認的Tab字體大小有點小,看起來不太舒服,當咱們去修改字體大小時卻發現,TabLayout竟然沒有提供跟TextSize相關的屬性。不過不用急,TabLayout其實提供了一個更靈活的屬性app:tabTextAppearance ,它能夠修改字體的樣式,從而間接修改字體的大小。

咱們在style.xml中自定義一個樣式,繼承於TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse ,在屬性android:textSize中設置咱們想要的字體大小,這裏我設爲20sp。

<style name="TabTextSize" parent="TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse">
        <item name="android:textSize">20sp</item>
    </style>

接下來在佈局文件中使用就能夠了。

運行一下,這下看起來清楚多了。

這裏寫圖片描述

3.2 添加分割線

TabLayout的標籤之間是默認沒有分割線的,若是咱們想添加分割線,讓標籤之間更有層次感的話,能夠添加如下的代碼:

//設置分割線
        LinearLayout linearLayout = (LinearLayout) tabLayout.getChildAt(0);
        linearLayout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
        linearLayout.setDividerDrawable(ContextCompat.getDrawable(this,
                R.drawable.divider)); //設置分割線的樣式
        linearLayout.setDividerPadding(dip2px(10)); //設置分割線間隔

自定義的分割線樣式:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#80c0c0c0" />
    <size android:width="1dp" />
</shape>

setDividerPadding方法中輸入的參數是單位是px,咱們須要轉換成像素:

//像素單位轉換
    public int dip2px(int dip) {
        float density = getResources().getDisplayMetrics().density;
        return (int) (dip * density + 0.5);
    }

3.2 顯示信息數目

如今咱們來嘗試一下這樣的效果:將tab的文字分爲兩行,第二行顯示信息的數目,固然,咱們並無真的信息,因此直接輸入一些假數據就能夠。爲了讓文字變爲兩行,咱們能夠加入換行符。

tabs.add("新消息"+"\n"+999);
        tabs.add("朋友圈"+"\n"+99);
        tabs.add("公衆號"+"\n"+9);

運行,發現文字確實分紅了兩行。可是等等,怎麼文字大小小了那麼多?
這裏寫圖片描述

若是你查一下TabLayout的源碼,就會發現這一切早就命中註定了。源碼中有這麼一段:

if (mIconView != null && mIconView.getVisibility() == VISIBLE) {
                    // If the icon view is being displayed, we limit the text to 1 line
                    maxLines = 1;
                } else if (mTextView != null && mTextView.getLineCount() > 1) {
                    // Otherwise when we have text which wraps we reduce the text size
                    textSize = mTabTextMultiLineSize;
                }

這裏是設置Tab的icon和字體大小的,在else if代碼塊中,咱們發現了,當TextView的文本大於一行時,就會強制使用特定的字體(textSize = mTabTextMultiLineSize),這就解釋了爲何咱們的字體設置不奏效了。

那麼,咱們該怎麼辦呢?

3.3 自定義標籤

條條大路通羅馬,TabLayout早就給咱們準備了另外一條路了,那就是自定義標籤佈局。(舒適提示:下面的代碼對以前的改動較大,你們可能會以爲以前作的都是無用功,可是凡事老是按部就班的,請沒必要灰心。)

自定義標籤佈局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_tab_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="標籤名"
        android:textColor="@drawable/tab_color"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/tv_tab_number"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="99"
        android:textColor="@drawable/tab_color"
        android:textSize="16sp" />
</LinearLayout>

這裏咱們用到了一個選擇器,代碼以下:

標籤顏色選擇器

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="true" android:color="#49dd12"/>
    <item android:state_selected="false" android:color="@android:color/darker_gray"/>
</selector>

在代碼中實現

/**
     * 設置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);
        }
    }

建立一個setTabView方法來設置Tab的樣式,在for循環中爲每個標籤建立佈局。setCustomView方法能夠設置Tab的佈局,getCustomView則能夠獲取當前Tab的佈局。

咱們既然使用的是自定義的佈局,那麼選中時的樣式也要手動設置了。跟ViewPager相似,TabLayout也有本身的選中監聽事件(addOnTabSelectedListener)。在標籤被選中時將狀態設置爲選中,並切換到相應的ViewPager頁面,未選中的頁面則將選中狀態設爲false便可。

補充一點,選中Tab後字體變大這一功能是我後面加上去的,因此代碼只在GitHub中更新了。因爲屬性android:textSize 不支持drawable文件,因此這裏不能用狀態選擇器,但好在代碼裏實現也不復雜,就沒必要過多解釋了。

完成效果圖

四、開拓思惟

既然TabLayout如此貼心地給咱們提供了自定義標籤佈局的方法,那麼咱們就要好好利用它,好比除了文字以外,咱們還能夠添加圖片,讓標籤頁的內容更加豐富。另外,TabLayout不必定非要放在頂部,也能夠放在底部,去掉下劃線以後就能夠實現與RadioGroup同樣的效果。

最後,是說好的源碼了:
CSDN
GitHub

相關文章
相關標籤/搜索