Android Jetpack Navigation 深入體驗報告

ShadowJoker發表於2019-03-18

Android Jetpack 之 Navigation深入體驗報告

前言

當前Android開發中使用Fragment來開發頁面已經成為主流做法。Fragment輕量、可控性強等優點讓人感覺很香。

但是Fragment也有自己的硬傷,那就是回退棧與頁面引數傳遞。雖然當前有例如Fragmentation這樣的開源庫解決了這類問題,而且這些三方開源庫也經受住了時間與專案的檢驗。但是總讓Android開發者心中覺得少了點什麼(尤其是學了iOS開發之後。。。)

Navigation的橫空出世,讓Android開發者終於看到了一線曙光。

上手接入

看到這麼個好藥,能治Android開發者的心病,我就抱著試試看的態度買了兩盒,額,不對,是接入了一下。

其實接入方法非常簡單,就是一頓新增依賴。Navigation主要是有兩個庫和一個gradle外掛。

// 需要新增依賴的兩個Navigation庫
implementation 'android.arch.navigation:navigation-fragment-ktx:1.0.0-beta02'
implementation 'android.arch.navigation:navigation-ui-ktx:1.0.0-beta02'

// 這段依賴新增在工程的build.gradle中,這個是一個gradle外掛
classpath 'android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-beta02'

// 在app用到的module中啟用這個外掛
apply plugin: 'androidx.navigation.safeargs'
複製程式碼

兩個依賴庫就是Navigation的主體,提供了Navigation的基礎能力。safeArgs這個外掛是用來支援頁面間型別安全的引數傳遞,不是必須的,但是建議使用,因為真的很方便,這個後面會講到。

建立一個Navigation

依賴都新增好了,開搞!

Navigation還是延用了Android的XML管理思想,每一個Navigation導航邏輯對應一個xml檔案(就是對應iOS的storyboard)。這個xml檔案在資源目錄的navigation資料夾下(和layout、drawable是平級的)

建立方法就是右鍵,選擇new一個Android Resource File,之後的彈窗裡,你會發現ResourceType的選項裡,多了一個Navigation,如下圖。(如果沒有,老弟兒,你的依賴一定沒配置對,或者你還是AndroidStudio3.2吧。。。)

企業微信20190220104315.png

起個名字,建立!操作皮膚長成這樣(不要在意我風騷的名字)

企業微信20190220104943.png

我的需求很簡單,我有一個桌面頁(desktop),我現在想上youtube,我先要跳轉的Google,然後在通過Google上youtube去看看外面的世界,完事以後回到桌面。嗯,就這麼簡單。

按照以前我們的做法,首先肯定要自己建立3個頁面的Fragment佈局,然後再建立Fragment類,然後還要在Activity裡面建立三個Fragment,然後通過SupportFragmentManager來新增頁面,管理頁面跳轉和回退,這個就是我們開篇時候說的痛點。

現在有了Navigation,怎麼做呢?放下一前的老思想吧,不用寫程式碼就能搞定。

首先,在剛才的皮膚中點選新增一個destination,destination(目的地)是一個新概念,用於頁面跳轉。一個destination對應一個Fragment。例如從A跳轉B,我的destination就是B。

企業微信20190220112403.png

建立destination的過程非常人性化,填寫Fragment名字,自動建立對應的xml佈局檔案。我們就新增三個頁面,Desktop、Google、youtube。新增完如圖所示:

企業微信20190220112840.png

OK,我們用到的3個頁面都在這裡了。我們的跳轉流程就是Desktop - > Google -> youtube -> Desktop。怎麼設定呢?連線就行了!

連線完畢是這樣的,不要在意那飄逸的線條。Desktop頁面左上角有個小房子圖示,這說明這個頁面是Navigation的起始頁面。這個可以在右側編輯欄裡的start destination中修改。

企業微信20190220113113.png

關聯Navigation到Activity

聯好線了,走到這一步,我們已經將頁面的跳轉關係宣告完畢。但是要提醒大家一下,我們編輯的這個Navigation是一個xml,而且是一個導航xml,並不是佈局啊。怎麼往頁面裡新增呢?

別急,這個也很簡單。先在Activity的佈局xml裡新增一個fragment標籤。然後新增navi屬性,具體如下:

<fragment
            android:id="@+id/nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:defaultNavHost="true"
            app:navGraph="@navigation/navi_youtube"/>
複製程式碼

關鍵屬性有兩個個,android:name app:navGraph

name中宣告這個Fragment是一個NavHostFragment。這個可以理解為所有導航頁面的容器。導航中宣告瞭各個頁面的跳轉關係,但是總要有一個容器來展示並控制它們吧?這個容器就是NavHostFragment,然後建立一個Activity,用這個Activity再包一下NavHostFragment就可以了。

navGraph屬性指定一個導航xml,就是我們剛才編輯的那個navi_pornhub。

配置完了就可以執行一下看看效果了,果然啟動後進入了Desktop頁面(這裡懶得截圖了。。。)

頁面間的跳轉

看的這裡可能各位會說,這玩意除了一個圖形化的編輯介面就完事了?這個我用原來的純程式碼也能做啊。。。它的優勢在哪?難道只是長得好看嗎?

各位別急啊

cc11728b4710b912bd7485c7cefdfc0392452255.jpg

以前頁面的跳轉怎麼做?SupportFragmentManager用FragmentTransaction,自己管理,寫好多行程式碼。

看看現在的新操作:

在Desktop頁面中點選TextView跳轉Google,只需要在onClick里加入這樣一行程式碼即可:

tv_desktop.setOnClickListener {
    findNavController().navigate(FragmentDesktopDirections.actionFragmentDesktopToFragmentGoogle())
}
複製程式碼

加完之後執行一下,發現真的可以跳轉了!

我們來仔細看看這行魔法程式碼。首先它執行findNavController(),這個函式點進去發現它是Navigation庫給Fragment做的一個擴充。

企業微信20190220021653.png
這裡有就是呼叫了NaviHostFragment的同名函式,這也印證了我們前文說到的NaviHostFragment的作用,它就是一個控制容器。

找到NavController之後,呼叫了navigate(NavDirections directions)函式。這個函式從名字看出來就是做導航跳轉用的。但是新姿勢是它的引數比較特殊,是一個NavDirections例項。這個類是幹什麼的呢?

還記得前面介紹的Destination概念嗎?Destination表示一個跳轉目的地,一個Destination對應一個Fragment。NavDirections表示的是兩個頁面之間的跳轉關係,其實就是我們最開始畫導航頁面關係時候,連線頁面的那個箭頭。一個箭頭對應一個NavDirections例項。

那跳轉的時候如何獲取NavDirections例項呢?這裡Navigation框架採用編譯器自動生成程式碼的技術,對導航xml每一個Fragment,都生成了一個對應的類,名字就是類名+Directions,例如FragmentGoogleDirections。這些自動生成的類可以在工程的generatedJava路徑下看到。程式碼如下:

企業微信20190220023322.png

其實就是一個Java靜態工具類。有一個action,名字就可以看出是由桌面跳轉Google,它返回一個NavDirections例項。所以在跳轉頁面時,我們直接呼叫這個函式獲取NavDirections例項即可。這個例項就包含了頁面跳轉資訊,告訴Navigation我要從桌面跳轉的Google頁面。

這裡只有一個跳轉action函式,因為桌面只有跳轉Google一個跳轉管理。如果頁面增加,桌面有多條跳轉關係,這裡會生成多個action函式,函式名稱對應導航xml中每個箭頭的action名稱。

同理,把Google跳轉youtube,和youtube跳轉桌面也加上,都很簡單。

tv_google.setOnClickListener {
    findNavController().navigate(FragmentGoogleDirections.actionFragmentGoogleToFragmentYoutube())
}

tv_youtube.setOnClickListener {
    findNavController().navigate(FragmentPornHubDirections.actionFragmentYoutubeToFragmentDesktop())
}
複製程式碼

這樣就實現了頁面間的跳轉邏輯。這種寫法首先很簡潔,思路清晰,看函式名一眼就能看出來是怎麼跳轉的,而且自動生成的程式碼也避免了人為犯錯。怎麼樣,回想一下以前用SupportFragmentManager做跳轉的方法,是不是感覺相見恨晚?

頁面回退管理

瞭解了頁面跳轉,我們再來看看頁面回退。執行一下剛才的程式,發現可以無限迴圈跳轉,桌面到Google,Google到youtube,youtube再到桌面。。。

我們點一下返回鍵看看會發生什麼。額。。。發現會一直回退,你剛才重複迴圈了跳轉多少次,就要回退多少次。這個說白了就是Activity的Standard啟動模式。

這個不是我們要的效果啊,我們要的是A到B,B到C,C返回A,之後頁面棧裡就只有A了。不能再返回了。說白了就是singleTask啟動模式。

這個需要對跳轉的屬性進行設定,開啟導航xml,選中youtube到桌面的連線箭頭。在右側的屬性編輯區裡,找到紅框裡的屬性,填寫好即可。

企業微信20190220025520.png

這裡首先宣告瞭這是一個pop跳轉操作,要跳回到Desktop。然後inclusive打上對勾,這個表示調回之後,要把destination也彈出。不勾選這個,會導致返回後棧裡有兩個桌面Fragment。不信可以試試哦。

之後再你的Activity裡面,覆寫這個函式:

override fun onSupportNavigateUp() =
        findNavController(this, R.id.nav_host_fragment).navigateUp()
複製程式碼

覆寫這個函式就是把Activity的返回操作權利移交給Navigation框架來處理。從此不用再寫onKeyDown了。。。

頁面引數傳遞

跳轉和回退都搞定了,就要看看頁面引數傳遞了。這個體驗之後發現是真香度最高的部分。

先回顧一下老方法,老方法當然就是setArguments。因為Fragment不支援你在建構函式中加入引數(當然你要硬加也行,各種報警告,賊難受),所以只能通過setArguments來搞,不管你怎麼封裝,都需要兩步:建立和設定引數。怎麼都不快樂。

現在來Navigation吧,這個新操作絕對眼前一亮。

需求:我想在桌面跳轉Google時,給頁面傳遞一個搜尋關鍵詞youtube,並且彈一個Toast。下面是做法:

首先在導航xml裡,選中GoogleFragment,在右面的操作皮膚裡,點選新增arguments,新增一個字串引數,叫keyword。不可空,沒有預設值。點選確認新增。

企業微信20190220031538.png

之後可以看見GoogleFragment裡面已經有一個叫keyword的引數了。

企業微信20190220031651.png

之後點選桌面到Google的箭頭(還記得箭頭其實就是一個NavDirections吧),你會發現這個Direction的屬性裡也有了一個引數,其實這個就是一個自動化的對應機制,因為GoogleFragment有一個引數,所以所有跳轉到這個頁面的Direction裡都應該有對應的引數。

引數新增好以後,修改一下跳轉的那一行魔法程式碼。

tv_desktop.setOnClickListener {
    findNavController().navigate(FragmentDesktopDirections.actionFragmentDesktopToFragmentGoogle("youtube"))
}
複製程式碼

這裡在呼叫action函式獲取NavDirections例項的時候,需要傳一個字串,就是keywork的值。同理如果有多個引數這裡就傳多個值。

OK,啟動方的引數傳遞已經完成了。我們來看看接收方。

在GoogleFragment中如何獲取到keyword的值呢?其實Navigation庫已經幫你把引數放到arguments裡面了,你直接取就行了。例如這樣:

val keyword = arguments?.getString("keyword") ?: ""
複製程式碼

直接這麼呼叫就能拿到傳遞過來的值了。但是感覺不是很香啊。那是因為你沒有使用safeArgs外掛!來看一下這個新操作。

val safeArgs = FragmentGoogleArgs.fromBundle(arguments!!)
val keyword = safeArgs.keyword
複製程式碼

這裡又用到了自動生成程式碼的技術,對於每一個有引數的Fragment,都生成一個類名+Args的類。呼叫第一行程式碼,就能得到一個safeArgs例項。之後需要用引數的時候,直接safeArgs.引數名就能拿到引數的值。

各位老鐵,感覺出safeArgs的好處了嗎?首先它是型別安全的,可以直接點出來,不用打問號,舒服了很多。最重要的是,老方法是getString("變數名")這個變數名是手打的,safeArgs可以直接點出變數名來取值,方便還不容易出錯。

體驗總結

  • 真香
  • 目前還是beta版本,還不建議在實際專案中使用
  • 會持續關注後續訊息,期待穩定版本
-- 2019.03.18更新,已釋出1.0.0正式版

相關文章