Android開發 Navigation—NavController的使用詳解


前言

  這篇博客只講解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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM