Jetpack之Navigation

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


Create Navigation Graph

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


选中热点并拖拽

Action创建完成
<?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()
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容