365連休

にわかのandroidとかの開発メモ。

Android Studio 3.5.0 のナビゲーションドロワーアクティビティを読み解く

経緯

  • 画面の左端をシュッと引っ張った時に出てくるメニューを実装したかった。
  • 右クリック->[新規]->[アクティビティ]->[ナビゲーションドロワーアクティビティ]を追加すればオッケーかと思ったら、大量のファイルが生成される・・・
  • しかも、メニュー項目を選択しても反応しない。バグ?
  • ちゃんと取り組まないとダメだと思い、開発者ドキュメントの解読を始めるも使われている技術が微妙に違うっぽい。
    ※追記:よく見たらドキュメントの後半Enに書いてあった。

 

ファイル構造

  • MainActivity(AppCompatActivity)とレイアウト(activity_main.xml)
  • メニューで遷移可能な6つの子フラグメント(Fragment継承クラス(XXXXFragment)とレイアウト(fragment_xxxx.xml)とViewModel継承クラス(XXXXViewModel)のセット構造)
  • activity_main.xmlへインクルードされるCoordinatorLayoutを含むレイアウト(app_bar_main.xml)
  • activity_main.xmlNavigationView(マテリアルコンポーネント)の属性として設定されるレイアウト(nav_header_main.xml、menu\activity_main_drawer)
  • app_bar_main.xmlにインクルードされるNavHostFragmentを含むレイアウト(content_main.xml)←古典的な名称・用法。activity_main.xmlで全体のレイアウトを定義し、コンテンツのレイアウトとしてcontent_main.xmlをインクルードしていたらしい
  • NavHostFragmentに突っ込むナビゲーショングラフ(mobile_navigation.xml)
  • メニューインフレータに突っ込むmenu\main.xml
  • valuesとstylesは補助的なものだから省略

とにかく多数のファイルがある。

 

大まかにいうと、メインアクティビティがあって、DrawerLayoutが最上位のレイアウト要素である。DrawerLayoutがスワイプメニューを実現する。DrawerLayoutは、メニュー構造であるNavigationViewと、メニュー以外のコンテナであるCoordinatorLayoutを持つ。
CoordinatorLayoutは、ツールバー(AppBarLayout)と、フローティングアクションボタン(FloatingActionButton)と、メニューで遷移する際の子フラグメントのコンテナ(NavHostFragment)を持つ。CoordinatorLayoutはマテリアルデザインを実現するためのコンテナであり、コンテンツを上下にスクロールした際にツールバーとかを連動して動かすための、見た目に特化したコンテナである。

 

メインアクティビティ

  • DrawerLayout ドロワーメニューを実現
    • NavigationView メニュー構造
    • CoordinatorLayout メニュー以外、マテリアルデザインコンテナ
    • FloatingActionButton 画面右下のボタン
    • ConstraintLayout
      • NavHostFragment フラグメントを切り替えるコンテナ

 

 

アプリ(アクティビティ)の機能

f:id:neet_rookie:20190904161417p:plain

ドロワーメニュー / Android4.4エミュレータ

※当方の環境では、ドロワーメニューの項目をタップしても他のフラグメントに遷移できなかった。

※※後述するが、Android Studio 3.5のバグらしい。

 

 

f:id:neet_rookie:20190904161706p:plain

オプションメニュー / Android4.4エミュレータ

 

f:id:neet_rookie:20190904162120p:plain

フローティングボタン / Android4.4エミュレータ

 

読み解いていく

なんとなくファイルを斜め読みし、ほぼ理解していない状態でこの記事を書いている。とりあえず、MainActivityから解読を始める。

MainActivity extends AppCompatActivity

public class MainActivity extends AppCompatActivity {

    private AppBarConfiguration mAppBarConfiguration;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

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

        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        DrawerLayout drawer = findViewById(R.id.drawer_layout);
        NavigationView navigationView = findViewById(R.id.nav_view);
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        mAppBarConfiguration = new AppBarConfiguration.Builder(
                R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow,
                R.id.nav_tools, R.id.nav_share, R.id.nav_send)
                .setDrawerLayout(drawer)
                .build();
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
        NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
        NavigationUI.setupWithNavController(navigationView, navController);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onSupportNavigateUp() {
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
        return NavigationUI.navigateUp(navController, mAppBarConfiguration)
                || super.onSupportNavigateUp();
    }
}

このコードの最大の問題はコメントがほぼ無いこと。
上司にさんざん言われたが、「コードを書く前にコメントを書け」と私も言いたい。
おそらく"言わずもがな"なコードである可能性が高いが、個人的には初体験なので手取り足取り教えて欲しいところ。
適当に加筆する。

 

/**
 * ナビゲーションドロワーアクティビティのテンプレート
 * Android Studio 3.5生成コード
 */
public class MainActivity extends AppCompatActivity {

    /** アプリケーションバーパターンと対話するNavigationUIメソッドの構成オプション */
    private AppBarConfiguration mAppBarConfiguration;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState); //定形文
        setContentView(R.layout.activity_main); //定形文

        //ツールバーの設定(androidx.appcompat.widget.Toolbar)
        Toolbar toolbar = findViewById(R.id.toolbar); //app_bar_main.xml内、(XMLファイルはアクティビティのレイアウトへインクルードされる)
        setSupportActionBar(toolbar); //アクティビティのアクションツールバーとして関連付ける。ナビゲーションメニューとオプションメニューが使用可能になる。

        //フローティングアクションボタンの設定、マテリアルデザインコンポーネント
        FloatingActionButton fab = findViewById(R.id.fab); //app_bar_main.xml内、fabって略し過ぎでは?
        fab.setOnClickListener(new View.OnClickListener() { //フローティングボタンのクリック
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) //スナックバー構築、Toastみたいに画面下部にメッセージを表示する
                        .setAction("Action", null).show(); //setActionの第2引数で任意のメッセージクリック時のアクションを設定できる
            }
        });

        //以下ドロワーメニューの設定
        DrawerLayout drawer = findViewById(R.id.drawer_layout); //AndroidXライブラリのドロワーレイアウト
        NavigationView navigationView = findViewById(R.id.nav_view); //マテリアルデザインコンポーネントのナビゲーションビュー

        //アプリケーションバーを構築する。
        //メニューのトップレベルの宛先となる項目のメニューIDを渡す。
        mAppBarConfiguration = new AppBarConfiguration.Builder(
                R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow,
                R.id.nav_tools, R.id.nav_share, R.id.nav_send)
                .setDrawerLayout(drawer)
                .build();

        //AndroidXライブラリのナビゲーションコントローラオブジェクトを取得する。第2引数はナビゲーションホストのID
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);

        //ナビゲーションコントローラにアクションバーを設定する。画面の切り替わりとアクションバーのタイトルが連動するようになる。
        //AppCompatActivity.getSupportActionBar()で取得できるアクションバーをセットアップし、NavControllerで使用する。
        //AppBarConfigurationはナビゲーションボタンの表示方法を制御する。
        //navigateUp(NavController, AppBarConfiguration)を呼び出し"アップ"ボタンを処理する。
        NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);

        //ナビゲーションコントローラで使用するナビゲーションビューを設定する。
        //メニュー項目が選択されるとonNavDestinationSelected(MenuItem, NavController)が呼び出される。
        //NavigationViewで選択した項目は、宛先が変更されると自動的に更新される。
        //NavigationViewがDrawerLayoutに含まれている場合、メニュー項目の選択と同時にドロワーが閉じる。
        //NavigationViewにBottomSheetBehaviorが関連付けられている場合、メニュー項目を選択すると、下のシートは非表示になる。(BottomSheetダイアログと同様の動作)
        NavigationUI.setupWithNavController(navigationView, navController);
} /** * アクティビティの標準オプションメニューの内容を初期化する。<br/> * オーバーライドで独自のオプションメニューを設定できる。<br/> * メニュー項目をMenuに配置する必要がある。<br/> * これは、オプションメニューが初めて表示される場合に呼び出される。<br/> * メニューが表示されるたびに更新するには{@link #onPrepareOptionsMenu(Menu)}を参照すること。 * @param menu アイテムを配置するオプションメニュー * @return true メニューを表示 / false メニューを表示しない */ @Override public boolean onCreateOptionsMenu(Menu menu) { // メニューをインフレート。アクションバーが存在すれば項目が追加される。 getMenuInflater().inflate(R.menu.main, menu); //メニュー表示 return true; } /** * ユーザがアクションバーから"アップ"ナビゲーションを選択するたびに呼び出される。<br/> * このアクティビティの親が設定(マニフェストまたはエイリアスで)されている場合、"アップ"は自動的に処理される。<br/> * たぶん、サポート処理の実装有無を返すのが目的ではなく、アップ処理そのものだと思う。 * 親を指定するには{@link #getSupportParentActivityIntent()}を参照すること。<br/> * @return true アップナビゲーションを処理 / false それ以外. */ @Override public boolean onSupportNavigateUp() { //AndroidXライブラリのナビゲーションコントローラオブジェクトを取得する。第2引数はナビゲーションホストのID NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment); //たぶん、アップ処理し、成否を返す return NavigationUI.navigateUp(navController, mAppBarConfiguration) //NavControllerにアップ処理をさせる。アップ処理が行われたらtrueが返る || super.onSupportNavigateUp(); //不明。既定のアップ処理? } }

 

Layoutファイル

インクルードするためファイル数が多く、これまた難解なので、アクティビティのレイアウトファイルをひとまとめにしてみる。ついでにコメントも書く。

<?xml version="1.0" encoding="utf-8"?>
<!-- ドロワーメニューを表示可能なレイアウトコンテナ -->
<androidx.drawerlayout.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">

    <!-- ドロワーメニュー 、app:headerLayoutでヘッダ部のレイアウトを指定し、app:menuでメニュー定義を指定している。-->
    <com.google.android.material.navigation.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" />

    <!-- ツールバーとFloatingActionButtonとコンテンツと一体として振る舞わせるコンテナ、たぶん -->
    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <!-- ツールバーのコンテナ -->
        <com.google.android.material.appbar.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">
            <!-- ツールバー -->
            <androidx.appcompat.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" />

        </com.google.android.material.appbar.AppBarLayout>

        <!-- フローティングアクションボタン -->
        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_margin="@dimen/fab_margin"
            app:srcCompat="@android:drawable/ic_dialog_email" />

        <!-- コンテンツ部 -->
        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">
            <!-- フラグメントを切り替えるコンテナ、メニュー操作で切り替える -->
            <fragment
                android:id="@+id/nav_host_fragment"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:defaultNavHost="true"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:navGraph="@navigation/mobile_navigation" />
        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

</androidx.drawerlayout.widget.DrawerLayout>

 

コンポーネントごとにたどってみる

ツールバー

  • DrawerLayout
    • NavigationView
    • CoordinatorLayout
      • AppBarLayout
        • Toolbar
    • FloatingActionButton
    • ConstraintLayout
      • NavHostFragment

レイアウトには、AppBarLayoutというメニューコンテナと実体としてのToolBarがある。

しかし、レイアウトだけでは完結せず、MainActivity#OnCreateにも初期化のコードがある。

        //ツールバーの設定(androidx.appcompat.widget.Toolbar)
        Toolbar toolbar = findViewById(R.id.toolbar); //app_bar_main.xml内、(XMLファイルはアクティビティのレイアウトへインクルードされる)
        setSupportActionBar(toolbar); //アクティビティのアクションツールバーとして関連付ける。ナビゲーションメニューとオプションメニューが使用可能になる。

AppCompatActivityやDrawerLayoutに自動的に組み込まれるものではないことが分かる。
試しにsetSupportActionBarをコメントアウトすると、ツールバーが表示されなくなる。

 

f:id:neet_rookie:20190909104751p:plain

消えるツールバー、ただし表示領域は確保されている / Android4.4エミュレータ

 

ツールバーはアプリ設定的な方法でOn/Off制御をしているのかと勝手に思っていたが、どうも違うらしい。ツールバーという画面要素をAppCompatActivityが提供しているらしい。「ほんとうに違うのか?」は趣旨と違うので今回は調査しない。

なお、ツールバーの初期化コードでfindViewByIdしたtoolbar変数は再利用されない。

 

フローティングアクションボタン

  • DrawerLayout
    • NavigationView
    • CoordinatorLayout
      • AppBarLayout
        • Toolbar
    • FloatingActionButton
    • ConstraintLayout
      • NavHostFragment
        //フローティングアクションボタンの設定、マテリアルデザインコンポーネント
        FloatingActionButton fab = findViewById(R.id.fab); //app_bar_main.xml内、fabって略し過ぎでは?
        fab.setOnClickListener(new View.OnClickListener() { //フローティングボタンのクリック
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) //スナックバー構築、Toastみたいに画面下部にメッセージを表示する
                        .setAction("Action", null).show(); //setActionの第2引数で任意のメッセージクリック時のアクションを設定できる
            }
        });

どうやら、ただのボタンのようである。

ツールバーとフローティングアクションボタンはCoordinatorLayoutで制御するための専用コンポーネントっぽい。

なお、フローティングアクションボタンの初期化コードでfindViewByIdしたfab変数は再利用されない。

 

オプションメニュー

アクティビティのレイアウトファイルには存在していないが、画面右上の3点コロンをタップすると"Settings"という項目が出現する。これは、Activity#onCreateOptionsMenuで初期化される。

    /**
     * アクティビティの標準オプションメニューの内容を初期化する。<br/>
     * オーバーライドで独自のオプションメニューを設定できる。<br/>
     * メニュー項目をMenuに配置する必要がある。<br/>
     * これは、オプションメニューが初めて表示される場合に呼び出される。<br/>
     * メニューが表示されるたびに更新するには{@link  #onPrepareOptionsMenu(Menu)}を参照すること。
     * @param menu アイテムを配置するオプションメニュー
     * @return true メニューを表示 / false メニューを表示しない
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // メニューをインフレート。アクションバーが存在すれば項目が追加される。
        getMenuInflater().inflate(R.menu.main, menu);
        //メニュー表示
        return true;
    }

MenuInflaterでインフレートされるメニュー(main.xml)は次の通り。
※メニューはファイル名が参照名になる。R.menu.main⇒main.xml

<?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/action_settings"
        android:orderInCategory="100"
        android:title="@string/action_settings"
        app:showAsAction="never" />
</menu>

試しにitemタグを2つ追加してみる。片方はorderInCategoryを変えてみる。

<?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/action_settings"
        android:orderInCategory="100"
        android:title="@string/action_settings"
        app:showAsAction="never" />
    <item
        android:id="@+id/action_settings2"
        android:orderInCategory="100"
        android:title="裏メニュー"
        app:showAsAction="never" />
    <item
        android:id="@+id/action_settings3"
        android:orderInCategory="99"
        android:title="裏の裏メニュー"
        app:showAsAction="never" />
</menu>

 

f:id:neet_rookie:20190909112055p:plain

オプションメニューをカスタマイズしてみた結果。 / Android4.4エミュレータ

 ちゃんと項目が増えていて、orderInCategoryに従ってソートされることもわかる。

 

しかし、オプションメニューが表示されるものの、タップしても反応しない。

これはオプションメニュー選択のイベントハンドラを実装していないためである。

実装するにはActivity#onOptionsItemSelectedをオーバーライドする。

    /**
     * オプションメニューの選択。
     * @return イベントの伝搬に影響がある。true メニューを処理 / false メニューを処理しない
     */
    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch(item.getItemId()){
            case R.id.action_settings:{
                Toast toast=Toast.makeText(this, "Settingsが選択された", Toast.LENGTH_LONG);
                toast.show();
                return true;}
            case R.id.action_settings2:{
                Toast toast=Toast.makeText(this, "裏メニューが選択された", Toast.LENGTH_LONG);
                toast.show();
                return true;}
            case R.id.action_settings3: return true;
            default: return false;
        }
        //return super.onOptionsItemSelected(item);
    }

 

f:id:neet_rookie:20190909113857p:plain

追加したオプションメニューをタップし、メッセージが表示される / Android4.4エミュレータ

トグル方式のメニュー項目であれば簡単に実装できそうだが、何かを設定するような画面を表示したい場合、ダイアログを出すかあるいは別なFragmentやActivityへ遷移する必要がある。

 

ドロワーメニュー

  • DrawerLayout
    • NavigationView
    • CoordinatorLayout
      • AppBarLayout
        • Toolbar
    • FloatingActionButton
    • ConstraintLayout
      • NavHostFragment

 レイアウト階層から分かるように複数の要素が関係している。

DrawerLayoutがユーザの操作に応じてメニューを表示し、

表示されるメニューはNavigationViewが処理し、

NavHostFragmentは子画面としてフラグメントを表示するコンテナである。

次に初期化コードを見てみる。

        //以下ドロワーメニューの設定
        DrawerLayout drawer = findViewById(R.id.drawer_layout); //AndroidXライブラリのドロワーレイアウト
        NavigationView navigationView = findViewById(R.id.nav_view); //マテリアルデザインコンポーネントのナビゲーションビュー

        //アプリケーションバーを構築する。
        //メニューのトップレベルの宛先となる項目のメニューIDを渡す。
        mAppBarConfiguration = new AppBarConfiguration.Builder(
                R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow,
                R.id.nav_tools, R.id.nav_share, R.id.nav_send)
                .setDrawerLayout(drawer)
                .build();

        //AndroidXライブラリのナビゲーションコントローラオブジェクトを取得する。第2引数はナビゲーションホストのID
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);

        //ナビゲーションコントローラにアクションバーを設定する。画面の切り替わりとアクションバーのタイトルが連動するようになる。
        //AppCompatActivity.getSupportActionBar()で取得できるアクションバーをセットアップし、NavControllerで使用する。
        //AppBarConfigurationはナビゲーションボタンの表示方法を制御する。
        //navigateUp(NavController, AppBarConfiguration)を呼び出し"アップ"ボタンを処理する。
        NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);

        //ナビゲーションコントローラで使用するナビゲーションビューを設定する。
        //メニュー項目が選択されるとonNavDestinationSelected(MenuItem, NavController)が呼び出される。
        //NavigationViewで選択した項目は、宛先が変更されると自動的に更新される。
        //NavigationViewがDrawerLayoutに含まれている場合、メニュー項目の選択と同時にドロワーが閉じる。
        //NavigationViewにBottomSheetBehaviorが関連付けられている場合、メニュー項目を選択すると、下のシートは非表示になる。(BottomSheetダイアログと同様の動作)
        NavigationUI.setupWithNavController(navigationView, navController);

まずレイアウト参照を取得し、
メニューを構築し、
ナビゲーションコントローラとアクティビティのツールバーを関連付け、
ナビゲーションコントローラにナビゲーションビューを関連付ける。

ここで注意しなければいけないのは、メニュー項目選択のリスナーを設定しない点である。
通常NavigationView#setNavigationItemSelectedListenerを呼び出しOnNavigationItemSelectedListenerを設定し、自身でフラグメントを切り替えるが、NavigationUIがもろもろうまくやってくれる。

NavigationUIがうまくやるための条件として、メニューID(activity_main_drawer.xml)とナビゲーショングラフ内(mobile_navigation.xml)のフラグメントIDを同一にしなければならない。

※ナビゲーショングラフとは、アクティビティの画面(宛先)一覧を定義するxmlファイル。

 

f:id:neet_rookie:20190909132323p:plain

入力補助に同じIDの候補が表示される / Android Studio 3.5 エディタ

 

f:id:neet_rookie:20190909151928p:plain

ナビゲーショングラフの編集 / Android Studio 3.5 エディタ

IDが同じであれば、メニューの定義と初期化を正しく行うだけで、メニューからの遷移が実装できるということ。

 

 

 

これで、一通り説明が終わったが、大きな問題がある。

 

それは、メニューをタップしても遷移できないということ(--〆)

 

バグを修正して動かす

そこでググってみると、どうもAndroid Studio3.5のNavigation Drawer Activityのテンプレートはバグがあるらしい。

stackoverflow.com

 

翻訳にかけて重要そうなとこを解読してみると、

「レイアウトファイル内の、NavigationDrawerの下のNavigationViewをinclude後へ移動せよ」って書いてあるっぽい。

そこでレイアウトファイルを次の通り修正。※位置が入れ替わるだけだがコピペ用に全文掲載する。

<?xml version="1.0" encoding="utf-8"?>
<!-- ドロワーメニューを表示可能なレイアウトコンテナ -->
<androidx.drawerlayout.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">

    <!-- ツールバーとFloatingActionButtonとコンテンツと一体として振る舞わせるコンテナ、たぶん -->
    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <!-- ツールバーのコンテナ -->
        <com.google.android.material.appbar.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">
            <!-- ツールバー -->
            <androidx.appcompat.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" />

        </com.google.android.material.appbar.AppBarLayout>

        <!-- フローティングアクションボタン -->
        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_margin="@dimen/fab_margin"
            app:srcCompat="@android:drawable/ic_dialog_email" />

        <!-- コンテンツ部 -->
        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">
            <!-- フラグメントを切り替えるコンテナ、メニュー操作で切り替える -->
            <fragment
                android:id="@+id/nav_host_fragment"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:defaultNavHost="true"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:navGraph="@navigation/mobile_navigation" />
        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

    <!-- ドロワーメニュー 、app:headerLayoutでヘッダ部のレイアウトを指定し、app:menuでメニュー定義を指定している。-->
    <com.google.android.material.navigation.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" />

</androidx.drawerlayout.widget.DrawerLayout>

これを実行してみると・・・

 

f:id:neet_rookie:20190909134502p:plain

画面遷移とバックスタック / Android4.4エミュレータ

 子フラグメントの中身がタイトルのみなので切り替わりが分かりにくいが、正常に操作できるようになった。

またメニューで遷移した際のバックスタックは、
Homeフラグメントが開始画面(宛先)となり、
他のフラグメントに遷移すると開始画面の次画面としてバックスタックに積まれ、
戻る操作で開始画面へと戻る。

メニュー項目で遷移しまくってもバックスタックは常に[開始画面]⇒[遷移先]となる。開発者ドキュメントによると、バックスタックの制御は自由にできるらしいが、ここでは説明しない。

 

NavigationUIが目に見えない処理をしているので分かりにくいサンプルアクティビティだったが、以上で大体読み解くことができたと思う。

メニューはメニューの流儀(原則)に従わないといけないようなので、開発者ドキュメントのコアトピックのナビゲーションのあたりは一通り読む必要がある。NavigationUIについてもコアトピック後半で出てくるので、いずれ意訳するかもしれない。

 

 

Thank you.

 

 

※上記の各ソースコードは、Android Studio 3.5で提供されるアクティビティのテンプレートの一つ「Navigation Drawer Activity ナビゲーションドロワーアクティビティ」である。

Android Open Source Projectのライセンス解釈
ソースコードから抽出されたドキュメントやコードはAndroid Open Source Projectの優先ライセンスであるApache 2.0 licenseが適用される。
よって、ページ内のコード転載についてはApache 2.0 licenseにおける頒布行為にあたると解釈している。

 

関連リンク:「NavigationUIを使用している場合に、メニューから遷移可能な宛先画面(Destination)をリロードする。」

neet-rookie.hatenablog.com