navigation是负责fragment之间切换的处理工具
有三个核心点:
- Navigation Graph (New XML Resource): 控制中心,包含所有页面及页面间的关系
- NavHostFragment (Layout XML View):特殊的Fragment,是其他Fragment的“容器”,Navigation Graph中所有Fragmnet正是通过这个特殊的Fragment进行展示的
- NavController (Kotlin / Java Object): 负责完成Navigation中具体的页面切换工作
三者之间的关系:当需要切换页面时,使用NavController对象,告诉它想要去的Navigation Graph中的哪个Fragment,NavController对象会将目的地的Fragment展示在NavHostFragment中。
一、基本使用
1. 添加依赖
dependencies {
......
def nav_version = "2.3.5"
// Kotlin
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}
2. 创建Navigation Graph
res文件夹 -> New -> Android Resource File
3. 添加NavHostFragmnet
在activity_main.xml文件中添加fragment作为NavHostFragment,其中有三点需要注意:
- name必须是"androidx.navigation.fragment.NavHostFragment"
- defaultNavHost设置为"true"
- navGraph设置为"@navigation/nav_graph"
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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
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/nav_graph" />
</RelativeLayout>
4. 创建Destination
在nav_graph.xml页面里,依次点击加号、 Create new destination创建一个Destination,首次在此创建一个MainFragment作为StartDestination
<?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/nav_graph"
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.example.mynavigationdemo.MainFragment"
android:label="fragment_main"
tools:layout="@layout/fragment_main" />
</navigation>
5. 页面切换
在nav_graph.xml页面里,就像创建MainFragment一样创建一个SecondFragment
<?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/nav_graph"
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.example.mynavigationdemo.MainFragment"
android:label="fragment_main"
tools:layout="@layout/fragment_main" />
<fragment
android:id="@+id/secondFragment"
android:name="com.example.mynavigationdemo.SecondFragment"
android:label="fragment_second"
tools:layout="@layout/fragment_second" />
</navigation>
5.1 创建Action
在nav_graph.xml页面的Design面板里,单击MainFragment,然后选中右侧的圆圈热点并拖拽指向右侧的SecondFragmnt
<?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/nav_graph"
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.example.mynavigationdemo.MainFragment"
android:label="fragment_main"
tools:layout="@layout/fragment_main">
<action
android:id="@+id/action_mainFragment_to_secondFragment"
app:destination="@id/secondFragment" />
</fragment>
<fragment
android:id="@+id/secondFragment"
android:name="com.example.mynavigationdemo.SecondFragment"
android:label="fragment_second"
tools:layout="@layout/fragment_second" />
</navigation>
5.2 使用NavController导航到目的地
在fragment_main.xml中添加一个按钮,然后在MainFragment文件中添加该按钮点击事件的监听器,当点击该按钮时通过NavController完成页面跳转
class MainFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Button>(R.id.btnToSecondFragment).setOnClickListener {
findNavController().navigate(R.id.action_mainFragment_to_secondFragment)
}
}
}
导航到目的地是使用 NavController 完成的,它是一个在 NavHost 中管理应用导航的对象。每个 NavHost 均有自己的相应NavController,NavController 提供了几种导航到目的地的不同方式,如需从 Fragment、Activity 或View中获取NavController,请使用以下某种方法:
- Fragment.findNavController()
- View.findNavController()
- Activity.findNavController(viewId: Int)
5.3 添加切换动画
在nav_graph.xml页面的Design面板里,点击MainFragment指向SecondFragment的箭头(即页面跳转Action),然后在Attributes面板的Animations部分中,点击要添加的动画旁边的下拉箭头,选择需要设置的动画
<?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/nav_graph"
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.example.mynavigationdemo.MainFragment"
android:label="fragment_main"
tools:layout="@layout/fragment_main">
<action
android:id="@+id/action_mainFragment_to_secondFragment"
app:destination="@id/secondFragment"
app:enterAnim="@android:anim/fade_in"
app:exitAnim="@android:anim/fade_out"
app:popEnterAnim="@android:anim/fade_in"
app:popExitAnim="@android:anim/fade_out" />
</fragment>
<fragment
android:id="@+id/secondFragment"
android:name="com.example.mynavigationdemo.SecondFragment"
android:label="fragment_second"
tools:layout="@layout/fragment_second" />
</navigation>
6. 传递参数
6.1 基本类型参数
6.1.1 添加参数
在nav_graph.xml页面的Design面板里,点击接收参数的目的地Fragment(此处为secondFragment), 然后在Attributes面板的Arguments列点击右侧添加按钮(+)可添加参数
<?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/nav_graph"
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.example.mynavigationdemo.MainFragment"
android:label="fragment_main"
tools:layout="@layout/fragment_main">
<action
android:id="@+id/action_mainFragment_to_secondFragment"
app:destination="@id/secondFragment"
app:enterAnim="@android:anim/fade_in"
app:exitAnim="@android:anim/fade_out"
app:popEnterAnim="@android:anim/fade_in"
app:popExitAnim="@android:anim/fade_out" />
</fragment>
<fragment
android:id="@+id/secondFragment"
android:name="com.example.mynavigationdemo.SecondFragment"
android:label="fragment_second"
tools:layout="@layout/fragment_second" >
<argument
android:name="name"
app:argType="string"
android:defaultValue='"HSG"' />
</fragment>
</navigation>
6.1.2 接收参数
class SecondFragment : Fragment() {
val TAG = "SecondFragment"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val name = arguments?.getString("name")
Log.d(TAG, "onCreate() called with: name = $name")
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_second, container, false)
}
}
logcat 输出:D/SecondFragment: onCreate() called with: name = "HSG"
6.1.3 传递参数
class MainFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Button>(R.id.btnToSecondFragment).setOnClickListener {
val bundle = Bundle()
bundle.putString("name", "Hello World")
findNavController().navigate(R.id.action_mainFragment_to_secondFragment, bundle)
}
}
}
logcat 输出:D/SecondFragment: onCreate() called with: name = Hello Word
6.2 使用safe args传递参数
该插件可以生成简单的 object和builder类,以便以类型安全的方式浏览和访问任何关联的参数。我们强烈建使用Safe Args进行数据传递,因为它可以确保类型安全。
6.2.1 添加依赖
在project的build.gradle文件中添加safe args插件:
buildscript {
repositories {
google()
}
dependencies {
def nav_version = "2.3.5"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}
在app的build.gradle文件中添加:
(1)如需生成适用于 Java 模块或 Java 和 Kotlin 混合模块的 Java 语言代码,请添加:
apply plugin: "androidx.navigation.safeargs"
(2)如需生成适用于仅 Kotlin 模块的 Kotlin 语言代码,请添加:
apply plugin: "androidx.navigation.safeargs.kotlin"
根据迁移到 AndroidX 文档,gradle.properties 文件中必须具有 android.useAndroidX=true
启用 Safe Args 后,生成的代码会为每个Action(包含发送方和接收方)提供类型安全的类和方法:
- 为Action的每一个发送方创建一个类。该类的名称是在发送方的名称后面加上“Directions”。例如,如果发送方是名为MainFragment的Fragment,则生成的类的名称为 MainFragmentDirections,该类会为发送方中定义的每个Action提供一个方法。
- 对于用于传递参数的每个Action,都会创建一个 inner 类,该类的名称根据Action的名称确定。例如,如果Action名称为 action_mainFragment_to_secondFragment,则类名称为 ActionMainFragmentToSecondFragment,可以此关联的 action 类来设置参数值。
- 为接收目方创建一个类。该类的名称是在接收方的名称后面加上“Args”。例如,如果接收方的 Fragment 的名称为 SecondFragment,则生成的类的名称为 SecondFragmentArgs。可以使用该类的 fromBundle() 方法获取传递的参数。
6.2.2 传递参数
class MainFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Button>(R.id.btnToSecondFragment).setOnClickListener {
// val bundle = Bundle()
// bundle.putString("name", "Hello World")
// findNavController().navigate(R.id.action_mainFragment_to_secondFragment, bundle)
val direction = MainFragmentDirections.actionMainFragmentToSecondFragment("Hello Android")
findNavController().navigate(direction)
}
}
}
6.2.3 接收参数
class SecondFragment : Fragment() {
val TAG = "SecondFragment"
private val args by navArgs<SecondFragmentArgs>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// val name = arguments?.getString("name")
Log.d(TAG, "onCreate() called with: name = ${args.name}")
}
}
logcat 输出:D/SecondFragment: onCreate() called with: name = Hello Android
7. 配置ActionBar
通过Navigation完成页面跳转已基本完成,但和Activity跳转相比仍有不足之处——当前的ActionBar标题不会跟随Navigation页面变化,而且也没有用于返回的小图标。因此,本节就来解决该问题,Navigation已提供了便捷的解决方案,只需配置NavController对ActionBar进行控制,即可解决该问题。
在MainActivity中,通过四步即可完成配置:
第1步,在Activity上获取NavController
navController = findNavController(R.id.nav_host_fragment)
第2步,配置AppBarConfiguration,以便NavController接管ActionBar后能正确进行控制
appbarConfiguration = AppBarConfiguration.Builder(navController.graph).build()
第3步,配置NavController对ActionBar进行控制
NavigationUI.setupActionBarWithNavController(this, navController, appbarConfiguration)
第4步,让NavController接管返回事件
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(
navController,
appbarConfiguration
) or super.onSupportNavigateUp()
}
完整代码:
class MainActivity : AppCompatActivity() {
private lateinit var appbarConfiguration: AppBarConfiguration
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
navController = findNavController(R.id.nav_host_fragment)
appbarConfiguration = AppBarConfiguration.Builder(navController.graph).build()
NavigationUI.setupActionBarWithNavController(this, navController, appbarConfiguration)
}
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(
navController,
appbarConfiguration
) or super.onSupportNavigateUp()
}
}