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。如下图
先创建一个 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 节点
start destination
也可以在 nav_home 的 design 视图下,选择 Create blank destination 来创建一个 Fragment,而不用先创建好再选择。
第一种方式是在 xml 里写 fragment。如下:
第二种方式是通过代码创建 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()
}
}
}
点击目标箭头,右侧添加动画:
代码自动变成:
支持 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 Proxy 从 No 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