Android:Navigation组件(附加:与BottomNavigationView一起使用)

Navigation组件的出现,避免了我们自己去管理Fragment的迁移栈

1、在Gradle中配置Navigation组件依赖

参考:https://developer.android.com/jetpack/androidx/releases/navigation#declaring_dependencies

dependencies {
    ...
    ...
    // For Kotlin use navigation-fragment-ktx
    implementation "android.arch.navigation:navigation-fragment-ktx:1.0.0"
    // For Kotlin use navigation-ui-ktx
    implementation "android.arch.navigation:navigation-ui-ktx:1.0.0"
}

2、创建导航图(Navigation Graph)

在项目窗口,右键点击项目目录,选择 New > Android Resource File:

image.png

这时会弹出添加新资源的对话框,输入文件名,例如“nav_graph”,选择 Resource Type > Navigation:

image.png

点击OK,导航图文件就创建好了:

image.png
image.png

3、在activity_main.xml中添加NavHost




    

  • android:name
    配置 NavHost 的实现类,使用默认配置的即可。
  • app:defaultNavHost="true"
    确保 NavHostFragment 能拦截系统返回按钮。
    点击返回键时,页面会按进入栈的顺序退出栈。
    如果在同一个布局中配置了多个 NavHost 实现,只能有一个 NavHost 实现可以把 defaultNavHost 设置为 true。
  • app:navGraph
    该属性将 NavHostFragment 与导航图关联起来。

添加完之后,在res / navigation / nav_graph.xml 中,HOST 下会显示已添加的 NavHost

image.png

4、在导航图(Navigation Graph)中添加目标Fragment(Destinations)

有3中创建 Destinations 的方式:


image.png
  • 方式1:Create new destination
    该方式会创建一个新的Fragment,创建完之后会自动把创建的Fragment添加到导航图中。

    image.png

  • 方式2:Placeholder destinations
    该方式会使用占位符来表示未实现的 Destinations。

    image.png

  • 方式3:直接选中已创建的Fragment
    该方式会把选中的Fragment作为 Destinations。

Destinations 添加完成后,res / navigation / nav_graph.xml 中显示如下:



    

  • app:startDestination
    该参数指定了启动 App 后默认显示的 Destinations(即 Fragment)

5、连接目标 Fragment(Destinations)

点击其中一个 Fragment 的右侧边框中间的小圆点,然后拖拽到另外一个 Fragment 上,即可完成连接。

image.png

连接完成后,res / navigation / nav_graph.xml 中显示如下:

image.png
  • android:id 属性
    我们在代码里可以通过该 id 完成两个 Fragment 之前的迁移。
  • app:destination 属性
    该属性指定了当前 action 所在的 Fragment 能迁移到的目的地 (Destinations)。
    这里表示使用该 action ,可以从 fragment1 迁移到 fragment2

还可以给 action 配置迁移动画,只需要指定动画xml即可:


action 还有两个参数需要注意:

  • app:popUpTo
    指定要返回到的目标页面的 destinationId
  • app:popUpToInclusive
    是否要把当前页面弹出返回栈

这两个参数与下面介绍的 popBackStack(@IdRes int destinationId, boolean inclusive) 方法类似。

6、代码中使用 Navigation

6.1、MainActivity 中使用 Navigation

// 获取 NavController
// R.id.nav_host_fragment 即 activity_main.xml 中配置的 NavHostFragment 的 id
val navController: NavController = Navigation.findNavController(this, R.id.nav_host_fragment)

// 获取当前正在显示的 Destinations
navController.currentDestination

// 获取当前正在显示的 Destinations 的 id
navController.currentDestination?.id

// 判断正在显示的 Destinations 是不是 Fragment1
// R.id.fragment1 即 res/navigation/nav_graph.xml 中对应 fragment 的 id
if (navController.currentDestination?.id == R.id.fragment1) {
  ......
}

6.2、Fragment 中使用 Navigation

// 获取 NavController
// view 即 Fragment 的 getView() 方法。
// view 拥有 findNavController() 方法,是因为Navigation组件依赖里对 View 进行了扩展:
// fun View.findNavController(): NavController = Navigation.findNavController(this)
val navController: NavController = view?.findNavController()

// 不带参数从 Fragment1 迁移到 Fragment2
// R.id.action_fragment1_to_fragment2 即 res/navigation/nav_graph.xml 中生成的 action 的 id
// 如果 action 配置了动画属性,则会使用动画迁移
view?.findNavController().navigate(R.id.action_fragment1_to_fragment2)

// 带参数从 Fragment1 迁移到 Fragment2
val args = Bundle()
args.putString("title",  "123")
view?.findNavController().navigate(R.id.action_fragment1_to_fragment2, args)

// 返回前一个 Fragment
// 这种方式返回:
// 如果栈中有多个Fragment,就直接调用 popBackStack方法进行Fragment回退
// 如果当前Fragment已经是栈中最后一个Fragment,最终会调用Activity的finish()结束当前Activity
view?.findNavController()?.navigateUp()

// 返回前一个 Fragment
// 这种方式返回,默认调用的是 popBackStack(getCurrentDestination().getId(), true)
// 即把当前 Fragment 弹出栈,并返回到前一个 Fragment
view?.findNavController()?.popBackStack()

// 根据指定的 destinationId 进行返回
// 第一个参数 destinationId 是 res/navigation/nav_graph.xml 中对应 fragment 的 id
// 第二个参数 inclusive 表示是否把指定 destinationId 对应的 Fragment 也弹出栈
view?.findNavController()?.popBackStack(@IdRes int destinationId, boolean inclusive)

// 比如当前是 Fragment2 ,要返回到 Fragment1
// 如果把 destinationId 设置为 R.id.fragment2,inclusive 设为 true,此时 Fragment2 会弹出栈,并返回到 Fragment1
view?.findNavController()?.popBackStack(R.id.fragment2, true)
// 与下面的效果一样
view?.findNavController()?.navigateUp()

// 如果把 inclusive 设为 false,此时不能成功的返回到 Fragment1,因为Fragment2未被弹出栈,一直在栈里,每次返回都是 Fragment2
view?.findNavController()?.popBackStack(R.id.fragment2, false)
// 如果想成功的返回到 Fragment1,可以改成下面这样,把 destinationId 设置为 R.id.fragment1
view?.findNavController()?.popBackStack(R.id.fragment1, false)

// 还是上面的例子
// 如果把 destinationId 设置为 R.id.fragment1,inclusive 设为 true,此时 Fragment2 会弹出栈,并返回到 Fragment1
view?.findNavController()?.popBackStack(R.id.fragment1, true)
// 但是,再次从 Fragment1 迁移到 Fragment2 时就会报异常:
view?.findNavController()?.navigate(R.id.action_fragment1_to_fragment2)
// 上面的代码会报异常:java.lang.IllegalArgumentException: navigation destination com.example.navigationdemo:id/action_fragment1_to_fragment2 is unknown to this NavController
// 出现上面异常的原因是因为,Fragment2 被弹出栈了,NavController 找不到 action_fragment1_to_fragment2 对应的目的地

6.3、Navigation 与 BottomNavigationView 一起使用

6.3.1、在Gradle中增加相关依赖

implementation 'com.android.support:design:28.0.0'

6.3.1、创建菜单UI

和导航图创建类似,选择 Resource Type > Menu。
创建好之后添加两个测试菜单:

image.png

注意:红色标识的 id 需要和下面 navigation/nav_graph.xml 中的一致,只有这样,点击 item 才会迁移到对应的 Fragment:

image.png

6.3.2、 修改 activity_main.xml,添加 BottomNavigationView



    

        

        
                
                
                app:labelVisibilityMode="labeled"
                
                app:menu="@menu/menu"/>
    

6.3.3、 修改MainActivity

把BottomNavigationView与Navigation进行绑定,绑定后即可测试使用了。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 获取NavController
        val navController = Navigation.findNavController(this, R.id.nav_host_fragment)
        // 把BottomNavigationView与NavController绑定。
        // 绑定后,当菜单项被选中时,会调用NavigationUI.onNavDestinationSelected(menuItem, navController)方法
        // 这里有个注意事项,上面创建菜单时有说明,下面手动绑定的注释也有说明
        // BottomNavigationView拥有setupWithNavController()方法,是因为Navigation组件依赖里对BottomNavigationView进行了扩展:
        // fun BottomNavigationView.setupWithNavController(navController: NavController) {
        //    NavigationUI.setupWithNavController(this, navController)
        // }
        bottom_menu.setupWithNavController(navController)

        // 如果不用上面的方式,可以用下面的方式自己手动把菜单选中事件与Navigation的导航事件绑定
        bottom_menu.setOnNavigationItemSelectedListener { menuItem ->
            // 导航到与menuItem菜单项关联的NavDestination,即与menu.xml中item的id相同的destinationId
            // destinationId即navigation/nav_graph.xml中fragment的id
            NavigationUI.onNavDestinationSelected(menuItem, navController)
        }
    }
}

你可能感兴趣的:(Android:Navigation组件(附加:与BottomNavigationView一起使用))