Navigation使用以及源碼簡單分析

Navigation最新版本查看
使用文檔

使用流程

根據(jù)自己是kotlin還是java來決定添加哪個

dependencies {
  def nav_version = "2.3.0"

  // Java language implementation
  implementation "androidx.navigation:navigation-fragment:$nav_version"
  implementation "androidx.navigation:navigation-ui:$nav_version"

  // Kotlin
  implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
  implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

  // Feature module Support
  implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"

  // Testing Navigation
  androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
}

可能還需要一些插件,開頭的地址有寫

Safe Args【非必須的】

要將 Safe Args 添加到您的項目,請在頂級 build.gradle 文件中包含以下 classpath

   buildscript {
       repositories {
           google()
       }
       dependencies {
           def nav_version = "2.3.0-alpha01"
           classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
       }
   }
   

您還必須應(yīng)用以下兩個可用插件之一。

要生成適用于 Java 或 Java 和 Kotlin 混合模塊的 Java 語言代碼,請將以下行添加到應(yīng)用或模塊build.gradle 文件中:

apply plugin: "androidx.navigation.safeargs"

此外,要生成適用于 Kotlin 獨有的模塊的 Kotlin 代碼,請?zhí)砑右韵滦校?/p>

apply plugin: "androidx.navigation.safeargs.kotlin"

根據(jù)遷移到 AndroidX) 文檔,您的 gradle.properties 文件 中必須具有 android.useAndroidX=true。

Navigation 組件旨在用于具有一個主 Activity 和多個 Fragment 目的地的應(yīng)用。主 Activity 與導(dǎo)航圖相關(guān)聯(lián),且包含一個負(fù)責(zé)根據(jù)需要交換目的地的 NavHostFragment。在具有多個 Activity 目的地的應(yīng)用中,每個 Activity 均擁有其自己的導(dǎo)航圖。

開始

  1. 要向項目添加導(dǎo)航圖,請執(zhí)行以下操作:

在“Project”窗口中,右鍵點擊 res 目錄,然后依次選擇 New > Android Resource File。此時系統(tǒng)會顯示 New Resource File 對話框。
在 File name 字段中輸入名稱,例如“nav_graph”。
從 Resource type 下拉列表中選擇 Navigation,然后點擊 OK。
當(dāng)您添加首個導(dǎo)航圖時,Android Studio 會在 res 目錄內(nèi)創(chuàng)建一個 navigation 資源目錄。該目錄包含您的導(dǎo)航圖資源文件(例如 nav_graph.xml)。

image.png

image.png

剛建好啥都沒有,自己可以點擊加號添加,下邊列出了所有的fragment和activity,然后左側(cè)那個紅框連接可以跳轉(zhuǎn)到文檔告訴你咋添加.


image.png
  1. 給activity的布局里添加NavHostFragment
    這個是系統(tǒng)寫好的Fragment,可以處理導(dǎo)航圖里的fragment,
    打開activity的布局文件,左側(cè)palette面板,選擇Containers,右側(cè)選擇NavHostFragment,然后拖動到布局里


    image.png

    然后出來個彈框,我們可以選擇紅框里我們剛剛建的那個nav_test文件,也可以點上邊的加號新建一個,完事點OK


    image.png

    然后看下activity的xml文件多了個fragment
    <fragment
        android:id="@+id/fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_test" />

請注意以下幾點:

android:name 屬性包含 NavHost 實現(xiàn)的類名稱。
app:navGraph 屬性將 NavHostFragment 與導(dǎo)航圖相關(guān)聯(lián)。導(dǎo)航圖會在此 NavHostFragment 中指定用戶可以導(dǎo)航到的所有目的地。
app:defaultNavHost="true" 屬性確保您的 NavHostFragment 會攔截系統(tǒng)返回按鈕。請注意,只能有一個默認(rèn) NavHost。如果同一布局(例如,雙窗格布局)中有多個主機,請務(wù)必僅指定一個默認(rèn) NavHost

  1. 添加fragment
    通過點擊navigation文件上邊的那個加號,可以添加新的destination fragment進來,如果已經(jīng)寫好的可以直接在列表里選,如果還沒有,可以直接點擊create new,也可以暫時用個placeholder來代替【編譯沒問題,你要往這里跳轉(zhuǎn)就不行了】

  2. 設(shè)置start Fragment,就是默認(rèn)加載的那個
    在navigation視圖里點擊那個fragment,完事右鍵 ,選擇save as fragment或者點擊上邊的房子圖標(biāo)也行
    然后navigation標(biāo)簽下就多了個屬性startDestination

<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_test"
    app:startDestination="@id/fragmentNavPreference">
  1. 添加跳轉(zhuǎn)流程
    選中某個fragment的時候,可以看到右側(cè)中間有個圓圈,點擊然后把它拖動到另外 一個fragment上松手,就可以看到兩者直接有條線了,如下圖


    image.png

    代碼里也會多出一個標(biāo)簽action
    id :就是這action的id,之后跳轉(zhuǎn)用到的
    destination:就是這個action要跳到的fragment的id

    <fragment
        android:id="@+id/fragmentNavRoot"
        android:name="com.mitac.app2020.sep.nav.FragmentNavRoot"
        android:label="FragmentNavRoot"
        tools:layout="@layout/fragment_nav_root">
        <action
            android:id="@+id/action_navRoot_to_navPreference"
            app:destination="@id/fragmentNavPreference" />
        <action
            android:id="@+id/action_fragmentNavRoot_to_fragmentCategory"
            app:destination="@id/fragmentCategory" />
    </fragment>
  1. 代碼里如何跳轉(zhuǎn)fragment?
    都是通過NavController來控制的,下邊是獲取Controller的方法


    image.png

跳轉(zhuǎn)的時候就要用到action里的id了

    <fragment
        android:id="@+id/fragmentNavRoot"
        android:name="com.mitac.app2020.sep.nav.FragmentNavRoot"
        android:label="FragmentNavRoot"
        tools:layout="@layout/fragment_nav_root">
        <action
            android:id="@+id/action_navRoot_to_navPreference"
            app:destination="@id/fragmentNavPreference" />
        <action
            android:id="@+id/action_fragmentNavRoot_to_fragmentCategory"
            app:destination="@id/fragmentCategory" />
    </fragment>

代碼

        iv1.setOnClickListener {
            findNavController().navigate(R.id.action_navRoot_to_navPreference)
        }

        iv6.setOnClickListener {

            findNavController().navigate(R.id.action_fragmentNavRoot_to_fragmentCategory)
        }
//也可以這樣寫
iv3.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_fragmentNavRoot_to_fragmentCategory))

當(dāng)然了navigate后邊參數(shù)有很多的,可以添加bundle啥的

  1. Safe Args
    如果你記不住id的話,又添加了文章開頭的Safe Args 插件的話,那么你build一下工程,然后就多了一些類了
    比如我們的fragment類名是FragmentNavRoot,那么就有一個FragmentNavRootDirection的類,這個類下邊有幾個方法,navigation圖里有幾個action就有幾個方法,方法名字貌似就是action的id
    如下
         val directions=FragmentNavRootDirections.actionFragmentNavRootToFragmentCategory()
        findNavController().navigate(directions)

        findNavController().navigate(FragmentNavRootDirections.actionNavRootToNavPreference())
  1. 返回
    咋回到上一頁了?默認(rèn)后退鍵就可以回去了,activity里已經(jīng)處理過了。如果我們需要點擊某個按鈕啥的返回,那么也簡單
 findNavController().navigateUp()

如果Fragment A 跳到B ,B再跳到C,然后C想直接回到A咋辦?
如下,第一個參數(shù)就是A的fragment id,第二個參數(shù)是為是否A也彈出,這里肯定false了,我們要跳到A的,
如果第一個參數(shù)寫B(tài)的id,那么第二個參數(shù)寫成true倒是剛好.

findNavController().popBackStack(R.id.fragmentA,false)

9.參數(shù)介紹
navigate 跳轉(zhuǎn)到 時候,其實除了第一個action id以外,還有3個參數(shù)可以設(shè)置的,bundle就不說了,這個都會,看下其他兩個

    public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions,
            @Nullable Navigator.Extras navigatorExtras)

注意一下,這些參數(shù)是可以寫在導(dǎo)航的action標(biāo)簽里的
9.1 NavOptions
一般使用里邊的builder來創(chuàng)建對象,kotlin下有擴展的方法可用,后邊有寫

    public static final class Builder {
        boolean mSingleTop;
        @IdRes
        int mPopUpTo = -1;
        boolean mPopUpToInclusive;
        @AnimRes @AnimatorRes
        int mEnterAnim = -1;//四種動畫就不講了
        @AnimRes @AnimatorRes
        int mExitAnim = -1;
        @AnimRes @AnimatorRes
        int mPopEnterAnim = -1;
        @AnimRes @AnimatorRes
        int mPopExitAnim = -1;

        /**
         * Launch a navigation target as single-top if you are making a lateral navigation
         * between instances of the same target (e.g. detail pages about similar data items)
         * that should not preserve history.
         *
         * @param singleTop true to launch as single-top
         */
        @NonNull
        public Builder setLaunchSingleTop(boolean singleTop) {
            mSingleTop = singleTop;
            return this;
        }

        /**
         * Pop up to a given destination before navigating. This pops all non-matching destinations
         * from the back stack until this destination is found.
         *跳轉(zhuǎn)之前先彈出一些Fragment,在destinationId之上的【inclusive決定是否也彈出這個destinationId】
         * @param destinationId The destination to pop up to, clearing all intervening destinations.
         * @param inclusive true to also pop the given destination from the back stack.
         * @return this Builder
         * @see NavOptions#getPopUpTo
         * @see NavOptions#isPopUpToInclusive
         */
        @NonNull
        public Builder setPopUpTo(@IdRes int destinationId, boolean inclusive) {
            mPopUpTo = destinationId;
            mPopUpToInclusive = inclusive;
            return this;
        }

下邊是kotlin版本的

 navOptions { NavOptionsBuilder().popUpTo=R.id.fragmentNavRoot }

java的

val navOptions=NavOptions.Builder().apply {
            setPopUpTo(R.id.fragmentNavRoot,false)
        }.build()

設(shè)置了popUpTo 我們可以在跳轉(zhuǎn)之前彈出一些Fragment.
9.2 Extras
這個就一個接口,沒有任何方法的

    /**
     * Interface indicating that this class should be passed to its respective
     * {@link Navigator} to enable Navigator specific behavior.
     */
    public interface Extras {
    }

搜了下,有兩個實現(xiàn)的地方


image.png

Fragment傳遞sharedElement

FragmentNavigator.Extras.Builder().addSharedElement(btn_go_search,"test").build()

Activity也差不多,可以傳遞共享元素

        val options = ActivityOptions.makeSceneTransitionAnimation(
            this,
            sharedView,
            "shared_element_container" // The transition name to be matched in Activity B.
        )

ActivityNavigator.Extras.Builder().setActivityOptions(options).build()

跳轉(zhuǎn)監(jiān)聽

我們導(dǎo)航來回跳轉(zhuǎn)fragment的時候,可能用的是同一個toolbar或者bottomBar之類的,這時候切換fragment的時候我們可能需要修改標(biāo)題啥的,咋辦?activity里添加一個監(jiān)聽即可,監(jiān)聽里可以拿到要跳轉(zhuǎn)的目的地信息以及參數(shù).

findNavController(R.id.fragment).addOnDestinationChangedListener(this)

findNavController(R.id.fragment).removeOnDestinationChangedListener(this)

    override fun onDestinationChanged(
        controller: NavController,
        destination: NavDestination,
        arguments: Bundle?
    ) {
        println("changed=======$destination=======${arguments?.size()}")
        changeTitle(destination.label?:"...")
    }

argument支持的參數(shù)類型

image.png
使用safe arg插件傳遞數(shù)據(jù)的demo

上邊的基礎(chǔ)數(shù)據(jù)類型沒啥說的,我們測試下序列化數(shù)據(jù)很枚舉的
注意:

  1. argType 后邊是類的完整名字,帶路徑的
  2. 如果默認(rèn)值是null的話,必須添加app:nullable="true"
        <action
            android:id="@+id/action_fragmentNavRoot_to_fragmentSearchResult"
            app:destination="@id/fragmentSearchResult"
            app:popUpToInclusive="false">
            <argument
                android:name="data1"
                android:defaultValue="@null"
                app:argType="com.xxx.app2020.sep.nav.ArgSerializableTest"
                app:nullable="true" />
            <argument
                android:name="data2"
                android:defaultValue="SUN"
                app:argType="com.xxx.app2020.sep.nav.ArgEnumTest" />
        </action>


    <fragment
        android:id="@+id/fragmentSearchResult"
        android:name="com.mitac.app2020.sep.nav.FragmentSearchResult"
        android:label="FragmentSearchResult"
        tools:layout="@layout/fragment_search_result">
        <argument
            android:name="data1"
            android:defaultValue="@null"
            app:nullable="true"
            app:argType="com.mitac.app2020.sep.nav.ArgSerializableTest" />
        <argument
            android:name="data2"
            android:defaultValue="SUN"
            app:argType="com.mitac.app2020.sep.nav.ArgEnumTest" />
    </fragment>

然后build一下工程,就能看到在build/generated/source/navigation-args/debug/{pakage name}/下自動生成的類了,如下圖
Fragment下帶action的會生成FragmentXXXDirections
Fragment下帶argument的會生成FragmentXXXArgs


image.png

然后就可以用了,這些FragmentXXXDirections 下就會多了一些帶argument參數(shù)的方法
傳遞數(shù)據(jù)

  val data1=ArgSerializableTest("test",22)
            val data2=ArgEnumTest.FLOWER
            findNavController().navigate(FragmentNavRootDirections.actionFragmentNavRootToFragmentSearchResult(data1 = data1,data2 = data2))

接收數(shù)據(jù)的Fragment代碼

 val args:FragmentSearchResultArgs by navArgs()//kotlin下Fragment的擴展方法

//   "${args.data1?.msg}  ${args.data1?.age}  ${args.data2}"

隨時補充

如下代碼

<?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"
    app:startDestination="@id/fragmentHome">
    <action
        android:id="@+id/go_StepOne"
        app:destination="@id/fragmentStepOne" />
    <fragment
        android:id="@+id/fragmentHome"
        android:name="com.charliesong.demo0327.navigation.FragmentHome"
        android:label="FragmentHome"
        tools:layout="@layout/nav_fragment_home">

    </fragment>
    <fragment
        android:id="@+id/fragmentStepOne"
        android:name="com.charliesong.demo0327.navigation.FragmentStepOne"
        android:label="FragmentStepOne"
        tools:layout="@layout/nav_fragment_step_one">
        <argument
            android:name="title"
            android:defaultValue="just test" />
        <argument
            android:name="title2"
            android:defaultValue="just test2" />
        <action
            android:id="@+id/fragmentStepOne1"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right"
            app:destination="@id/fragmentStepTwo" />
    </fragment>

    <fragment
        android:id="@+id/fragmentStepOne1"
        android:name="com.charliesong.demo0327.navigation.FragmentStepOne"
        android:label="FragmentStepOne"
        tools:layout="@layout/nav_fragment_step_one">
        <action
            android:id="@+id/go_StepTwo"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right"
            app:destination="@id/fragmentStepTwo" />
    </fragment>

    <fragment
        android:id="@+id/fragmentStepTwo"
        android:name="com.charliesong.demo0327.navigation.FragmentStepTwo"
        android:label="FragmentStepTwo"
        tools:layout="@layout/nav_fragment_step_two">
        <action
            android:id="@+id/go_StepOne"
            app:destination="@id/fragmentStepOne" />
        <action
            android:id="@+id/go_home"
            app:popUpTo="@id/fragmentHome"/>
    </fragment>


</navigation>

說明:action標(biāo)簽可以寫在fragment外邊,也可以是里邊,區(qū)別在于。
寫在外邊,這個id哪里都可以用。寫在fragment里邊的,只有這個fragment可以用,其他fragment用這個action的id就會報錯。
問題:
因為我基類里寫了如下的代碼,也就是使用了toolbar的后退鍵

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when(item.itemId){
            android.R.id.home->{
                onBackPressed()
//忘了加 return true了,所以引發(fā)了下邊的執(zhí)行了2次后退操作
            }
        }
        return super.onOptionsItemSelected(item)
    }

而現(xiàn)在加了navigation以后,我們的fragment也要攔截后退鍵的

override fun onSupportNavigateUp(): Boolean = findNavController(my_nav_host_fragment).navigateUp()

然后就發(fā)現(xiàn),點擊手機上的物理后退鍵是沒有任何問題了。fragment是正常的一次后退一個。
可點擊toolbar上的后退箭頭,就發(fā)現(xiàn)一次退了至少2個fragment。然后去看下了代碼
如下AppCompatActivity里,果然也處理 android.R.id.home,這等于處理了2次。

    public final boolean onMenuItemSelected(int featureId, android.view.MenuItem item) {
        if (super.onMenuItemSelected(featureId, item)) {
      //super的代碼在下邊,由于忘了寫return true了,所以后退執(zhí)行了2次。
            return true;
        }

        final ActionBar ab = getSupportActionBar();
        if (item.getItemId() == android.R.id.home && ab != null &&
                (ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
            return onSupportNavigateUp();
        }
        return false;
    }

再看下Activity里的代碼

    public boolean onMenuItemSelected(int featureId, MenuItem item) {
        CharSequence titleCondensed = item.getTitleCondensed();

        switch (featureId) {
            case Window.FEATURE_OPTIONS_PANEL:
                // Put event logging here so it gets called even if subclass
                // doesn't call through to superclass's implmeentation of each
                // of these methods below
                if(titleCondensed != null) {
                    EventLog.writeEvent(50000, 0, titleCondensed.toString());
                }
                if (onOptionsItemSelected(item)) {
                    return true;
                }

首先Navigation這個工具類

公開的靜態(tài)方法就是下邊這幾個


image.png

然后我們看下獲取
可以看到,是對我們傳入的view,讀取getTag來得到的,如果沒有,就找view的parent。
需要注意的是,如果沒找到,它就直接拋出異常了。所以傳的view要確保有


image.png

然后看下設(shè)置,就是把controller設(shè)置為tag

    public static void setViewNavController(@NonNull View view,
            @Nullable NavController controller) {
        view.setTag(R.id.nav_controller_view_tag, controller);
    }

看看哪里設(shè)置的,就是系統(tǒng)的NavHostFragment


image.png

我們在布局里一般這么寫的,所以如上圖所注釋的,我們的controller就是設(shè)置給了這個fragment的View拉。

   <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"
       />

另外獲取controller也可以通過如下方法
NavHostFragment里有

public static NavController findNavController(@NonNull Fragment fragment)

或者

    public NavController getNavController() {
        if (mNavController == null) {
            throw new IllegalStateException("NavController is not available before onCreate()");
        }
        return mNavController;
    }

需要注意的是controller是在NavHostFragment的onCreate方法里創(chuàng)建的,所以在這個生命周期之前獲取不到的。

  @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final Context context = getContext();

        mNavController = new NavController(context);
        mNavController.getNavigatorProvider().addNavigator(createFragmentNavigator());
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容