Navigation 詳解一

Navigation 詳解二

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)備

  1. 在 Android Studio 3.2 Canary 14 以上的版本中楼熄,打開 Preferences -> Experimental -> Enable Navigation Editor,然后重啟浩峡。

  2. 添加依賴

    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
    
  3. 創(chuàng)建資源文件

    在 res 目錄右擊可岂,選擇 New > Android Resource File,Resource type 選擇 Navigation翰灾。如下圖

    navigation_res.png

創(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)建好再選擇。

navigation_create.png

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 視圖中搜索選擇

navigation_add.png

navigation_add2.png

直接在右側(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)畫:


navigation_anim.png

代碼自動(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 ProxyNo 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)
}
navigation_safeargs_1.png

如果 FirstFragment 去掉 action.setName("Silas")承边,那么 SecondFragment 里得到的也是默認(rèn)值 Max博助。

navigation_safeargs_2.png

看生成的 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è)吧城瞎。

參考:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蜒灰,隨后出現(xiàn)的幾起案子肩碟,更是在濱河造成了極大的恐慌,老刑警劉巖翅溺,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咙崎,死亡現(xiàn)場離奇詭異褪猛,居然都是意外死亡伊滋,警方通過查閱死者的電腦和手機(jī)队秩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門燥撞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來迷帜,“玉大人戏锹,你說我怎么就攤上這事≤欤” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵盯荤,是天一觀的道長秋秤。 經(jīng)常有香客問我,道長灼卢,這世上最難降的妖魔是什么鞋真? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任涩咖,我火速辦了婚禮抠藕,結(jié)果婚禮上蒋困,老公的妹妹穿的比我還像新娘雪标。我一直安慰自己村刨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著逆粹,像睡著了一般僻弹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芭毙,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天粘咖,我揣著相機(jī)與錄音涂炎,去河邊找鬼设哗。 笑死网梢,一個(gè)胖子當(dāng)著我的面吹牛赂毯,可吹牛的內(nèi)容都是我干的党涕。 我是一名探鬼主播膛堤,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼绿渣!你這毒婦竟也來了中符?” 一聲冷哼從身側(cè)響起淀散,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎踏志,沒想到半個(gè)月后针余,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡帆谍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了朴肺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,703評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡西土,死狀恐怖需了,靈堂內(nèi)的尸體忽然破棺而出肋乍,到底是詐尸還是另有隱情敷存,我是刑警寧澤锚烦,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布谱煤,位于F島的核電站,受9級特大地震影響禽拔,放射性物質(zhì)發(fā)生泄漏刘离。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一睹栖、第九天 我趴在偏房一處隱蔽的房頂上張望硫惕。 院中可真熱鬧,春花似錦野来、人聲如沸恼除。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽豁辉。三九已至令野,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間徽级,已是汗流浹背现使。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工殴胧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留竿屹,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親铛只。 傳聞我的和親對象是個(gè)殘疾皇子蜕着,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容