ActionBarタブ・ドロップダウンリストの置き換え方法まとめ

はじめに

API level 21以降、NAVIGATION_MODE_LISTやNAVIGATION_MODE_TABSといったActionBarと連動したナビゲーション要素が非推奨となりました。 これらのUIの置き換え方法についてはAndroid Developersで該当定数のリファレンスに「Consider using other common navigation patterns instead.」と記述されており、リンク先のページの「Changing view within a screen」という項目では以下のような実装案が文字列でのみ提示されています。

Examples of such view changes are:

  • Switching views using tabs and/or left-and-right swipes
  • Switching views using a dropdown (aka collapsed tabs)
  • Filtering a list
  • Sorting a list
  • Changing display characteristics (such as zooming)

具体的な置き換え例がないためか、いまでもそのまま利用しているアプリも多いですが、非推奨となってしまったからにはいつかは置き換える必要があります。 特にNAVIGATION_MODE_TABSはAPI level 21以降導入されたToolbarと共存できないため、できるだけ早く他の実装に置き換えるべきです。

ActionBar.NAVIGATION_MODE_TABS

ActionBar.NAVIGATION_MODE_TABSは名前の通りActionBarと一体化したタブUIを提供していましたが、API level 21以降非推奨となりました。 同様の機能を提供していたappcompat-v7版もr21以降非推奨となっています。

このタブUIはスマートフォンではActionBarの真下に表示される全幅のタブバーとして表示されますが、横画面(landscape)やタブレットではではActionBar内に格納され、さらに表示領域が足りない場合はドロップダウンリストになります。 さらにActionBarの一部だったためにNavigationDrawerよりも上のレイヤーに表示されてしまうなど、独特の扱いづらさがありました。

ActionBar.NAVIGATION_MODE_TABSの大半はViewPagerと組み合わせて利用されていたので、本項でもViewPagerとの組み合わせについて説明します。 また、Material DesignガイドラインにもタブUIの説明があるので、こちらも参照してください。

Tabs - Components - Google design guidelines

PagerTitleStrip/PagerTabStrip で置き換える

PagerTitleStripおよびPagerTabStripはViewPagerと合わせて実装されたViewPagerの上下にページ名を表示できるUIです。 ただし、これは見た目からもわかる通り現在表示中のページ名を表示するインジケータとしての役割が強く、ActionBarタブの置き換えにはあまり向いていません。 また、ViewPagerと密着したレイアウト構造のため、ActionBarと連動してアニメーションで非表示にするといったUIにしづらいこともデメリットになります。

PagerTitleStrip、PagerTabStripはViewPagerの見た目と違いは下記のブログ記事が詳しいです。

PagerTitleStripよりPagerTabStripのほうがいいと思う | made in kuluna

SlidingTabs で置き換える

SlidingTabsはAndroid Developersのサンプル(Apache License 2.0)に含まれているタブ実装になります。 「Sliding」という名の通り、ページ遷移時に現在ページを示すインジケータがスライドするアニメーションが入ることが特徴です。 SlidingTabsに関するサンプルは2種類用意されていますが、サンプルアプリの実装の違いなのであまり意識しなくても良いでしょう。

SlidingTabsBasic | Android Developers SlidingTabsColors | Android Developers

このサンプルアプリはレイアウトが標準的でない上あまり説明が親切でないため、実際に導入する際の手順を簡単に説明します。 前提条件として、appcompat-v7のToolbarをある程度実装してるプロジェクトを想定しています。

まず、サンプルアプリのviewクラス2つを自分のプロジェクトにコピーします。

SlidingTabLayout.java SlidingTabStrip.java

次に、レイアウトファイルでToolbarの真下にSlidingTabLayoutを配置します。 このとき、SlidingTabLayoutのbackgroundをcolorPrimaryにしておくとToolbarと並んだときに背景色が同色になります。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.Toolbar
        android:id="@+id/tool_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize">
    </android.support.v7.widget.Toolbar>

    <MY_PACKAGE.SlidingTabLayout
        android:background="?attr/colorPrimary"
        android:id="@+id/sliding_tabs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

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

ソース側ではSlidingTabLayout#setViewPagerにより対象のViewPagerを設定するだけでViewPagerの動きと連動するようになります。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity);

    Toolbar toolbar = (Toolbar) findViewById(R.id.tool_bar);
    setSupportActionBar(toolbar);

    ViewPager viewpager = (ViewPager) findViewById(R.id.view_pager);
    viewpager.setAdapter(new MyAdapter());

    SlidingTabLayout slidingTabLayout = (SlidingTabLayout) findViewById(R.id.sliding_tabs);
    slidingTabLayout.setViewPager(viewpager);
}

これだけでも動作するようになっていますが、SlidingTabLayoutはToolbarとの連携を意識したクラスになっていないため、選択中タブのフッターバーデフォルト色が固定値になっています。 SlidingTabLayout#setSelectedIndicatorColorsで色を指定することでも設定できますが、Toolbarと背景色をあわせるのであれば、フッターバーはアクセント色(colorAccent)を読み込むようにしたほうが良さそうです。

SlidingTabString.java 75行目の以下の文を置き換えます。

mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR);

このとき、appcompat-v7を利用している場合はR.attr.colorAccentではなくandroid.support.v7.appcompat.R.attr.colorAccentを指定しないとうまく取得することができません。

TypedValue accentValue = new TypedValue();
context.getTheme().resolveAttribute(android.support.v7.appcompat.R.attr.colorAccent, accentValue, true);
final int accentColor= accentValue.data;
mDefaultTabColorizer.setIndicatorColors(accentColor);

以上で最低限の設定は完了です。 SlidingTabLayout/SlidingTabStripはあまり複雑な処理はしていないので、このようにデザインやアニメーションなどをカスタマイズしましょう。

PagerSlidingTabStrip で置き換える

jpardogo/PagerSlidingTabStrip

PagerSlidingTabStripというMaterialDesignに対応したタブUIライブラリが提供されているようです。 カスタマイズに利用できるattr属性も多いようなので、SlidingTabsをカスタマイズするよりも簡単に好みのデザインにできそうです。

NAVIGATION_MODE_LIST

ActionBar.NAVIGATION_MODE_LISTはActionBarから呼び出せるドロップダウンリストのUIを提供していましたが、API level 21以降非推奨となりました。 同様の機能を提供していたappcompat-v7版もr21以降非推奨となっています。 NAVIGATION_MODE_TABSと異なり、現状はNAVIGATION_MODE_LISTはToolbarを利用した場合でも動作しますが、やはり早い段階で置き換えたほうが良いでしょう。

Material DesignsガイドラインのMenusコンポーネントのページに近いUIの説明があります。 Menus - Components - Google design guidelines

Spinner(with Toolbar) で置き換える

NAVIGATION_MODE_LISTはもともとSpinnerAdapterを利用するようになっており、UIもSpinnerとほぼ一緒です。 そのため、Toolbar内にSpinnerを配置し、元と同じAdapterをセットしてやるだけでほぼ同じ見た目のまま置き換えることができます。

元のNAVIGATION_MODE_LISTによる実装が以下のようになっていたとします。

@Override
protected void onCreate(Bundle savedInstanceState) {

    // 略

    getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
    getSupportActionBar().setListNavigationCallbacks(
            ArrayAdapter.createFromResource(this, R.array.list_items, android.R.layout.simple_spinner_dropdown_item),
            new ActionBar.OnNavigationListener() {
                @Override
                public boolean onNavigationItemSelected(int i, long l) {
                    // 処理
                    return true;
                }
            });
    getSupportActionBar().setDisplayShowTitleEnabled(false);
}

Spinnerに置き換えるため、Toolbarの中にSpinnerを配置します。 (下記レイアウトはToolbarと関係ない部分を省略しています)

    <android.support.v7.widget.Toolbar
        android:id="@+id/tool_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize">

        <Spinner
            android:id="@+id/tool_bar_spinner"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </android.support.v7.widget.Toolbar>

次に、先述のActivityの実装を以下のように変更します。

@Override
protected void onCreate(Bundle savedInstanceState) {

    // 略

    Toolbar toolbar = (Toolbar) findViewById(R.id.tool_bar);
    setSupportActionBar(toolbar);

    Spinner spinner = (Spinner) toolbar.findViewById(R.id.tool_bar_spinner);
    spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            // 処理
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {
        }
    });
    spinner.setAdapter(ArrayAdapter.createFromResource(this, R.array.list_names, android.R.layout.simple_spinner_dropdown_item));
}

これでほぼ同じ見た目のままToolbar内のSpinnerに実装を置き換えることができました。