個人博客
Jetpack學(xué)習(xí)-Navigation
Navigation是什么
Navigation翻譯過來就是導(dǎo)航。
導(dǎo)航是指支持用戶導(dǎo)航、進(jìn)入和退出應(yīng)用中不同內(nèi)容片段的交互褂微。Android Jetpack 的導(dǎo)航組件可幫助您實現(xiàn)導(dǎo)航呐籽,無論是簡單的按鈕點擊随闪,還是應(yīng)用欄和抽屜式導(dǎo)航欄等更為復(fù)雜的模式搀崭,該組件均可應(yīng)對捻浦。導(dǎo)航組件還通過遵循一套既定原則來確保一致且可預(yù)測的用戶體驗粗卜。
導(dǎo)航組件由以下三個關(guān)鍵部分組成:
導(dǎo)航圖:在一個集中位置包含所有導(dǎo)航相關(guān)信息的 XML 資源屋确。這包括應(yīng)用內(nèi)所有單個內(nèi)容區(qū)域(稱為目標(biāo))以及用戶可以通過應(yīng)用獲取的可能路徑。
NavHost:顯示導(dǎo)航圖中目標(biāo)的空白容器续扔。導(dǎo)航組件包含一個默認(rèn) NavHost 實現(xiàn) (NavHostFragment)攻臀,可顯示 Fragment 目標(biāo)。
NavController:在 NavHost 中管理應(yīng)用導(dǎo)航的對象纱昧。當(dāng)用戶在整個應(yīng)用中移動時刨啸,NavController 會安排 NavHost 中目標(biāo)內(nèi)容的交換。
在應(yīng)用中導(dǎo)航時识脆,您告訴 NavController设联,您想沿導(dǎo)航圖中的特定路徑導(dǎo)航至特定目標(biāo)善已,或直接導(dǎo)航至特定目標(biāo)。NavController 便會在 NavHost 中顯示相應(yīng)目標(biāo)离例。
導(dǎo)航組件提供各種其他優(yōu)勢换团,包括以下內(nèi)容:
處理 Fragment 事務(wù)。
默認(rèn)情況下宫蛆,正確處理往返操作艘包。
為動畫和轉(zhuǎn)換提供標(biāo)準(zhǔn)化資源。
實現(xiàn)和處理深層鏈接耀盗。
包括導(dǎo)航界面模式(例如抽屜式導(dǎo)航欄和底部導(dǎo)航)想虎,用戶只需完成極少的額外工作。
Safe Args - 可在目標(biāo)之間導(dǎo)航和傳遞數(shù)據(jù)時提供類型安全的 Gradle 插件袍冷。
ViewModel 支持 - 您可以將 ViewModel 的范圍限定為導(dǎo)航圖磷醋,以在圖表的目標(biāo)之間共享與界面相關(guān)的數(shù)據(jù)。
此外胡诗,您還可以使用 Android Studio 的 Navigation Editor 來查看和編輯導(dǎo)航圖邓线。
以上內(nèi)容來自官方文檔(我只是一個搬運工\(^o^)/)
簡單使用
引入Navigation
在需要使用Navigation的模塊的build.gradle中引入
def nav_version = "2.3.0-alpha01"
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
建立導(dǎo)航圖
在res目錄右鍵-New-Android Resource File
在彈出的界面中,F(xiàn)ile name可隨意輸入煌恢,Resource type選擇Navigation,點擊確定
點擊確定后骇陈,會在res目錄下創(chuàng)建navigation目錄,以及剛才定義的導(dǎo)航文件
雙擊打開剛才創(chuàng)建的導(dǎo)航文件瑰抵,在Design界面可以看到目前還沒有內(nèi)容你雌,可以點擊上方的+號圖標(biāo)添加fragment,也可以自己手動在xml中添加
我們需要為這個文件指定startDestination二汛,即起始的界面
startDestination指定為mainFragment婿崭,mainFragment對應(yīng)的布局為fragment_main
Navigation首先會加載一個默認(rèn)的Fragment,這個需要在Activity中指定
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
</LinearLayout>
配置defaultNavHost為true,即指定這個fragment為默認(rèn)的NavHost肴颊,每個導(dǎo)航圖只能指定一個默認(rèn)的NavHost氓栈。這里的name
配置為androidx.navigation.fragment.NavHostFragment
,navGraph
配置為nav_graph,即指定nav_graph為導(dǎo)航圖婿着。這樣當(dāng)Activity啟動時授瘦,會首先通過activity布局里的fragment去加載導(dǎo)航圖中的startDestination配置的fragment。
導(dǎo)航
通過一個Fragment導(dǎo)航到另一個Fragment竟宋,可以通過
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
loginBtn = view.findViewById(R.id.fragment_main_login);
loginBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Bundle bundle = new Bundle();
bundle.putString("name", "zs");
Navigation.findNavController(v).navigate(R.id.action_mainFragment_to_loginFragment, bundle);
}
});
return view;
}
這里通過點擊一個按鈕進(jìn)行跳轉(zhuǎn)提完,通過Navigation.findNavController(v).navigate()
方法導(dǎo)航。這里還可以通過Bundle進(jìn)行傳值丘侠。
在目的Fragment,可以通過getArguments()
來獲取到傳遞過來的數(shù)據(jù)
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
String name = getArguments().getString("name", "null");
Toast.makeText(getContext(), name, Toast.LENGTH_SHORT).show();
View view = inflater.inflate(R.layout.fragment_login, container, false);
backBtn = view.findViewById(R.id.fragment_login_back);
backBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Navigation.findNavController(v).popBackStack();
}
});
return view;
}
在目的Fragment徒欣,還可以通過一個按鈕返回上一個Fragment:Navigation.findNavController(v).popBackStack()
原理
Navigation的簡單使用流程就介紹到這,可以在官方文檔上看更多相關(guān)的使用方法蜗字。下面來分析下Navigation的流程
顯示起始Fragment
在Activity啟動時帚称,會先實例化NavHostFragment
,這個是我們前面在布局中指定的官研。
首先會執(zhí)行onInflate
方法
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();
}
這個方法秽澳,其實就是解析出來我們要使用哪個導(dǎo)航圖闯睹,獲取到了graphId。還獲取了是否為默認(rèn)的Host:defaultHost
然后會執(zhí)行onAttach
方法
public void onAttach(@NonNull Context context) {
super.onAttach(context);
// TODO This feature should probably be a first-class feature of the Fragment system,
// but it can stay here until we can add the necessary attr resources to
// the fragment lib.
if (mDefaultNavHost) {
getParentFragmentManager().beginTransaction()
.setPrimaryNavigationFragment(this)
.commit();
}
}
由于在onInflate已經(jīng)獲取到mDefaultNavHost為true,因此這里會將當(dāng)前Fragment通過commit加入到FragmentManager()中
然后執(zhí)行onCreate
方法
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Context context = requireContext();
//設(shè)置NavHostController,NavHostController里初始化了NavigatorProvider
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()
//前面執(zhí)行onInflate后担神,已經(jīng)獲取到mGraphId楼吃,因此會執(zhí)行下面的setGraph代碼
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);
}
}
}
在這個方法里,設(shè)置了NavHostController及NavigatorProvider妄讯,然后執(zhí)行NavController.setGraph
方法
public void setGraph(@NavigationRes int graphResId) {
setGraph(graphResId, null);
}
繼續(xù)調(diào)用setGraph
public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
}
繼續(xù)調(diào)用setGraph
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);
}
調(diào)用onGraphCreated
private void onGraphCreated(@Nullable Bundle startDestinationArgs) {
if (mNavigatorStateToRestore != null) {
ArrayList<String> navigatorNames = mNavigatorStateToRestore.getStringArrayList(
KEY_NAVIGATOR_STATE_NAMES);
if (navigatorNames != null) {
for (String name : navigatorNames) {
Navigator<?> navigator = mNavigatorProvider.getNavigator(name);
Bundle bundle = mNavigatorStateToRestore.getBundle(name);
if (bundle != null) {
navigator.onRestoreState(bundle);
}
}
}
}
if (mBackStackToRestore != null) {
for (Parcelable parcelable : mBackStackToRestore) {
NavBackStackEntryState state = (NavBackStackEntryState) parcelable;
NavDestination node = findDestination(state.getDestinationId());
if (node == null) {
throw new IllegalStateException("unknown destination during restore: "
+ mContext.getResources().getResourceName(state.getDestinationId()));
}
Bundle args = state.getArgs();
if (args != null) {
args.setClassLoader(mContext.getClassLoader());
}
NavBackStackEntry entry = new NavBackStackEntry(mContext, node, args,
mLifecycleOwner, mViewModel,
state.getUUID(), state.getSavedState());
mBackStack.add(entry);
}
updateOnBackPressedCallbackEnabled();
mBackStackToRestore = null;
}
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
//首次進(jìn)入時孩锡,會執(zhí)行這個代碼
navigate(mGraph, startDestinationArgs, null, null);
}
}
}
首次進(jìn)入Activity,會最終執(zhí)行navigate(mGraph, startDestinationArgs, null, null)
方法來導(dǎo)航到起始的目的Fragment
private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
boolean popped = false;
//...
Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
node.getNavigatorName());
Bundle finalArgs = node.addInDefaultArgs(args);
NavDestination newDest = navigator.navigate(node, finalArgs,
navOptions, navigatorExtras);
//...
}
這個方法亥贸,會調(diào)用navigator.navigate(node, finalArgs,navOptions, navigatorExtras)
方法躬窜,這個方法的實現(xiàn)在NavGraphNavigator
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);
}
在這個方法里調(diào)用navigator.navigate方法,這個方法實現(xiàn)在FragmentNavigator
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;
}
//通過反射實例化Fragment
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);
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;
}
}
在這個方法中炕置,通過反射實例化目的Fragment荣挨,然后replace原來的Fragment,并commit,這樣目的Fragment就顯示出來了朴摊。
導(dǎo)航到其它Fragment
通過Navigation.findNavController(v).navigate(resId)
可以導(dǎo)航到指定的Fragment
public static NavController findNavController(@NonNull View view) {
NavController navController = findViewNavController(view);
if (navController == null) {
throw new IllegalStateException("View " + view + " does not have a NavController set");
}
return navController;
}
然后調(diào)用findViewNavController
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;
}
這里通過子View往父View不停查找NavController默垄,這個NavController在前面onCreate的時候已經(jīng)附加到了view上。
找到NavController后甚纲,調(diào)用navigate口锭。這個過程和前面第一次導(dǎo)航到起始Fragment是一樣的流程,這里不再分析介杆。
其實這里只是比較粗的一個梳理鹃操,涉及很多細(xì)節(jié)并沒有具體去看,暫且先有一個流程的印象吧春哨。
附上一張時序圖