網(wǎng)站首頁 編程語言 正文
前言
最近項(xiàng)目中使用到了BottomNavigationView結(jié)合Navigation實(shí)現(xiàn)底部導(dǎo)航欄切換頁面業(yè)務(wù)。
NavigationUI.setupWithNavController(bottomNavigationView, navController);
結(jié)果發(fā)現(xiàn)每次點(diǎn)擊底部導(dǎo)航欄切換的時候都會重建Fragment,于是分析了源碼,并研究了解決方案。
源碼分析:
首先看布局文件:
<?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:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="0dp" android:layout_height="0dp" app:defaultNavHost="true" app:layout_constraintBottom_toTopOf="@+id/bottom_nav_view" 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="0dp" android:layout_height="50dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:menu="@menu/bottom_menu" /> </androidx.constraintlayout.widget.ConstraintLayout>
當(dāng)調(diào)用NavigationUI.setupWithNavController(BottomNavigationView, NavController)以后,setupWithNavController方法內(nèi)部其實(shí)通過調(diào)用BottomNavigationView#setOnNavigationItemSelectedListener方法監(jiān)聽導(dǎo)航欄選中事件。
在BottomNavigationView.OnNavigationItemSelectedListener監(jiān)聽中,最終會調(diào)用到NavController#navigate方法,進(jìn)入Navigation源碼中。
Navigation源碼分析
首先看NavHostFragment的執(zhí)行流程。
1. NavHostFragment#onInflate
因?yàn)樵趚ml中聲明fragment因此,首先調(diào)用Fragment的onInflate方法。
@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);
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);
if (defaultHost) {
mDefaultNavHost = true;
}
a.recycle();
}
onInflate方法中主要是從XML屬性中解析navGraph屬性和defaultNavHost屬性值。
2. NavHostFragment#onAttach
根據(jù)Fragment生命周期,然后執(zhí)行的是onAttach方法。
@CallSuper
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (mDefaultNavHost) {
getParentFragmentManager().beginTransaction()
.setPrimaryNavigationFragment(this)
.commit();
}
}
onAttach方法中主要是設(shè)置NavHostFragment為導(dǎo)航器的主導(dǎo)航容器。
3. NavHostFragment#onCreate
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
final Context context = requireContext();
// 1. 實(shí)例化NavHostController
mNavController = new NavHostController(context);
mNavController.setLifecycleOwner(this);
mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher());
mNavController.enableOnBackPressed(
mIsPrimaryBeforeOnCreate != null && mIsPrimaryBeforeOnCreate);
mIsPrimaryBeforeOnCreate = null;
mNavController.setViewModelStore(getViewModelStore());
// 2. 創(chuàng)建DialogFragmentNavigator和FragmentNavigator并添加示例到NavigatorProvider中
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) {
// 3. 設(shè)置導(dǎo)航配置文件
mNavController.setGraph(mGraphId);
} else {
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);
}
}
super.onCreate(savedInstanceState);
}
onCreate方法中主要做三件事:
- 實(shí)例化NavHostController對象
- 創(chuàng)建DialogFragmentNavigator和FragmentNavigator并添加到NavHostController的父類NavController的NavigatorProvider類型的成員變量mNavigatorProvider中
- 調(diào)用NavHostController#setGraph方法設(shè)置導(dǎo)航配置文件nav_graph
public class NavHostController extends NavController {
public NavHostController(@NonNull Context context) {
super(context);
}
...
}
主要看父類初始化方法:
public class NavController {
private NavigatorProvider mNavigatorProvider = new NavigatorProvider();
public NavController(@NonNull Context context) {
...
mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider));
mNavigatorProvider.addNavigator(new ActivityNavigator(mContext));
}
}
主要是創(chuàng)建NavGraphNavigator和ActivityNavigator實(shí)例并添加到NavController的成員變量mNavigatorProvider中。
4. NavHostFragment#onCreateNavController
@CallSuper
protected void onCreateNavController(@NonNull NavController navController) {
navController.getNavigatorProvider().addNavigator(
new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}
onCreate方法中調(diào)用了onCreateNavController方法添加DialogFragmentNavigator和FragmentNavigator示例。
5. NavigatorProvider
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類的注解Navigator.Name
Navigator.Name annotation = navigatorClass.getAnnotation(Navigator.Name.class);
name = annotation != null ? annotation.value() : null;
...
sAnnotationNames.put(navigatorClass, name);
}
return name;
}
private final HashMap<String, Navigator<? extends NavDestination>> mNavigators = new HashMap<>()
@Nullable
public final Navigator<? extends NavDestination> addNavigator(
@NonNull Navigator<? extends NavDestination> navigator) {
String name = getNameForNavigator(navigator.getClass());
return addNavigator(name, navigator);
}
@CallSuper
@Nullable
public Navigator<? extends NavDestination> addNavigator(@NonNull String name,
@NonNull Navigator<? extends NavDestination> navigator) {
return mNavigators.put(name, navigator);
}
}
NavigatorProvider類內(nèi)部主要是存儲了鍵值為自定義Navigator時注解Navigator.Name指定的名稱,值為對應(yīng)的Navigator示例。
因此onCreate方法執(zhí)行以后,NavigatorProvider中的mNavigators的值為:
("navigation", NavGraphNavigator)
("activity", ActivityNavigator)
("dialog", DialogFragmentNavigator)
("fragment", FragmentNavigator)
6. NavController#setGraph
@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);
}
@CallSuper
public void setGraph(@NonNull NavGraph graph, @Nullable Bundle startDestinationArgs) {
if (mGraph != null) {
popBackStackInternal(mGraph.getId(), true);
}
mGraph = graph;
onGraphCreated(startDestinationArgs);
}
@NonNull
public NavInflater getNavInflater() {
if (mInflater == null) {
mInflater = new NavInflater(mContext, mNavigatorProvider);
}
return mInflater;
}
這個方法中首先是實(shí)例化NavInflater并調(diào)用NavInflater#inflate解析導(dǎo)航配置文件,解析以后的結(jié)構(gòu)存放在NavGraph類中。NavGraph是可以按ID獲取的NavDestination節(jié)點(diǎn)的樹形結(jié)構(gòu)。
7. NavInflater#inflate
public final class NavInflater {
private Context mContext;
private NavigatorProvider mNavigatorProvider;
public NavInflater(@NonNull Context context, @NonNull NavigatorProvider navigatorProvider) {
mContext = context;
mNavigatorProvider = navigatorProvider;
}
@NonNull
public NavGraph inflate(@NavigationRes int graphResId) {
...
NavDestination destination = inflate(res, parser, attrs, graphResId);
...
}
@NonNull
private NavDestination inflate(@NonNull Resources res, @NonNull XmlResourceParser parser,
@NonNull AttributeSet attrs, int graphResId)
throws XmlPullParserException, IOException {
Navigator<?> navigator = mNavigatorProvider.getNavigator(parser.getName());
final NavDestination dest = navigator.createDestination();
dest.onInflate(mContext, attrs);
final int innerDepth = parser.getDepth() + 1;
int type;
int depth;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& ((depth = parser.getDepth()) >= innerDepth
|| type != XmlPullParser.END_TAG)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
if (depth > innerDepth) {
continue;
}
final String name = parser.getName();
if (TAG_ARGUMENT.equals(name)) {
inflateArgumentForDestination(res, dest, attrs, graphResId);
} else if (TAG_DEEP_LINK.equals(name)) {
inflateDeepLink(res, dest, attrs);
} else if (TAG_ACTION.equals(name)) {
inflateAction(res, dest, attrs, parser, graphResId);
} else if (TAG_INCLUDE.equals(name) && dest instanceof NavGraph) {
final TypedArray a = res.obtainAttributes(
attrs, androidx.navigation.R.styleable.NavInclude);
final int id = a.getResourceId(
androidx.navigation.R.styleable.NavInclude_graph, 0);
((NavGraph) dest).addDestination(inflate(id));
a.recycle();
} else if (dest instanceof NavGraph) {
((NavGraph) dest).addDestination(inflate(res, parser, attrs, graphResId));
}
}
return dest;
}
...
}
NavInflater的主要工作就是解析導(dǎo)航配置文件。接下來再回頭看setGraph方法中調(diào)用的onGraphCreated方法。
8. NavController#onGraphCreated
private void onGraphCreated(@Nullable Bundle startDestinationArgs) {
...
if (mGraph != null && mBackStack.isEmpty()) {
boolean deepLinked = !mDeepLinkHandled && mActivity != null
&& handleDeepLink(mActivity.getIntent());
if (!deepLinked) {
// Navigate to the first destination in the graph
// if we haven't deep linked to a destination
navigate(mGraph, startDestinationArgs, null, null);
}
} else {
dispatchOnDestinationChanged();
}
}
剛開始的時候會執(zhí)行到navigate方法。
9. NavController#navigate
private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
boolean popped = false;
boolean launchSingleTop = false;
if (navOptions != null) {
if (navOptions.getPopUpTo() != -1) {
popped = popBackStackInternal(navOptions.getPopUpTo(),
navOptions.isPopUpToInclusive());
}
}
Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
node.getNavigatorName());
Bundle finalArgs = node.addInDefaultArgs(args);
NavDestination newDest = navigator.navigate(node, finalArgs,
navOptions, navigatorExtras);
...
}
根據(jù)分析得出getNavigator獲取到的Navigator是NavGraphNavigator實(shí)例。
10. NavGraphNavigator#navigate
@Nullable
@Override
public NavDestination navigate(@NonNull NavGraph destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Extras navigatorExtras) {
int startId = destination.getStartDestination();
if (startId == 0) {
throw new IllegalStateException("no start destination defined via"
+ " app:startDestination for "
+ destination.getDisplayName());
}
NavDestination startDestination = destination.findNode(startId, false);
if (startDestination == null) {
final String dest = destination.getStartDestDisplayName();
throw new IllegalArgumentException("navigation destination " + dest
+ " is not a direct child of this NavGraph");
}
Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
startDestination.getNavigatorName());
return navigator.navigate(startDestination, startDestination.addInDefaultArgs(args),
navOptions, navigatorExtras);
}
navigate方法中通過startId找到NavDestination變量,再根據(jù)NavDestination#getNavigatorName方法獲取到的名稱得到對應(yīng)的Navigator實(shí)例,此處獲取到的是FragmentNavigator實(shí)例。
11. FragmentNavigator#navigate
@Nullable
@Override
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
...
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;
}
}
navigate方法中使用的是FragmentFactory(反射)創(chuàng)建fragment實(shí)例。最后通過FragmentTransaction#replace方法添加fragment實(shí)例。
再回頭看NavHostFragment的生命周期onCreateView方法。
11. NavHostFragment#onCreateView
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
FragmentContainerView containerView = new FragmentContainerView(inflater.getContext());
// When added via XML, this has no effect (since this FragmentContainerView is given the ID
// automatically), but this ensures that the View exists as part of this Fragment's View
// hierarchy in cases where the NavHostFragment is added programmatically as is required
// for child fragment transactions
containerView.setId(getContainerId());
return containerView;
}
NavHostFragment默認(rèn)展示視圖是FragmentContainerView。
12. NavHostFragment#onViewCreated
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Navigation.setViewNavController(view, mNavController);
// When added programmatically, we need to set the NavController on the parent - i.e.,
// the View that has the ID matching this NavHostFragment.
if (view.getParent() != null) {
mViewParent = (View) view.getParent();
if (mViewParent.getId() == getId()) {
Navigation.setViewNavController(mViewParent, mNavController);
}
}
}
public static void setViewNavController(@NonNull View view,
@Nullable NavController controller) {
view.setTag(R.id.nav_controller_view_tag, controller);
}
private static NavController findViewNavController(@NonNull View view) {
while (view != null) {
NavController controller = getViewNavController(view);
if (controller != null) {
return controller;
}
ViewParent parent = view.getParent();
view = parent instanceof View ? (View) parent : null;
}
return null;
}
private static NavController getViewNavController(@NonNull View view) {
Object tag = view.getTag(R.id.nav_controller_view_tag);
NavController controller = null;
if (tag instanceof WeakReference) {
controller = ((WeakReference<NavController>) tag).get();
} else if (tag instanceof NavController) {
controller = (NavController) tag;
}
return controller;
}
將NavController設(shè)置為NavHostFragment的根視圖View的tag,以后調(diào)用Navigation#findNavController時, 會從傳入視圖及其所有父視圖中找tag,直到找到NavController為止。
從以上分析可以看出,每次調(diào)用NavController#navigate方法都會重新生成一個新的Fragment并且調(diào)用FragmentTransaction#replace添加,所以每次都會看到重建Fragment的現(xiàn)象。
解決方案
自定義Navigator重寫Navigator#navigate方法。
@Navigator.Name("customNavigator")
public class CustomNavigator extends FragmentNavigator {
private Context context;
private FragmentManager fragmentManager;
private int containerId;
public CustomNavigator(@NonNull Context context, @NonNull FragmentManager fragmentManager, int containerId) {
super(context, fragmentManager, containerId);
this.context = context;
this.fragmentManager = fragmentManager;
this.containerId = containerId;
}
@Nullable
@Override
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
FragmentTransaction ft = fragmentManager.beginTransaction();
// 獲取當(dāng)前顯示的Fragment
Fragment fragment = fragmentManager.getPrimaryNavigationFragment();
if (fragment != null) {
ft.hide(fragment);
}
final String tag = String.valueOf(destination.getId());
fragment = fragmentManager.findFragmentByTag(tag);
if (fragment != null) {
ft.show(fragment);
} else {
fragment = instantiateFragment(context, fragmentManager, destination.getClassName(), args);
ft.add(containerId, fragment, tag);
}
ft.setPrimaryNavigationFragment(fragment);
ft.setReorderingAllowed(true);
ft.commit();
return destination;
}
}
然后通過NavigatorProvider添加即可:
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
navController.getNavigatorProvider().addNavigator(new CustomNavigator(this, getSupportFragmentManager(), R.id.nav_host_fragment));
原文鏈接:https://juejin.cn/post/7143232985012600840
相關(guān)推薦
- 2022-07-09 Python如何保留float類型小數(shù)點(diǎn)后3位_python
- 2022-05-15 Python+Selenium實(shí)現(xiàn)讀取網(wǎng)易郵箱驗(yàn)證碼_python
- 2022-08-05 C#?GDI+實(shí)現(xiàn)時鐘表盤_C#教程
- 2022-07-20 C/C++舉例講解關(guān)鍵字的用法_C 語言
- 2022-08-23 python可以美化表格數(shù)據(jù)輸出結(jié)果的兩個工具_(dá)python
- 2022-10-23 Linux服務(wù)器VPS的Windows?DD包詳細(xì)的制作教程_Linux
- 2022-08-04 GoFrame框架gcache的緩存控制淘汰策略實(shí)踐示例_Golang
- 2022-10-16 Python?Flask框架使用介紹_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支