nav_graph.xml源码如下
<?xml version="1.0" encoding="utf-8"?>
<!--
android:id="@+id/nav_graph",android:id可以是任意的
-->
<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/mainFragment"
tools:ignore="UnusedNavigation">
<!--
app:startDestination值是要开启的页面
android:name值要开启页面的全类名
android:label要开启页面的标签
tools:layout用作预览
app:enterAnim页面进入的动画
app:exitAnim页面退出的动画
app:popUpTo 会告知 Navigation 库在调用 navigate() 的过程中从返回堆栈上弹出一些目的地。属性值是应保留在堆栈中的最新目的地的 ID。
如果存在A→B→C→A,
在到达目的地 C 之后,返回堆栈包含每个目的地(A、B、C)的实例。当返回到目的地 A 时,我们也 popUpTo A,
也就是说我们会在导航过程中从堆栈中移除 B 和 C。利用 app:popUpToInclusive="true",我们还会将第一个
A 从堆栈上弹出,从而有效地清除它。请注意,如果您不使用 app:popUpToInclusive,则返回堆栈会包含两个目的地 A 的实例。
-->
<fragment
android:id="@+id/mainFragment"
android:name="com.study.jetpackstudykotlin.navigation.main.MainFragment"
android:label="MainFragment"
tools:layout="@layout/main_fragment">
<!--
app:popUpToInclusive="true"开启DetailFragment时,将会弹出MainFragment
-->
<action
android:id="@+id/action_mainFragment_to_detailFragment"
app:destination="@id/detailFragment"
app:enterAnim="@anim/fragment_close_enter"
app:exitAnim="@anim/fragment_close_exit"
app:launchSingleTop="true"
app:popEnterAnim="@anim/fragment_fade_enter"
app:popExitAnim="@anim/fragment_fade_exit"
app:popUpTo="@id/mainFragment"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="@+id/detailFragment"
android:name="com.study.jetpackstudykotlin.navigation.detail.DetailFragment"
android:label="DetailFragment"
tools:layout="@layout/detail_fragment">
<action
android:id="@+id/action_detailFragment_to_webViewActivity"
app:destination="@id/webViewDetail"
app:enterAnim="@anim/fragment_close_enter"
app:exitAnim="@anim/fragment_close_exit"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_open_exit" />
<deepLink app:uri="inApp://jump" />
</fragment>
<activity
android:id="@+id/webViewDetail"
android:name="com.study.jetpackstudykotlin.navigation.WebViewActivity"
android:label="WebViewActivity"
tools:layout="@layout/activity_web_view" />
</navigation>
package com.study.jetpackstudykotlin.navigation
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.widget.Toolbar
import androidx.navigation.NavController
import androidx.navigation.findNavController
import androidx.navigation.ui.setupActionBarWithNavController
import com.study.jetpackstudykotlin.R
class NavigationMainActivity : AppCompatActivity() {
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_navigation_main)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
//在此使用时只有Fragment切换时才会展示默认的返回按钮
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeButtonEnabled(true)
//导航控制器
navController = findNavController(R.id.fragment)
setupActionBarWithNavController(navController, null)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
/*
* MainFragment→DetailFragment使用了app:popUpToInclusive="true"
*
* 当调用navigateUp方法时会重新开启MainFragment,而直接点击系统返回按钮
* 就不会存在该问题
* */
if (item.itemId == android.R.id.home) {
navController.navigateUp()
return true
}
return super.onOptionsItemSelected(item)
}
}
activity_navigation_main.xml源码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".navigation.NavigationMainActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#86B951"
android:fitsSystemWindows="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:title="@string/app_name" />
<fragment
android:id="@+id/fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar"
app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
package com.study.jetpackstudykotlin.navigation.main
import android.net.Uri
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import com.study.jetpackstudykotlin.R
import kotlinx.android.synthetic.main.main_fragment.*
class MainFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.main_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btn.setOnClickListener {
//导航至DetailFragment
findNavController().navigate(R.id.action_mainFragment_to_detailFragment)
}
//隐式链接
btn_detail.setOnClickListener {
val deeplink = Uri.parse("inApp://jump")
findNavController().navigate(deeplink)
}
}
}
使用隐式链接跳转时,AndroidMainfest.xml源码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.study.jetpackstudykotlin">
<!-- To access Google+ APIs: -->
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".navigation.WebViewActivity"></activity>
<activity android:name=".navigation.NavigationMainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
//隐式链接,必须添加。编译时将会将其转换为intent-filter
<nav-graph android:value="@navigation/nav_graph" />
</activity>
</application>
</manifest>
package com.study.jetpackstudykotlin.navigation.detail
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import com.study.jetpackstudykotlin.R
import kotlinx.android.synthetic.main.detail_fragment.*
class DetailFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.detail_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tv_detail.setOnClickListener {
//导航至WebViewActivity
findNavController().navigate(R.id.action_detailFragment_to_webViewActivity)
}
}
}
//由父级调用,xml文件中NavHostFragment的入口
@CallSuper
@Override
public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs,
@Nullable Bundle savedInstanceState) {
super.onInflate(context, attrs, savedInstanceState);
final TypedArray navHost = context.obtainStyledAttributes(attrs,
androidx.navigation.R.styleable.NavHost);
//获取app:navGraph的值
final int graphId = navHost.getResourceId(
androidx.navigation.R.styleable.NavHost_navGraph, 0);
if (graphId != 0) {
mGraphId = graphId;
}
navHost.recycle();
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment);
final boolean defaultHost = a.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false);
//判断当前是否是主NavHost
if (defaultHost) {
mDefaultNavHost = true;
}
a.recycle();
}
@CallSuper
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
//为true时,将当前实例添加到父级的Fragment管理器中,同时设置
//其为主导航的Fragment
if (mDefaultNavHost) {
getParentFragmentManager().beginTransaction()
.setPrimaryNavigationFragment(this)
.commit();
}
}
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Context context = requireContext();
//创建导航控制器
mNavController = new NavHostController(context);
//将Fragment与导航器绑定
mNavController.setLifecycleOwner(this);
//赋予mNavController处理用户点击系统返回键的事件
mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher());
//激活返回监听
mNavController.enableOnBackPressed(
mIsPrimaryBeforeOnCreate != null && mIsPrimaryBeforeOnCreate);
mIsPrimaryBeforeOnCreate = null;
mNavController.setViewModelStore(getViewModelStore());
//创建导航器
onCreateNavController(mNavController);
Bundle navState = null;
//是否之前有过缓存
if (savedInstanceState != null) {
navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE);
if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) {
mDefaultNavHost = true;
getParentFragmentManager().beginTransaction()
.setPrimaryNavigationFragment(this)
.commit();
}
mGraphId = savedInstanceState.getInt(KEY_GRAPH_ID);
}
if (navState != null) {
// 恢复导航状态
mNavController.restoreState(navState);
}
if (mGraphId != 0) {
// Set from onInflate()
mNavController.setGraph(mGraphId);
} else {
// 通过Bundle获取graphId
final Bundle args = getArguments();
final int graphId = args != null ? args.getInt(KEY_GRAPH_ID) : 0;
final Bundle startDestinationArgs = args != null
? args.getBundle(KEY_START_DESTINATION_ARGS)
: null;
if (graphId != 0) {
mNavController.setGraph(graphId, startDestinationArgs);
}
}
}
上面的mNavController.setGraph方法最终调用的是NavController类中的onGraphCreated方法,该方法源码如下所示:
private void onGraphCreated(@Nullable Bundle startDestinationArgs) {
//....
if (mGraph != null && mBackStack.isEmpty()) {
boolean deepLinked = !mDeepLinkHandled && mActivity != null
&& handleDeepLink(mActivity.getIntent());
//不存在隐式的调用
if (!deepLinked) {
// 开始导航
navigate(mGraph, startDestinationArgs, null, null);
}
}
}
上面navigate方法的源码如下所示:
private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
//....实现Fragment添加的关键代码
NavDestination newDest = navigator.navigate(node, finalArgs,
navOptions, navigatorExtras);
//....
}
Demo中运用的是FragmentNavigator,因此此处只针对FragmentNavigator的navigate方法进行了分析。该方法
源码如下:
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
if (mFragmentManager.isStateSaved()) {
Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
+ " saved its state");
return null;
}
String className = destination.getClassName();
if (className.charAt(0) == '.') {
className = mContext.getPackageName() + className;
}
final Fragment frag = instantiateFragment(mContext, mFragmentManager,
className, args);
frag.setArguments(args);
final FragmentTransaction ft = mFragmentManager.beginTransaction();
int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
//动画处理
if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
enterAnim = enterAnim != -1 ? enterAnim : 0;
exitAnim = exitAnim != -1 ? exitAnim : 0;
popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
}
//替换fragment
ft.replace(mContainerId, frag);
ft.setPrimaryNavigationFragment(frag);
//....
}