除了使用 FragmentManager 之外,還有什麼方式可以切換我們的 fragment 呢!今天就要來認識一下 Navigation,以下如有解釋不清或是描述錯誤的地方還請大家多多指教:
透過介面的形式來操作 fragment transaction, backstack, deeplink, 傳遞資料等相關頁面操作,而 navigation 包含以下這三個部分:
至於資料傳遞有兩種方式可以實作:
一種 gradle 的外掛程式,在頁面導轉時提供安全方式傳遞我們的資料,在使用之前必須分別在兩個 gradle 檔加上 plugin,要注意的是 gradle.properties 的檔案要有 android.useAndroidX=true
// project gradle
buildscript {
repositories {
google()
}
dependencies {
def nav_version = "2.5.2"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}
// module gradle
plugins {
id 'androidx.navigation.safeargs.kotlin'
}
並透過 by navArgs() 來取得傳遞的參數:
val args: HomeFragmentArgs by navArgs()
如果不使用 Safe Args 也可以透過 Bundle 來傳遞資料,使用 argument 取得資料:
arguments?.getString("name")
而參數傳遞有提供以下幾種 type:
資料傳遞可支援以下型態,如果參數是 nullable 的狀態可以加入 android:defaultValue="@null"
在 gradle 中加入 navigation 的 library
def nav_version = "2.5.2"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
先在 resource 建立 navigation 的檔案
並將 Main 的 XML 改成如下:
...
<androidx.fragment.app.FragmentContainerView
android:id="@+id/myNavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
...
在 navigation 中加入主頁的 fragment:
<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/navigation"
app:startDestination="@id/homeFragment">
<fragment android:id="@+id/homeFragment"
android:name="com.snc.weathby.HomeFragment"
android:label="HomeFragment"
tools:layout="@layout/fragment_home"/>
</navigation>
透過左上方的按鈕來加入新的頁面:
設置要傳入 detail 的 argument:
<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/navigation"
app:startDestination="@id/homeFragment">
<fragment android:id="@+id/homeFragment"
android:name="com.snc.weathby.HomeFragment"
android:label="HomeFragment"
tools:layout="@layout/fragment_home">
<action
android:id="@+id/action_homeFragment_to_detailFragment"
app:destination="@id/detailFragment" />
</fragment>
<fragment
android:id="@+id/detailFragment"
android:name="com.snc.weathby.detail.DetailFragment"
android:label="fragment_detail"
tools:layout="@layout/fragment_detail" >
<argument
android:name="city"
app:argType="com.snc.weathby.home.CityCard" />
</fragment>
</navigation>
資料型態我選擇傳入 Parcelable 資料,所以需要更動我的 data class,並在 gradle 加入 apply plugin: 'kotlin-parcelize'
:
sealed class HomeVo(val id: Int)
@Parcelize
data class CityCard(
val cityId: Int,
val cityName: String,
val day: String,
val temp: Int,
val wind: Int,
val wet: Int,
val rain: Int,
val isMarked: Boolean,
val dayTem: List<CityDayTemp>
) : HomeVo(cityId), Parcelable
@Parcelize
data class CityDayTemp(
val day: String,
val icon: IconType,
val maxTemp: Int,
val minTemp: Int
): Parcelable
...
開始設置導轉頁面,並傳遞預設的假資料:
...
// Safe Args
private val cardAdapter by lazy { HomeCityAdapter(
HomeCityAdapter.OnClickListener { it: CityCard // system display
val action = HomeFragmentDirections.actionHomeFragmentToDetailFragment(it)
this.findNavController().navigate(action)
}
)}
// Bundle
private val cardAdapter by lazy { HomeCityAdapter(
HomeCityAdapter.OnClickListener { it: CityCard // system display
val bundle = bundleOf("city" to it)
this.findNavController().navigate(R.id.action_homeFragment_to_detailFragment, bundle)
}
)}
actionHomeFragmentToDetailFragment
為 action 的 id @+id/action_homeFragment_to_detailFragment 編譯過來的。
在 detail 頁取得資料:
// Safe Args
val args: DetailFragmentArgs by navArgs()
private fun setupView() = binding.apply {
args.city.also {
cityName.text = it.cityName
weekName.text = it.day
cityTem.text = it.temp
cityWindy.text = it.wind
cityRainy.text = it.rain
cityWet.text = it.wet
dayTempList.adapter = listAdapter
listAdapter.submitList(it.dayTem)
}
}
// Bundle
private fun setupView() = binding.apply {
arguments?.getParcelable<CityCard>("city")?.also {
cityName.text = it.cityName
weekName.text = it.day
cityTem.text = it.temp
cityWindy.text = it.wind
cityRainy.text = it.rain
cityWet.text = it.wet
dayTempList.adapter = listAdapter
listAdapter.submitList(it.dayTem)
}
}