Android开发Android开发Android开发经验谈

Android:Navigation组件(附加:与BottomN

2019-04-21  本文已影响9人  Alisallon

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

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        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"/>
</android.support.constraint.ConstraintLayout>

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

image.png

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

有3中创建 Destinations 的方式:


image.png

Destinations 添加完成后,res / navigation / nav_graph.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/nav_graph"
            app:startDestination="@id/fragment1">
    <fragment
            android:id="@+id/fragment1"
            android:name="com.example.navigationdemo.Fragment1"
            android:label="fragment1"
            tools:layout="@layout/fragment1"/>
</navigation>

5、连接目标 Fragment(Destinations)

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

image.png

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

image.png

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

<action
        android:id="@+id/action_fragment1_to_fragment2"
        app:destination="@id/fragment2"
        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"/>

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

这两个参数与下面介绍的 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

<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

        <fragment
                android:id="@+id/nav_host_fragment"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                app:defaultNavHost="true"
                app:navGraph="@navigation/nav_graph"/>

        <android.support.design.widget.BottomNavigationView
                android:id="@+id/bottom_menu"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                <!-- 菜单项多于3个时,也显示Label -->
                <!-- Design 27 使用反射来修改 mShiftingMode 属性实现 -->
                <!-- Design 28 中使用下面代码实现 -->
                app:labelVisibilityMode="labeled"
                <!-- 如果要禁用水波纹效果,请修改app:itemBackground属性 -->
                app:menu="@menu/menu"/>
    </LinearLayout>
</android.support.constraint.ConstraintLayout>

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)
        }
    }
}
上一篇下一篇

猜你喜欢

热点阅读