woshidan's blog

あいとゆうきとITと、とっておきの話。

Support LibraryのNavigationViewでどこまでできるのか試してみる

久しぶりにサイドメニューのコードいじろうかと思ったのですが、デザインのカスタマイズ性の都合でサードパーティライブラリを使うのが辛く、ここから整理するよりSupport LibraryのNavigationViewを使って書き直した方が楽ではないか、と思ったので、どこまでできるのか試してみます。

内容

  • ライブラリのバージョンについて
  • 下記のコードを試す上で基本となるレイアウトなどのソースコード
  • HeaderViewのレイアウトを変更する
  • サイドメニューの項目を変更する場合
  • サイドメニューの項目にバッジをつけたい場合
  • 下に固定のfooterをつけたい場合

ライブラリのバージョンについて

gradleで指定していたバージョンは下記。

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])

    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.android.support:design:23.4.0'
    compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha3'
}

下記のコードを試す上で基本となるレイアウトなどのソースコード

Android Studioに用意されているNavigation Drawer Activityのプロジェクトのものをほとんどそのまま利用しています。

一応見たいという方はこちらよりご確認ください。

HeaderViewのレイアウトを変更する

NavigationView.removeHeaderView(View view)NavigationView.addHeaderView(View view)を使えば特に難しいことを考えずサクッと入れ替えられました。

        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // HeaderViewを変更できるテストとしてHeaderViewが1個以上あれば
                // HeaderViewを消すテスト
                if(navigationView.getHeaderCount() >= 1) {
                    navigationView.removeHeaderView(navigationView.getHeaderView(0));
                } else {
                    navigationView.inflateHeaderView(R.layout.nav_header_main);
                }
            }
        });

f:id:woshidan:20160907222153g:plain

サイドメニューの項目を変更する場合

一部の項目の表示/非表示にする場合。

    boolean isMenuGroup1Visible = true;


        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 一部のmenuの項目の表示/非表示を切り替えるテスト
                navigationView.getMenu().setGroupVisible(R.id.menu_group_1, !isMenuGroup1Visible);
                isMenuGroup1Visible = !isMenuGroup1Visible;
            }
        });

f:id:woshidan:20160907223531g:plain

サイドメニューの項目をがらっと変えたい場合。

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // サイドメニューの項目をmenuリソースから生成し直す場合
                navigationView.getMenu().clear();
                navigationView.inflateMenu(R.menu.test);
            }
        });
<!-- test.xml -->
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <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>

f:id:woshidan:20160907224022g:plain

サイドメニューの項目にバッジをつけたい場合

stackoverflow.com

menuリソースのitem要素の属性に、バッジの元となるWidgetのクラスとしてTextViewを指定して...とありましたが、この方法だと、どうも追加した要素の高さをうまく調整できなかったので、MenuItemCompat.setActionView(MenuItem menuItem, int layoutRes)メソッドで、バッジ用に用意したレイアウトを与えて、そのレイアウトを変更してやる方がいじりやすそうでした。

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <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>
<!-- nav_menu_badge.xml  -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:gravity="center"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:textColor="@android:color/white"
        android:id="@+id/badge_text"
        android:background="@color/colorAccent"
        android:padding="4dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>
        MenuItem galleryItem = MenuItemCompat.setActionView(navigationView.getMenu().findItem(R.id.nav_gallery), R.layout.nav_menu_badge);
        TextView badgeText = (TextView) galleryItem.getActionView().findViewById(R.id.badge_text);
        badgeText.setText("99");

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // バッジを消したり、内容を変更したりするテスト
                LinearLayout galleryBadge = (LinearLayout) MenuItemCompat.getActionView(navigationView.getMenu().findItem(R.id.nav_gallery));
                TextView badgeText = (TextView) galleryBadge.findViewById(R.id.badge_text);

                if(badgeText.getText() == "99+") {
                    MenuItemCompat.setActionView(navigationView.getMenu().findItem(R.id.nav_gallery), null);
                } else {
                    badgeText.setText("99+");
                }

            }
        });

f:id:woshidan:20160908001443g:plain

下に固定のfooterをつけたい場合

NavigationViewの中にandroid:layout_gravity="bottom"の指定をつけて要素を追加したらよさそうでした。

<!-- activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
    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:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:openDrawer="start">

    <include
        layout="@layout/app_bar_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/nav_header_main"
        app:menu="@menu/activity_main_drawer" >

        <TextView
            android:text="Footerのテスト"
            android:gravity="center"
            android:layout_gravity="bottom"
            android:background="@color/colorAccent"
            android:padding="16dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </android.support.design.widget.NavigationView>
</android.support.v4.widget.DrawerLayout>
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
            this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawer.setDrawerListener(toggle);
        toggle.syncState();
    }

f:id:woshidan:20160908075544p:plain

追加したFooterとかぶった部分についてスクロールさせたいときはどうするかについては、対処が若干微妙で、アイコン透明なものを用意した選択不可能なメニューをその部分に追加するのが自分が探した中で綺麗な方の解決方法でした....^^;