Part 1ではTextInputLayoutとFloatingActionButtonについて、Part 2ではSnackbarとCoordinatorLayoutについて調べたことを、それぞれ書きました。
今回は、TabLayoutについて書いていきます。
TabLayoutの概要
リファレンスを確認すると、TabLayoutクラスはHorizontalScrollViewクラスを継承していました。
API Level 20まで使われていたActionBar.Tabなどとは、関連がないようです。
Tabの生成にはTabLayout.TabクラスのnewTab()
メソッドを使います。
MaterialDesignにおけるTabの使い方は、デザインガイドラインで細かく指定されています。 このクラスの見た目や振る舞いは、ガイドラインに沿った形で実装されているようです。
基本的な使い方
とりあえずTabを画面に配置するために、各Tabのコンテンツは一旦無視して、以下のように実装してみました。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <android.support.design.widget.TabLayout android:id="@+id/tabs" android:layout_width="match_parent" android:layout_height="wrap_content" /> </RelativeLayout>
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs); tabLayout.addTab(tabLayout.newTab().setText("tab 1")); tabLayout.addTab(tabLayout.newTab().setText("tab 2")); tabLayout.addTab(tabLayout.newTab().setText("tab 3")); } }
やっていることは、newTab()
メソッドでタブを生成してsetText()
メソッドでタブのタイトルを設定しているだけですが、デフォルトでも以下のような点が実現出来てました。
- タブのタイトルが自動でAllCaps
- タブのインジケーターがアニメーションする
- カレントでないタブのタイトル色が薄くなる
またsetIcon()
メソッドでアイコンをタブに表示することも出来ます。アイコンサイズは24dpです。
tabLayout.addTab(tabLayout.newTab().setIcon(R.drawable.ic_event_grey_600_24dp)); tabLayout.addTab(tabLayout.newTab().setIcon(R.drawable.ic_devices_grey_600_24dp)); tabLayout.addTab(tabLayout.newTab().setIcon(R.drawable.ic_directions_subway_grey_600_24dp));
ただし、setText()
とsetIcon()
を両方使うと横並びに表示されてしまい、ガイドラインのTabs with icons and textとは異なってしまいました。この場合は、setCustomView()
メソッドでガイドラインに沿うよう、補うしかなさそうです。
ModeとGravity
ガイドラインを見ると、Tabには以下のような2つのModeと2種類の見た目について、記述されています。
- Mode : Fixed / Scrollable
- Gravity : Center / Fill
それぞれsetTabMode()
メソッドとsetTabGravity()
メソッドで指定出来ました。
1. Fixed & Center
2. Fixed & Fill
3. Scrollable & Fill
4. Scrollable & Center
ViewPagerとの併用
リファレンスにも書かれているように、TabLayoutクラスはViewPagerと併用するためのインターフェースやメソッドが用意されています。 個人的には、これがTabLayoutクラスを使う醍醐味だと感じました。
セットする方法は2パターン用意されていて、簡単に言うとマニュアル方式とオートマチック方式です。 マニュアル方式だと、以下の3ステップで設定します。
TabLayout#setTabsFromPagerAdapter(PagerAdapter)
メソッドでPagerAdapterをTabLayoutにセットするTabLayout.TabLayoutOnPageChangeListener
インターフェースでTabの切り替えイベントをViewPagerにリンクするTabLayout.ViewPagerOnTabSelectedListener
インターフェースでViewPagerの切り替えイベントをTabLayoutにリンクする
オートマチック方式では、上記3ステップと同等の処理を1つのメソッド(TabLayout#setupWithViewPager(ViewPager)
)の内部で、全てやってくれるそうです。
ちなみにどちらの方式も、タブタイトルは内部でPagerAdapter#getPageTitle(int)
メソッドから取ってきたものを、セットするとのことです。
これらを踏まえてコードで書くと、以下のようになりました。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <android.support.design.widget.TabLayout android:id="@+id/tabs" android:layout_width="match_parent" android:layout_height="wrap_content" /> <android.support.v4.view.ViewPager android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/tabs" /> </RelativeLayout>
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/page_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textSize="32sp" /> </RelativeLayout>
public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs); ViewPager viewPager = (ViewPager) findViewById(R.id.pager); FragmentPagerAdapter adapter = new FragmentPagerAdapter(getSupportFragmentManager()) { @Override public Fragment getItem(int position) { return TestFragment.newInstance(position + 1); } @Override public CharSequence getPageTitle(int position) { return "tab " + (position + 1); } @Override public int getCount() { return 3; } }; viewPager.setAdapter(adapter); viewPager.addOnPageChangeListener(this); //オートマチック方式: これだけで両方syncする tabLayout.setupWithViewPager(viewPager); //マニュアル方式: これでViewPagerのPositionとTabのPositionをsyncさせるらしい //tabLayout.setTabsFromPagerAdapter(adapter); //viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout)); //tabLayout.setOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(viewPager)); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { Log.d("MainActivity", "onPageSelected() position="+position); } @Override public void onPageScrollStateChanged(int state) { } public static class TestFragment extends Fragment { public TestFragment() { } public static TestFragment newInstance(int page) { Bundle args = new Bundle(); args.putInt("page", page); TestFragment fragment = new TestFragment(); fragment.setArguments(args); return fragment; } @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { int page = getArguments().getInt("page", 0); View view = inflater.inflate(R.layout.fragment_test, container, false); ((TextView) view.findViewById(R.id.page_text)).setText("Page " + page); return view; } } }
もしタブタイトルにアイコンを設定したい場合は、getPageTitle()
でnullなどを返してTabLayoutにViewPagerをセットした後で、setIcon()
を使えば良さそうです。
先程のコードを、以下のように書き換えました。(変更がない箇所は省略しています)
... @Override public CharSequence getPageTitle(int position) { return null; } ... //オートマチック方式: これだけで両方syncする tabLayout.setupWithViewPager(viewPager); //アイコンセット tabLayout.getTabAt(0).setIcon(R.drawable.ic_devices_grey_600_24dp); tabLayout.getTabAt(1).setIcon(R.drawable.ic_directions_subway_grey_600_24dp); tabLayout.getTabAt(2).setIcon(R.drawable.ic_event_grey_600_24dp); ...
ブランドカラーの設定
以下のようにブランドカラーを設定する場合は、ほぼToolbarと同じ設定の仕方でした。
DarkかLiteかは親のテーマを引き継ぎますし、背景色はandroid:background=?attr/colorPrimary
の記述が必要です。
インジケーターには特に何も指定しなくてもcolorAccent
が適用されるようです。
もしタブタイトルのテキスト色を変えたい場合は、setTabTextColors(ColorStateList)
メソッドかsetTabTextColors(int, int)
メソッドを使って変更出来ます。
<style name="AppTheme" parent="Theme.AppCompat.NoActionBar"> <item name="colorPrimary">#3f51b5</item> <item name="colorPrimaryDark">#303f9f</item> <item name="colorAccent">#ff4081</item> </style>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" /> <android.support.design.widget.TabLayout android:id="@+id/tabs" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/toolbar" android:background="?attr/colorPrimary" /> <android.support.v4.view.ViewPager android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/tabs" /> </RelativeLayout>
まとめ
TabLayoutいいクラスですね。タブの実装が、かなり楽になりそうだなと思いました。 特にViewPagerと使うことを十分に考慮してあるのが、とても助かります。
ただタブタイトルを自動でgetPageTitle()
からセット出来たりするとこまでやってくれるのであれば、いっそTabPagerViewクラスとか作ってしまってsetTabEnable(boolean)
メソッドとかでタブが生えたり消えたり出来たら良かったなーとも思いました。
とはいえ、ほとんど場合は今まで通りViewPagerを生成して、TabLayout#setupWithViewPager(PagerAdapter)
を呼べば済みそうなので、本当に便利なクラスだと思います。