Navigation屬於具有比較完善回退棧的Fragment導航器. 而且Navigation能夠和BottomNavigationView/NavigationView/Toolbar等結合使用, 內部使用的是Fragment的replace
而非show|hidden
, 因此不存在穿透現象. 生命週期存在detachView, 故視圖須要本身複用. 雖然Fragment的使用變得比較清晰和方便了, 可是我依舊不推薦使用Fragment去替代Activity. 那只是徒增麻煩而已.java
若是是使用Java語言我不推薦使用任何新框架了, 就本身玩本身的吧.android
ktx (除基本依賴外還包含一些Kotlin新特性的函數)shell
implementation 'androidx.navigation:navigation-fragment-ktx:2.0.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.0.0'
複製代碼
經常使用關鍵詞數組
關鍵字 | 描述 |
---|---|
navigation | 導航或者說回退棧 |
graph | 導航圖 |
destination | 目的地, 即在要跳轉的頁面 |
pop | 出棧, 會一直將destination 出棧, 直到到達指定id所在頁面, 能夠理解爲Fragment的singleTask 模式 |
navHost | 即全部頁面的容器. 相似網頁中的host, 全部path路徑都是在host以後跟隨, host固定不變. |
點擊NavResourceFile中的Design便可查看佈局編輯器, 佈局編輯器分爲三欄.安全
左側是已添加的導航, 中間是頁面瀏覽, 中間欄的工具欄能夠建立和快速添加標籤以及整理頁面, 右側屬性欄方便添加屬性.bash
navigation
這是個嵌套的圖表, 能夠點擊打開新的圖表頁面.架構
Activity佈局中app
<LinearLayout .../>
<androidx.appcompat.widget.Toolbar .../>
<fragment android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:id="@+id/my_nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" app:navGraph="@navigation/mobile_navigation" app:defaultNavHost="true" />
<com.google.android.material.bottomnavigation.BottomNavigationView .../>
</LinearLayout>
複製代碼
android:name="androidx.navigation.fragment.NavHostFragment"
固定寫法
app:navGraph
指定navigation資源文件, 也能夠不指定後面經過代碼中動態設置
app:defaultNavHost
是否攔截返回鍵事件, false表示不須要回退棧.
複製代碼
手寫建立NavHostFragment時並不會自動代碼補全, 可使用Editor Designer建立.框架
建立資源文件編輯器
res目錄建立 AndroidResourceFile 選擇 Navigation. 而後 new-> NavigationResourceFile
navigation節點
app:startDestination="@+id/home_dest" 指定初始目標
複製代碼
navigation能夠嵌套navigation標籤.在佈局編輯器中會顯示爲:
嵌套navigation沒法互相關聯
<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_global" app:startDestination="@id/mainFragment">
<!-- <action android:id="@+id/global_action" app:destination="@id/navigation" />-->
<fragment android:id="@+id/mainFragment" android:name="com.example.frameexample.MainFragment" android:label="fragment_main" tools:layout="@layout/fragment_main">
<action android:id="@+id/action_mainFragment_to_personInfoFragment" app:destination="@id/settingFragment" />
</fragment>
<navigation android:id="@+id/navigation" app:startDestination="@id/settingFragment">
<fragment android:id="@+id/settingFragment" android:name="com.example.frameexample.SettingFragment" android:label="fragment_setting" tools:layout="@layout/fragment_setting" />
</navigation>
</navigation>
複製代碼
上面的mainFragment
沒法直接app:destination="@id/settingFragment"
這會致使運行錯誤. 只能先導航到navigation
.(即NavHostFragment所在的界面)
fragment 節點
android:id 不言而喻
android:name 目標要實例化的fragment徹底限定類名
tools:layout 用於顯示在佈局編輯器
android:label 用於後面綁定Toolbar等自動更新標題
複製代碼
argument 節點
android:name="myArg"
app:argType="integer"
android:defaultValue="0"
複製代碼
在跳轉導航頁面的時候會自動在argument
中帶上參數(要求指定參數默認值). 數組和Paraclable/Serializable
不支持默認值設置, 經過下面要講的SafeArg
能夠在編譯器校驗參數類型安全問題.
action 節點
動做 用於頁面跳轉時指定目標頁面
android:id="@+id/next_action"
動做id
app:destination="@+id/flow_step_two_dest">
目標頁面
app:popUpTo="@id/home_dest"
當前屬於彈出棧
app:popUpToInclusive="true/false"
彈出棧是否包含目標
app:launchSingleTop="true/false"
是否開啓singleTop模式
app:enterAnim=""
app:exitAnim=""
導航動畫
app:popEnterAnim=""
app:popExitAnim=""
彈出棧動畫
複製代碼
若是從導航頁面到新的Activity頁面, 動畫不支持. 請使用默認的Activity設置動畫去支持.
全局動做
NavController只能使用當前頁面在XMl中的節點的子節點action
. 不能使用其餘的Fragment下的動做.
可是能夠經過直接給navigation
標籤建立子標籤action
, 這屬於全局動做
, 即每一個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/nav_main" app:startDestination="@id/homeFragment">
<action android:id="@+id/action_categoryFragment_to_main2Activity" app:destination="@id/main2Activity" />
<fragment android:id="@+id/categoryFragment" android:name="com.example.frame.fragment.CategoryFragment" android:label="fragment_category" tools:layout="@layout/fragment_category"/>
</navigation>
複製代碼
佔位頁
佔位頁面若是運行時沒有指定Class而且導航到該佔位頁面時會拋出異常
總結
通常狀況下咱們其實只會在XML中定義Fragment
節點.
這些action|argument
節點我認爲主要是給SafeArgsGradle插件使用. 用於給插件掃描自動生成類比較方便, 若是不使用插件我不建議使用者兩個節點, 在文章末尾會詳細說起若是使用該插件. 該插件主要是自動處理參數的傳遞和獲取以及目的地跳轉.
NavController 控制導航的跳轉和彈出棧
NavOptions 控制跳轉過程當中的配置選項, 例如動畫和singleTop模式
Navigation 工具類 建立點擊事件或者獲取控制器 (雞肋), 本身實現擴展函數可能更加方便.
NavHostFragment 導航的容器, 能夠設置和獲取導航圖(NavGraph)
NavGraph 用於描述導航中頁面關係的對象 能夠增刪改查頁面,設置起始頁等
NavigationUI 用於將導航和一系列菜單控件自動綁定的工具類
Navigator 頁面的根接口, 若是想建立一個新的類型頁面就要自定義他
NavigatorProvider 一個內部保存着[名稱:Navigator]鍵值對的HashMap. 通常狀況不須要使用.
NavDeepLinkBuilder 構建一個能打開導航頁面的Intent
NavInflater 用於將XML建立成NavGraph對象, NavController能夠經過更方便的函數建立不須要直接使用這個類.
void NavInflater(@NonNull Context context, @NonNull NavigatorProvider navigatorProvider) // 要求當前NavHostFragment的內部變量 // 構造函數, 但通常狀況使用NavController.getNavInflater獲取對象 void NavGraph inflate(@NavigationRes int graphResId) 複製代碼
NavBackStackEntry 表示一個目的地所對應
NavController用於跳轉頁面和參數傳遞等控制, 能夠經過擴展函數獲得實例.
經過三個擴展函數能夠獲得實例
1. Fragment.findNavController()
2. View.findNavController()
3. Activity.findNavController(viewId: Int) // NavHostFragment的Id
複製代碼
跳轉目的
public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) // 可設置共享元素動畫參數 public void navigate(Uri deepLink, NavOptions navOptions, Navigator.Extras navigatorExtras) 複製代碼
resId
都是可選參數經過實現抽象類NavDirections
建立自定義的對象來描述跳轉目標(action)和傳參(bundle)
public void navigate (NavDirections directions, NavOptions navOptions, Navigator.Extras navigatorExtras) 複製代碼
directions
都是可選參數resId
能夠是XML中的action
或者destination
節點id, 若是是action則會附帶action的配置, 若是是destination則不會附帶destination節點下的子節點action(寫了白寫).
args
即須要在fragment之間傳遞的Bundle參數, 可是導航還支持另一種插件形式的傳遞參數方式-安全參數SafeArgs, 後面提到.
navOptions 即導航頁面一些配置選項(例如動畫)
返回上級
public boolean navigateUp () public boolean navigateUp (DrawerLayout drawerLayout) public boolean navigateUp (AppConfiguration appConfiguration) 複製代碼
出棧
彈出棧, 即從Nav回退棧中清除Fragment.
public boolean popBackStack (int destinationId, // 目標id boolean inclusive) // 是否包含參數目標 public boolean popBackStack () // 彈出當前Fragment 複製代碼
目的地變化監聽器
每次進行跳轉等頁面變化時都會回調該監聽器
public void addOnDestinationChangedListener (NavController.OnDestinationChangedListener listener) public void removeOnDestinationChangedListener (NavController.OnDestinationChangedListener listener) 複製代碼
public interface OnDestinationChangedListener {
/** * 導航完成之後回調函數(可是可能動畫還在播放中) * * @param 控制導航到目標的導航控制器NavController * @param 目標頁面 * @param 導航到目標頁面的參數 */
void onDestinationChanged(@NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments);
}
複製代碼
DeepLink
NavDeepLinkBuilder createDeepLink () // 內部就是調用的DeepLinkBuilder的構造函數 boolean handleDeepLink(Intent intent) 複製代碼
NavDestination getCurrentDestination () NavigatorProvider getNavigatorProvider () NavInflater getNavInflater() Bundle saveState () void restoreState (Bundle navState) // 用於處理NavController的狀態獲取和恢復 複製代碼
關於NavGraph函數
void setGraph(NavGraph graph, Bundle startDestinationArgs);
void setGraph(int graphResId, Bundle startDestinationArgs);
NavGraph getGraph () 複製代碼
startDestinationArgs
可選參數, 用於傳遞給起始目的地的參數對象一個抽象類用於自定義動做和參數, 若是你想複用某個跳轉邏輯就能夠自定義繼承該類. 通常狀況下咱們並不會手動去繼承該類由於過於麻煩, 這個類主要是用於SafeArgs插件自動生成的派生類.
前面提到參數 navigatorExtras
目前是用於支持轉場動畫的共享元素.
經過擴展函數能夠快速建立
fun FragmentNavigatorExtras(vararg sharedElements: Pair<View, String>)
fun ActivityNavigatorExtras(activityOptions: ActivityOptionsCompat? = null, flags: Int = 0)
複製代碼
能夠從源碼看到內部都是使用的Navigator.Extras.Builder構造器建立的.
屬於導航時的附加選項設置
XML示例
<fragment
android:id="@+id/home_dest"
android:name="com.example.android.codelabs.navigation.HomeFragment"
android:label="@string/home"
tools:layout="@layout/home_fragment">
<action
android:id="@+id/next_action"
app:destination="@+id/flow_step_one_dest"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
</fragment>
複製代碼
建立NavOptions對象至關於代碼動態實現了XML中的<action>
標籤的屬性設置(可是沒法指定目標).
目前功能只有設置動畫和singleTop(啓動模式), popUp(彈出棧)
建立對象
提供一個DSL做用域
fun navOptions(optionsBuilder: NavOptionsBuilder.() -> Unit): NavOptions 複製代碼
示例
val options = navOptions {
anim {
enter = R.anim.slide_in_right // 進入頁面動畫
exit = R.anim.slide_out_left
popEnter = R.anim.slide_in_left // 彈出棧動畫
popExit = R.anim.slide_out_right
}
launchSingleTop = true
popUpTo = R.id.categoryFragment
}
findNavController().navigate(R.id.flow_step_one_dest, null, options)
複製代碼
彈出棧
public NavOptions.Builder setPopUpTo (int destinationId, boolean inclusive) 複製代碼
函數並不會決定目的地, 只是在導航到目的地以前先執行一個彈出棧指令.
場景: 例如我如今購買一個商品支付成功, 這個時候我要將前面的商品詳情; 訂單配置等頁面所有關閉 而後進入<支付成功>頁面
順便說下在以前的作法是發送事件而後finish
表示當前目標對象
NavController
能夠獲取NavDestination
findNavController().currentDestination
複製代碼
函數
void addDeepLink(String uriPattern) boolean hasDeepLink(Uri deepLink) Navigator getNavigator() NavGraph getParent() NavAction getAction(int id) void putAction(int actionId, NavAction action) void putAction(int actionId, int destId) void removeAction(int actionId) void setDefaultArguments(Bundle args) void addDefaultArguments(Bundle args) Bundle getDefaultArguments() void setId(int id) int getId() void setLabel(CharSequence label) CharSequence getLabel() 複製代碼
該對象爲Navigation提供一個容器
通常使用狀況是在佈局中直接定義, 可是也能夠經過代碼構建實例, 而後經過代碼建立視圖(例如ViewPager等)
public static NavHostFragment create (int graphResId)
複製代碼
Navigation能夠與一系列視圖組件進行聯動
BottomNavigationView
經過擴展函數能夠綁定多個導航圖, 且關聯BNV.
fun BottomNavigationView.setupWithNavController( navGraphIds: List<Int>, fragmentManager: FragmentManager, containerId: Int, intent: Intent ): LiveData<NavController>
複製代碼
navGraphIds
集合中包含navGrap的Id. 每一個BNV的按鈕對應一個navGrap.ActionBar
設置導航到新頁面時自動更新標題文字
fun AppCompatActivity.setupActionBarWithNavController( navController: NavController, drawerLayout: DrawerLayout? )
fun AppCompatActivity.setupActionBarWithNavController( navController: NavController, configuration: AppBarConfiguration = AppBarConfiguration(navController.graph)
)
複製代碼
這裏出現個參數AppBarConfiguration, 用於配置Toolbar/ActionBar/CollapsingToolbarLayout.
AppBarConfiguration
構造器模式使用Builder建立實例
AppBarConfiguration.Builder(NavGraph navGraph)
AppBarConfiguration.Builder(Menu topLevelMenu)
AppBarConfiguration.Builder(int... topLevelDestinationIds)
AppBarConfiguration.Builder(Set<Integer> topLevelDestinationIds)
// 構造函數效果等同
複製代碼
函數
AppBarConfiguration.Builder setDrawerLayout(DrawerLayout drawerLayout) // 綁定Toolbar同時綁定一個DrawerLayout聯動 AppBarConfiguration.Builder setFallbackOnNavigateUpListener(AppBarConfiguration.OnNavigateUpListener fallbackOnNavigateUpListener) // 監聽返回操做 AppBarConfiguration build() 複製代碼
AppBarConfiguration.OnNavigateUpListener 該回調接口會在每次點擊向上導航時回調
public interface OnNavigateUpListener {
/** * 回調處理向上導航 * * @return 返回true表示向上導航, false不處理 */
boolean onNavigateUp();
}
複製代碼
Toolbar
Toolbar也能夠綁定Nav自動更新對應頁面的標題
源碼:
fun Toolbar.setupWithNavController( navController: NavController, drawerLayout: DrawerLayout? ) {
NavigationUI.setupWithNavController(this, navController,
AppBarConfiguration(navController.graph, drawerLayout))
}
複製代碼
CollapsingToolbarLayout
fun CollapsingToolbarLayout.setupWithNavController( toolbar: Toolbar, navController: NavController, configuration: AppBarConfiguration = AppBarConfiguration(navController.graph)
)
fun CollapsingToolbarLayout.setupWithNavController( toolbar: Toolbar, navController: NavController, drawerLayout: DrawerLayout? )
複製代碼
Menu
綁定菜單條目點擊自動導航
fun MenuItem.onNavDestinationSelected(navController: NavController): Boolean =
NavigationUI.onNavDestinationSelected(this, navController)
複製代碼
使用方式以下:
例如在onOptionsItemSelected
函數中設置
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return item.onNavDestinationSelected(findNavController(R.id.my_nav_host_fragment))
}
複製代碼
NavigationView
fun NavigationView.setupWithNavController(navController: NavController) {
NavigationUI.setupWithNavController(this, navController)
}
fun BottomNavigationView.setupWithNavController(navController: NavController) {
NavigationUI.setupWithNavController(this, navController)
}
複製代碼
查看源碼能夠知道這些擴展函數本質上只是封裝使用NavigationUI
的靜態函數.
深層連接屬於新建回退棧的開啓一個頁面. Navigation支持兩種方式開啓DeepLink.
很簡單
findNavController().createDeepLink()
.setDestination(R.id.dynamicFragment)
.createPendingIntent()
.send()
複製代碼
Nav聲明一個DeepLink(深層連接)只須要給Fragment添加一個子標籤便可
AndroidManifest中的對應頁面的Activity須要添加如下兩個子節點
<activity>
<action android:name="android.intent.action.VIEW" />
<nav-graph android:value="@navigation/mobile_navigation" />
</activity>
複製代碼
深層連接
<deepLink app:uri="www.example.com/{myarg}" />
複製代碼
http://
能夠省略經過ADB測試
adb shell am start -a android.intent.action.VIEW -d "http://www.YourWebsite.com/fromWeb"
複製代碼
2334456
即傳遞過去的參數
{}
包裹的字段屬於變量, *
能夠匹配任意字符
經過NavDeepLinkBuilder建立DeepLink Intent
NavDeepLinkBuilder setArguments(Bundle args) NavDeepLinkBuilder setDestination(int destId) NavDeepLinkBuilder setGraph(int navGraphId) NavDeepLinkBuilder setGraph(NavGraph navGraph) 複製代碼
生成PendingIntent能夠用於開啓界面(例如傳給Notification)
PendingIntent createPendingIntent() TaskStackBuilder createTaskStackBuilder() 複製代碼
安全類型插件, 基於Gradle實現的插件.
他的目的就是根據你在NavRes中聲明argument
標籤生成工具類, 而後所有使用工具類而不是字符串去獲取和設置參數. 避免先後二者參數類型不一致而崩潰.
插件
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0'
}
}
複製代碼
應用插件
apply plugin: 'androidx.navigation.safeargs'
複製代碼
在NavigationResourceFile中聲明<argument>
標籤後會自動生成類
插件會根據頁面自動生成{當前類名}Directions
類, 該類包含該頁面能使用的全部跳轉動做(包含全局動做和自身動做).
生成類會包含一個有規則的靜態函數用於獲取Directions
的實現類(*Directions的靜態內部類), 函數名稱規則爲
action{頁面名稱}To{目標頁面名稱}
全局動做名稱規則則爲: 動做id的變量命名法
例: public static ActionMainFragmentToPersonInfoFragment actionMainFragmentToPersonInfoFragment()
複製代碼
這裏看下NavDirections
接口的含義
public interface NavDirections {
/**
* 返回動做id
*
* @return id of an action
*/
@IdRes
int getActionId();
/**
* 返回目標參數
*/
@NonNull
Bundle getArguments();
}
複製代碼
能夠總結爲 包含攜帶參數和動做.
可是若是navRes中還包含<argument>
標籤, 則還會生成對應的{當前類名}Args
類.
完整的導航頁面且傳遞數據寫法
跳轉目的地
findNavController().navigate(CategoryFragmentDirections.actionCategoryFragmentToDynamicFragment())
複製代碼
若是你跳轉的目的地要求傳遞參數flag
findNavController().navigate(CategoryFragmentDirections.actionCategoryFragmentToDynamicFragment().setFlag(2))
複製代碼
在Fragment中獲得參數
tv.text = PersonInfoFragmentArgs.fromBundle(arguments!!).name
複製代碼
本質上插件就是限制開發者跳轉目的地時傳遞參數時區分可選和必選的傳遞參數
關於Google推進的SingleActivity構建應用我說下個人見解, 我認爲整個應用使用一個Activity仍是比較麻煩的.
列舉下所謂麻煩
我認爲Navigation替代FragmentManger仍是駕輕就熟的, 而且導航圖看起來也頗有邏輯感.