📚

[Android][kotlin]Androidでの画面遷移(Navigation Compon)の標準テンプレート

に公開

https://github.com/aaaa1597/AndKotlin-NavigationComponent-AminSample

Abstract

AndroidでのNavigation Componentを使った画面遷移(Android推奨)の方法のサンプル

概要

今の今まで Navigation Component を触らずにやってきたけど、いい加減知っとく必要があるかと思って調べてみた。
で、Navigation Componentって早い話、res/navigation/xxxx.xmlのこと。xmlファイルをAndroid Studioが読み込んで、いい感じでActivityとかFragmentとかの画面遷移としてGUIで表示してくれる。状態遷移図が必要になりそうな時に Navigation Component で作っとくときっと幸せになれる。 必要になってから作るのだともう遅いと思う(いろいろ作り直しになるから)。

ざっくり手順

  1. Fragmentを準備
  2. Navigationリソース(Resource Type = Navigation)を生成
  3. Destination:デスティネーション(遷移先)を追加
  4. ActivityにNavigation Componentをくっつける。
  5. 他のFragmentもデスティネーション(遷移先)に設定する。
  6. 画面遷移処理を実装する

1. Fragmentを準備

まぁ適当に。

2. Navigationリソース(Resource Type = Navigation)を生成


File name: aaanavi
Resource type: Navigation
後はデフォルトで

OK押下

res\navigation配下にaaanaviが出来る。
ダブルクリックで開いとく

で、ここまでの操作をすることの実質変更点は下記。

  1. res/navigation/aaanavi.xmlファイルを新規作成
  2. build.gradle.ktsのdependenciesにimplを追加
  3. libs.versions.tomlに定義を追加
    ※ 1は自分でも作れるけど、2と3を自動でやってくれるのは便利。
  • res/navigation/aaanavi.xmlファイルを新規作成
aaanavi.xml
+ <?xml version="1.0" encoding="utf-8"?>
+ <navigation xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:app="http://schemas.android.com/apk/res-auto"
+     android:id="@+id/aaanavi">
+ 
+ </navigation>
  • build.gradle.kts(45-46)のdependenciesにimplを追加
build.gradle.kts
+   implementation(libs.androidx.navigation.fragment.ktx)
+   implementation(libs.androidx.navigation.ui.ktx)
  • libs.versions.toml(12-13,24-25)に定義を追加
libs.versions.toml
+ navigationFragmentKtx = "2.9.0"
+ navigationUiKtx = "2.9.0"

+ androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" }
+ androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" }

3. Destination:デスティネーション(遷移先)を追加

ActivityやらFragmentやらを遷移先に指定できる。
※ちなみにこの操作して、build→実行してもまだ何も表示されない

下図の赤丸アイコンを押下 → 遷移先を追加


↓ こうなった

で、この操作をしたことの実質変更点は下記。

  1. res/navigation/aaanavi.xmlファイルにfragmentの定義を追加
    ※この手順は自分でも出来る。
  • res/navigation/aaanavi.xml(4,6,8-12)
aaanavi.xml
 <navigation 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/aaanavi"
+   app:startDestination="@id/mainFragment">

+   <fragment
+       android:id="@+id/mainFragment"
+       android:name="com.aaa.navigationcomponent.MainFragment"
+       android:label="fragment_main"
+       tools:layout="@layout/fragment_main" />

4. ActivityにNavigation Componentをくっつける。

  1. Activityに設定しているlayautファイル(activity_main.xml)を開く
  2. Design画面を開いとく
  3. frag と入力。 NavHostFragmentを検索
  4. NavHostFragment を activity_main.xmlにDrag & Drop




5. 出てきたpopupで手順3で設定したFragment(or Acivity)を選択 → OK

この操作をしたことの実質変更点は下記。

  1. activity_main.xmlファイルに FragmentContainerView を追加
    ※結局、手で直したので、ツールでやる意味あんまないかも。
  • activity_main.xml(16-25)
activity_main.xml
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout
    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/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fcv_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

+   <androidx.fragment.app.FragmentContainerView
+       android:id="@+id/fragmentContainerView"
+       android:name="androidx.navigation.fragment.NavHostFragment"
+       android:layout_width="170dp"
+       android:layout_height="8dp"
+       app:defaultNavHost="true"
+       app:navGraph="@navigation/aaanavi"
+       tools:layout_editor_absoluteX="1dp"
+       tools:layout_editor_absoluteY="182dp" />

 </androidx.constraintlayout.widget.ConstraintLayout>

うーん、fragmentのコンテナとしてfcv_containerを準備してたのに、勝手に別のFragmentContainerViewを追加されてしまった。準備してたfcv_containerへの追加の方法はわかんない。

手で修正する。

  • activity_main.xml(13,16-17,19-25)
activity_main.xml
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout
    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/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fcv_container"
+       android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
+       app:defaultNavHost="true"
+       app:navGraph="@navigation/aaanavi" />

-   <androidx.fragment.app.FragmentContainerView
-       android:id="@+id/fragmentContainerView"
-       android:name="androidx.navigation.fragment.NavHostFragment"
-       android:layout_width="170dp"
-       android:layout_height="8dp"
-       app:defaultNavHost="true"
-       app:navGraph="@navigation/aaanavi"
-       tools:layout_editor_absoluteX="1dp"
-       tools:layout_editor_absoluteY="182dp" />

 </androidx.constraintlayout.widget.ConstraintLayout>

※ここまでやったら、ビルド→実行で画面が表示される様になった。

5. 他のFragmentもデスティネーション(遷移先)に設定する。

手順3(デスティネーション(遷移先)を追加)の要領で、デスティネーションを追加


マウス操作で、丸のアイコンのやつをつかんで、


遷移先の画面にDropする。


繋がる。


同じ操作を下の SubFragment2 にも実行する。

この操作での変更点

  1. aaanavi.xmlファイルに actionの定義とFrgmentの定義を追加
  • res/navigation/aaanavi.xml(12-29)
aaanavi.xml
 <?xml version="1.0" encoding="utf-8"?>
 <navigation 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/aaanavi"
    app:startDestination="@id/mainFragment">

    <fragment
        android:id="@+id/mainFragment"
        android:name="com.aaa.navigationcomponent.MainFragment"
        android:label="fragment_main"
        tools:layout="@layout/fragment_main" >
+       <action
+           android:id="@+id/action_mainFragment_to_subFragment12"
+           app:destination="@id/subFragment1" />
+       <action
+           android:id="@+id/action_mainFragment_to_subFragment2"
+           app:destination="@id/subFragment2" />
    </fragment>
+   <fragment
+       android:id="@+id/subFragment1"
+       android:name="com.aaa.navigationcomponent.SubFragment1"
+       android:label="fragment_sub1"
+       tools:layout="@layout/fragment_sub1" />
+   <fragment
+       android:id="@+id/subFragment2"
+       android:name="com.aaa.navigationcomponent.SubFragment2"
+       android:label="fragment_sub2"
+       tools:layout="@layout/fragment_sub2" />
 </navigation>

6. 画面遷移処理を実装する

  1. Fragment::findNavController()で、NavControllerを取得
  2. NavController.navigateを呼ぶ



NavControllerの取得方法は他にもあって以下の通り。

  1. Fragment.findNavController()
  2. View.findNavController()
  3. Activity.findNavController(viewId: Int)

実装

  • 画面遷移用のボタンの追加 fragment_main.xml(31-49)
fragment_main.xml
  ~略~
+   <Button
+       android:id="@+id/btn_callsub1"
+       android:layout_width="wrap_content"
+       android:layout_height="wrap_content"
+       android:text="@string/callSubfragment1"
+       app:layout_constraintTop_toBottomOf="@id/imv_nyandar001"
+       app:layout_constraintLeft_toLeftOf="parent"
+       app:layout_constraintRight_toLeftOf="@id/btn_callsub2"
+       app:layout_constraintHorizontal_chainStyle="packed"/>

+   <Button
+       android:id="@+id/btn_callsub2"
+       android:layout_width="wrap_content"
+       android:layout_height="wrap_content"
+       android:text="@string/callSubfragment2"
+       app:layout_constraintTop_toBottomOf="@id/imv_nyandar001"
+       app:layout_constraintLeft_toRightOf="@id/btn_callsub1"
+       app:layout_constraintRight_toRightOf="parent"/>

 </androidx.constraintlayout.widget.ConstraintLayout>
  • MainFragment.kt(8-11, 42-55)
MainFragment.kt
  ~略~

+ import android.view.ViewGroup
+ import android.widget.Button
+ import androidx.core.os.bundleOf
+ import androidx.navigation.fragment.NavHostFragment
+ import androidx.navigation.fragment.findNavController

  ~略~

+   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+       super.onViewCreated(view, savedInstanceState)
+
+       view.findViewById<Button>(R.id.btn_callsub1).setOnClickListener {
+           val params = bundleOf("aaa" to "aaa", "bbb" to "bbb")
+           findNavController().navigate(R.id.action_mainFragment_to_subFragment12, params)
+       }
+
+       view.findViewById<Button>(R.id.btn_callsub2).setOnClickListener {
+           val params = bundleOf("aaa" to "aaa", "bbb" to "bbb")
+           findNavController().navigate(R.id.action_mainFragment_to_subFragment2, params)
+       }
+   }
+
  ~略~

実質の変更部分は、findNavController().navigate()の呼出だけ。
これで、画面遷移の実装ができた。
引数も期待通り受け渡しできてる。
ソースコードガリガリ書くよりNavigation Component使う方がすっきりしてて好きかも。

次に画面遷移アニメーションを追加してみる。

画面遷移アニメーションを追加するざっくり手順

  1. アニメーションxmlを準備
  2. actionを追加
  3. 画面遷移を実装する

1. アニメーションxmlを準備

今回はhttps://at-sushi.work/blog/21/さんのを使わせてもらった。
基本的なアニメーションは網羅してくれてるのでほとんどのプロジェクトで流用できる気がする。

res\animにxmlファイルを準備する。4ファイルで1セット。

  • enterAnim ... 開始のアニメーション
  • exitAnim ... 終了のアニメーション
  • popEnterAnim ... Backボタン押下のときの開始アニメーション
  • popExitAnim ... Backボタン押下のときの終了アニメーション

2. actionを追加

res\navigation配下のナビゲーション.xml(aaanavi.xml)にactionを追加していく。
UIを操作するより、xmlコードを直接いじった方が早い。

  • aaanavi.xml(19-60)
aaanavi.xml
  ~略~
        <action
            android:id="@+id/action_mainFragment_to_subFragment2"
            app:destination="@id/subFragment2" />
+       <action
+           android:id="@+id/action_to_fade1"
+           app:destination="@id/subFragment1"
+           app:enterAnim="@anim/nav_default_enter_anim"
+           app:exitAnim="@anim/nav_default_exit_anim"
+           app:popEnterAnim="@anim/nav_default_pop_enter_anim"
+           app:popExitAnim="@anim/nav_default_pop_exit_anim" />
+       <action
+           android:id="@+id/action_to_slide2"
+           app:destination="@id/subFragment2"
+           app:enterAnim="@anim/nav_slide_enter_anim"
+           app:exitAnim="@anim/nav_slide_exit_anim"
+           app:popEnterAnim="@anim/nav_slide_pop_enter_anim"
+           app:popExitAnim="@anim/nav_slide_pop_exit_anim" />
+       <action
+           android:id="@+id/action_to_zoom1"
+           app:destination="@id/subFragment1"
+           app:enterAnim="@anim/nav_zoom_enter_anim"
+           app:exitAnim="@anim/nav_zoom_exit_anim"
+           app:popEnterAnim="@anim/nav_zoom_pop_enter_anim"
+           app:popExitAnim="@anim/nav_zoom_pop_exit_anim" />
+       <action
+           android:id="@+id/action_to_up2"
+           app:destination="@id/subFragment2"
+           app:enterAnim="@anim/nav_up_enter_anim"
+           app:exitAnim="@anim/nav_up_exit_anim"
+           app:popEnterAnim="@anim/nav_up_pop_enter_anim"
+           app:popExitAnim="@anim/nav_up_pop_exit_anim" />
+       <action
+           android:id="@+id/action_to_dynamic1"
+           app:destination="@id/subFragment1"
+           app:enterAnim="@anim/nav_dynamic_enter_anim"
+           app:exitAnim="@anim/nav_dynamic_exit_anim"
+           app:popEnterAnim="@anim/nav_dynamic_pop_enter_anim"
+           app:popExitAnim="@anim/nav_dynamic_pop_exit_anim" />
+       <action
+           android:id="@+id/action_to_dynamic2"
+           app:destination="@id/subFragment2"
+           app:enterAnim="@anim/nav_dynamic_enter_anim"
+           app:exitAnim="@anim/nav_dynamic_exit_anim"
+           app:popEnterAnim="@anim/nav_dynamic_pop_enter_anim"
+           app:popExitAnim="@anim/nav_dynamic_pop_exit_anim" />
    </fragment>
    <fragment
        android:id="@+id/subFragment1"
        android:name="com.aaa.navigationcomponent.SubFragment1"
        android:label="fragment_sub1"
        tools:layout="@layout/fragment_sub1" />
  ~略~

3. 画面遷移を実装する

画面遷移処理の呼出部分はコーディングする。

  • MainFragment.kt(50-69)
MainFragment.kt
  ~略~
+       view.findViewById<Button>(R.id.btn_callsub1fade).setOnClickListener {
+           val params = bundleOf("aaa" to "aaa222", "bbb" to "bbb222")
+           findNavController().navigate(R.id.action_to_fade1, params)
+       }
+       view.findViewById<Button>(R.id.btn_callsub1zoom).setOnClickListener {
+           val params = bundleOf("aaa" to "aaa333", "bbb" to "bbb333")
+           findNavController().navigate(R.id.action_to_zoom1, params)
+       }
  ~略~
+        view.findViewById<Button>(R.id.btn_callsub2slide).setOnClickListener {
+           val params = bundleOf("aaa" to "aaa", "bbb555" to "bbb55")
+           findNavController().navigate(R.id.action_to_slide2, params)
+       }
+       view.findViewById<Button>(R.id.btn_callsub2up).setOnClickListener {
+           val params = bundleOf("aaa" to "aaa", "bbb555" to "bbb55")
+           findNavController().navigate(R.id.action_to_up2, params)
+       }
  ~略~

これで、画面遷移のアニメーション実装ができた。
引数も期待通り受け渡しできてる。
アニメーションだけを拡張するだけなら結構分かりやすかった。

番外編 画面遷移時に、遷移先Fragmentに引数を渡す。

  1. 送信側で: findNavController().navigate()を呼ぶときにBundle型の引数渡す
    1-1. Bundle型の引数を生成
    1-2. navigate()の第2引数にセット
  2. 受信側で: onCreate()の中で、argumentsメンバから取り出す。

1. 送信側で: findNavController().navigate()を呼ぶときにBundle型の引数渡す

  • MainFragment.kt(47-48)
MainFragment.kt
  ~略~
        view.findViewById<Button>(R.id.btn_callsub1).setOnClickListener {
-           findNavController().navigate(R.id.action_mainFragment_to_subFragment1)
+           val params = bundleOf(ARG_PARAM1 to "aaa111", ARG_PARAM2 to "bbb111")
+           findNavController().navigate(R.id.action_mainFragment_to_subFragment1, params)
        }
  ~略~

2. 送信側で: findNavController().navigate()を呼ぶときにBundle型の引数渡す

  • SubFragment1.kt(25-26,30-34)
SubFragment1.kt
  ~略~
+  private var param1: String? = null
+  private var param2: String? = null

   override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
+       arguments?.let {
+           param1 = it.getString(ARG_PARAM1)
+           param2 = it.getString(ARG_PARAM2)
+           Log.d("aaaaa", "aaaaa aaa=${param1} bbb=${param2}")
+       }
    }
  ~略~

出来た。

Discussion