背景
2017年9月ごろ、AndroidでWebViewベースのガワネイティブアプリを実装する機会があり、WebViewのナビゲーション部分をBottomNavigationView実装してみた。そのときBottomNavigationViewのデザイン調整で苦労したのでまとめてみた。
デザインの課題
BottomNavigationViewは、アイテムが3つまでは均等サイズのアイテムが並びます。しかしアイテムが4つ以上になると選択中のアイテムのみアイコン下部にテキストが表示されたり、アニメーション、エフェクトが効いて選択中のアイテムのみが強調されたりする。どうしてもアプリの仕様上4つのアイテムを均等に並べたい、いろいろなサイトを調査し、BottomNavigationViewをカスタマイズしてみました。
実装時のビルドツール、コンパイルSDK、ターゲットSDK
compileSdkVersion 25
buildToolsVersion "25.0.2"
minSdkVersion 19
targetSdkVersion 25
目次
- 事前準備
- まずBottomNavigationViewを追加
- 4つのアイテム(BottomNavigationItemView)を均等の大きさで並べる
- アイテム(BottomNavigationItemView)のテキストを非表示にする
- アイテム(BottomNavigationItemView)のアイコンサイズを大きくする
- アイテム(BottomNavigationItemView)を選択したときのアニメーション?、エフェクト?を無効にする
- アイテム(BottomNavigationItemView)をタップできないようにアイテムを無効にする
事前準備
新規プロジェクト作成時にアプリの雛形で選択できる「Navigation Drawer Activity」の雛形を使用し、「MyApp」というプロジェクトを作って説明します。
まずBottomNavigationViewを追加
BottomNavigationViewで表示するアイテムを作成する
「bottom_navigation_item.xml」を新規作成し、4つのアイテムを適当に作る(アイコンはandroid sdk デフォルトのアイコン)
<?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」を新規作成しアイテムの状態によって色を変更するようにする。
<?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」を設定する。
<?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"
4つ以上のアイテムを均等の大きさで並べる
4つ以上のアイテムを均等に並べるにはレイアウトをごにょごにょする必要があるが、今回はBottomNavigationViewを継承してBottomNavigationViewの独自クラスをするのではなく
「BottomNavigationViewHelper」というヘルパークラスを作成してBottomNavigationViewを作るときにレイアウトを調整する。
BottomNavigationViewHelperを新規に作成する
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を渡し調整、その後ついでに選択のリスナーを実装
// ボトムナビゲーションを読み込む
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;
}
});
アイテム(BottomNavigationItemView)のテキストを非表示にする
BottomNavigationItemViewは未選択時のテキストレイアウト「android.support.design.R.id.smallLabel」と選択中の大きめのテキスト「android.support.design.R.id.largeLabel」が存在する。テキストが全て不要だったので、親のレイアウトごと削除した。
BottomNavigationViewHelper#disableShiftModeのfor文の中に以下を追加して、常にテキストを非表示にした。
/**
* アイテムのテキストを非表示にする。
* アイテムのテキストビューをくくってるBaselineLayoutをGONE
*/
final View smallLabel = menuView.getChildAt(i).findViewById(android.support.design.R.id.smallLabel);
BaselineLayout baselineLayout = (BaselineLayout) smallLabel.getParent();
baselineLayout.setVisibility(View.GONE);
アイテム(BottomNavigationItemView)のアイコンサイズを大きくする
BottomNavigationViewHelper#disableShiftModeのfor文の中に以下を追加して、アイコンサイズを大きくした
/**
* アイコンサイズを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);
アイテム(BottomNavigationItemView)を選択したときのアニメーション?、エフェクト?を無効にする
選択すると少しアイコンが上部に移動するアニメーションは、チェックされた状態となる。この効果をなくすには、ずっと未チェック状態にすればいい。常にチェックされないよう修正する。
BottomNavigationViewHelper#disableShiftModeのfor文の中に以下を追加すると全部のアイテムがチェックしていない状態に出来る
// noinspection RestrictedApi
bottomNavigationItemView.setEnabled(false);
またタップ(選択)したときのリスナーの戻り値もfalseにする
// 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;
}
});
アイテム(BottomNavigationItemView)をタップできないようにアイテムを無効にする
アイテムの選択が無効状態にできる。
// noinspection RestrictedApi
bottomNavigationItemView.setChecked(false);
無効状態のアイコンの色も設定する
<?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>