
在今年的 IO 大會(huì)上,發(fā)布了一套叫 Android Jetpack 的程序庫(kù)。Android Jetpack 里的組件大部分我們都接觸過(guò)了,其中也有一些全新的組件,其中一個(gè)就是 Navigation。
簡(jiǎn)介
Navigation 是用來(lái)管理 APP 里頁(yè)面跳轉(zhuǎn)的。起初,我以為它是用來(lái)代替 startActivity 的,但其實(shí)并不是,大家往下看就知道它的作用了。
另外,iOS 的同學(xué)可能會(huì)有似曾相識(shí)的感覺(jué),Navigation 應(yīng)該是有借鑒 Storyboard 的。
使用
我們先來(lái)看看 Navigation 的實(shí)現(xiàn)過(guò)程。
添加依賴
首先,需要使用 Android Studio 3.2 以上版本才能使用 Navigation。
在 build.gradle 中添加依賴:
implementation "android.arch.navigation:navigation-fragment:$nav_version"
implementation "android.arch.navigation:navigation-ui:$nav_version"
創(chuàng)建 navigation xml 文件
使用 「Android Resource File」創(chuàng)建 xml 文件的時(shí)候,可以看到在類型里,多了一個(gè) Navigation 的選項(xiàng):

創(chuàng)建成功后,就來(lái)到了文章開(kāi)頭的那個(gè)一個(gè)可視化的操作界面。點(diǎn)擊左上角的添加小圖標(biāo),會(huì)出現(xiàn) Activity 和 Fragment,我們這里添加兩個(gè) Activity 和兩個(gè) Fragment:

配置 Action
Fragment 的右邊有個(gè)小圓圈,點(diǎn)擊并拖到另一個(gè)頁(yè)面,這樣我們就給這個(gè) Fragment 添加了一個(gè)跳轉(zhuǎn)行為,也就是 Action。
但是可以發(fā)現(xiàn),Activity 的右邊是沒(méi)有這個(gè)小圓圈的,所以 Navigation 并不能處理從 Activity 發(fā)起的跳轉(zhuǎn)。
左上角有個(gè)小房子的是顯示的第一個(gè)頁(yè)面,但由于 Activity 無(wú)法發(fā)起跳轉(zhuǎn),所以這里把 MainActivity 刪除,把 MainFragment 作為主頁(yè)面,并給它添加跳轉(zhuǎn)到 SecondFragment 和 SecondActivity 的 Action:

自動(dòng)生成的 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"
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.example.navigation.MainFragment"
android:label="fragment_main"
tools:layout="@layout/fragment_main">
<action
android:id="@+id/action_mainFragment_to_secondFragment"
app:destination="@id/secondFragment"
app:enterAnim="@anim/slide_in_right" />
<action
android:id="@+id/action_mainFragment_to_secondActivity"
app:destination="@id/secondActivity" />
</fragment>
<fragment
android:id="@+id/secondFragment"
android:name="com.example.navigation.SecondFragment"
android:label="fragment_second"
tools:layout="@layout/fragment_second" />
<activity
android:id="@+id/secondActivity"
android:name="com.example.navigation.SecondActivity"
android:label="activity_second"
tools:layout="@layout/activity_second" />
</navigation>
布局中添加 Fragment
現(xiàn)在,我們第一個(gè)頁(yè)面是 MainFragment,而 Fragment 需要 Activity 作為容器,修改 MainActivity 的布局:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav" />
</FrameLayout>
其中有三個(gè)屬性需要注意。使用 android:name 指定 Fragment 的類型為 NavHostFragment,使用 app:navGraph 指定 Navigation 文件。app:defaultNavHost="true" 的作用是,讓 Navigation 處理返回事件,點(diǎn)返回按鈕時(shí)并不是返回上一個(gè) Activity,而是返回上一個(gè)「頁(yè)面」,上一個(gè)「頁(yè)面」有可能是 Activity,也可能是 Fragment。
至此,Navigation 的簡(jiǎn)單配置就算完成了,接下來(lái)看如何使用它。
配置跳轉(zhuǎn)
在 Navigation 里,頁(yè)面的跳轉(zhuǎn)是交給 NavController 來(lái)處理的,獲取 NavController 的方法有這么三種:
NavHostFragment.findNavController(Fragment)
Navigation.findNavController(Activity, @IdRes int viewId)
Navigation.findNavController(View)
拿到后,通過(guò) navigate 方法,通過(guò)傳入 Action 的 id,實(shí)現(xiàn)跳轉(zhuǎn),比如:
NavHostFragment
.findNavController(this)
.navigate(R.id.action_firstFragment_to_secondFragment)
在簡(jiǎn)單配置了兩個(gè)跳轉(zhuǎn)后,看一下目前的效果:

傳參
頁(yè)面的跳轉(zhuǎn)少不了數(shù)據(jù)的傳遞,使用 Navigation,和我們?cè)瓉?lái)的跳轉(zhuǎn)一樣,可以通過(guò) Bundle 來(lái)傳遞參數(shù):
val bundle = Bundle()
bundle.putString("name", "SouthernBox")
NavHostFragment
.findNavController(this)
.navigate(R.id.action_firstFragment_to_secondFragment, bundle)
如果跳轉(zhuǎn)到 Activity,可以從 intent.extras 獲取到 bundle,如果是 Fragment,則從 arguments 獲取到。
此外,還可以在 Navigation 的 xml 文件中配置傳參,但這種方式目前支持的數(shù)據(jù)類型比較少,連 boolean 都不支持,而且我還碰到了 bug,所以目前不建議用。
轉(zhuǎn)場(chǎng)動(dòng)畫
如果需要自定義的頁(yè)面轉(zhuǎn)場(chǎng)動(dòng)畫,使用 Navigation 可以很方便的實(shí)現(xiàn)。
這里舉個(gè)例子,比如我們需要一個(gè)從右向左切入的過(guò)場(chǎng)動(dòng)畫,先創(chuàng)建這個(gè)動(dòng)畫的 xml 文件:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="600"
android:fromXDelta="100%"
android:toXDelta="0" />
</set>
然后我們回到 Navigation 的可視化編輯頁(yè)面來(lái),點(diǎn)擊跳轉(zhuǎn)的線,右邊會(huì)出現(xiàn)過(guò)場(chǎng)動(dòng)畫的配置選項(xiàng),將 xxxx 設(shè)為剛才創(chuàng)建的動(dòng)畫:

這么簡(jiǎn)單就搞定了,效果如下:

Navigation 的使用介紹就到這里。
思考
你可能已經(jīng)明白,Navigation 主要是用來(lái)處理 Fragment 的跳轉(zhuǎn),所以說(shuō)它并不是用來(lái)代替 startActivity,而是用來(lái)代替 FragmentTransaction 的相關(guān)操作。
在官方文檔里,可以看到一個(gè)將傳統(tǒng)跳轉(zhuǎn)遷移到 Navigation 的建議。我簡(jiǎn)單理解為,將原本兩個(gè) Activity 之間的跳轉(zhuǎn),逐漸修改為使用一個(gè) Activity 作為容器,用兩個(gè) Fragment 作為頁(yè)面跳轉(zhuǎn)。
看到這里,我聯(lián)想到了在去年,Jake Wharton(目前在谷歌)有這么一個(gè)有爭(zhēng)議的言論:
“一個(gè) APP 只需要一個(gè) Activity?!?/strong>
在過(guò)去,要實(shí)現(xiàn)這種方式,就需要去解決復(fù)雜的 Fragment 堆棧處理,而且早期的 Fragment 坑比較多,處理不好容易出現(xiàn)頁(yè)面穿透等問(wèn)題?,F(xiàn)在 Navigation 恰好解決了這些問(wèn)題。
這一切聯(lián)系起來(lái),是不是能說(shuō)明官方間接支持了「少用 Activity 多用 Fragment」的做法?你怎么看?