无论您如何构建应用界面(使用 Fragment、Activity 还是其他组件),都要为应用设计屏幕间导航的功能。那么可以试试Naviagtion。
文章目录
- 基本使用
- 创建项目
- 观察使用代码
- 底部导航控件
- fragment代码
- 底部navigationView与顶部fragment的联动
- Navigation视图编辑器
- 页面跳转
- 跳转传参数
- Navigation源码分析
- 配置文件是如何被加载的
- fragment的切换方式
- 修改NavFragmentHost切换fragment的方式
基本使用
创建项目
在AndroidStudio可以直接创建
运行起来这样的效果:
观察使用代码
这是目录结构
一个主的Activity,三个Fragment
主Activity的布局代码main_activity.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"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/bottom_nav_menu" />
<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:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
底部导航控件
这一部分:就是我们的底部切换的Tab,BottomNavigationView,底部导航控件
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/bottom_nav_menu" />
它显示的内容由哪里控制呢?
app:menu=“@menu/bottom_nav_menu”
这里有一个menu的属性,所显示的内容,就是由这里控制的。比如上图我们看到Home、Dashboard、Notifications
我们打开这个文件看看:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home_black_24dp"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_dashboard"
android:icon="@drawable/ic_dashboard_black_24dp"
android:title="@string/title_dashboard" />
<item
android:id="@+id/navigation_notifications"
android:icon="@drawable/ic_notifications_black_24dp"
android:title="@string/title_notifications" />
</menu>
到这里的话,显示出底部的几个tab,是可以的。但是怎么样跟顶部的Fragment进行联动呢?
fragment代码
<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:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
这里竟然是一个fragment,但是上方是怎么显示多个fragment并且切换的呢?
其实这个fragment是管理的fragment,也就是一个容器。
我们通过配置文件,把其他的配置文件填到这个容器里,仔细观察会发现
app:navGraph=“@navigation/mobile_navigation”
这又是一个配置文件,这个配置文件,就是配置这个hostFragment要管理的fragment,代码如下:
<?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/mobile_navigation"
app:startDestination="@+id/navigation_home">
<fragment
android:id="@+id/navigation_home"
android:name="com.example.navigationdemo.ui.home.HomeFragment"
android:label="@string/title_home"
tools:layout="@layout/fragment_home" />
<fragment
android:id="@+id/navigation_dashboard"
android:name="com.example.navigationdemo.ui.dashboard.DashboardFragment"
android:label="@string/title_dashboard"
tools:layout="@layout/fragment_dashboard" />
<fragment
android:id="@+id/navigation_notifications"
android:name="com.example.navigationdemo.ui.notifications.NotificationsFragment"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications" />
</navigation>
里面有三个Fragment,拿第一个来讲
<fragment
android:id="@+id/navigation_home"
android:name="com.sunofbeaches.navigationdemo.ui.home.HomeFragment"
android:label="@string/title_home"
tools:layout="@layout/fragment_home" />
id,name属性指向我们的fragment,label是标签,layout是布局
app:startDestination="@+id/navigation_home"
表示默认要显示的页面,这里指向的是navigation_home,所以当我们启动起来的时候,默认显示的是HomeFragment
再回去看看首页布局的代码
<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:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
这里有一个
app:defaultNavHost="true"
这个其实是把返回按键的事件交给NavHostFragment处理。控制被管理的fragment/activity/dailog的返回。
那么它们两是怎么联动起来的呢?
点击底部的tab,就会切换顶部的fragment
底部navigationView与顶部fragment的联动
我们面向对象的思想,最简单的思维就是监听BottomNavigationView的选中变化,然后切换顶部的fragment。
这些都是Android的官方套件,所以google也希望我们一起使用。
既然希望我们组合使用的话,所以他们把这个切换的动作已经实现了,简单地关联上就行了。
//找到底部的导航控件
val navView: BottomNavigationView = findViewById(R.id.nav_view)
//找到hostFragment
val navController = findNavController(R.id.nav_host_fragment)
//关联起来
navView.setupWithNavController(navController)
这样子,就关联起来了。底部的tab切换,上方的fragment则会切换了。
Navigation视图编辑器
打开我们的/res/navigation/mobile_navigation.xml文件
然后切换到设计视图界面,右上角
从左侧视图可以看出结构,以及入口
前面我们说了,我们的navigation不仅可以管理fragment,也可以有activity,fragmentDialog
我们创建一个登录的activity吧
记得要在清单文件里注册一下。
然后点击视图左上角的添加按钮
添加完以后:
对应的xml文件也会多了一个activity
那又如何进行跳转呢?
页面跳转
比如说,我们要在首页-跳转-登录的activity上
我们选中home fragment,然后点击添加action的图标
还可以添加动画,如图等
添加完以后
对应的xml文件就会有:
<fragment
android:id="@+id/navigation_home"
android:name="com.sunofbeaches.navigationdemo.ui.home.HomeFragment"
android:label="@string/title_home"
tools:layout="@layout/fragment_home" >
<action
android:id="@+id/to_login_activity"
app:destination="@id/loginActivity"
app:enterAnim="@anim/fragment_fade_enter"
app:exitAnim="@anim/fragment_close_exit" />
</fragment>
到这一步,还没行,我们只是声明了跳转关系,我们真的要跳转,还得添加简单的代码。
在我们的HomeFragment里添加一个跳转的按钮
val root = inflater.inflate(R.layout.fragment_home, container, false)
val loginBtn = root.findViewById<Button>(R.id.toLoginPage)
loginBtn.setOnClickListener {
Navigation.findNavController(root).navigate(R.id.to_login_activity)
}
这里面主要有一个Navigation.findNavController(root).navigate方法。
这个方法就是跳转到某个地方去,有多个重载方法:
可以使用deepLink,可以使用directions,也可以传参。
跳转效果:
跳转传参数
比如说我们的LoginActivity你可以给它传一个phoneNumber
跳转的时候,我们携带参数
loginBtn.setOnClickListener {
val userInfo = Bundle()
userInfo.putString("phoneNumber", "15353979727")
Navigation.findNavController(root).navigate(R.id.to_login_activity, userInfo)
}
那么在LoginActivity,我们怎么获取呢?
val phoneNum = intent.extras!!.get("phoneNumber")
println(phoneNum)
Navigation源码分析
配置文件是如何被加载的
app:navGraph=“@navigation/mobile_navigation”
我们看到NavHostFragment的onCreate方法
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
final Context context = requireContext();
mNavController = new NavHostController(context);
mNavController.setLifecycleOwner(this);
mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher());
// Set the default state - this will be updated whenever
// onPrimaryNavigationFragmentChanged() is called
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) {
// Navigation controller state overrides arguments
mNavController.restoreState(navState);
}
if (mGraphId != 0) {
// Set from onInflate()
mNavController.setGraph(mGraphId);
} else {
// See if it was set by NavHostFragment.create()
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);
}
}
// We purposefully run this last as this will trigger the onCreate() of
// child fragments, which may be relying on having the NavController already
// created and having its state restored by that point.
super.onCreate(savedInstanceState);
}
我们可以找到
mNavController.setGraph(mGraphId);
这个代码,如果mGraphId不为0直接处理,如果mGraphId为0,则通过其他方式获取
通过资源ID调用的方法,会走到这里
@CallSuper
public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
}
看这个
getNavInflater().inflate(graphResId)
这个方法的调用,就是把xml转成bean类
/**
* Inflate a NavGraph from the given XML resource id.
*
* @param graphResId
* @return
*/
@SuppressLint("ResourceType")
@NonNull
public NavGraph inflate(@NavigationRes int graphResId) {
Resources res = mContext.getResources();
XmlResourceParser parser = res.getXml(graphResId);
final AttributeSet attrs = Xml.asAttributeSet(parser);
try {
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
// Empty loop
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
String rootElement = parser.getName();
NavDestination destination = inflate(res, parser, attrs, graphResId);
if (!(destination instanceof NavGraph)) {
throw new IllegalArgumentException("Root element <" + rootElement + ">"
+ " did not inflate into a NavGraph");
}
return (NavGraph) destination;
} catch (Exception e) {
throw new RuntimeException("Exception inflating "
+ res.getResourceName(graphResId) + " line "
+ parser.getLineNumber(), e);
} finally {
parser.close();
}
}
也就是会返回对象NavGraph,这个给到了谁呢?
public void setGraph(@NonNull NavGraph graph, @Nullable Bundle startDestinationArgs) {
if (mGraph != null) {
// Pop everything from the old graph off the back stack
popBackStackInternal(mGraph.getId(), true);
}
mGraph = graph;
onGraphCreated(startDestinationArgs);
}
就在NavController里头。
fragment的切换方式
前面我们是不是有切换fragment的动作呀?我们点击底部的tab,会切换上方的fragment。
这就是我们的入口了。
//找到底部的导航控件
val navView: BottomNavigationView = findViewById(R.id.nav_view)
//找到hostFragment
val navController = findNavController(R.id.nav_host_fragment)
navView.setupWithNavController(navController)
这是一个扩展函数
fun BottomNavigationView.setupWithNavController(navController: NavController) {
NavigationUI.setupWithNavController(this, navController)
}
跟进来
public static void setupWithNavController(
@NonNull final BottomNavigationView bottomNavigationView,
@NonNull final NavController navController) {
bottomNavigationView.setOnNavigationItemSelectedListener(
new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
return onNavDestinationSelected(item, navController);
}
});
final WeakReference<BottomNavigationView> weakReference =
new WeakReference<>(bottomNavigationView);
navController.addOnDestinationChangedListener(
new NavController.OnDestinationChangedListener() {
@Override
public void onDestinationChanged(@NonNull NavController controller,
@NonNull NavDestination destination, @Nullable Bundle arguments) {
BottomNavigationView view = weakReference.get();
if (view == null) {
navController.removeOnDestinationChangedListener(this);
return;
}
Menu menu = view.getMenu();
for (int h = 0, size = menu.size(); h < size; h++) {
MenuItem item = menu.getItem(h);
if (matchDestination(destination, item.getItemId())) {
item.setChecked(true);
}
}
}
});
}
对bottomNavigationView中的NavigationItem进行选择监听,并返回onNavDestinationSelected方法
public static boolean onNavDestinationSelected(@NonNull MenuItem item,
@NonNull NavController navController) {
NavOptions.Builder builder = new NavOptions.Builder()
.setLaunchSingleTop(true);
if (navController.getCurrentDestination().getParent().findNode(item.getItemId())
instanceof ActivityNavigator.Destination) {
builder.setEnterAnim(R.anim.nav_default_enter_anim)
.setExitAnim(R.anim.nav_default_exit_anim)
.setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
.setPopExitAnim(R.anim.nav_default_pop_exit_anim);
} else {
builder.setEnterAnim(R.animator.nav_default_enter_anim)
.setExitAnim(R.animator.nav_default_exit_anim)
.setPopEnterAnim(R.animator.nav_default_pop_enter_anim)
.setPopExitAnim(R.animator.nav_default_pop_exit_anim);
}
if ((item.getOrder() & Menu.CATEGORY_SECONDARY) == 0) {
builder.setPopUpTo(findStartDestination(navController.getGraph()).getId(), false);
}
NavOptions options = builder.build();
try {
//TODO provide proper API instead of using Exceptions as Control-Flow.
navController.navigate(item.getItemId(), null, options);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
有这么一行
navController.navigate(item.getItemId(), null, options);
一直走会到这里
Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
node.getNavigatorName());
Bundle finalArgs = node.addInDefaultArgs(args);
NavDestination newDest = navigator.navigate(node, finalArgs,
navOptions, navigatorExtras);
这个navigator是什么类型,还得看拿出来的是什么东西。我们这里研究的是Fragment,所以拿出来的应该是
所以调用navigate的时候,调用的其实是FragmentNavitor里的navigate方法,也就是这个
@Nullable
@Override
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);
}
ft.replace(mContainerId, frag);
ft.setPrimaryNavigationFragment(frag);
final @IdRes int destId = destination.getId();
final boolean initialNavigation = mBackStack.isEmpty();
// TODO Build first class singleTop behavior for fragments
final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
&& navOptions.shouldLaunchSingleTop()
&& mBackStack.peekLast() == destId;
boolean isAdded;
if (initialNavigation) {
isAdded = true;
} else if (isSingleTopReplacement) {
// Single Top means we only want one instance on the back stack
if (mBackStack.size() > 1) {
// If the Fragment to be replaced is on the FragmentManager's
// back stack, a simple replace() isn't enough so we
// remove it from the back stack and put our replacement
// on the back stack in its place
mFragmentManager.popBackStack(
generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
}
isAdded = false;
} else {
ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
isAdded = true;
}
if (navigatorExtras instanceof Extras) {
Extras extras = (Extras) navigatorExtras;
for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) {
ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
}
}
ft.setReorderingAllowed(true);
ft.commit();
// The commit succeeded, update our view of the world
if (isAdded) {
mBackStack.add(destId);
return destination;
} else {
return null;
}
}
可以看到,这里面是replace的方式进行切换的
所以,当我们切换fragment的时候,生命周期的变化会频繁销毁和创建。
修改NavFragmentHost切换fragment的方式
从前面的源码分析,我们知道了,切换是由FragmentNavigator去负责的。
如果我们直接继承这个类,去覆写navigate的方法,某些私有的属性用不了。
那怎么办呢?
既然都是继承自Navigator的,那我们也写一个我们的FragmentNavigator不就可以了吗?
其他代码复制FragmentNavigator的,然后修改即可。
修改完以后这样子了
/**
* {@inheritDoc}
* <p>
* This method should always call
* {@link FragmentTransaction#setPrimaryNavigationFragment(Fragment)}
* so that the Fragment associated with the new destination can be retrieved with
* {@link FragmentManager#getPrimaryNavigationFragment()}.
* <p>
* Note that the default implementation commits the new Fragment
* asynchronously, so the new Fragment is not instantly available
* after this call completes.
*/
@SuppressWarnings("deprecation") /* Using instantiateFragment for forward compatibility */
@Nullable
@Override
public NavDestination navigate(@NonNull FragmentNavigator.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;
}
String tag = className.substring(className.lastIndexOf(".") + 1);
Fragment frag = mFragmentManager.findFragmentByTag(tag);
//判断是否有添加,如果没有添加,则添加,并且显示
//如果已经添加了,直接显示
if (frag == null) {
System.out.println(" create new fragment..." + tag);
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);
}
//隐藏上一个显示的内容
for (Fragment fragment : mFragmentManager.getFragments()) {
System.out.println("hide fragment -- > " + fragment.getClass().getName());
ft.hide(fragment);
}
if (!frag.isAdded()) {
System.out.println("add fragment ... " + tag);
ft.add(mContainerId, frag, tag);
}
ft.show(frag);
//ft.replace(mContainerId, frag);
ft.setPrimaryNavigationFragment(frag);
final @IdRes int destId = destination.getId();
final boolean initialNavigation = mBackStack.isEmpty();
// TODO Build first class singleTop behavior for fragments
final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
&& navOptions.shouldLaunchSingleTop()
&& mBackStack.peekLast() == destId;
boolean isAdded;
if (initialNavigation) {
isAdded = true;
} else if (isSingleTopReplacement) {
// Single Top means we only want one instance on the back stack
if (mBackStack.size() > 1) {
// If the Fragment to be replaced is on the FragmentManager's
// back stack, a simple replace() isn't enough so we
// remove it from the back stack and put our replacement
// on the back stack in its place
mFragmentManager.popBackStack(
generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
}
isAdded = false;
} else {
ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
isAdded = true;
}
if (navigatorExtras instanceof HideSwitchFragmentNavigator.Extras) {
HideSwitchFragmentNavigator.Extras extras = (HideSwitchFragmentNavigator.Extras) navigatorExtras;
for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) {
ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
}
}
ft.setReorderingAllowed(true);
ft.commit();
// The commit succeeded, update our view of the world
if (isAdded) {
mBackStack.add(destId);
return destination;
} else {
return null;
}
}
这里是主要的代码,把replace替换成了hide
然后,我们写一个类去继承NavHostFragment,覆写里面的方法
NavHostFragment是在下面方法创建FragmentNavigator的
protected void onCreateNavController(@NonNull NavController navController) {
navController.getNavigatorProvider().addNavigator(
new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}
所以我们覆写createFragmentNavigator的方法
class CustomNavHostFragment : NavHostFragment() {
/**
* Create the FragmentNavigator that this NavHostFragment will use. By default, this uses
* [FragmentNavigator], which replaces the entire contents of the NavHostFragment.
*
*
* This is only called once in [.onCreate] and should not be called directly by
* subclasses.
* @return a new instance of a FragmentNavigator
*/
@Deprecated("Use {@link #onCreateNavController(NavController)}")
override fun createFragmentNavigator(): Navigator<out FragmentNavigator.Destination?> {
return HideSwitchFragmentNavigator(
requireContext(), childFragmentManager,
getContainerId()
)
}
private fun getContainerId(): Int {
val id = id
return if (id != 0 && id != View.NO_ID) {
id
} else R.id.nav_host_fragment_container
// Fallback to using our own ID if this Fragment wasn't added via
// add(containerViewId, Fragment)
}
}
改成这样
<fragment
android:id="@+id/nav_host_fragment"
android:name="com.example.navigationdemo.view.CustomNavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//找到底部的导航控件
val navView: BottomNavigationView = findViewById(R.id.nav_view)
//找到hostFragment
val navController = findNavController(R.id.nav_host_fragment)
navView.setOnNavigationItemSelectedListener {
when (it.itemId) {
R.id.navigation_home -> {
navController.navigate(R.id.navigation_home)
}
R.id.navigation_dashboard -> {
navController.navigate(R.id.navigation_dashboard)
}
R.id.navigation_notifications -> {
navController.navigate(R.id.navigation_notifications)
}
}
true
}
}