作者
咱们好,我叫小琪;
自己16年毕业于中南林业科技大学软件工程专业,毕业后在教育职业做安卓开发,后来于19年10月参加37手游安卓团队;
目前首要担任国内发行安卓相关开发,同时统筹内部几款App开发。
目录
-
navigation——入门篇
-
navigation——进阶篇(本章解说)
-
navigation——实战篇 (敬请期待…)
前语
上篇对Navigation的一些概念进行了介绍,并在前语中提到了app中常用的一个场景,便是app的主页,一般都会由一个activity+多个子tab组成,这种场景有很多种完成办法,比方能够运用RadioGroup、FrgmentTabHost、TabLayout或者自界说view等办法,但这些都离不开经典的FragmentManager来办理fragment之间的切换。
现在,咱们有了新的完成办法,Navigation+BottomNavigationView,废话不多说,先看终究要完成的作用
第一个实例
先保证引入了navigation相关依靠
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'
很简单,包括三个页面,主页、发现、我的,点击底部能够切换页面,有了上一篇的根底,先新建一个nav_graph的导航资源文件,包括三个framgent子节点
<?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/nav_graph"
app:startDestination="@id/FragmentHome">
<fragment
android:id="@+id/FragmentHome"
android:name="com.example.testnavigation.FragmentHome"
android:label="fragment_home"
tools:layout="@layout/fragment_home">
</fragment>
<fragment
android:id="@+id/FragmentDicover"
android:name="com.example.testnavigation.FragmentDicover"
android:label="fragment_discover"
tools:layout="@layout/fragment_discover">
</fragment>
<fragment
android:id="@+id/FragmentMine"
android:name="com.example.testnavigation.FragmentMine"
android:label="fragment_mine"
tools:layout="@layout/fragment_mine">
</fragment>
</navigation>
然后在activity的布局中(这儿为MainActivity的activity_main)中增加BottomNavigationView控件,
<?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=".MainActivity">
<fragment
android:id="@+id/fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/bottom_nav_menu" />
</androidx.constraintlayout.widget.ConstraintLayout>
其间fragment节点在上面已经介绍过了,这篇不再解说,BottomNavigationView是谷歌的一个完成底部导航的组件, app:menu特点为底部导航栏指定元素,新建一个bottom_nav_menu的menu资源文件
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/FragmentHome"
android:icon="@mipmap/icon_tab_home"
android:title="主页" />
<item
android:id="@+id/FragmentDicover"
android:icon="@mipmap/icon_tab_find"
android:title="发现" />
<item
android:id="@+id/FragmentMine"
android:icon="@mipmap/icon_tab_mine"
android:title="我的" />
</menu>
留意:这儿item标签的id和上面nav_graph中fragment标签的id共同
资源准备好后,在MainActivity中
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//fragment的容器视图,navHost的默许完成——NavHostFragment
val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragment) as NavHostFragment
//办理运用导航的方针
val navController = navHostFragment.navController
//fragment与BottomNavigationView的交互交给NavigationUI
bottom_nav_view.setupWithNavController(navController)
}
}
经过NavigationUI库,将BottomNavigationView和navigation相关,就能完成上面的作用图了,是不是so easy!
是不是很疑问,这是怎样做到的?,此刻咱们进到源码看看,进入setupWithNavController办法
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);
}
});
......
}
在这儿能够看到,给bottomNavigationView设置了一个item点击事情,进到onNavDestinationSelected办法,
public static boolean onNavDestinationSelected(@NonNull MenuItem item,
@NonNull NavController navController) {
NavOptions.Builder builder = new NavOptions.Builder()
.setLaunchSingleTop(true)
.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()办法进行页面切换的。
运用Navigation+BottomNavigationView结合navigationUI扩展库,这种办法是不是相比于以往的完成办法更简单?可能咱们迫不及待的想运用到自己的项目中去了,可殊不知还有坑在里面。
navigation的坑
别离在三个fragment中的首要生命周期中打印各自的log,运转程序,翻开FragmentHome,能够看到生命周期是正常执行的

然后点击底部的发现切换到FragmentDiscover,FragmentDiscover生命周期也是正常的,但却发现FragmentHome回调了onDestoryView()办法,

再次点击主页切回到FragmentHome,神奇的事情发生了,原来的FragmentHome销毁了,却又从头创立了一个新的FragmentHome实例,即fragment的重绘,并且从log日志中也能够看到,刚刚翻开的FragmentDiscover也执行了onDestory相同也销毁了。
下面从源码角度剖析为什么会这样。
原因
从NavHostFragment入手,首要看到它的oncreate办法中,
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
......
mNavController = new NavHostController(context);
......
onCreateNavController(mNavController);
......
}
去掉无关代码,只看中心代码,能够看到,有一个NavHostController类型的mNavController成员变量,mNavController便是前篇文章中提到的办理导航的navController方针,只不过它是承继自NavController的,戳进去结构办法,发现调用了父类的结构办法,再戳进去来到了NavController的结构办法,
public NavController(@NonNull Context context) {
mContext = context;
.......
mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider));
mNavigatorProvider.addNavigator(new ActivityNavigator(mContext));
}
在结构办法中,mNavigatorProvider增加了两个navigator,首要看看mNavigatorProvider是个什么东东,
public class NavigatorProvider {
private static final HashMap<Class<?>, String> sAnnotationNames = new HashMap<>();
......
@NonNull
static String getNameForNavigator(@NonNull Class<? extends Navigator> navigatorClass) {
String name = sAnnotationNames.get(navigatorClass);
if (name == null) {
Navigator.Name annotation = navigatorClass.getAnnotation(Navigator.Name.class);
name = annotation != null ? annotation.value() : null;
if (!validateName(name)) {
throw new IllegalArgumentException("No @Navigator.Name annotation found for "
+ navigatorClass.getSimpleName());
}
sAnnotationNames.put(navigatorClass, name);
}
return name;
}
}
看中心的一个办法getNameForNavigator,该办法传入一个承继了Navigator的类,然后获取其注解为Navigator.Name的值,并经过sAnnotationNames缓存起来,这说起来好像有点抽象,咱们看具体的,前面有提到mNavigatorProvider增加了两个navigator,别离是NavGraphNavigator和ActivityNavigator,咱们戳进去ActivityNavigator源码,

getNameForNavigator办法对应到这儿,其实便是获取到了Navigator.Name的注解值activity,由此能够知道,mNavigatorProvider调用addNavigator办法,就会缓存key为navigator的类,值为这个类的Navigator.Name注解值。
回到前面的NavHostFragment的onCreate办法中,
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
......
mNavController = new NavHostController(context);
......
onCreateNavController(mNavController);
......
}
看完了mNavController的结构函数,持续onCreateNavController办法,
@CallSuper
protected void onCreateNavController(@NonNull NavController navController) {
navController.getNavigatorProvider().addNavigator(
new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}
createFragmentNavigator办法
@Deprecated
@NonNull
protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
return new FragmentNavigator(requireContext(), getChildFragmentManager(),
getContainerId());
}
能够看到,又持续增加了DialogFragmentNavigator和FragmentNavigator两个navigator,至此总共缓存了四个navigator。
回到NavHostFragment的oncreate办法,持续看后面的代码
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
......
mNavController = new NavHostController(context);
......
onCreateNavController(mNavController);
......
if (mGraphId != 0) {
// Set from onInflate()
mNavController.setGraph(mGraphId);
} else {
......
}
}
在onInflate()办法中能够看出,mGraphId便是在布局文件中界说NavHostFragment时,经过app:navGraph特点指定的导航资源文件,
跟进setGraph()办法,
public void setGraph(@NavigationRes int graphResId) {
setGraph(graphResId, null);
}
public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
}
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);
}
在第二个重载办法中,经过getNavInflater().inflate办法创立出一个NavGraph方针,传到第三个重载的办法中,并赋值给成员变量mGraph,终究在onGraphCreated办法中将第一个页面显现出来。
由此可见,导航资源文件nav_graph会被解析成一个NavGraph方针,看下NavGraph
public class NavGraph extends NavDestination implements Iterable<NavDestination> {
final SparseArrayCompat<NavDestination> mNodes = new SparseArrayCompat<>();
}
NavGraph承继了NavDestination,NavDestination其实便是nav_graph.xml中navigation下的一个个节点,也便是一个个页面,NavGraph内部有个调集mNodes,用来保存一组NavDestination。
至此咱们具体剖析了两个重要的过程,一个是navigator的,一个是nav_graph.xml是怎样被解析并相关到navController,弄清楚这两个过程,对接下来的剖析大有协助。
还记得前面有剖析到,BottomNavigationView是怎样做到页面切换的吗,把上面代码照样搬过来,
public static boolean onNavDestinationSelected(@NonNull MenuItem item,
@NonNull NavController navController) {
NavOptions.Builder builder = new NavOptions.Builder()
.setLaunchSingleTop(true)
.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(),由此能够知道,上面提到过的,界说BottomNavigationView时 app:menu特点指定的menu资源文件中,item标签的id和nav_graph中fragment标签的id保持共同的原因了吧,咱们持续跟踪,
public void navigate(@IdRes int resId, @Nullable Bundle args,
@Nullable NavOptions navOptions) {
navigate(resId, args, navOptions, null);
}
public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions,
@Nullable Navigator.Extras navigatorExtras) {
......
@IdRes int destId = resId;
.......
NavDestination node = findDestination(destId);
......
navigate(node, combinedArgs, navOptions, navigatorExtras);
}
private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
......
Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
node.getNavigatorName());
Bundle finalArgs = node.addInDefaultArgs(args);
NavDestination newDest = navigator.navigate(node, finalArgs,
navOptions, navigatorExtras);
......
}
能够看到,在第二个重载办法中,经过findDestination办法传入导航到方针页面的id,获得NavDestination方针node,在第三个重载办法中,经过mNavigatorProvider获取navigator,那么这个navigator是什么呢,还记得上面剖析的NavHostFragment经过oncreate办法之后,navigatorProvider总共缓存了四个navigator吗, 由于在nav.graph.xml中,界说的是标签,所以这儿navigator终究拿到的是一个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);
}
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;
}
}
经过Destination拿到ClassName,instantiateFragment办法经过内反射创立出对应的fragment,终究经过FragmentTransaction的replace办法创立fragment。
至此,终于真相大白了!咱们知道replace办法每次都会从头创立fragment,所以运用Navigation创立的底部导航页面,每次点击切换页面当时fragment都会重建。
处理
既然知道了fragment重绘的原因,那就能够对症下药了,咱们知道,fragment的切换除了replace,还能够经过hide和show,那怎样做到呢,经过前面的剖析,其实能够自界说一个navigator承继FragmentNavigator,重写它的navigate办法,然后到达经过hide和show进行fragment切换的目的。
这儿新建一个FixFragmentNavigator类,咱们期望在nav_graph中经过fixFragment标签来指定每个导航页面
@Navigator.Name("fixFragment")
class FixFragmentNavigator(context: Context, manager: FragmentManager, containerId: Int) :
FragmentNavigator(context, manager, containerId) {
private val mContext = context
private val mManager = manager
private val mContainerId = containerId
private val TAG = "FixFragmentNavigator"
override fun navigate(
destination: Destination,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
): NavDestination? {
if (mManager.isStateSaved) {
Log.i(TAG, "Ignoring navigate() call: FragmentManager has already" + " saved its state")
return null
}
var className = destination.className
if (className[0] == '.') {
className = mContext.packageName + className
}
val ft = mManager.beginTransaction()
var enterAnim = navOptions?.enterAnim ?: -1
var exitAnim = navOptions?.exitAnim ?: -1
var popEnterAnim = navOptions?.popEnterAnim ?: -1
var popExitAnim = navOptions?.popExitAnim ?: -1
if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
enterAnim = if (enterAnim != -1) enterAnim else 0
exitAnim = if (exitAnim != -1) exitAnim else 0
popEnterAnim = if (popEnterAnim != -1) popEnterAnim else 0
popExitAnim = if (popExitAnim != -1) popExitAnim else 0
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
}
/**
* 1、先查询当时显现的fragment 不为空则将其hide
* 2、根据tag查询当时增加的fragment是否不为null,不为null则将其直接show
* 3、为null则经过instantiateFragment办法创立fragment实例
* 4、将创立的实例增加在业务中
*/
val fragment = mManager.primaryNavigationFragment //当时显现的fragment
if (fragment != null) {
ft.hide(fragment)
ft.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
}
var frag: Fragment?
val tag = destination.id.toString()
frag = mManager.findFragmentByTag(tag)
if (frag != null) {
ft.show(frag)
ft.setMaxLifecycle(frag, Lifecycle.State.RESUMED);
} else {
frag = instantiateFragment(mContext, mManager, className, args)
frag.arguments = args
ft.add(mContainerId, frag, tag)
}
ft.setPrimaryNavigationFragment(frag)
@IdRes val destId = destination.id
/**
* 经过反射的办法获取 mBackStack
*/
val mBackStack: ArrayDeque<Int>
val field = FragmentNavigator::class.java.getDeclaredField("mBackStack")
field.isAccessible = true
mBackStack = field.get(this) as ArrayDeque<Int>
val initialNavigation = mBackStack.isEmpty()
val isSingleTopReplacement = (navOptions != null && !initialNavigation
&& navOptions.shouldLaunchSingleTop()
&& mBackStack.peekLast() == destId)
val isAdded: Boolean
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
mManager.popBackStack(
zygoteBackStackName(mBackStack.size, mBackStack.peekLast()),
FragmentManager.POP_BACK_STACK_INCLUSIVE
)
ft.addToBackStack(zygoteBackStackName(mBackStack.size, destId))
}
isAdded = false
} else {
ft.addToBackStack(zygoteBackStackName(mBackStack.size + 1, destId))
isAdded = true
}
if (navigatorExtras is Extras) {
val extras = navigatorExtras as Extras?
for ((key, value) in extras!!.sharedElements) {
ft.addSharedElement(key, value)
}
}
ft.setReorderingAllowed(true)
ft.commit()
// The commit succeeded, update our view of the world
if (isAdded) {
mBackStack.add(destId)
return destination
} else {
return null
}
}
private fun zygoteBackStackName(backIndex: Int, destid: Int): String {
return "$backIndex - $destid"
}
}
新建一个导航资源文件fix_nav_graph.xml,将本来的fragment换成fixFragment
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemams.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/FragmentHome">
<fixFragment
android:id="@+id/FragmentHome"
android:name="com.example.testnavigation.FragmentHome"
android:label="fragment_home"
tools:layout="@layout/fragment_home">
</fixFragment>
<fixFragment
android:id="@+id/FragmentDicover"
android:name="com.example.testnavigation.FragmentDicover"
android:label="fragment_discover"
tools:layout="@layout/fragment_discover">
</fixFragment>
<fixFragment
android:id="@+id/FragmentMine"
android:name="com.example.testnavigation.FragmentMine"
android:label="fragment_mine"
tools:layout="@layout/fragment_mine">
</fixFragment>
</navigation>
然后把activity_main.xml中的app:navGraph特点值替换为fix_nav_graph,
“修正版的”FragmentNavigator写好后,在MainActivity中,经过navController把它增加到fragmentNavigator中,
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navController = Navigation.findNavController(this, R.id.fragment)
val fragment =
supportFragmentManager.findFragmentById(R.id.fragment) as NavHostFragment
val fragmentNavigator =
FixFragmentNavigator(this, supportFragmentManager, fragment.id)
//增加自界说的FixFragmentNavigator
navController.navigatorProvider.addNavigator(fragmentNavigator)
bottom_nav_view.setupWithNavController(navController)
}
报错信息很明显,找不到fixFragment对应的navigator,有必要经过addNavigator办法进行增加,这怎样回事呢?分明已经调用addNavigator办法增加自界说的FixFragmentNavigator了。别急,还是回到NavHostFragment的onCreate()办法中,
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
......
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);
}
}
}
上面已经说过了mGraphId便是经过app:navGraph指定的导航资源文件,那么mGraphId此刻不等于0,走到if语句中,
@CallSuper
public void setGraph(@NavigationRes int graphResId) {
setGraph(graphResId, null);
}
@CallSuper
public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
}
进到getNavInflater().inflate
@SuppressLint("ResourceType")
@NonNull
public NavGraph inflate(@NavigationRes int graphResId) {
......
try {
......
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();
}
}
进到inflate办法,
@NonNull
private NavDestination inflate(@NonNull Resources res, @NonNull XmlResourceParser parser,
@NonNull AttributeSet attrs, int graphResId)
throws XmlPullParserException, IOException {
Navigator<?> navigator = mNavigatorProvider.getNavigator(parser.getName());
......
}
进到getNavigator办法
@CallSuper
@NonNull
public <T extends Navigator<?>> T getNavigator(@NonNull String name) {
if (!validateName(name)) {
throw new IllegalArgumentException("navigator name cannot be an empty string");
}
Navigator<? extends NavDestination> navigator = mNavigators.get(name);
if (navigator == null) {
throw new IllegalStateException("Could not find Navigator with name \"" + name
+ "\". You must call NavController.addNavigator() for each navigation type.");
}
return (T) navigator;
}
原来报错的信息在这儿,这儿其实便是经过标签获取对应的navigator,然而在NavHostFragmen执行oncreate后,默许只增加了本来的四个navigator,而此刻在解析fixFragment节点时,咱们自界说的FixFragmentNavigator还未增加进来,所以抛了这个异常。
那么咱们是不能在布局文件中经过app:navGraph特点指定自界说的导航资源文件了,只能在布局文件中去掉app:navGraph这个特点,然后在增加FixFragmentNavigator的同时,经过代码将导航资源文件设置进去。
终究代码如下:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navController = Navigation.findNavController(this, R.id.fragment)
val fragment =
supportFragmentManager.findFragmentById(R.id.fragment) as NavHostFragment
val fragmentNavigator =
FixFragmentNavigator(this, supportFragmentManager, fragment.id)
//增加自界说的FixFragmentNavigator
navController.navigatorProvider.addNavigator(fragmentNavigator)
//经过代码将导航资源文件设置进去
navController.setGraph(R.navigation.fix_nav_graph)
bottom_nav_view.setupWithNavController(navController)
}
运转程序,观察各fragment的生命周期,发现已经不会从头走生命周期了。
总结
本篇在上篇的根底上,结合BottomNavigationView完成了第一个底部导航切换的实例,然后介绍了这种办法引发的坑,进而经过源码剖析了发生这种现象的原因,并给出了处理的思路。读懂源码才是最重要的,现在再总结一下navigator进行页面切换的原理:
-
首要需要一个承载页面的容器NavHost,这个容器有个默许的完成NavHostFragment
-
NavHostFragment有个mNavController成员变量,它是一个NavController方针,终究页面导航都是经过调用它的navigate办法完成的
-
mNavController内部经过NavigatorProvider办理navigator
-
NavHostFragment在oncreate办法中,mNavController增加了四个navigator,别离是FragmentNavigator、ActivityNavigator、DialogFragmentNavigator、NavGraphNavigator,别离完成各自的navigate办法,进行页面切换
-
mNavController经过调用setGraph()办法,传入导航资源文件,并进行解析,获取导航资源文件中的节点,得到NavDestination
-
FragmentNavigator的navigate办法中,是经过replace办法到达fragment的切换目的,因此会引起fragment的重绘



