Androidで画面をたくさんある場合、その画面の数分layout.xmlとFragmentを作成することになるが、どのFragmentからどのFramgmentへ、何の条件のときに遷移するかを制御するのがコードでベタベタになってしまいます。
Android Jetpack の Navigationを使うとその辺がスッキリします。
参考URL
本家Android developers
[Android] 10分で作る、Navigationによる画面遷移
Navigation Componentの使い方(概要〜画面遷移〜データの受け渡し編)
簡単に手順だけ説明しますので詳細はgitHubに上がっているソースを見てください。
gitHub
build.gladle
build.gradleに依存関係を追加します
dependencies {
・・・
def nav_version = "2.5.2"
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
implementation "androidx.navigation:navigation-compose:$nav_version"
・・・
}
resの下にnavigationフォルダ、xmlを作る
resの下に「navigation」という名前でフォルダを作ります。
フォルダを作ったら、「navigation」を右クリック、→「新規」→「Navigation Resource File」を選択します。これは「res」を右クリックしても出てきません。
こんな画面が出てきます。ファイル名にxmlのファイル名を入れます。(何でもいい)とりあえず、「navigation_graph.xml」と入れます。
とりあえず、navigation_graph.xmlをダブルクリックして開いてみましょう。こんな画面が表示されるはずです。
今はまだ空っぽでなにもありません。
Fragmentを作成、MainActivityとつながるようにする
取り敢えず、Fratmentを3つ作り、MainActivityからつながるようにします。ここはAcdroidStudioの雛形に沿って作ります。
class | layout xml |
---|---|
FragmentMain.kt | fragment_main.xml |
Fragment01.kt | fragment_01.xml |
Fragment02.kt | fragment_01.xml |
FragmentMainはMainActivityから呼ばれる最初のFragmentです。そのあとは
FragmentMain、Fragment01.kt、Fragment02.ktの3つのFragmentを3各点回しで画面遷移できるようにします。
まず、MainActivityから
MainActivityは単純に、activity_main.xmlのレイアウトを呼んであげます。
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater).apply {
setContentView(this.root)
}
}
}
次に、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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainerView"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:navGraph="@navigation/navigation_graph"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
activity_main.xmlのミソは、この3行です。
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/navigation_graph"
app:defaultNavHost="true"
FragmentのNavigationを編集する
Navigationのフローの編集に入ります。開いたnavigation_graph.xmlの上の+のボタンをクリックするとFragmantの一覧が出てきます。
FragmentMain、Fragment01、Fragment02を適当な位置に配置します。こんな感じです。
お互いのFragmentを線で結んでやります。お互い3つのFragmentが行き来できるようにすると、このようなフローになります。
それぞれのFragmentから2本づつ、合計6本の線がでているのがわかるでしょうか。
上から順に
Mian → 01
Main → 02
01 → 02
01 → Main
02 → 01
02 → Main
の6本です。
FragmentMain
FragmentMainはFragment01とFragment02に遷移するフローがありますので、これをボタンのイベントにくくりつけてやります。
FragmentMain.kt
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentMainBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.button01.setOnClickListener {
// Fragment01へ
Navigation.findNavController(binding.root).navigate(R.id.action_fragmentMain_to_fragment01)
}
binding.button02.setOnClickListener {
// Fragment02へ
Navigation.findNavController(binding.root).navigate(R.id.action_fragmentMain_to_fragment02)
}
}
ここで注目してほしいのはFragmentMainの中で直接遷移先のFragmentを指定しているわけではないということです。
Mian → 01 の場合、
R.id.action_fragmentMain_to_fragment01
が遷移先を示しています。このR.id.XXXXはNavigationフローの編集のウィンドウのConponetTreeに出ているactionの名前と一致します。つまり、フローでつながっている1本の線を指しているわけです。R.id.action_XXXX_to_YYYYのXXXとYYYYがモロ、Fragmentの名前なのでアレですが、ここは名前を変えることができるので、意味のあるイベント名とかにもできます。例えばXXX_BUTTON_PUSHとか、LOGINとか。
同じ要領で、Fragment01.kt
Fragment01.ktも同じ要領で、01 → 02、01 → Mainに遷移する処理を書いてやります
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = Fragment01Binding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.button02.setOnClickListener {
// Fragment02へ
findNavController().navigate(R.id.action_fragment01_to_fragment02)
}
binding.buttonMain.setOnClickListener {
// FragmentMainへ
findNavController().navigate(R.id.action_fragment01_to_fragmentMain)
}
}
同じ要領で、Fragment02.ktも
Fragment02.ktも同じ要領で、02 → 01、02 → Mainに遷移する処理を書いてやります
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = Fragment02Binding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.button01.setOnClickListener {
// Fragment01へ
findNavController().navigate(R.id.action_fragment02_to_fragment01)
}
binding.buttonMain.setOnClickListener {
// FragmentMainへ
findNavController().navigate(R.id.action_fragment02_to_fragmentMain)
}
}
これで3つのFragmentの3各点回しができるようになりました。\(^o^)/
actionに引数を定義してみる
ただ、これだけでは物足りません。各FragmentとFragmentを繋ぐ線のactionに引数を定義することができます。
フローのFragmentを選択するとその右のAttributeのビューに引数を追加できるビューが表示されます。
試しにStringの引数を2つ定義してみました。
param1
param2
この引数の型ですが、String、Intぐらいであればうまく動くようですが、他の型だとうまく動かないという報告もあります。
Main → 01
に行くaction(FragmentとFragmentを繋ぐ線)がどうなっているか見てみます。
「Argment Default Vale」に追加した引数、param1、param2が表示され、ここでデフォルト値が設定できます。デフォルト値は各action(FragmentとFragmentを繋ぐ線)毎に別々に設定できます。
同じように、Main → 02
に行くaction(FragmentとFragmentを繋ぐ線)は、
同じように引数、param1、param2が表示され、デフォルト値が設定できるようになっています。
同じ要領で、Fragment01、Fragment02も引数を増やし、6本のaction全部に2個づつ引数を増やして、デフォルト値を設定してやります。
Fragmentの方は、引数をFragmentのEditTextから取ってきて設定してやる処理と、前のFragmentから引数が渡ってくるのでTextVoewに表示してやる処理を追加します。
FragmentMain.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.message1.setText(param1)
binding.message2.setText(param2)
binding.button01.setOnClickListener {
val action = FragmentMainDirections.actionFragmentMainToFragment01()
action.param1 = binding.message1.text.toString()
action.param2 = binding.message2.text.toString()
// Fragment01へ
Navigation.findNavController(binding.root).navigate(action)
}
binding.button02.setOnClickListener {
// Fragment02へ
val action = FragmentMainDirections.actionFragmentMainToFragment02()
action.param1 = binding.message1.text.toString()
action.param2 = binding.message2.text.toString()
Navigation.findNavController(binding.root).navigate(action)
}
}
Fragment01.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.message1.setText(param1)
binding.message2.setText(param2)
binding.button02.setOnClickListener {
val action =Fragment01Directions.actionFragment01ToFragment02()
action.param1 = binding.message1.text.toString()
action.param2 = binding.message2.text.toString()
// Fragment02へ
findNavController().navigate(action)
}
binding.buttonMain.setOnClickListener {
val action =Fragment01Directions.actionFragment01ToFragmentMain()
action.param1 = binding.message1.text.toString()
action.param2 = binding.message2.text.toString()
// FragmentMainへ
findNavController().navigate(action)
}
}
Fragment02.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.message1.setText(param1)
binding.message2.setText(param2)
binding.button01.setOnClickListener {
val action = Fragment02Directions.actionFragment02ToFragment01()
action.param1 = binding.message1.text.toString()
action.param2 = binding.message2.text.toString()
// Fragment01へ
findNavController().navigate(action)
}
binding.buttonMain.setOnClickListener {
val action = Fragment02Directions.actionFragment02ToFragmentMain()
action.param1 = binding.message1.text.toString()
action.param2 = binding.message2.text.toString()
// FragmentMainへ
findNavController().navigate(action)
}
}
以上、駆け足でAndroidのNavigationの説明しましたが、完成形はgitHubを見てください。