目次
いにしえの時代のFragment間の移動
7年くらい前までであれば、Fragment間の移動はソースで記述していました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// FragmentManagerからFragmentTransactionを作成 FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); // 実際に使用するFragmentの作成 Fragment nextFragment = new SecondFragment(); // Fragmentを組み込む fragmentTransaction.replace(R.id.fragment_container, nextFragment); // backstackに追加 fragmentTransaction.addToBackStack(null); // 上記の変更を反映する fragmentTransaction.commit(); |
Fragment Managerを呼び出してTransactionを生成して、遷移先のFragmentを生成してから書き換えるという感じです。
懐かしさすら覚えますが、当時はこれはこれで便利だなと思いました。
もちろん今でもこの方法は使えますので、既存のアプリをわざわざ後述するNavigation Componentに置き換えるメリットは薄いと思います。
いつのまにかNavigation Componentと呼ばれるものができ、Fragment間の挙動をxmlで記述し、プログラム上から呼び出すだけで簡単に遷移できるようになりました。
今回はFirstFragmentからSecondFragmentへの遷移を行うという動きを入れましょう。
作成手順は以下のようになります。
- resを右クリック
- Resource Typeを Navigation を選択。
- name にファイル名を入力します。(今回はnav_graph)
- 必要なライブラリーがbuild.gradleのdependenciesに存在しない場合、ダイアログが表示されるため、OKを押す。
- res フォルダに navigation フォルダが作成され、nav_graph.xmlファイルが作成される
それではxmlファイルを見ていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<?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/nav_graph" app:startDestination="@id/navigation_first"> <fragment android:id="@+id/first_fragment" android:name="xxx.fragment.FirstFragment" android:label="@string/title_label_section1" tools:layout="@layout/fragment_first"> </fragment> <fragment android:id="@+id/second_fragment" android:name="xxx.fragment.SecondFragment" android:label="@string/title_label_section2" tools:layout="@layout/fragment_second"> </fragment> </navigation> |
先ほどの新規で作った場合は<fragment>タグは存在しませんが、今回は説明のため、あらかじめ2つのフラグメントの設定を行っておきます。
<fragment>が2つありますが、これはそれぞれFirstFragment SecondFragmentを設定します。
これはGUI上でも直接XMLを触ってもいいですが、XMLで触る場合は以下を参考にしてください。
id | ID |
---|---|
name | Fragmentを指定 |
label | actionBarのタイトルで表示される文字列 |
layout | Fragmentで指定されているレイアウト |
これで2つのFragmentをNavigation Componentに登録しました。
次に遷移の動きを入れましょう。
先ほどのfragmentの中に
1 2 3 |
<action android:id="@+id/action_First_to_Second" app:destination="@id/navigation_second" /> |
を入れます。
id | ID |
---|---|
destination | 遷移先のFragmentのIDを指定 |
enterAnim | 画面遷移時の最初のアニメーション (任意) |
exitAnim | 画面遷移時の終了時のアニメーション (任意) |
popEnterAnim | 戻った時の最初のアニメーション(任意) |
popExitAnim | 戻ったときの終了時のアニメーション(任意) |
必要なのはidとdestinationなので、まずはこれを入れてください。
今回はFirstFragmentからSecondFragmentへの遷移なのでこれだけでいいです。
Activityの設定
これだけだと表示がされないので、Activityの設定を行います。activity_main.xmlの記述を次のように変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?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:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <androidx.fragment.app.FragmentContainerView android:id="@+id/host_fragment" android:layout_width="match_parent" android:layout_height="match_parent" android:name="androidx.navigation.fragment.NavHostFragment" app:navGraph="@navigation/nav_graph" app:defaultNavHost="true" /> </androidx.constraintlayout.widget.ConstraintLayout> |
今回は単純な画面なのですっきりしていますが、実際はボトムナビゲーションやらFABつけたりするともう少しごちゃごちゃするので、そこらへんは対応してください。
先ほど作った navigation/nav_graph をここで設定しています。
Safe Argsの利用
どうもSafe Argsという機能を使ってNavHostを利用するのがよさげなので、それを利用します。ただそれを利用するにはbuild.gradleに追加を行う必要があります。
バージョンはそのときによって変わるのでご注意ください。
1 2 3 |
dependencies { classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.0-rc01" } |
1 2 3 |
plugins { id 'androidx.navigation.safeargs.kotlin' } |
以上を入力してSync nowしたのちにビルドを行います。
遷移
それでは遷移させましょう。遷移させるイベントはなんでもいいですが、ボタンをタップしたタイミングで遷移させるのがいいかなと思います。
1 2 3 4 5 6 7 8 9 10 11 12 |
val view = inflater.inflate(R.layout.fragment_first, container, false) val buttonGo = view.findViewById<AppCompatButton>(R.id.button_go) buttonGo.setOnClickListener{ // NavHostの取得 val navHostFragment = requireActivity().supportFragmentManager.findFragmentById(R.id.host_fragment) as NavHostFragment val navController = navHostFragment.navController val action = FirstFragmentDirections.actionFirstToSecond() navController.navigate(action) } |
Safe Argsを利用したことで、自動的に フラグメント名+Directions というクラス名が生成されます。(今回は FirstFragmentDirections)
また navigations に追加したidは[action_First_to_Second]となりますが、これも
actionFirstToSecond()となります。
最後にnavController.navigate(action) で遷移します。
遷移時のアニメーション
どうせなら画面遷移時にアニメーションを設定しましょう以前のやり方でも設定できましたが、今回はnavigationのactionで設定できるのでかなり手軽に行えます。
アニメーションの追加
まずアニメーションを作ります。res/anim(なければ作る)の中に以下のXMLファイルを作ります。
左側からスライド
1 2 3 4 5 6 7 |
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromXDelta="-100%p" android:toXDelta="0" android:duration="@android:integer/config_mediumAnimTime" /> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="@android:integer/config_mediumAnimTime" /> </set> |
右側からスライド
1 2 3 4 5 6 7 |
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromXDelta="100%p" android:toXDelta="0" android:duration="@android:integer/config_mediumAnimTime" /> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="@android:integer/config_mediumAnimTime" /> </set> |
左側へスライド
1 2 3 4 5 6 7 |
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromXDelta="0" android:toXDelta="-100%p" android:duration="@android:integer/config_mediumAnimTime" /> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="@android:integer/config_mediumAnimTime" /> </set> |
右側へスライド
1 2 3 4 5 6 7 |
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromXDelta="0" android:toXDelta="100%p" android:duration="@android:integer/config_mediumAnimTime" /> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="@android:integer/config_mediumAnimTime" /> </set> |
この4つを使って先ほどのに設定を行います。
1 2 3 4 5 6 7 |
<action android:id="@+id/action_First_to_Second" app:destination="@id/secondFragment" app:enterAnim="@anim/slide_from_right" app:exitAnim="@anim/slide_to_left" app:popEnterAnim="@anim/slide_from_left" app:popExitAnim="@anim/slide_to_right" /> |
これを設定することで画面遷移時には右からスライド、戻ってきた場合は左へスライドというiOSライクな動きができると思います。
(AndroidアプリでiOSの挙動をするのはどうかとは思いますが)
遷移時に値を渡す
画面を遷移する際に値を渡したいことがあると思います。その場合も簡単に値を渡すことができます。
渡し方
遷移先(受け取り先)のの中に渡したい型と名前を設定します。
1 2 3 4 |
<argument android:name="number" app:argType="integer" android:defaultValue="1" /> |
1 2 3 4 5 |
val navHostFragment = requireActivity().supportFragmentManager.findFragmentById(R.id.host_fragment) as NavHostFragment val navController = navHostFragment.navController val action = FirstFragmentDirections.actionFirstToSecond(31) // ← 引き渡したい値を入れる navController.navigate(action) |
受け取り方
受け取り側も簡単です。
1 2 |
val args: SecondFragmentArgs by navArgs() val number = args.number |
今回はInt型のnumber を渡したので、受け取り側もInt型で受け取れば簡単に渡せます。
例文は1つの値を渡していますが、複数を渡す場合は<argument>を複数記述し、渡す値を増やしていけば、簡単に複数の値を渡せます。
自作クラスの値を渡す場合は?
自作Classを渡す場合、例えば以下のクラスを渡したい場合
1 2 3 4 |
class Keywords ( val id: Int, val keyword: String ) |
このままでは渡せないのでParcelable形式にします。
ただ、普通に設定すると面倒なので、以下のような設定にします。
(Kotlin Android Extensionsは非推奨になっているようなのでdataBindingを使うようにします)
1 2 3 4 5 6 7 8 9 10 11 |
plugins { id 'kotlin-parcelize' ← 追加 } ... android { buildFeatures { dataBinding = true ← 追加 } } ... |
記述後にSync nowを行い
1 2 3 4 5 |
@Parcelize class Keywords ( val id: Int, val keyword: String ): Parcelable |
と自作クラスの記述を調整します。
あとはを
1 2 3 |
<argument android:name="keywords" app:argType="jp.co.xxx.xxxx.Keywords" /> |
app:argTypeはフルパスで記述しましょう
あとはいつも通り
1 2 3 4 5 6 7 8 |
// 引き渡すkeywords型(自作クラス) val keywords: Keywords = Keywords(id=0, keyword = "blog") val navHostFragment = requireActivity().supportFragmentManager.findFragmentById(R.id.host_fragment) as NavHostFragment val navController = navHostFragment.navController val action = FirstFragmentDirections.actionFirstToSecond(keywords) navController.navigate(action) |
で送って
1 2 |
val args: SecondFragmentArgs by navArgs() val keywords = args.keywords |
で受け取ります。
本当に簡単ですね。
終わりに
Fragmentの遷移はXCodeのStoryboardと違って視覚的にわかりづらく、ソースで処理する必要がありましたが、Navigation Componentを使うと視覚的にもわかりやすくしかもアニメーションや値渡しが簡単にできるようになります。あまりにも変わりすぎるので、過去のアプリの資産がなかなか使いづらくなりますが、いいものは積極的に導入したいですね。
参考URL
デスティネーション間でデータを渡す[Android] NavigationでSafeArgsを使って引数付き画面遷移をする
Kotlin Android Extensions から View Binding に置き換える