Navigation 是 JetPack 中的一個(gè)組件痕支,用于方便的實(shí)現(xiàn)頁面的導(dǎo)航卧须,所以抽象出了一個(gè) destination
的概念,大部分情況一個(gè) destination 就表示一個(gè) Fragment花嘶,但是它同樣可以指代 Activity、其它的導(dǎo)航圖
干签。
最初要有個(gè)起始頁面,叫 start destination
拆撼,處于棧底容劳,是啟動(dòng)時(shí)的第一個(gè)頁面,當(dāng)然也是返回可見的最后一個(gè)頁面闸度。多個(gè) destination 連接起來就組成了一個(gè)導(dǎo)航圖
竭贩,類似于一種棧結(jié)構(gòu),頁面先進(jìn)后出莺禁。destination 之間的連接叫做 action
留量。
概念略抽象,下面看具體的應(yīng)用哟冬。
準(zhǔn)備
在 Android Studio 3.2 Canary 14 以上的版本中楼熄,打開 Preferences -> Experimental -> Enable Navigation Editor,然后重啟浩峡。
-
添加依賴
def nav_version = "1.0.0-alpha05" implementation "android.arch.navigation:navigation-fragment:$nav_version" // use -ktx for Kotlin implementation "android.arch.navigation:navigation-ui:$nav_version" // use -ktx for Kotlin // optional - Test helpers androidTestImplementation "android.arch.navigation:navigation-testing:$nav_version" // use -ktx for Kotlin
-
創(chuàng)建資源文件
在 res 目錄右擊可岂,選擇 New > Android Resource File,Resource type 選擇 Navigation翰灾。如下圖
創(chuàng)建 destination
先創(chuàng)建一個(gè) Fragment
// FirstFragment.kt
class FirstFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_first, container, false)
}
}
<!-- fragment_first.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal"
tools:context=".navigation.FirstFragment" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:textSize="20sp"
android:text="@string/hello_first_fragment"
android:layout_marginTop="20dp"/>
</LinearLayout>
然后配置 navigation 文件缕粹,打開 res/navigation/nav_graph
文件,添加一個(gè) fragment 節(jié)點(diǎn)
- name 指定 Fragment 的路徑
- tools:layout 指定布局文件
- app:startDestination 指定這個(gè) Fragment 是
start destination
<?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/nav_graph_first_fragment">
<fragment
android:id="@+id/nav_graph_first_fragment"
android:name="pot.ner347.androiddemo.navigation.FirstFragment"
android:label="first"
tools:layout="@layout/fragment_first"/>
</navigation>
也可以在 nav_graph 的 design 視圖下纸淮,選擇 Create blank destination 來創(chuàng)建一個(gè) Fragment平斩,而不用先創(chuàng)建好再選擇。
Activity 中引用
第一種方式是在 xml 里寫 fragment咽块。如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
... >
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/nav_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true" />
</android.support.constraint.ConstraintLayout>
- android:name 是 NavHostFragment绘面,它實(shí)現(xiàn)了 NavHost,這是一個(gè)用于放置管理 destination 的空視圖侈沪。
- app:navGraph 用于將這個(gè) NavHostFragment 和 nav_graph.xml 關(guān)聯(lián)起來飒货。
- app:defaultNavHost 表示 NavHostFragment 可以攔截處理返回鍵。
第二種方式是通過代碼創(chuàng)建 NavHostFragment峭竣,先修改 Activity 的 xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
... >
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/frame_layout" />
</android.support.constraint.ConstraintLayout>
然后在 Activity 中引入:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_navigation)
val finalHost = NavHostFragment.create(R.navigation.nav_graph)
supportFragmentManager.beginTransaction()
.replace(R.id.frame_layout, finalHost)
.setPrimaryNavigationFragment(finalHost) // 等價(jià)于 xml 中的 app:defaultNavHost="true"
.commit()
}
連接兩個(gè) destination
再創(chuàng)建一個(gè) SecondFragment。到 nav_graph 中添加 SecondFragment晃虫,直接在 design 視圖中搜索選擇
直接在右側(cè)設(shè)置 id皆撩,label 等扛吞。然后在頁面上拖滥比,代碼里自動(dòng)多了一段 action
:
<fragment
android:id="@+id/nav_graph_first_fragment"
android:name="pot.ner347.androiddemo.navigation.FirstFragment"
android:label="first"
tools:layout="@layout/fragment_first">
<action
android:id="@+id/action_nav_graph_first_fragment_to_nav_graph_second_fragment"
app:destination="@id/nav_graph_second_fragment" />
</fragment>
<fragment
android:id="@+id/nav_graph_second_fragment"
android:name="pot.ner347.androiddemo.navigation.SecondFragment"
android:label="second"
tools:layout="@layout/fragment_second" >
</fragment>
- app:destination 指定要跳轉(zhuǎn)到 Fragment 的 id盲泛。
- app:id 定義這個(gè) action 的 id寺滚,代碼里執(zhí)行跳轉(zhuǎn)時(shí)要用到村视。
處理跳轉(zhuǎn)
跳轉(zhuǎn)通過 NavController 對象蚁孔,它有三種獲取方法:
NavHostFragment.findNavController(Fragment)
Navigation.findNavController(Activity, @IdRes int viewId)
Navigation.findNavController(View)
調(diào)用 NavController 的 navigate 方法執(zhí)行跳轉(zhuǎn)杠氢,navigate 的參數(shù)可以是一個(gè) destination(這里就是 fragment 在導(dǎo)航圖 nav_graph 中的 id)修然,也可以是 action 的 id愕宋。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
button.onClick {
// NavHostFragment.findNavController(this@FirstFragment)
// .navigate(R.id.action_nav_graph_first_fragment_to_nav_graph_second_fragment)
Navigation.findNavController(getView()!!)
.navigate(R.id.action_nav_graph_first_fragment_to_nav_graph_second_fragment)
}
}
添加跳轉(zhuǎn)動(dòng)畫
點(diǎn)擊目標(biāo)箭頭中贝,右側(cè)添加動(dòng)畫:
代碼自動(dòng)變成:
<fragment
android:id="@+id/nav_graph_first_fragment"
android:name="pot.ner347.androiddemo.navigation.FirstFragment"
android:label="first"
tools:layout="@layout/fragment_first">
<action
android:id="@+id/action_nav_graph_first_fragment_to_nav_graph_second_fragment"
app:destination="@id/nav_graph_second_fragment"
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" />
</fragment>
支持 View 動(dòng)畫和屬性動(dòng)畫蝎土,enterAnim 和 exitAnim 是去往棧里添加一個(gè) destination 時(shí)兩個(gè) destination 的動(dòng)畫誊涯,popEnterAnim 和 popExitAnim 是從棧里移除一個(gè) destination 時(shí)的動(dòng)畫暴构。
傳遞數(shù)據(jù)
要跳轉(zhuǎn)到 SecondFragment,要往 SecondFragment 里帶數(shù)據(jù)耗绿,在目的 Fragment 里添加 <argument>
<!-- nav_graph.xml -->
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
... >
<fragment
android:id="@+id/nav_graph_second_fragment"
android:name="pot.ner347.androiddemo.navigation.SecondFragment"
android:label="second"
tools:layout="@layout/fragment_second" >
<argument android:name="name" android:defaultValue="Max"/>
</fragment>
</navigation>
FirstFragment 添加數(shù)據(jù)
button.onClick {
val bundle = bundleOf("name" to "silas")
Navigation.findNavController(getView()!!)
.navigate(R.id.action_nav_graph_first_fragment_to_nav_graph_second_fragment, bundle)
}
SecondFragment 獲取數(shù)據(jù)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
arguments?.getString("name")?.let { toast("hello $it") }
return inflater.inflate(R.layout.fragment_second, container, false)
}
如果 FirstFragment 沒有帶數(shù)據(jù),那么 SecondFragment 將收到默認(rèn)值 “Max”晴埂。
類型安全方式傳遞數(shù)據(jù)
項(xiàng)目的 build.gradle 中添加
buildscript {
repositories {
google()
}
dependencies {
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha05"
}
}
module 的 build.gradle 中應(yīng)用:
apply plugin: "androidx.navigation.safeargs"
同步發(fā)現(xiàn)要升級 gradle 版本到 4.6邑时,隨之 gradle tools 必須到 3.2.0-rc02,然后要升級 kotlin 版本黍氮,然后又讓下載 build tools 28.0.2沫浆,然后總是不能下載滚秩,看網(wǎng)上方法郁油,關(guān)閉代理,把 Preferences -> HTTP Proxy 從 No proxy 改成 Auto-detect proxy settings拄显。
<!-- nav_graph.xml -->
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
... >
<fragment
android:id="@+id/nav_graph_second_fragment"
android:name="pot.ner347.androiddemo.navigation.SecondFragment"
android:label="second"
tools:layout="@layout/fragment_second" >
<argument android:name="name" android:defaultValue="Max" app:argType="string"/>
</fragment>
</navigation>
和普通的區(qū)別就在于 <argument>
多了個(gè) argType 指定了數(shù)據(jù)類型躬审。
FirstFragment 修改
val action = FirstFragmentDirections.actionNavGraphFirstFragmentToNavGraphSecondFragment()
action.setName("Silas")
Navigation.findNavController(getView()!!).navigate(action)
SecondFragment 接收
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
toast("hello ${SecondFragmentArgs.fromBundle(arguments).name}")
return inflater.inflate(R.layout.fragment_second, container, false)
}
如果 FirstFragment 去掉 action.setName("Silas")
承边,那么 SecondFragment 里得到的也是默認(rèn)值 Max博助。
看生成的 FirstFragmentDirections 的 setName 和 SecondFragmentArgs 的 fromBundle:
@NonNull
public ActionNavGraphFirstFragmentToNavGraphSecondFragment setName(@NonNull String name) {
if (name == null) {
throw new IllegalArgumentException("Argument \"name\" is marked as non-null but was passed a null value.");
}
this.name = name;
return this;
}
@NonNull
public static SecondFragmentArgs fromBundle(Bundle bundle) {
SecondFragmentArgs result = new SecondFragmentArgs();
bundle.setClassLoader(SecondFragmentArgs.class.getClassLoader());
if (bundle.containsKey("name")) {
result.name = bundle.getString("name");
if (result.name == null) {
throw new IllegalArgumentException("Argument \"name\" is marked as non-null but was passed a null value.");
}
}
加了一些判斷罗心,所謂安全也就是指這個(gè)吧城瞎。
參考: