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(或NavigationResource 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