Android BottomNavigationViewのカスタマイズ


## 背景
2017年9月ごろ、AndroidでWebViewベースのガワネイティブアプリを実装する機会があり、WebViewのナビゲーション部分をBottomNavigationView実装してみた。そのときBottomNavigationViewのデザイン調整で苦労したのでまとめてみた。

## デザインの課題
BottomNavigationViewは、アイテムが３つまでは均等サイズのアイテムが並びます。しかしアイテムが４つ以上になると選択中のアイテムのみアイコン下部にテキストが表示されたり、アニメーション、エフェクトが効いて選択中のアイテムのみが強調されたりする。どうしてもアプリの仕様上４つのアイテムを均等に並べたい、いろいろなサイトを調査し、BottomNavigationViewをカスタマイズしてみました。


### 実装時のビルドツール、コンパイルSDK、ターゲットSDK

``` gradle
compileSdkVersion 25
buildToolsVersion "25.0.2"

minSdkVersion 19
targetSdkVersion 25
```

# 目次
* 事前準備
* まずBottomNavigationViewを追加
* ４つのアイテム(BottomNavigationItemView)を均等の大きさで並べる
* アイテム(BottomNavigationItemView)のテキストを非表示にする
* アイテム(BottomNavigationItemView)のアイコンサイズを大きくする
* アイテム(BottomNavigationItemView)を選択したときのアニメーション？、エフェクト？を無効にする
* アイテム(BottomNavigationItemView)をタップできないようにアイテムを無効にする

# 事前準備
新規プロジェクト作成時にアプリの雛形で選択できる「Navigation Drawer Activity」の雛形を使用し、「MyApp」というプロジェクトを作って説明します。

<a href="http://blog.local-c.com/wp-content/uploads/2017/09/Screenshot_1505622967.png"><img src="http://blog.local-c.com/wp-content/uploads/2017/09/Screenshot_1505622967-169x300.png" alt="" width="169" height="300" class="alignnone size-medium wp-image-2075" /></a>



# まずBottomNavigationViewを追加

### BottomNavigationViewで表示するアイテムを作成する

「bottom_navigation_item.xml」を新規作成し、４つのアイテムを適当に作る（アイコンはandroid sdk デフォルトのアイコン）

``` /MyApp/app/src/main/res/menu/bottom_navigation_item.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/nav_camera"
        android:icon="@drawable/ic_menu_camera"
        android:title="Import"/>
    <item
        android:id="@+id/nav_gallery"
        android:icon="@drawable/ic_menu_gallery"
        android:title="Gallery"/>
    <item
        android:id="@+id/nav_slideshow"
        android:icon="@drawable/ic_menu_slideshow"
        android:title="Slideshow"/>
    <item
        android:id="@+id/nav_manage"
        android:icon="@drawable/ic_menu_manage"
        android:title="Tools"/>

</menu>
```

### BottomNavigationViewのアイテムを選択したときのホバー的な設定

「bottom_navigation_item_state.xml」を新規作成しアイテムの状態によって色を変更するようにする。

``` /MyApp/app/src/main/res/drawable/bottom_navigation_item_state.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- チェック状態 -->
	<item android:color="?colorAccent" android:state_checked="true"/>
    <!-- タップしたとき -->
	<item android:color="?colorPrimaryDark" android:state_pressed="true"/>
    <!-- 通常 -->
	<item android:color="#FFFFFF"/>
</selector>
```


### 「app_bar_main.xml」へBottomNavigationViewを追加する

プロジェクト作成時、自動で出来る「app_bar_main.xml」のファイルに「android.support.design.widget.BottomNavigationView」タグを追加する。
「app:menu」には、先ほど作成した「bottom_navigation_item.xml」を設定し、「app:itemIconTint」に「navigation_item_state.xml」を設定する。

``` /MyApp/app/src/main/res/layout/app_bar_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
	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:layout_width="match_parent"
	android:layout_height="match_parent"
	tools:context="com.myapp.myapp.MainActivity">

	<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>

	<include layout="@layout/content_main"/>

	<android.support.design.widget.FloatingActionButton
		android:id="@+id/fab"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_gravity="bottom|end"
		android:layout_marginRight="16dp"
		android:layout_marginBottom="80dp"
		app:srcCompat="@android:drawable/ic_dialog_email"/>

	<!-- BottomNavigationView を追加-->
	<android.support.design.widget.BottomNavigationView
		android:id="@+id/bottom_navigation"
		android:layout_width="match_parent"
		android:layout_height="?actionBarSize"
		android:layout_gravity="bottom"
		android:background="@color/colorPrimary"
		app:itemTextColor="@drawable/bottom_navigation_item_state"
		app:itemIconTint="@drawable/bottom_navigation_item_state"
		app:menu="@menu/bottom_navigation_item"/>

</android.support.design.widget.CoordinatorLayout>
```
その他に、「android.support.design.widget.FloatingActionButton」のメールのボタンが邪魔なので、配置場所を調整してます。

```
android:layout_marginRight="16dp"
android:layout_marginBottom="80dp"
```

<a href="http://blog.local-c.com/wp-content/uploads/2017/09/Screenshot_1505637591.png"><img src="http://blog.local-c.com/wp-content/uploads/2017/09/Screenshot_1505637591-169x300.png" alt="" width="169" height="300" class="alignnone size-medium wp-image-2092" /></a>

# ４つ以上のアイテムを均等の大きさで並べる

４つ以上のアイテムを均等に並べるにはレイアウトをごにょごにょする必要があるが、今回はBottomNavigationViewを継承してBottomNavigationViewの独自クラスをするのではなく
「BottomNavigationViewHelper」というヘルパークラスを作成してBottomNavigationViewを作るときにレイアウトを調整する。


### BottomNavigationViewHelperを新規に作成する
``` Java
package com.myapp.myapp;

import android.support.design.internal.BottomNavigationItemView;
import android.support.design.internal.BottomNavigationMenuView;
import android.support.design.widget.BottomNavigationView;

import java.lang.reflect.Field;

public class BottomNavigationViewHelper {

	/**
	 * BottomNavigationViewのアイテムのサイズの調整、アイコンサイズ調整、タイトルの削除
	 *
	 * @param view
	 */
	public static void disableShiftMode(BottomNavigationView view) {
		BottomNavigationMenuView menuView = (BottomNavigationMenuView) view.getChildAt(0);
		try {

			Field shiftingMode = menuView.getClass().getDeclaredField("mShiftingMode");
			shiftingMode.setAccessible(true);
			shiftingMode.setBoolean(menuView, false);
			shiftingMode.setAccessible(false);

			for (int i = 0; i < menuView.getChildCount(); i++) {

				/**
				 * アイテムの幅調整
				 */
				BottomNavigationItemView bottomNavigationItemView = (BottomNavigationItemView) menuView.getChildAt(i);
				// noinspection RestrictedApi
				bottomNavigationItemView.setShiftingMode(false);
				// チェックされた値を設定すると、ビューが更新されるみたい
				// noinspection RestrictedApi
				bottomNavigationItemView.setChecked(false);

			}

		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}

	}
}
```


### ActivityからBottomNavigationViewHelperを呼びす

onCreateでBottomNavigationViewHelper＃disableShiftModeへBottomNavigationViewを渡し調整、その後ついでに選択のリスナーを実装

``` Java
// ボトムナビゲーションを読み込む
		BottomNavigationView bottomavigation = (BottomNavigationView) findViewById(R.id.bottom_navigation);
		// BottomNavigationViewHelperでアイテムのサイズ、アニメーションを調整
		BottomNavigationViewHelper.disableShiftMode(bottomavigation);
		// BottomNavigationViewを選択したときのリスナー
		bottomavigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
			@Override
			public boolean onNavigationItemSelected(@NonNull MenuItem item) {

				// 各選択したときの処理
				switch (item.getItemId()) {
					case R.id.nav_camera:

						return true;
					case R.id.nav_gallery:

						return true;
					case R.id.nav_slideshow:

						return true;
					case R.id.nav_manage:

						return true;
				}
				return false;
			}
		});
```


<a href="http://blog.local-c.com/wp-content/uploads/2017/09/Screenshot_1505632563.png"><img src="http://blog.local-c.com/wp-content/uploads/2017/09/Screenshot_1505632563-169x300.png" alt="" width="169" height="300" class="alignnone size-medium wp-image-2076" /></a>




# アイテム(BottomNavigationItemView)のテキストを非表示にする

BottomNavigationItemViewは未選択時のテキストレイアウト「android.support.design.R.id.smallLabel」と選択中の大きめのテキスト「android.support.design.R.id.largeLabel」が存在する。テキストが全て不要だったので、親のレイアウトごと削除した。
BottomNavigationViewHelper#disableShiftModeのfor文の中に以下を追加して、常にテキストを非表示にした。

``` Java
/**
 * アイテムのテキストを非表示にする。
 * アイテムのテキストビューをくくってるBaselineLayoutをGONE
 */
final View smallLabel = menuView.getChildAt(i).findViewById(android.support.design.R.id.smallLabel);
BaselineLayout baselineLayout = (BaselineLayout) smallLabel.getParent();
baselineLayout.setVisibility(View.GONE);
```



<a href="http://blog.local-c.com/wp-content/uploads/2017/09/Screenshot_1505633768.png"><img src="http://blog.local-c.com/wp-content/uploads/2017/09/Screenshot_1505633768-169x300.png" alt="" width="169" height="300" class="alignnone size-medium wp-image-2077" /></a>




# アイテム(BottomNavigationItemView)のアイコンサイズを大きくする


BottomNavigationViewHelper#disableShiftModeのfor文の中に以下を追加して、アイコンサイズを大きくした

``` Java
/**
 * アイコンサイズを40dpに調整
 */
final View iconView = menuView.getChildAt(i).findViewById(android.support.design.R.id.icon);
final ViewGroup.LayoutParams layoutParams = iconView.getLayoutParams();
final DisplayMetrics displayMetrics = view.getResources().getDisplayMetrics();
layoutParams.height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, displayMetrics);
layoutParams.width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, displayMetrics);
iconView.setLayoutParams(layoutParams);
```

<a href="http://blog.local-c.com/wp-content/uploads/2017/09/Screenshot_1505634124.png"><img src="http://blog.local-c.com/wp-content/uploads/2017/09/Screenshot_1505634124-169x300.png" alt="" width="169" height="300" class="alignnone size-medium wp-image-2078" /></a>


# アイテム(BottomNavigationItemView)を選択したときのアニメーション？、エフェクト？を無効にする

選択すると少しアイコンが上部に移動するアニメーションは、チェックされた状態となる。この効果をなくすには、ずっと未チェック状態にすればいい。常にチェックされないよう修正する。

BottomNavigationViewHelper#disableShiftModeのfor文の中に以下を追加すると全部のアイテムがチェックしていない状態に出来る


``` Java
// noinspection RestrictedApi
bottomNavigationItemView.setEnabled(false);
```

またタップ（選択）したときのリスナーの戻り値もfalseにする

``` Java
// BottomNavigationViewを選択したときのリスナー
		bottomavigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
			@Override
			public boolean onNavigationItemSelected(@NonNull MenuItem item) {

				// 各選択したときの処理
				switch (item.getItemId()) {
					case R.id.nav_camera:

						return false;  // falseを返す
					case R.id.nav_gallery:

						return false;  // falseを返す
					case R.id.nav_slideshow:

						return false;  // falseを返す
					case R.id.nav_manage:

						return false;  // falseを返す
				}
				return false;
			}
		});
```

<a href="http://blog.local-c.com/wp-content/uploads/2017/09/Screenshot_1505635436.png"><img src="http://blog.local-c.com/wp-content/uploads/2017/09/Screenshot_1505635436-169x300.png" alt="" width="169" height="300" class="alignnone size-medium wp-image-2079" /></a>

# アイテム(BottomNavigationItemView)をタップできないようにアイテムを無効にする


アイテムの選択が無効状態にできる。

``` Java
// noinspection RestrictedApi
bottomNavigationItemView.setChecked(false);
```


無効状態のアイコンの色も設定する

``` /MyApp/app/src/main/res/drawable/bottom_navigation_item_state.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
	<!-- 無効状態 -->
	<item android:state_enabled="false" android:color="?colorPrimaryDark"/>
	<!-- 有効状態 -->
	<item android:state_enabled="true" android:color="#FFFFFF"/>
	<!-- チェック状態 -->
	<item android:color="?colorAccent" android:state_checked="true"/>
	<!-- タップしたとき -->
	<item android:color="?colorPrimaryDark" android:state_pressed="true"/>
	<!-- 通常 -->
	<item android:color="#FFFFFF"/>
</selector>
```
<a href="http://blog.local-c.com/wp-content/uploads/2017/09/Screenshot_1505636813.png"><img src="http://blog.local-c.com/wp-content/uploads/2017/09/Screenshot_1505636813-169x300.png" alt="" width="169" height="300" class="alignnone size-medium wp-image-2086" /></a>



元記事：https://blog.local-c.com/archives/2073