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
xmlnsandroid="http://schemas.android.com/apk/res/android"
androidlayout_width="match_parent"
androidlayout_height="match_parent"
toolscontext=".MainActivity">
<androidsupportdesignwidgetTabLayout
androidid="@+id/tabs"
androidlayout_width="match_parent"
androidlayout_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
xmlnsandroid="http://schemas.android.com/apk/res/android"
xmlnstools="http://schemas.android.com/tools"
androidlayout_width="match_parent"
androidlayout_height="match_parent"
toolscontext=".MainActivity">
<androidsupportdesignwidgetTabLayout
androidid="@+id/tabs"
androidlayout_width="match_parent"
androidlayout_height="wrap_content"
/>
<androidsupportv4viewViewPager
androidid="@+id/pager"
androidlayout_width="match_parent"
androidlayout_height="wrap_content"
androidlayout_below="@id/tabs"
/>
</RelativeLayout>
xml version="1.0" encoding="utf-8"
<RelativeLayout
xmlnsandroid="http://schemas.android.com/apk/res/android"
androidlayout_width="match_parent"
androidlayout_height="match_parent">
<TextView
androidid="@+id/page_text"
androidlayout_width="wrap_content"
androidlayout_height="wrap_content"
androidlayout_centerInParent="true"
androidtextSize="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);
tabLayout.setupWithViewPager(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;
}
...
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
xmlnsandroid="http://schemas.android.com/apk/res/android"
xmlnstools="http://schemas.android.com/tools"
androidlayout_width="match_parent"
androidlayout_height="match_parent"
toolscontext=".MainActivity">
<androidsupportv7widgetToolbar
androidid="@+id/toolbar"
androidlayout_width="match_parent"
androidlayout_height="?attr/actionBarSize"
androidbackground="?attr/colorPrimary"
/>
<androidsupportdesignwidgetTabLayout
androidid="@+id/tabs"
androidlayout_width="match_parent"
androidlayout_height="wrap_content"
androidlayout_below="@id/toolbar"
androidbackground="?attr/colorPrimary"
/>
<androidsupportv4viewViewPager
androidid="@+id/pager"
androidlayout_width="match_parent"
androidlayout_height="wrap_content"
androidlayout_below="@id/tabs"
/>
</RelativeLayout>
まとめ
TabLayoutいいクラスですね。タブの実装が、かなり楽になりそうだなと思いました。
特にViewPagerと使うことを十分に考慮してあるのが、とても助かります。
ただタブタイトルを自動でgetPageTitle()
からセット出来たりするとこまでやってくれるのであれば、いっそTabPagerViewクラスとか作ってしまってsetTabEnable(boolean)
メソッドとかでタブが生えたり消えたり出来たら良かったなーとも思いました。
とはいえ、ほとんど場合は今まで通りViewPagerを生成して、TabLayout#setupWithViewPager(PagerAdapter)
を呼べば済みそうなので、本当に便利なクラスだと思います。