前言
這篇博客只講解NavController控制器的一些API的使用詳解與應用環境的說明。不在講解一些Navigation的基礎知識,如果你還不了解Navigation建議你去另外一篇博客了解 https://www.cnblogs.com/guanxinjing/p/11555217.html
為什么要了解NavController
NavController字面意思就是導航控制器,它負責操作Navigation框架下的Fragment的跳轉與退出、動畫、監聽當前Fragment信息,當然這些是基本操作。但是更重要的是知道它可以使用的范圍,一般情況下我們以為它只能在Fragment里調用,實際情況下它在Activity里也可以調用。靈活的使用它,可以幫你實現所有形式的頁面跳轉。除此之外你甚至還能使用TabLayout配合Navigation進行主頁的分頁設計。極端點你甚至還能在某個分頁里再次添加TabLayout配合Navigation進行嵌套設計。
如何獲取NavController實例
在Activity中獲取
NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment); //這個R.id.fragment就是Activity布局里fragment控件的id
在Fragment中獲取
NavController navController = Navigation.findNavController(getView());
API講解與使用場景
請注意一下代碼有些是在Activity里操作NavController,但是實際上下面的代碼也可以在Fragment里執行,這2者是沒有區別的。所以這里並不重復貼Fragment里的代碼了
在Activity根據業務需要使用setGraph切換不同的Navigation
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); initNav(2); } private void initNav(int type){ NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment); //在Activity里獲取NavController實例 if (type == 1){ controller.setGraph(R.navigation.demo_one_nav); //設置xml文件 return; } if (type == 2){ Bundle bundle = new Bundle(); bundle.putString("name", "demo"); controller.setGraph(R.navigation.demo_two_nav, bundle); //設置xml文件的並傳入數據,這個數據可以在啟動的Fragment里獲取到 return; } DemoActivity.this.finish(); }
上面的第二種方式,我們在設置xml文件的同時還傳入了數據,這個數據可以在Fragment里建議用下面的方式獲取
@Override public void onAttach(@NonNull Context context) { //在Fragment里重寫onAttach,在這里獲取可以保證數據不會為null super.onAttach(context); Log.e("觸發", "onAttach: 傳入數據=" + getArguments().getString("name")); }
在Activity使用navigate跳轉Fragment
使用Action的id跳轉
請注意!下面的navigate的值是action的id,而使用action的id跳轉Fragment是不能憑空在沒有上一個Fragment的情況下跳轉下一個Fragment的,這是會報錯的。
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment); controller.setGraph(R.navigation.demo_one_nav); controller.navigate(R.id.action_oneFragment_to_twoFragment); //從默認啟動的oneFragment跳轉到TwoFragment }
使用fragment的id直接跳轉
<fragment android:id="@+id/threeFragment" android:name="com.zh.fragmentdemo.ThreeFragment" android:label="fragment_three" tools:layout="@layout/fragment_three" />
在activity里
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment); controller.setGraph(R.navigation.demo_one_nav); controller.navigate(R.id.threeFragment); //直接跳轉到threeFragment }
使用在navigation xml文件的fragment的id可以直接跳轉到某一個Fragment里,這種跳轉方式可以不需要是否有上一個Fragment。例如我們需要直接跳轉到threeFragment。請注意!下面使用navigate直接指定id目標Fragment上是會創建一個新的Fragment到堆棧里的。這個時候就會看到每次navigate都會創建一個新的實例從而導致內存泄漏. 實際項目在一些tabLayout功能下最好配合popBackStack來使用。如果popBackStack跳轉到指定目標為false,在使用navigate來跳轉到指定Fragment。如下
if (!mNavController.popBackStack(R.id.threeFragment, false)) { mNavController.navigate(R.id.threeFragment); }
使用navigate帶動畫跳轉或者彈出Fragment
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment); controller.setGraph(R.navigation.demo_one_nav); NavOptions navOptions = new NavOptions.Builder() .setEnterAnim(R.anim.from_right) //進入動畫 .setExitAnim(R.anim.to_left) //退出動畫 .setPopEnterAnim(R.anim.to_left) //彈出進入動畫 .setPopExitAnim(R.anim.from_right) //彈出退出動畫 .build(); controller.navigate(R.id.action_oneFragment_to_twoFragment, null , navOptions); }
使用popBackStack彈出Fragment
方式一 彈出當前的Fragment
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment); controller.setGraph(R.navigation.demo_one_nav); controller.navigate(R.id.action_oneFragment_to_twoFragment); //從oneFragment進入到twoFragment controller.navigate(R.id.action_twoFragment_to_threeFragment); //從twoFragment進入到threeFragment controller.popBackStack(); //彈出threeFragment,回到twoFragment }
方式二 彈出到指定的Fragment,並且設置布爾值是否連這個指定Fragment一起彈出
請注意! 請重視 public boolean popBackStack(@IdRes int destinationId, boolean inclusive) 這個方法。因為此方法可以實現清空中間導航棧堆的需求,舉例假如 現在有 A -> B -> C -> D 這四個導航Fragment,如果我們現在當前導航到D並且想回到A,如果使用navigate()方法回到A,你就會發現用A Fragment里按返回鍵,不是直接退出而是直接到D Fragment。 如果是使用popBackStack(@IdRes int destinationId, boolean inclusive)方法,就不會回到D Fragment
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment); controller.setGraph(R.navigation.demo_one_nav); controller.navigate(R.id.action_oneFragment_to_twoFragment); //從oneFragment進入到twoFragment controller.navigate(R.id.action_twoFragment_to_threeFragment); //從twoFragment進入到threeFragment controller.popBackStack(R.id.twoFragment, true); //彈出到twoFragment,第二個參數的布爾值如果為true則表示參數一的Fragment一起彈出,這個時候我們就會回到oneFragment }
另外,如果你發現目標Fragment不存在跳轉失敗后,你可以參考以下這種實現,直接創建指定目標的Fragment
boolean jumpStatus = Navigation.findNavController(getView()).popBackStack(R.id.scenesTimingFragment, false); if (!jumpStatus){ Navigation.findNavController(getView()).navigate(R.id.scenesTimingFragment); }
navigateUp() 向上導航
Navigation.findNavController(getView()).navigateUp();
navigateUp也是執行返回上一級Fragment的功能。 有小朋友會問了popBackStack和navigateUp()的區別是什么呢?
navigateUp向上返回的功能其實也是調用popBackStack的。 但是,navigateUp的源碼里多了一層判斷,源碼如下:就是判斷這個Navigation是否是最后一個Fragment,並且這個Navigation與里面的Fragment是不是有可能是其他Navigation跳轉過來的。如果是其他Navigation跳轉過來的就會回到之前的Navigation上。並且銷毀當前Navigation的Activity。
public boolean navigateUp() { if (getDestinationCountOnBackStack() == 1) { // If there's only one entry, then we've deep linked into a specific destination // on another task so we need to find the parent and start our task from there NavDestination currentDestination = getCurrentDestination(); int destId = currentDestination.getId(); NavGraph parent = currentDestination.getParent(); while (parent != null) { if (parent.getStartDestination() != destId) { TaskStackBuilder parentIntents = new NavDeepLinkBuilder(this) .setDestination(parent.getId()) .createTaskStackBuilder(); parentIntents.startActivities(); if (mActivity != null) { mActivity.finish(); } return true; } destId = parent.getId(); parent = parent.getParent(); } // We're already at the startDestination of the graph so there's no 'Up' to go to return false; } else { return popBackStack(); } }
使用getCurrentDestination獲取當前導航目的地
NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment); NavDestination navDestination = controller.getCurrentDestination(); //獲取當前目的地的信息 Log.e(TAG, "onCreate: NavigatorName = " + navDestination.getNavigatorName()); Log.e(TAG, "onCreate: id = " + navDestination.getId()); Log.e(TAG, "onCreate: Parent = " + navDestination.getParent());
添加或者移除導航目的地監聽
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment); NavController.OnDestinationChangedListener listener = new NavController.OnDestinationChangedListener() { @Override public void onDestinationChanged(@NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments) { Log.e(TAG, "onDestinationChanged: id = " + destination.getId()); } }; controller.addOnDestinationChangedListener(listener); //添加監聽 controller.setGraph(R.navigation.demo_one_nav); controller.navigate(R.id.action_oneFragment_to_twoFragment); controller.navigate(R.id.action_twoFragment_to_threeFragment); controller.removeOnDestinationChangedListener(listener); //移除監聽 }
getNavInflater Navigation創建
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo); NavController controller = Navigation.findNavController(DemoActivity.this, R.id.fragment); NavGraph navGraph = controller.getNavInflater().inflate(R.navigation.demo_one_nav); //獲得NavGraph 就是navigation,可以操作各種Fragment的增加或移除功能,一個是代碼操作一個是xml操作 controller.setGraph(navGraph); //其實用這種方式setGraph(navGraph)與直接setGraph(R.navigation.demo_one_nav);一樣 }
Fragment里嵌套Navigation
在Fragment里
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mTabLayout = (TabLayout) view.findViewById(R.id.tab_layout); mAddBtn = (ImageView) view.findViewById(R.id.add_btn); mNavController = Navigation.findNavController(getActivity(), R.id.scenes_fragment);//請注意,這個 R.id.scenes_fragment是Fragment布局里的fragment }
判斷當前頁面顯示的Fragment是不是目標Fragment
public fun <F : Fragment> isActiveFragment(fragmentClass: Class<F>): Boolean { val navHostFragment = this.supportFragmentManager.fragments.first() as NavHostFragment navHostFragment.childFragmentManager.fragments.forEach { if (fragmentClass.isAssignableFrom(it.javaClass)) { return true } } return false }
End