Navigation组件的使用详解

 

 

 

Navigation 是 JetPack 中的一个组件,用于方便的实现页面的导航,所以抽象出了一个 destination 的概念,大部分情况一个 destination 就表示一个 Fragment,但是它同样可以指代 Activity、其它的导航图

最初要有个起始页面,叫 start destination,处于栈底,是启动时的第一个页面,当然也是返回可见的最后一个页面。多个 destination 连接起来就组成了一个导航图,类似于一种栈结构,页面先进后出。destination 之间的连接叫做 action

准备

1、在 Android Studio 3.2 Canary 14 以上的版本中,打开 Preferences -> Experimental -> Enable Navigation Editor,然后重启。

2、添加依赖:

       implementation 'androidx.navigation:navigation-fragment-ktx:2.0.0'

       implementation 'androidx.navigation:navigation-ui-ktx:2.1.0'

创建资源文件

3、在 res 目录右击,选择 New > Android Resource File,Resource type 选择 Navigation。如下图

Navigation组件的使用详解_第1张图片

创建 destination

先创建一个 Fragment

class HomeFragment : Fragment() {

    // TODO: Rename and change types of parameters
    private var mParam1: String? = null
    private var mParam2: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (arguments != null) {
            mParam1 = arguments!!.getString(ARG_PARAM1)
            mParam2 = arguments!!.getString(ARG_PARAM2)
        }
    }

    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)
    }

    companion object {
  
        // TODO: Rename and change types and number of parameters
        fun newInstance(param1: String, param2: String): HomeFragment {
            val fragment = HomeFragment()
            val args = Bundle()
            args.putString(ARG_PARAM1, param1)
            args.putString(ARG_PARAM2, param2)
            fragment.arguments = args
            return fragment
        }
    }

}

然后配置 navigation 文件,打开 res/navigation/nav_home 文件,添加一个 fragment 节点

  • name 指定 Fragment 的路径
  • tools:layout 指定布局文件
  • app:startDestination 指定这个 Fragment 是 start destination

Navigation组件的使用详解_第2张图片

 

也可以在 nav_home 的 design 视图下,选择 Create blank destination 来创建一个 Fragment,而不用先创建好再选择。

Navigation组件的使用详解_第3张图片

Activity 中引用

第一种方式是在 xml 里写 fragment。如下:




    

    

        
    

    

        

    

    

        
    



  • android:name 是 HomeFragment,它实现了 NavHost,这是一个用于放置管理 destination 的空视图。
  • app:navGraph 用于将这个HomeFragment 和 nav_home.xml 关联起来。
  • app:defaultNavHost 表示 NavHostFragment 可以拦截处理返回键。

第二种方式是通过代码创建 NavHostFragment,先修改 Activity 的 xml:




    

然后在 Activity 中引入:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_navigation)
    
    val finalHost = HomeFragment.create(R.navigation.nav_home)
    supportFragmentManager.beginTransaction()
            .replace(R.id.frame_layout, finalHost)
            .setPrimaryNavigationFragment(finalHost) // 等价于 xml 中的 app:defaultNavHost="true"
            .commit()
}

多个Fragment切换控制:

底部Tab使用的是BottomNavigationView

tab_menu.xml



    
    
    

MainActivity类代码:

package com.king.navigationdemo

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.navigation.NavController
import androidx.navigation.Navigation
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    companion object {
        const val HOME_FRAGMENT = 0
        const val SECOND_FRAGMENT = 1
        const val PERSON_FRAGMENT = 2
    }

    private var homeFragment: NavController? = null
    private var secondFragment: NavController? = null
    private var personFragment: NavController? = null
    private var fragmentList = mutableListOf()
    private var showFragment = 0
    private var isBack = false
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initNavigationController()
        initTab()
    }

    private fun initTab() {
        fragmentList.add(home_fragment_layout)
        fragmentList.add(second_fragment_layout)
        fragmentList.add(person_fragment_layout)
        //设置导航栏菜单项Item选中监听
        bottomNavigationView.setOnNavigationItemSelectedListener { item ->
            when (item.itemId) {
                R.id.homeFragment -> {
                    showFragmentNavigation(HOME_FRAGMENT)
                }
                R.id.secondFragment ->{
                    showFragmentNavigation(SECOND_FRAGMENT)
                }
                R.id.personFragment ->{
                    showFragmentNavigation(PERSON_FRAGMENT)
                }
            }
            true
        }
    }

    private fun showFragmentNavigation(index: Int) {
        showFragment = index
        fragmentList.forEachIndexed { i, layout ->
            layout.visibility = if (i == index) {
                View.VISIBLE
            } else {
                View.GONE
            }

        }
    }

    private fun initNavigationController() {
        homeFragment = Navigation.findNavController(this, R.id.home_fragment)
        secondFragment = Navigation.findNavController(this, R.id.second_fragment)
        personFragment = Navigation.findNavController(this, R.id.person_fragment)
    }
    override fun onBackPressed() {
        when (showFragment) {
            HOME_FRAGMENT -> {
                isBack = homeFragment?.popBackStack() ?: false
            }
            SECOND_FRAGMENT -> {
                isBack = secondFragment?.popBackStack() ?: false
            }
            PERSON_FRAGMENT -> {
                isBack = personFragment?.popBackStack() ?: false
            }
        }
        if (!isBack) {
            super.onBackPressed()
        }

    }
}

添加跳转动画

点击目标箭头,右侧添加动画:

Navigation组件的使用详解_第4张图片

代码自动变成:



    

    
    
    

支持 View 动画和属性动画,enterAnim 和 exitAnim 是去往栈里添加一个 destination 时两个 destination 的动画,popEnterAnim 和 popExitAnim 是从栈里移除一个 destination 时的动画。

传递数据

要跳转到 SecondFragment,要往 SecondFragment 里带数据,在目的 Fragment 里添加 






    

FirstFragment 添加数据

button.onClick {
     val bundle = bundleOf("name" to "silas")
     Navigation.findNavController(getView()!!)
            .navigate(R.id.action_nav_graph_first_fragment_to_nav_graph_second_fragment, bundle)
}

 

SecondFragment 获取数据

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {
    arguments?.getString("name")?.let { toast("hello $it") }
    return inflater.inflate(R.layout.fragment_second, container, false)
}

类型安全方式传递数据

项目的 build.gradle 中添加

buildscript {
    repositories {
        google()
    }
    dependencies {
        classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha05"
    }
}

module 的 build.gradle 中应用:

apply plugin: "androidx.navigation.safeargs"

同步发现要升级 gradle 版本到 4.6,随之 gradle tools 必须到 3.2.0-rc02,然后要升级 kotlin 版本,然后又让下载 build tools 28.0.2,然后总是不能下载,看网上方法,关闭代理,把 Preferences -> HTTP ProxyNo proxy 改成 Auto-detect proxy settings





    
        
    

和普通的区别就在于 多了个 argType 指定了数据类型。

FirstFragment 修改

val action = FirstFragmentDirections.actionNavGraphFirstFragmentToNavGraphSecondFragment()
action.setName("Silas")
Navigation.findNavController(getView()!!).navigate(action)

SecondFragment 接收

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
    toast("hello ${SecondFragmentArgs.fromBundle(arguments).name}")
    return inflater.inflate(R.layout.fragment_second, container, false)
}

 

如果 FirstFragment 去掉 action.setName("Silas"),那么 SecondFragment 里得到的也是默认值 Max。

看生成的 FirstFragmentDirections 的 setName 和 SecondFragmentArgs 的 fromBundle:

@NonNull
public ActionNavGraphFirstFragmentToNavGraphSecondFragment setName(@NonNull String name) {
  if (name == null) {
    throw new IllegalArgumentException("Argument \"name\" is marked as non-null but was passed a null value.");
  }
  this.name = name;
  return this;
}
@NonNull
public static SecondFragmentArgs fromBundle(Bundle bundle) {
SecondFragmentArgs result = new SecondFragmentArgs();
bundle.setClassLoader(SecondFragmentArgs.class.getClassLoader());
if (bundle.containsKey("name")) {
  result.name = bundle.getString("name");
  if (result.name == null) {
    throw new IllegalArgumentException("Argument \"name\" is marked as non-null but was passed a null value.");
  }
}

加了一些判断,所谓安全也就是指这个吧。

Demo源码地址:https://github.com/wangzhuang/NavigationDemo.git

 

你可能感兴趣的:(Android,Java,Kotlin)