Android Navigation 详解

一、导航概述

    Navigation 用于 Fragment 的管理。他可以让 Fragrant 之间的切换,拥有像 Activity 间一样的跳转。与 DrawerLayout(抽屉式布局)、ActionBar(导航栏)等有简洁完美的对接。

二、开发环境设置

注意:Navigation 需要在 Android Studio 3.3 或更高版本中才可使用。(并且在 androidx 中支持的更好,之前没有使用 androidx 一直无法添加 Fragment 目的地)

若要在项目中包含导航支持,请将以下依赖项添加到应用程序的 build.gradle 文件中:

dependencies {
  def nav_version = "2.1.0-rc01"

  // Java
  implementation "androidx.navigation:navigation-fragment:$nav_version"
  implementation "androidx.navigation:navigation-ui:$nav_version"

  // Kotlin
  implementation "androidx.navigation:navigation-fragment-ktx:$nav_dep"
  implementation "androidx.navigation:navigation-ui-ktx:$nav_dep"

}

三、创建 navigation graph (导航图)

  导航图是一个 XML 资源文件,在导航图中有两个概念:

    1.Destination:导航图中的每一个导航被称为 Destination (目的地)。

    2.Action:导航与导航之间使用 Action (事件) 连接,用于说明两个导航之间的跳转关系。

若要向项目添加导航图,请执行以下操作:

    在项目的 res 目录下,新建一个 navigation 文件夹,右键 navigation 文件夹,依次选择:NewNavigation resource file

    填写文件名称,如:nav_graph,点击 OK。

四、编辑导航图

    在编辑导航图前,需要先创建 Activity,与相关的 Fragment。(此处使用 1 个 Activity,三个 Fragment)

/**
 * @Author: Eli Shaw
 * @Date: 2019-08-18 10:10:42
 * @Description: Activity 容器类
 */
class NavigationActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_navigation)
    }
}
/**
 * @Author: Eli Shaw
 * @Date: 2019-08-18 10:07:13
 * @Description: 登录页
 */
class LoginFragment : Fragment(), View.OnClickListener {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        val viewRoot = inflater.inflate(R.layout.fragment_login, container, false)
        return viewRoot
    }
}
/**
 * @Author: Eli Shaw
 * @Date: 2019-08-18 10:08:52
 * @Description: 注册页
 */
class RegisterFragment : Fragment(), View.OnClickListener {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        val viewRoot = inflater.inflate(R.layout.fragment_register, container, false)
        return viewRoot
    }
}
/**
 * @Author: Eli Shaw
 * @Date: 2019-08-18 10:40:42
 * @Description: 首页
 */
class HomeFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_home, container, false)
    }
}

    创建好 nav_graph 导航图后,双击进行编辑(与常规布局文件很相似)

1、指定 Activity 容器

    在 Activity 布局文件中指定导航图的容器

<LinearLayout
        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=".navigation.NavigationActivity">

    <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"/>
</LinearLayout>

    android:name:指定 Fragment 的类型为 NavHostFragment。

    app:defaultNavHost="true":让 Navigation 容器处理返回事件,在 Navigation 容器中如果有页面的跳转,点击返回按钮会先处理 容器中 Fragment 页面间的返回,处理完容器中的页面,再处理 Activity 页面的返回。如果值为 false 则直接处理 Activity 页面的返回。

    app:navGraph:指定 Navigation 文件。

2、添加 Action (事件)

    拖动选中的(目的地)右方的圆点到另一个(目的地)即可创建一个 Action(事件)。

    点击左下角的 Text ,将显示如下代码

<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/loginFragment">

    <!--登录 Fragment 包含两个事件:一个是注册页,一个是首页 -->
    <fragment android:id="@+id/loginFragment" android:name="cn.eli.jetpack.navigation.LoginFragment"
              android:label="fragment_login" tools:layout="@layout/fragment_login">
        <action android:id="@+id/actionLoginToRegister" app:destination="@id/registerFragment"/>
        <action android:id="@+id/actionLoginToHome" app:destination="@id/homeFragment"/>
    </fragment>

    <!--注册 Fragment 包含一个事件:返回到登录页-->
    <fragment android:id="@+id/registerFragment" android:name="cn.eli.jetpack.navigation.RegisterFragment"
              android:label="fragment_register" tools:layout="@layout/fragment_register">
        <action android:id="@+id/actionRegisterToLogin" app:destination="@id/loginFragment"/>
    </fragment>

    <!--首页 Fragment 没有事件-->
    <fragment android:id="@+id/homeFragment" android:name="cn.eli.jetpack.navigation.HomeFragment"
              android:label="fragment_home" tools:layout="@layout/fragment_home"/>
</navigation>

3、导航图的复用

    <navigation> 是导航图的根元素,Destination (目的地) 与 Action (事件) 都是 <navigation> 元素中的节点。

    <navigation> 中可以包含 <navigation> 节点,这样使用的目的是可以复用相同的导航图。例如:增加一个找回密码页,这个找回密码页在另一个导航图中,找回密码后再继续登录操作:

    此图是一个包含导航图的预览页,文本内容如下

<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_multiplex"
            app:startDestination="@id/findPwdFragment">

    <fragment android:id="@+id/findPwdFragment" android:name="cn.eli.jetpack.navigation.FindPwdFragment"
              android:label="fragment_find_pwd" tools:layout="@layout/fragment_find_pwd">
        <action android:id="@+id/action_findPwdFragment_to_nav_graph" app:destination="@id/nav_graph"/>
    </fragment>

    <!--引入另一个导航图-->
    <include app:graph="@navigation/nav_graph"/>

</navigation>

五、导航图间的跳转

1、获取 NavController 的方式

    导航图使用 NavController 控制(目的地)之间的跳转,在 Activity,Fragment 中获取 NavController 有以下 6 种方式:

1.Fragment.findNavController()
2.View.findNavController()
3.Activity.findNavController(viewId: Int) //只有 Activity 中可以使用
4.NavHostFragment.findNavController(Fragment)
5.Navigation.findNavController(Activity, @IdRes int viewId) //只有 Activity 中可以使用
6.Navigation.findNavController(View)

2、导航中目的地间的传值

    导航中目的地间也可以使用 Bundle 进行传值

//使用 bundle 传值
val bundle = Bundle()
bundle.putInt(ARG_NAV, navController)

findNavController().navigate(R.id.actionLoginToRegister, bundle)

下面是登录页、注册页完整代码

private const val ARG_NAV = "nav"

/**
 * @Author: Eli Shaw
 * @Date: 2019-08-18 10:07:13
 * @Description: 登录页
 */
class LoginFragment : Fragment(), View.OnClickListener {

    private var navController: Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            navController = it.getInt(ARG_NAV)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        val viewRoot = inflater.inflate(R.layout.fragment_login, container, false)
        //注册点击事件
        viewRoot.findViewById<Button>(R.id.btnRegister).setOnClickListener(this)
        //登录点击事件
        viewRoot.findViewById<Button>(R.id.btnLogin).setOnClickListener {
            findNavController().navigate(R.id.actionLoginToHome)
        }
        return viewRoot
    }

    /**
     * 注册点击事件
     */
    override fun onClick(view: View) {
        if (++navController > 4)
            navController = 1

        //使用 bundle 传值
        val bundle = Bundle()
        bundle.putInt(ARG_NAV, navController)

        //根据 navController 值的不同,使用不同的获取 NavController 的方式
        when (navController) {
            //Fragment.findNavController()
            1 -> findNavController().navigate(R.id.actionLoginToRegister, bundle)
            //View.findNavController()
            2 -> view.findNavController().navigate(R.id.actionLoginToRegister, bundle)
            //NavHostFragment.findNavController(Fragment)
            3 -> NavHostFragment.findNavController(this).navigate(R.id.actionLoginToRegister, bundle)
            //Navigation.findNavController(View)
            4 -> Navigation.findNavController(view).navigate(R.id.actionLoginToRegister, bundle)
        }
    }
}
private const val ARG_NAV = "nav"

/**
 * @Author: Eli Shaw
 * @Date: 2019-08-18 10:08:52
 * @Description: 注册页
 */
class RegisterFragment : Fragment(), View.OnClickListener {

    private var navController: Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            navController = it.getInt(ARG_NAV)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        val viewRoot = inflater.inflate(R.layout.fragment_register, container, false)
        viewRoot.findViewById<Button>(R.id.btnBackLogin).setOnClickListener(this)
        return viewRoot
    }

    override fun onClick(view: View) {
        val bundle = Bundle()
        bundle.putInt(ARG_NAV, navController)
        
        view.findNavController().navigate(R.id.actionRegisterToLogin, bundle)
    }
}

3、导航中目的地间的转场动画

自定义转场动画

cut_to_enter.xml (入场动画)

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
            android:toYDelta="0"
            android:fromYDelta="100%"
            android:duration="1500"/>
</set>

cut_to_exit.xml(出场动画)

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
            android:duration="1500"
            android:fromYDelta="0"
            android:toYDelta="-100%"/>
</set>

在导航图中编辑

发布了17 篇原创文章 · 获赞 46 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/xiaojinlai123/article/details/99177804