Android開發navigation入門詳解


前言

Google 在2018年推出了 Android Jetpack,在Jetpack里有一種管理fragment的新架構模式,那就是navigation. 字面意思是導航,但是除了做APP引導頁面以外.也可以使用在App主頁分tab的情況.. 甚至可以一個功能模塊就一個activity大部分頁面UI都使用fragment來實現,而navigation就成了管理fragment至關重要的架構.

但是,它不單單只能管理fragment也可以管理activity.這點你格外注意.

使用條件

你的Android studio 必需升級到3.2版本以上,此博客正在寫的時候Android studio已經到達4.2.1,所以升級到最新版本即可.

依賴

implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'

 Android studio的4.2.1版本默認是添加這兩個依賴的

使用流程

  創建navigation目錄

1.選中項目資源文件夾 res 右擊 >> New >> New Resource Directory(或 Android Resource Directory)

2.選中navigation 點擊創建 (注意這個目錄只有在Android studio3.2版本以上才能出現)

3、選擇Resource type 為 navigation, 點擊OK

點擊OK

 

 

 

然后選中navigation文件夾目錄,創建navigation目錄下的xml文件

1.選中項目資源文件夾 res 右擊 >> New >> New Resource File(或Navigation Resource File)

2.選擇navigation ,輸入xml文件名稱,點擊ok創建

 

 

 

配置創建的xml文件

 上面我們創建了一個叫demo_nav.xml的navigation文件,現在我們需要來設配它來管理fragment

 1.打開這個文件選模式使用視圖手動配置.

 2.切換到Design模式后,我們可以看到下面這個界面(恩,一片空白). 我們可以在左上角點擊添加圖標,進入添加內容的操作.

3.點擊后,可以看到下面這個彈窗,這里解釋一下:

第一個 Create new destinattion,字面意思創建一個新目標(其實就是創建fragment,當然你也可以手動另外創建fragment不一定需要在這里創建)

第二個 placeholder,這個就是重點了. 這是一個管理fragment跳轉的節點,我們點擊后可以創建它.為了了解它的使用方式,點擊3次創建三個節點

 

 

 4.節點創建后可以看到三個節點(看下面圖片,這些節點都是我已經導入fragment了.不要急后面會講解如何導入).這里有一個重點! 你可以點擊這些頁面(會有一個藍點),點擊藍點按住向右分配它需要跳轉的另外一個頁面.(它會自動生成一些我們跳轉的代碼)

 

6.然后點擊左下角的Text模式,在Text模式下,可以看到如下代碼,在上面的圖片中你可以很清楚的看到創建了3個節點,並且是一個跳轉一個的.從第一個fragment跳轉到第二個fragment,再從第二個fragment跳轉到第三個fragment

下面我們來重點講解下下面的這些代碼的關鍵點了:

在<navigation里的屬性:

1.android:id="@+id/demo_nav" 這個屬性是你這個xml文件navigation的id,很重要,我們需要在activity的xml布局里引用,記得寫上不要忘記

2.app:startDestination="@id/homeFragment" 這個屬性是你首次加載的第一個頁面,很重要,一般就是第一個fragment

在<fragment 里的屬性:

其實就是一個節點你也可以理解成一個fragment

1.android:id="@+id/homeFragment"  每一個fragment節點都需要有自己的id,很重要. 我們需要在后面的節點上使用這些id指定跳轉目標

2.android:name="com.example.jgdemo.fragments.HomeFragment"   這個屬性是你這個節點所對應的fragment(需要你導入指定的fragment文件路徑),這個很重要

3.android:label="HomeFragment" 一個標簽名稱,用於記錄這個節點的標簽信息(大概可能是在代碼里的Intent里獲取來知曉此次是那個fragment節點在跳轉,沒深究了)

4.tools:layout="@layout/fragment_home" 這個屬性不是重要的,設置它后你可以在切換到Design模式后看到,視圖頁面的fragment的預覽圖(就在上面的圖片里,可以直接看到fragment效果)

在<action 里的屬性:

action 負責編寫跳轉動作

1. android:id="@+id/action_homeFragment_to_myOneFragment" 這個很重要,它是這個跳轉動作的id, 這個id我們將在后面的代碼中調用,用於執行fragment的跳轉

2. app:destination="@id/myOneFragment" 跳轉的目標fragment,這個很重要

3、

app:enterAnim="@anim/nav_default_enter_anim"

app:exitAnim="@anim/nav_default_exit_anim"

app:popEnterAnim="@anim/nav_default_pop_enter_anim"

app:popExitAnim="@anim/nav_default_pop_exit_anim"

是使用的系統自帶的動畫

整體如下:

<?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"
    android:id="@+id/demo_nav"
    app:startDestination="@id/homeFragment"><!--首次加載的第一個頁面-->

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.example.jgdemo.fragments.HomeFragment"
        android:label="HomeFragment"
        tools:layout="@layout/fragment_home">
        <action
            android:id="@+id/action_homeFragment_to_myOneFragment"
            app:destination="@id/myOneFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim" />
        <argument
            android:name="userName"
            app:argType="string"
            app:nullable="true"
            android:defaultValue="unknown" />
        <argument
            android:name="age"
            app:argType="integer"
            android:defaultValue="0" />

    </fragment>
    <fragment
        android:id="@+id/myOneFragment"
        android:name="com.example.jgdemo.fragments.MyOneFragment"
        android:label="MyOneFragment"
        tools:layout="@layout/fragment_myone">
        <action
            android:id="@+id/action_myOneFragment_to_myTwoFragment"
            app:destination="@id/myTwoFragment" />
        <action
            android:id="@+id/action_myOneFragment_to_myThreeFragment"
            app:destination="@id/myThreeFragment" />

        <argument
            android:name="myname"
            app:argType="string"
            app:nullable="true"
            android:defaultValue="unknown" />
        <argument
            android:name="myage"
            app:argType="integer"
            android:defaultValue="0" />
    </fragment>
    <fragment
        android:id="@+id/myTwoFragment"
        android:name="com.example.jgdemo.fragments.MyTwoFragment"
        android:label="MyTwoFragment"
        tools:layout="@layout/fragment_mytwo"/>
    <fragment
        android:id="@+id/myThreeFragment"
        android:name="com.example.jgdemo.fragments.MyThreeFragment"
        android:label="MyThreeFragment"
        tools:layout="@layout/fragment_mythree"/>


</navigation>

  

讓navigation與Activity關聯起來

   現在我們已經創建了navigation,但是使用它還需要一個根Activity,它畢竟還是需要依托Activity的.

 1.創建了一個叫DemoActivity的Activity.這個沒啥,下面來看這個Activity的布局xml怎么配(如下xml代碼)

  我們就關注fragment的一些屬性

1.android:name="androidx.navigation.fragment.NavHostFragment"  這個非常重要,這是你告知fragment需要使用navigation模式的關鍵屬性,另外它是固定死的.你必需寫.

2. app:defaultNavHost="true"  這是你實現物理按鍵(比如返回鍵),是按一下退出一個fragment 還是直接退出這個Activity的關鍵屬性

3.app:navGraph="@navigation/demo_nav"  很重要,這就是我們前面創建的navigation的xml文件

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!--Fragment容器-->
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/demo_nav"></androidx.fragment.app.FragmentContainerView>
</FrameLayout>

  

實現fragment跳轉與返回

 進入到MainActivity后,首先會自動加載到第一個fragment. 然后我們看看如何跳轉到其他fragment中

 1.從第一個碎片跳轉到第二個碎片,關鍵代碼  Navigation.findNavController(binding.root).navigate(R.id.action_homeFragment_to_myOneFragment)

class HomeFragment : Fragment(){
     //使用viewBinding直接省去findViewById
    private lateinit var binding: FragmentHomeBinding

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentHomeBinding.inflate(layoutInflater)
        binding.jumptomyoneBtn.setOnClickListener {
//跳轉到MyOneFragment Navigation.findNavController(binding.root).navigate(R.id.action_homeFragment_to_myOneFragment) } return binding.root } }

  2.從第二個碎片返回到第一個碎片,關鍵代碼 Navigation.findNavController(binding.root).popBackStack()

class MyOneFragment : Fragment(){
//使用viewBinding直接省去findViewById
private lateinit var binding: FragmentMyoneBinding
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentMyoneBinding.inflate(layoutInflater)
        //返回
        binding.closemyoneBtn.setOnClickListener {
            //關閉頁面
            Navigation.findNavController(binding.root).popBackStack()
        }
      
        return binding.root
    }
}

上面我使用了谷歌的ViewBinding直接省去findViewById
用法:在build.gradle的 android 閉包中添加
buildFeatures {
        viewBinding true
   
    }

  

實現fragment傳值

打開你的nvagation文件,選中Design

 

 

 選中你的Fragment視圖

 

 

 然后右側選中Arguments,點擊加號,添加你要傳遞的參數

 

 

 然后會自動生成代碼

         <argument
            android:name="userName"
            app:argType="string"
            app:nullable="true"
            android:defaultValue="unknown" />
        <argument
            android:name="age"
            app:argType="integer"
            android:defaultValue="0" />

 傳遞參數

            val bundle = HomeFragmentArgs("張三",24).toBundle()
            Navigation.findNavController(binding.root).navigate(R.id.action_homeFragment_to_myOneFragment,bundle)

  接收參數

//         接收傳遞來的參數  方法1
        val bundle = arguments
        val username = bundle?.getString("userName")
        val age = bundle?.getInt("age",0)
        Log.i("打印接收傳遞來的數據:","用戶名:$username ,年齡: ${age.toString()}")

        接收傳遞來的參數  方法2
        val bundle = arguments
        val username = arguments?.let { HomeFragmentArgs.fromBundle(it).userName }
        val age = arguments?.let { HomeFragmentArgs.fromBundle(it).age }
        Log.i("打印接收傳遞來的數據:","用戶名:$username ,年齡: ${age.toString()}")

  使用了 safe args插件傳遞參數,配置如下

 在Project的build.gradle文件中添加safe args插件

        def nav_version = "2.3.0-alpha01"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"

  如下:

buildscript {
    ext.kotlin_version = "1.5.0"
// ext.nav_version = "2.3.0-alpha01" repositories { google() mavenCentral() jcenter() } dependencies { classpath "com.android.tools.build:gradle:4.2.1" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" } } allprojects { repositories { google() mavenCentral() } } task clean(type: Delete) { delete rootProject.buildDir }

  然后需要引用這個插件。在app的build.gradle中添加依賴。
如果需要java或jave和kotlin的,添加這個:

id 'androidx.navigation.safeargs'

  純kotlin的,添加這個:

id 'androidx.navigation.safeargs.kotlin'

  如圖:

 

最后上一下我完整代碼:

demo_nav.xml

<?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"
    android:id="@+id/demo_nav"
    app:startDestination="@id/homeFragment"><!--首次加載的第一個頁面-->

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.example.jgdemo.fragments.HomeFragment"
        android:label="HomeFragment"
        tools:layout="@layout/fragment_home">
        <action
            android:id="@+id/action_homeFragment_to_myOneFragment"
            app:destination="@id/myOneFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim" />
        <argument
            android:name="userName"
            app:argType="string"
            app:nullable="true"
            android:defaultValue="unknown" />
        <argument
            android:name="age"
            app:argType="integer"
            android:defaultValue="0" />

    </fragment>
    <fragment
        android:id="@+id/myOneFragment"
        android:name="com.example.jgdemo.fragments.MyOneFragment"
        android:label="MyOneFragment"
        tools:layout="@layout/fragment_myone">
        <action
            android:id="@+id/action_myOneFragment_to_myTwoFragment"
            app:destination="@id/myTwoFragment" />
        <action
            android:id="@+id/action_myOneFragment_to_myThreeFragment"
            app:destination="@id/myThreeFragment" />

        <argument
            android:name="myname"
            app:argType="string"
            app:nullable="true"
            android:defaultValue="unknown" />
        <argument
            android:name="myage"
            app:argType="integer"
            android:defaultValue="0" />
    </fragment>
    <fragment
        android:id="@+id/myTwoFragment"
        android:name="com.example.jgdemo.fragments.MyTwoFragment"
        android:label="MyTwoFragment"
        tools:layout="@layout/fragment_mytwo"/>
    <fragment
        android:id="@+id/myThreeFragment"
        android:name="com.example.jgdemo.fragments.MyThreeFragment"
        android:label="MyThreeFragment"
        tools:layout="@layout/fragment_mythree"/>


</navigation>

HomeFragment

class HomeFragment : Fragment(){

    private lateinit var binding: FragmentHomeBinding

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentHomeBinding.inflate(layoutInflater)
        binding.jumptomyoneBtn.setOnClickListener {
            //方法1、使用Directions
            //不傳遞參數
            //var action = HomeFragmentDirections.actionHomeFragmentToMyOneFragment()
            //findNavController().navigate(action)

            //傳遞參數,需要接收參數的fragment中寫argument
            var action = HomeFragmentDirections.actionHomeFragmentToMyOneFragment("這是我的名字",24)//參數直接寫入
            findNavController().navigate(action)


            //方法2、直接使用Id
            //不傳遞參數
            //Navigation.findNavController(binding.root).navigate(R.id.action_homeFragment_to_myOneFragment)

            //傳遞參數1 對應 接收參數1和2
            //val bundle = HomeFragmentArgs("張三",24).toBundle()
            //Navigation.findNavController(binding.root).navigate(R.id.action_homeFragment_to_myOneFragment,bundle)

        }
        return binding.root
    }
}

  

MyOneFragment

class MyOneFragment : Fragment(){

    private lateinit var binding: FragmentMyoneBinding

    private val args: MyOneFragmentArgs by navArgs()

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentMyoneBinding.inflate(layoutInflater)
        //返回
        binding.closemyoneBtn.setOnClickListener {
            //方法1、使用Directions
           //findNavController().popBackStack()
            //方法2、

            Navigation.findNavController(binding.root).popBackStack()

        }
        //跳轉到第三個頁面
        binding.jumptomytwoBtn.setOnClickListener {
            //方法1、使用Directions
            //val action = MyOneFragmentDirections.actionMyOneFragmentToMyTwoFragment()
            //findNavController().navigate(action)

            //方法2、直接使用Id
            Navigation.findNavController(binding.root).navigate(R.id.action_myOneFragment_to_myTwoFragment)
        }

        //接收傳遞來的參數  方法1
        //val bundle = arguments
        //val username = bundle?.getString("userName")
        //val age = bundle?.getInt("age",0)
        //Log.i("打印接收傳遞來的數據:","用戶名:$username ,年齡: ${age.toString()}")

        //接收傳遞來的參數  方法2
        //val bundle = arguments
        //val username = arguments?.let { HomeFragmentArgs.fromBundle(it).userName }
        //val age = arguments?.let { HomeFragmentArgs.fromBundle(it).age }
        //Log.i("打印接收傳遞來的數據:","用戶名:$username ,年齡: ${age.toString()}")

        //接收傳遞來的參數
        Log.i("打印接收的參數",args.myname +" ,"+ args.myage)


        return binding.root
    }
}

  

 

完成

 

參考:

https://www.cnblogs.com/guanxinjing/p/11555217.html

https://blog.csdn.net/nanquan11/article/details/109807501

https://www.it1352.com/1534512.html

 


免責聲明!

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



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