BottomNavigationViewのカスタマイズ - Re.Ra.Ku アドベントカレンダー day 18

Re.Ra.Ku アドベントカレンダー 18日目です。

こんにちは。安部です。

BottomNavigationViewのカスタマイズを試してみました。

ちょっと無理やりやってる感じの箇所もあります。

Version

使用したSupportLibraryは25.1.0です。

BottomNavigationViewの基本

基本的な使い方は以前、私の書いたものを参照してください。

BottomNavigationViewを試してみる - Qiita

アイコンとテキストの色を状態によって変更する

未選択の状態、タップされてる状態、選択された状態で色を変更する方法です。

f:id:STAR_ZERO:20161217224859p:plain:w200

res/color/bottom_navigation.xml を下記のように作成します。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true" android:color="#FF0000" />
    <item android:state_pressed="true" android:color="#00FF00" />
    <item android:color="#FFFFFF" />
</selector>

次にそれをレイアウトで指定してあげます。app:itemIconTintapp:itemTextColorです。

<android.support.design.widget.BottomNavigationView
    android:id="@+id/navigation"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/colorPrimaryDark"
    app:itemIconTint="@color/bottom_navigation"
    app:itemTextColor="@color/bottom_navigation"
    app:menu="@menu/bottom_navigation" />

常にテキストを表示させる

メニューの数が3つを超えると選択されているメニュー以外のテキストが表示されなくなってしまいます。それを無理やり表示する方法です。

正直よろしくない方法だと思います。現状で頑張った結果です。

MatrialDesignの仕様を確認すると3つ超えるとテキスト出てないので、別に無理やりやる必要はないのですが、なんか要望とかであがってきそうなパターンだなって思ってやってみました。

f:id:STAR_ZERO:20161217225142p:plain:w200

BottomNavigationView navigationView = (BottomNavigationView) findViewById(R.id.navigation);
BottomNavigationMenuView menuView = (BottomNavigationMenuView) navigationView.getChildAt(0);
for (int i = 0; i < menuView.getChildCount(); i++) {
    BottomNavigationItemView itemView = (BottomNavigationItemView) menuView.getChildAt(i);
    itemView.setShiftingMode(false);
    itemView.setChecked(false); // Viewの状態を反映させるために呼んでいる
}

スクロール時に隠す

RecyclerView等をスクロールしたときにBottomNavigationViewを隠す方法です。Toolbarでよくあるやつですね。

f:id:STAR_ZERO:20161217225309g:plain:w200

CoordinatorLayout.Behaviorを継承した下記のクラスを作成します。スクロールに合わせてViewの位置を変更しています。

public class BottomNavigationBehavior extends CoordinatorLayout.Behavior<BottomNavigationView> {

    private int defaultTop;
    private int defaultBottom;
    private int defaultHeight;

    public BottomNavigationBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onLayoutChild(CoordinatorLayout parent, BottomNavigationView child, int layoutDirection) {
        defaultTop = child.getTop();
        defaultBottom = child.getBottom();
        defaultHeight = defaultBottom - defaultTop;
        return super.onLayoutChild(parent, child, layoutDirection);
    }

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, BottomNavigationView child, View directTargetChild, View target, int nestedScrollAxes) {
        return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
    }

    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, BottomNavigationView child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        ViewCompat.offsetTopAndBottom(child, dyConsumed);
        if (dyConsumed > 0 && child.getTop() > defaultBottom) {
            child.setTop(defaultBottom);
        } else if (child.getTop() < defaultTop) {
            child.setTop(defaultTop);
        }
        child.setBottom(child.getTop() + defaultHeight);
    }
}

次にレイアウトファイルです。ちょっと長いですが全部のせておきます。

BottomNavigationViewapp:layout_behaviorに先程のクラスを指定します。これでスクロール時に隠れるようになります。

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

    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">

        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                app:popupTheme="@style/AppTheme.PopupOverlay" />

        </android.support.design.widget.AppBarLayout>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:scrollbars="vertical"
            app:layout_behavior="@string/appbar_scrolling_view_behavior" />

        <android.support.design.widget.BottomNavigationView
            android:id="@+id/navigation"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:background="@color/colorPrimaryDark"
            app:itemIconTint="@android:color/white"
            app:itemTextColor="@android:color/white"
            app:layout_behavior="com.star_zero.example.bottomnavigation.BottomNavigationBehavior"
            app:menu="@menu/bottom_navigation" />
    </android.support.design.widget.CoordinatorLayout>

</LinearLayout>

まとめ

BottomNavigationViewは制限がけっこうあって、5つより多くのメニューを設定しようとするとExceptionが投げられてクラッシュしたり、アイコンやテキストの大きさを自由に変えたりがかなり厳しいです。

あとはバグがそれなりある気がします。試した感じSnackbarがうまく表示できていない感じでした。Issue Trackerにもあがっています。

現状ではアプリの仕様によっては実現が難しいものが出てくるかもしれませんので慎重に導入を検討したいですね。