iOSエンジニアのつぶやき

毎朝8:30に iOS 関連の技術について1つぶやいています。まれに釣りについてつぶやく可能性があります。

知識ゼロからの Kotlin Android アプリリリースへの軌跡 / Day11【Define navigation paths編】

学ぶこと

  • navigation graphs の使用方法
  • アプリで navigation path を定義する方法
  • Up button とは何か、および button を追加する方法
  • オプションメニューの作成方法
  • navigation drawer を作成する方法

すること

  • navigation library と Navigation Editor を使用して、fragment の navigation graph を作成します。
  • アプリに navigation paths を作成する。
  • オプションメニューを使用して navigation を追加します。
  • ユーザーがアプリ内のどこからでもタイトル画面に戻ることができるように、Up button を実装します。
  • navigation drawer メニューを追加します。

アプリ概要

このコードラボでは、AndroidTrivia アプリを次の方法で更新します。

  • アプリの navigation graph を作成します。
  • タイトル画面とゲーム画面の navigation を追加します。
  • 画面をアクションに接続し、Play をタップしてゲーム画面に移動する方法をユーザに提供します。
  • 一部の画面の上部に左矢印として表示される Up button を追加します。

プロジェクトに Navigation components を追加する

Navigation の依存関係を追加する

navigation library を使用するには、Gradle ファイルに navigation の依存関係を追加する必要があります。

  1. Android Studio で AndroidTrivia アプリを開きます。

  2. Gradle Scripts フォルダーを開きます。プロジェクトレベルの build.gradle ファイルをダブルクリックしてファイルを開きます。

  1. プロジェクトレベルの build.gradle ファイルの上部に、他の ext 変数と共に、navigationVersion の変数を追加します。最新の Navigation version number を確認するには、Android 開発者向け Declaring dependencies をご覧ください。

  2. Gradle Scripts フォルダーで、モジュールレベルの build.gradle ファイルを開きます。次に示すように、navigation-fragment-ktxnavigation-ui-ktx の依存関係を追加します。

  3. Peoject を rebuild します。

プロジェクトに navigation graph を追加する

  1. res フォルダーで、New > Android Resource File を選択します。

  2. New Resource File ダイアログで、Resource type として Navigation を選択します。

  3. File name フィールドで、navigation と名付けます。

  4. Chosen qualifiers ボックスが空であることを確認して、OK をクリックします。新しいファイル navigation.xmlres > navigation フォルダーに表示されます。

  1. res > navigation > navigation.xml ファイルを開き、Design タブをクリックして Navigation Editor を開きます。レイアウトエディターに No NavHostFragments found というメッセージが表示されることに注意してください。この問題は次のタスクで修正します。

NavHostFragment を作成する

navigation host Fragment は、Navigation graph の fragment host として機能します。navigation host の fragment は通常、NavHostFragment という名前です。

ユーザーが navigation graph で定義された目的地間を移動すると、navigation host の Fragment は必要に応じて Fragment を入れ替えます。Fragment は、適切な Fragment バックスタックも作成および管理します。

このタスクでは、コードを変更して TitleFragmentNavHostFragment に置き換えます。

  1. res > layout > activity_main.xml を開き、Code タブを開きます。

  2. activity_main.xml ファイルで、既存のタイトル Fragment の名前を androidx.navigation.fragment.NavHostFragment に変更します。

  3. id を myNavHostFragment に変更します。

  4. Navigation host Fragment は、使用する navigation graph リソースを認識する必要があります。app:navGraph attribute を追加して、@navigation/navigation に設定します。

  5. app: defaultNavHost attribute を追加して、"true" に設定します。これで、この navigation がデフォルトの host になり、システムの Back button をインターセプトします。

activity_main.xml レイアウトファイル内で、fragment は次のようになります。

<!-- The NavHostFragment within the activity_main layout -->
            <fragment
                android:id="@+id/myNavHostFragment"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:navGraph="@navigation/navigation"
                app:defaultNavHost="true" />

Navigation Graph に Fragment を追加する

このタスクでは、タイトルの Fragment とゲームの Fragment をアプリの Navigatio Graph に追加します。Fragment を相互に接続します。次に、クリックハンドラーを Play ボタンに追加して、ユーザがタイトル画面からゲーム画面に移動できるようにします。

Navigation Graph に2つの Fragment を追加し、それらをアクションに接続します

  1. navigation リソースフォルダーから navigation.xml を開きます。Navigation Editornew Destication ボタンをクリックします。Fragment と Activities のリストが表示されます。

  1. fragment_title を選択します。TitleFragment Fragment はアプリユーザがアプリを最初に開いた時に起動する場所であるため、最初に fragment_title を追加します。

  1. New Destination ボタンを使用して、GameFragment を追加します。

preview に "Preview Unavailable" というメッセージが表示された場合は、Code タブをクリックして navigation XML を開きます。次に示すように、gameFragmentfragment 要素に tools:layout="@layout/fragment_game" が含まれていることを確認してください。

<!-- The game fragment within the navigation XML, complete with tools:lay
out. -->
<fragment
   android:id="@+id/gameFragment"
   android:name="com.example.android.navigation.GameFragment"
   android:label="GameFragment"
   tools:layout="@layout/fragment_game" />
  1. Layout Editor で、Game Fragment を右にドラッグして、Title Fragment と重ならないようにします。

  1. preview で Title Fragment にポインターを合わせます。Fragment View の右側に円形の接続ポイントが表示されます。接続ポイントをクリックして、Game Fragment preview にドラッグします。2つの Fragment を接続する Action が作成されます。

  2. Actions's attribute を表示するには、2つの Fragment を結ぶ線をクリックします。Attributes ペインで、action's ID が action_titleFragment_to_gameFragment に設定されていることを確認します。

Play Button にクリックハンドラーを追加する

Title Fragment は、action によって Game Fragment に接続されます。次に、タイトル画面の Play ボタンでユーザをゲーム画面に移動させます。

  1. Android StudioTitleFragment.kt ファイルを開きます。onCreateView() メソッド内で、return ステートメントの前に次のコードを追加します。
binding.playButton.setOnClickListener{}
  1. setOnClickListener 内に、binding class を通じて Play button にアクセスするコードを追加し、Game Fragment に移動します。
//The complete onClickListener with Navigation
binding.playButton.setOnClickListener { view : View -> 
       view.findNavController().navigate(R.id.action_titleFragment_to_gameFragment)
}
  1. アプリをビルドし、必要なインポートが全て含まれていることを確認します。たとえば、次の行を TitleFragment.kt ファイルに追加する必要がある場合があります。
import androidx.navigation.findNavController
  1. アプリを実行し、タイトル画面の Play button をタップします。ゲーム画面が開きます。

Conditional Navigation を追加する

このステップでは、conditional Navigation(条件付き Navigation) を追加します。これは、特定のコンテキストでのみユーザが使用できる Navigation です。conditional navigation の一般的な使用例は、ユーザがログインしているかどうかに応じて、アプリのフローが異なる場合です。

アプリは別のケースです: アプリは、ユーザが全ての質問に正しく答えたかどうかに基づいて、別のフラグメントに移動します。

スターターコードには、conditional navigation で使用する2つの fragment が含まれています:

  • GameWonFragment は、ユーザに "Congratulations!" メッセージを示す画面を表示します。
  • GameOverFragment は、ユーザに "Try Again" メッセージを表示する画面を表示します。

Navigation graph に GameWonFragmentGameOverFragment を追加する

  1. navigation フォルダーにある navigation.xml ファイルを開きます。

  2. game-over Fragment を navigation graph に追加するには、Navigation Editor で New Destination ボタンをクリックします、fragment_game_over を選択します。

  1. レイアウトエディターの Preview 領域で、game-over Fragment を game Fragment の右側にドラッグして、2つがオーバーラップしないようにします。必ず game-over Fragment の ID attribute を gameOverFragmen に変更してください。

  2. game-won Fragment を navigation graph に追加するには、New Destination ボタンをクリックして、fragment_game_won を選択します。

  1. game-won Fragment を game-over Fragment の下にドラッグして、2つが重ならないようにします。game-won Fragment の ID attribute の名前を gameWonFragment にしてください.

Layout Editor は次のスクリーンショットのようになります:

ゲームの Fragment をゲーム結果の Fragment に接続する

このステップでは、game Fragment を、game-won Fragment と game-over Fragment の両方に接続します。

  1. Layout Editor のプレビュー領域で、円型の connection point が表示されるまで、ポインターを game Fragment の上におきます。

  2. connection point をクリックして、game-over Fragment にドラッグします。青い接続線が表示され、game Fragment を game-over Fragment に接続するアクションを表します。

  3. 同様に、game Fragment を game-won Fragment に接続するアクションを作成します。Layout Editor は次のスクリーンショットのようになります。

  1. Preview で、game Fragment と game-won Fragment を結ぶ線の上にポインタを置きます。アクションの ID が自動的に割り当てられていることに注意してください。

Fragment 間を移動するコードを追加する

GameFragment は、ゲームの質問と回答を含む Fragment クラスです。このクラスには、ユーザがゲームに勝利するか敗北するかを決定するロジックも含まれています。プレーヤーの勝敗に応じて、GameFragment クラスに conditional navigation を追加する必要があります。

  1. GameFragment.kt ファイルを開きます。onCreateView() メソッドは、プレーヤーが勝ったか負けたかを決定する if / else 条件を定義します。
binding.submitButton.setOnClickListener @Suppress("UNUSED_ANONYMOUS_PARAMETER")
        { 
              ...
                // answer matches, we have the correct answer.
                if (answers[answerIndex] == currentQuestion.answers[0]) {
                    questionIndex++
                    // Advance to the next question
                    if (questionIndex < numQuestions) {
                        currentQuestion = questions[questionIndex]
                        setQuestion()
                        binding.invalidateAll()
                    } else {
                        // We've won!  Navigate to the gameWonFragment.
                    }
                } else {
                    // Game over! A wrong answer sends us to the gameOverFragment.
                }
            }
        }
  1. ゲームに勝利した時の else 条件内に、gameWonFragment に移動する次のコードを追加します。アクション名(action_gameFragment_to_gameWonFragment)が、navigation.xml ファイルを設定されているものと正確に一致していることを確認してください。
// We've won!  Navigate to the gameWonFragment.
view.findNavController()
   .navigate(R.id.action_gameFragment_to_gameWonFragment)
  1. ゲームに敗北した時の else 条件内に、gameOverFragment に移動する次のコードを追加します。
// Game over! A wrong answer sends us to the gameOverFragment.
view.findNavController().
   navigate(R.id.action_gameFragment_to_gameOverFragment)
  1. アプリを実行し、質問に答えてゲームをプレイします。3つの質問全て正しく答えると、アプリは GameWonFragment に移動します。

答えが間違っている場合、アプリはすぐに GameOverFragment に移動します。

Android システムの Back button は、上のスクリーンショットでは ① と表示されています。ユーザが game-won Fragment または game-over Fragment の BackButton を押すと、アプリは、質問画面に移動します。理想的には、BackButton はアプリのタイトル画面に戻る必要があります。次のタスクで Back Button の遷移先を変更します。

戻るボタンの遷移先を変更する

Android システムは、ユーザが Android 搭載デバイスでナビゲートした場所を追跡します。ユーザがデバイスの新しい宛先に移動するたびに、Android はその宛先を back stack に追加します。

ユーザが Back button を押すと、アプリは back stack の斎場にある宛先に移動します。デフォルトでは、back stack の最上位は、ユーザが最後に表示した画面です。以下に示すように、Back button は通常、画面下部の左端のボタンです。(Back button の正確な外観は、デバイスによって異なります。)

これまでは、Navigation Controller にバックスタックを処理させていました。ユーザがアプリの目的地に移動すると、 Android はこの目的地をバックスタックに追加します。

AndroidTrivia アプリでは、ユーザが GameOverFragment または GameWonFragment 画面から Back button を押すと、GameFragment に戻ります。しかし、ゲームが終わったので、ユーザを GameFragment に送りたくありません。ユーザはゲームを再開できますが、より良い体験は、タイトル画面に戻ることです。

Navigation アクションは、back stack を変更できます。このタスクでは、GameFragment から移動するアクションを変更して、アクションが back stack から GameFragment を削除するようにします。ユーザがゲームに勝った時、負けた時に Back Button を押すと、アプリは GameFragment をスキップして TitleFragment に戻ります。

navigation action のポップ動作を設定する

このステップでは、ユーザが GameWonGameOver 画面にいる時に Back Button を押すとタイトル画面に戻るように、back stack を管理します。Fragment を接続するアクションの "pop" 動作を設定して、back stack を管理します:

  • アクションの popUpTo 属性は、navigate する前に、指定された宛先に back stack をポップアップします。(宛先は back stack から削除されます。)

  • popUpToInclusive attribute が false または設定されていない場合、popUpTp は指定された宛先まで宛先を削除しますが、指定された宛先を back stack に残します。

  • popUpToInclusivetrue に設定されている場合、popUpTo 属性は、指定された宛先までの全ての宛先を back stack から削除します。

  • popUpToInclusivetrue で、popUpTo がアプリの開始場所に設定されている場合、アクションは全てのアプリの宛先をバックスタックから削除します。Back Button を押すと、ユーザはアプリから離れます。

このステップでは、前のタスクで作成した2つのアクションの popUpTo 属性を設定します。これを行うには、Layout Editor の Attributes ペインの Pop To フィールドを使用します。

  1. res > navigation フォルダにある navigation.xml を開きます。navigation graph がレイアウトエディターに表示されない場合は、Design タブに切り替えます。

  2. gameFragment から gameOverFragment に移動するアクションを選択します。(プレビュー領域では、アクションは2つの Fragment を結ぶ青い線で表されます。)

  3. Attributes ペインで、Pop TogameFragment に設定します。inclusive チェックボックスを選択します。

これにより、popUpTo および popUpTpInclusive attribute が XML に設定されます。attributes は、Navigation Component に、back stack から GameFragment までの Fragment を削除するように指示します。(これは、Pop To フィールドを titleFragment に設定し、Inclusive チェックボックスをオフにするのと同じ効果があります。)

  1. gameFragment から gameWonFragment に移動するアクションを選択します。再度 Attributes ペインで Pop TogameFragment に設定し、Inclusive チャックボックスを選択します。

  1. アプリを実行してゲームをプレイし、Back button を押します。勝っても負けても Back button をクリックすると TitleFragment に戻ります。

Navigation Action を追加し、onClick ハンドラーを追加します

現在、アプリには次のユーザーフローがあります:

  • ユーザがゲームをプレイして勝ちまたは負け、アプリが GameWonGameOver 画面に移動します。
  • この時点でユーザがシステムの Back button を押すと、アプリは TitleFragment に移動します。

このステップでは、ユーザーフローにさらに2つのステップ実装します:

  • ユーザが Next Match または Try Again ボタンをタップすると、アプリは gameFragment 画面に移動します。

  • この時点でユーザがシステムの Back button を押すとアプリは TitleFragment 画面に移動します。

このユーザーフローを作成するには、PopUpTo attributes を使用して、back stack を管理します:

  1. navigation.xml ファイル内に、gameOverFragmentgameFragment に接続する navigation action を追加します。アクションの ID のフラグメント名が、XML のフラグメント名と一致していることを確認してください。例えば、アクション の ID は action_gameOverFragment_to_gameFragment のようになります。

  1. Attributes ペインで、アクションの Pop To attribute を titleFragment に設定します。

  2. back stack から削除される宛先に titleFragment を含めたくないので、Inclusive チェックボックスをクリアします。代わりに、TitleFragment までの全てを back stack から削除します。

  1. navigation.xml ファイル内に、gameWonFragmentgameFragment に接続する Navigation Action を追加します。

  1. 作成したアクションの Pop To attribute を titleFragment に設定し、Inclusive チェックボックスをオフにします。

次に、Try Again ボタンと Next Match ボタンに機能を追加します。ユーザがいずれかのボタンをタップした時に、アプリが GameFragment 画面に移動して、ユーザがゲームを再開できるようにする必要があります。

  1. GameOverFragment.kt Kotlin ファイルを開きます。onCreateView() メソッドの最後の return ステートメントの前に、次のコードを追加します。コードは、Try Again ボタンにクリックリスナーを追加します。ユーザがボタンをタップすると、アプリはゲームのフラグメントに移動します。
// Add OnClick Handler for Try Again button
        binding.tryAgainButton.setOnClickListener{view: View->
        view.findNavController()
                .navigate(R.id.action_gameOverFragment_to_gameFragment)}
  1. GameWinFragment.kt Kotlin ファイルを開きます。onCreateView() メソッドの最後の return ステートメントの前に、次のコードを追加します。
// Add OnClick Handler for Next Match button
        binding.nextMatchButton.setOnClickListener{view: View->
            view.findNavController()
                    .navigate(R.id.action_gameWonFragment_to_gameFragment)}
  1. アプリを実行してゲームをプレイし、Next MatchTry Again ボタンをテストします。どちらのボタンでもゲーム画面に戻るので、もう一度ゲームを試すことができます。

  2. ゲームに勝利または敗北した後、Next Match または Try Again をタップします。ここで、システムの Back button をタップします。アプリは、元の画面に戻るのではなく、タイトル画面に移動する必要があります。

アプリバーに上ボタンを追加する

App bar

app bar と呼ばれることもあるアプリバーは、アプリのブランディングアイデンティティのための専用スペースです。例えば、アプリバーの色を設定できます。アプリバーを使用すると、オプションメニューなどの使い慣れたナビゲーション機能にアクセスできます。アプリバーからオプションメニューにアクセスするには、縦に3つ並んだドットのアイコンをタップします。

アプリバーには、各画面で変更できるタイトル文字列が表示されます。AndroidTrivia アプリのタイトル画面では、アプリバーに "Android Trivia" と表示されます。質問画面では、タイトル文字列はユーザがどの質問をしているかも示します。("1/3", "2/3", "3/3")

Up button

現在アプリでは、ユーザはシステムの Back button を使用して前の画面に移動します。ただし、Android アプリには、アプリバーの左上に表示される画面上ボタンもあります。

AndroidTrivia アプリでは、タイトル画面を除く全ての画面に Up button を表示する必要があります。タイトル画面はアプリの画面階層の最上位にあるため、ユーザがタイトル画面に到達すると Up button は表示されなくなります。

Up button と Back button:

  • 下のスクリーンショットで 1 として示されている Up button がアプリバーに表示されます。
  • Up button は、画面間の階層関係に基づいて、アプリ内を移動します。Up button は、ユーザをアプリの外に移動させることはありません
  • 下のスクリーンショットで 2 として示されている Back button は、開いているアプリに関係なく、System Navigation Bar に表示されるか、デバイス自体の機械的なボタンとして表示されます。
  • Back button は、ユーザが最近操作した画面を逆方向に移動します(back stack)

詳細については、Designing Back and Up navigation を参照してください。

Up button のサポートを追加する

Navigation Component には、NavigationUI と呼ばれる UIライブラリが含まれています。Navigation Controller はアプリバーと統合して、Up button の動作を実装するので、自分で行う必要はありません。

次のステップでは、navigation controller を使用して、Up button をアプリに追加します:

  1. MainActivity.kt ファイルを開きます。onCreate() メソッド内に、Navigation Controller オブジェクトを検索するコードを追加します。
val navController = this.findNavController(R.id.myNavHostFragment)
  1. また、onCreate() メソッド内に、Navigation Controller をアプリバーにリンクするコードを追加します。
NavigationUI.setupActionBarWithNavController(this,navController)
  1. onCreate() メソッドの後で、onSupportNavigateUp() メソッドをオーバーライドして、navigation controller で navigateUp() を呼び出します。
override fun onSupportNavigateUp(): Boolean {
        val navController = this.findNavController(R.id.myNavHostFragment)
        return navController.navigateUp()
    }
  1. アプリを実行します。Up button は、タイトル画面を除く全ての画面のアプリバーに表示されます。アプリのどこにいても、Up button をタップすると、タイトル画面に移動します。

オプションメニューを追加する

Android には、オプションメニューなど、様々な種類のメニューがあります。最近の Androidバイスでは、ユーザはアプリバーに表示される3つの縦のドットをタップしてオプションメニューにアクセスします。

このタスクでは、オプションメニューにバージョン情報メニュー項目を追加します。ユーザが About メニュー項目をタップすると、アプリは AboutFragment に移動し、アプリの使用方法に関する情報が表示されます。

AboutFragment を Navigation Graph に追加します

  1. navigation.xml ファイルを開き、Design タブをクリックして、Navigation Graph を表示します。

  2. New Destination ボタンをクリックして、fragment_about を選択します。

  1. Layout Editor で、"about" Fragment を左にドラッグして、他の Fragment と重ならないようにします。Fragment の IDabountFragment であることを確認してください。

オプションメニューリソースを追加する

  1. res フォルダーを右クリックし、New > Android Resource File を選択します。

  2. New Resource File ダイアログで、ファイルに option_menu という名前をつけます。

  3. Resource Type として Menu を選択し、OK をクリックします。

  1. res > menu フォルダーから option_menu.xml ファイルを開き、Design タブをクリックして Layout Editor を表示します。

  2. Palette ペインから Menu Item をドラッグし、デザインエディターの任意の場所にドロップします。menu item がプレビューと Component Tree に表示されます。

  1. プレビューまたは Component Tree で、メニュー項目をクリックして、Attribute ペインに attributes を表示します。

  2. menu item's の ID を aboutFragment に設定します。タイトルを @string/about に設定します。

Tip:

追加したメニュー項目の ID が、Navigation Graph に追加した AboutFragment の ID と完全に同じであることを確認してください。これにより、onClick ハンドラーのコードが遥かに簡単になります。

onClick ハンドラーを追加する

このステップでは、ユーザが About メニュー項目をタップした時の動作を実装するコードを追加します。

  1. TitleFragment.kt Kotlin ファイルを開きます。onCreateView() メソッド内で、return 前に、setHasOptionsMenu() メソッドを呼び出し、true を渡します。
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                         savedInstanceState: Bundle?): View? {
   ...
   setHasOptionsMenu(true)
   return binding.root
}
  1. onCreateView() メソッドの後で、onCreateOptionsMenu() メソッドをオーバーライドします。メソッドで、オプションメニューを追加し、メニューリソースファイルを拡張します。
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.options_menu, menu)
}
  1. onOptionsItemSelected() メソッドをオーバーライドして、メニュー項目がタップされた時に適切なアクションを実行します。この場合のアクションは、選択したメニュー項目と同じ id を持つ Fragment に移動することです
override fun onOptionsItemSelected(item: MenuItem): Boolean {
     return NavigationUI.
            onNavDestinationSelected(item,requireView().findNavController())
            || super.onOptionsItemSelected(item)
}
  1. アプリがビルドされない場合は、コード内の未解決の参照を修正するためにパッケージをインポートする必要があるかどうかを確認してください。例えば、いくつかの参照を解決するために importandroid.view.* を追加できます。(そして import android.view.ViewGroup などのより具体的なインポートを置き換えます。)

  2. アプリを実行し、オプションメニューにある About メニューをテストします。メニュー項目をタップすると、アプリは "About" 画面に移動します。

Navigation Drawer を追加する

このタスクでは、AndroidTrivia アプリに Navigation drawer を追加します。Navigation Drawer は、画面の端からスライドして出るパネルです。ドロワーには通常、ヘッダーとメニューが含まれています。

phone-size デバイスでは、使用しない時は Navigation drawer は非表示になります。2種類のユーザアクションにより、navigation drawer を表示できます。

  • drawer は、ユーザが画面の開始端から終了端に向かってスワイプすると表示されます。AndroidTrivia アプリでは、ユーザが左から右にスワイプすると Navigation drawer が表示されます。

  • drawer は、ユーザがアプリの開始先にいて、アプリバーのドロワーアイコンをタップすると表示されます。(drawer は、nav drawer button または humburger icon と呼ばれることもあります。)

以下のスクリーンショットは、開いている Navigation Drawer を示しています。

navigation drawer は、Material Components for Android、またはマテリアルライブラリの一部です、マテリアルライブラリを使用して、Googleマテリアルデザインガイドラインの一部であるパターンを実装します。

AndroidTrivia アプリでは、Navigation drawer に2つのメニュー項目が含まれます。最初の項目は既存の"about" Fragment を指し、二番目の項目は新しい"rule" Fragment を指します。

マテリアルライブラリをプロジェクトに追加する

  1. アプリレベルの Gradle build ファイルで、マテリアルライブラリの依存関係を追加します:
dependencies {
    ...
    implementation "com.google.android.material:material:$supportlibVersion"
    ...
}
  1. プロジェクトを同期します。

Destination Fragment に ID があることを確認してください

navigation drawer には2つのメニュー項目があり、それぞれが Navigation Drawer から到達できるフラグメンを表します。両方の Destination は、Navigation Graph で ID を持っている必要があります。

AboutFragment の Navigation Graph には既に ID がありますが、RulesFragment にはないので、ここで追加します。

  1. fragment_rules.xml レイアウトファイルを開いて、ファイルの外観を確認します。Desing タブをクリックして、デザインエディタでプレビューを確認します。

  2. Navigation Editor で、navigation.xml ファイルを開きます。New Destination ボタンをクリックして、rules Fragment を追加します。その IDrulesFragment に設定します。

Drawer メニューと Drawer レイアウトを作成する

navigation drawer を作成するには、navigation menu を作成します。レイアウトファイルの DrawerLayout 内に View を配置する必要もあります。

  1. drawer メニューを作成します。res フォルダーを右クリックし、New Resource File を選択します。ファイルに navdrawer_menu という名前を付け、リソースタイプをMenu に設定して、OK をクリックします。

  1. res > menu フォルダーから、navdrawer_menu.xml を開き、Design タブをクリックします。2つの menu item を Palette ペインから Component Tree にドラッグして追加します。

  2. 最初のメニュー項目の idrulesFragment に設定します。(メニュー項目の ID は Fragment の ID と同じである必要があります。)タイトルを @string/rules に、アイコンを @drawable/rules に設定します。

  1. 2つ目のメニュー項目では、idaboutFragment に、タイトル文字列を @string/about に、アイコンを @drawable/about_android_trivia に設定します。

Note:

メニュー項目に destination Fragment と同じ ID を使用する場合、onClick リスナーを実装するためにコードを記述する必要はありません。

  1. activity_main.xml レイアウトファイルを開きます。全ての Drawer 機能を無料で利用するには、View を DrawerLayout 内に配置します。<LinearLyout> 全体を <DrawerLayout> でラップします。(つまり、DrawerLayout を root view として追加します)
<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
   <androidx.drawerlayout.widget.DrawerLayout
       android:id="@+id/drawerLayout"
       android:layout_width="match_parent"
       android:layout_height="match_parent">

   <LinearLayout
       . . . 
       </LinearLayout>
   </androidx.drawerlayout.widget.DrawerLayout>
</layout>
  1. 次に、Drawer を追加します。これは、先ほど定義した navdrawer_menu を使用する NavigationView です。次のコードを DrawerLayout<LinearLayout> 要素の後に追加します。
<com.google.android.material.navigation.NavigationView
   android:id="@+id/navView"
   android:layout_width="wrap_content"
   android:layout_height="match_parent"
   android:layout_gravity="start"
   app:headerLayout="@layout/nav_header"
   app:menu="@menu/navdrawer_menu" />

Navigation Drawer を表示する

Navigation Drawer と Navigation Drawer Layout のメニュー項目を作成しました。次に、Navigation Drawer を Navigation Controller に接続して、ユーザが Navigation Drawer でアイテムを選択すると、アプリが適切な Fragment に移動するようにします。

  1. MainActivity.kt ファイルを開きます。onCreate() で、ユーザが Navigation Drawer を表示できるようにするコードを追加します。これには、setupWithNavController() を呼び出します。onCreate() の下部に次のコードを追加します。
NavigationUI.setupWithNavController(binding.navView, navController)
  1. アプリを実行します。左端からスワイプして Navigation Drawer を表示し、Drawer の各メニュー項目が正しい場所に移動していることを確認します。

navigation drawer は機能しますが、もう1つ修正する必要があります。通常、アプリでは、ホーム画面のアプリバーにある drawer ボタンをタップして表示することもできます。アプリのホーム画面にはまだ drawer が表示されていません。

Drawer ボタンから navigation drawer を表示する

最後のステップは、ユーザがアプリバーの左上にある引き出しボタンから navigation drawer にアクセスできるようにすることです。

  1. MainActivity.kt Kotlin ファイルで、lateinitdrawerLayout メンバー変数を追加して、drawer layout を表します。
private lateinit var drawerLayout: DrawerLayout

Note:

Kotlin は null-safety 言語です。null の安全性を提供する方法の1つは、lateinit を使用することです。これにより、null 参照を返す危険なしに変数の初期化を遅らせることができます。

  1. onCreate() メソッド内で、binding 変数が初期化された後で、drawerLayout を初期化します。
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this,
                R.layout.activity_main)

drawerLayout = binding.drawerLayout
  1. 三番目のパラメータとして、draweLayout を setupActionBarWithNavController() メソッドに追加します。
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
  1. onSupportNavigateUp() メソッドを編集して、PnavController.navigateUp を返す代わりに NavigatioUI.navigateUp を返します、navigation controllerと drawerLayout をnavigateUp()渡します。メソッドは次のようになります。
override fun onSupportNavigateUp(): Boolean {
   val navController = this.findNavController(R.id.myNavHostFragment)
   return NavigationUI.navigateUp(navController, drawerLayout)
}
  1. アプリを実行して挙動を確認します。

まとめ

Navigation components

Android navigation library を使用するには、いくつかの設定を行う必要があります。

  • モジュールレベルの build.gradle ファイルに、navigation-fragment-ktx および navigation-uiktx の依存関係を追加します。

  • プロジェクトレベルの build.gradle ファイルに、navigationVersionext 変数を追加します。

Navigation destinations は、ユーザが移動する Fragment、Activity、またはその他のアプリコンポーネントです。Navigation graph は、ある navigation 先から次の navigation 先への可能なパスを定義します。

  • navigation graph を作成するには、Navigation タイプの新しい Android Resource file を作成します。このファイルは、アプリの navigation フローを定義します。ファイルは res/navigation フォルダーにあり、通常は navigation.xml と呼ばれます。

  • Navigation Editor で Navigation graph を表示するには、navigation.xml ファイルを開き Design タブをクリックします。

  • Navigation Editor を使用して、Fragment などの宛先を navigation graph に追加します。

  • ある destination から別の destination へのパスを定義するには、navigation graph を使用して、destination を接続するアクションを作成します。navigation.xml ファイルでは、これらの各接続は ID を持つ action として表されます。

通常は navigation host は NavHostFragment と名付けられ、navigation graph のホストとして機能します:

  • ユーザが navigation graph で定義された宛先間を移動すると、NavHostFragment は fragment をスワップインおよびスワップアウトし、Fragment をバックスタックを管理します。

  • activity_main.xml レイアウトファイルでは、NavHostFragmentandroid:name="androidx.navigation.fragment.NavHostFragment" という名前の fragment 要素で表されます。

ユーザが view などをタップした時に表示される fragment を定義するには、view の onClick リスナーを設定します:

  • onClick リスナーで、view に対して findNavController().navigate() を呼び出します。

  • 宛先に繋がる actionID を指定します。

Conditional navigation は、ある場合には1つの画面に移動し、別の場合には別の画面に移動します。conditional navigation を作成するには:

  1. Navigation Editor を使用して、Starting Fragment から可能な各 destination fragments への接続を作成します。

  2. 各接続に一意の ID を割り当てます。

  3. View のクリックリスナーメソッドで、条件を検出するコードを追加します。次に、View で findNavController().navigate() を呼び出し、適切な action ID を渡します。

Back button

システムの Back button は通常、デバイスの下部にあります。デフォルトでは、Back button は、ユーザが最後に表示した画面に戻ります。状況によって、Back button でユーザが移動する場所を制御することもできます。

  • Navigation Editor では、Attributes ペインを使用して Pop To 設定を変更できます。この設定により、宛先が back stack から削除されます。これにより、Back button がユーザをどこに移動させるかを制御します。

  • Pop To 設定は、navigation.xml ファイルの popUpTp attributes として表示されます。

  • Inclusive チェックボックスを選択すると、popUpToInclusive attribute が true に設定されます。この宛先までの全ての宛先は、back stack から削除されます。

  • action's の popUpTo attribute がアプリの開始先に設定され、popUpToInclusivetrue に設定されている場合、Back button はユーザをアプリから離脱させます。

Up button

Android アプリの画面には、app bar に左上に表示される画面上のボタンがあります(アプリバーは action bar と呼ばれることもあります)。Up button は、画面間の階層関係に基づいて、アプリの画面内を "upwards" に移動します。

navigation controller の NavigationUI ライブラリはアプリバーと統合されているため、ユーザは app bar の Up button をタップして、アプリのどこからでもアプリのホーム画面に戻ることができます。

Navigation Controller を app bar にリンクするには:

  1. onCreate() で、NavigationUI クラスの stupActionBarWithNavController() を呼び出し、navigation controller に渡します。
val navController = this.findNavController(R.id.myNavHostFragment)
NavigationUI.setupActionBarWithNavController(this,navController)
  1. onSupportNavigateUp() メソッドをオーバーライドして、navigation controller で navigateUp() を呼び出します。
override fun onSupportNavigateUp(): Boolean {
        val navController = this.findNavController(R.id.myNavHostFragment)
        return navController.navigateUp()
    }
}

Options menu

options menu は、ユーザがアプリバーから縦に3つ並んだドットのアイコンをタップしてアクセスするメニューです。Fragment を表示するメニュー項目を含む options menu を作成するには、Fragment に ID があることを確認してください。次に option menu を定義し、メニュー項目の onOptionsItemSelected() ハンドラをコーディングします。

  1. Fragment に ID があることを確認します:
  2. destination Fragment を navigation graph に追加し、Fragment の ID をメモします。(ID は必要に応じて変更できます。)
  3. オプションメニューを定義する:
  4. 通常は options_menu.xml という名前の、menu type の Android resource file を作成します。ファイルは Res > Menu フォルダに保存されます。
  5. デザインエディターで options_menu.xml ファイルを開き、Menu Item widgetPalette ペインからメニューにドラッグします。
  6. 便宜上、メニュー項目の ID を、ユーザがこのメニュー項目をクリックした時に表示される Fragment の ID と同じにします。このステップは必須ではありませんが、メニュー項目の onClick 動作のコーディングが簡単になります。
  7. menu item の onClick ハンドラーをコーディングします。
  8. option menu を表示する fragment または activity のonCreateView() 内で、setHasOptionsMenu(true) を呼び出し、options menu を有効にします。
  9. onCreateOptionsMenu() を実装して、options menu を inflate します。
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.options_menu, menu)
}
  • onOptionsItemSelected() メソッドをオーバーライドして、menu item がクリックされた時に適切なアクションを実行します。
  • 次のコードは、メニュー項目と同じ ID を持つ Fragment を表示します。(このコードは、menu item と fragment に同じ ID がある場合にのみ起動します。)
override fun onOptionsItemSelected(item: MenuItem): Boolean {
     return NavigationUI.
            onNavDestinationSelected(item,requireView().findNavController())
            || super.onOptionsItemSelected(item)
}

Navigation drawer

navigation drawer は、画面の端からスライドして出るパネルです。ユーザが navigation drawer を開く方法は2つあります。

  • 任意の画面で、starting edge(通常は左)からスワイプします。
  • アプリの上部にある app bar の drawer ボタンをタップします。

アプリに navigation drawer を追加するには:

  1. build.gradle(app) に依存関係を追加します。

  2. destination fragment に ID があることを確認してください。

  3. drawer メニューを作成します。

  4. drawer を Fragment のレイアウトに追加します。

  5. drawer を navigation controller に追加します。

  6. app bar の drawer ボタンを設定します。

これらの手順については、以下で詳しく説明します。

  1. buidle.gradle に依存関係を追加する
  2. navigation drawer は、Android ライブラリの Material Components の一部です。マテリアルライブラリを build.gradle(app) ファイルに追加します。
dependencies {
    ...
    implementation "com.google.android.material:material:$supportlibVersion"
    ...
}
  1. destination に ID を指定します。

  2. navigation drawer から Fragment に到達できる場合は、Navigation graph で Fragment を開いて ID があることを確認します。

  3. Drawer の Menu を作成する。

  4. navigation drawer menu 用の Menu タイプの Android Resource file を作成します。これにより、Res > Menu フォルダーに新しい navdrawer_menu.xml ファイルが作成されます。
  5. デザインエディターで、Menu Item widgetMenu に追加します。

  6. Fragment の layout に drawer を追加します。

  7. navigation host fragment を含むレイアウト(通常は main layout) で、root view として <androidx.drawerlayout.widget.DrawerLayout> を使用します。
  8. <com.google.android.material.navigation.NavigationView> をレイアウトに追加します。

  9. Drawer を navigation controller に接続します:

  10. navigation controllerを作成する activit を開きます。onCreate() で、NavigationUI.setupWithNavController() を使用して、navigation drawer を navigation controller に接続します。
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
       this, R.layout.activity_main)
NavigationUI.setupWithNavController(binding.navView, navController)
  1. app bar の drawer button を設定します。

  2. Navigation controller を作成する activity の onCreate() で、drawer layout を三番目のパラメーターとして NavigationUI.setupActionBarWithNavController い渡します:

val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
    this, R.layout.activity_main)

NavigationUI.setupActionBarWithNavController(
    this, navController, binding.drawerLayout)
  • Up button を drawer button と連動させるにはonSuportNavigateUp() を編集して NavigatioUI.navigateUp() を返します。navigation controllre と drawer layout を navigateUp に渡します。
override fun onSupportNavigateUp(): Boolean {
   val navController = this.findNavController(R.id.myNavHostFragment)
   return NavigationUI.navigateUp(navController, drawerLayout)
}

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com