还没有用过navigation的小伙伴赶紧来用它,实在是太方便了,·再也不用写supportFragmentManager.beginTransaction().add()和supportFragmentManager.beginTransaction().replace()来切换布局了,只要将所有的需要跳转的Frament全部放到布局里面就好了,navigation和flutter的路由差不多,它只不过是将frament装到了作为一个个的跳转点而已。我觉着它最大的优势是可以通过android studio清晰的看到每个界面的跳转点。如图所示:
不用运行就可以看见每个模块的界面跳转,是不是很炫。大家可以参考一下官方文档android导航。
用起来也比较简单首先在build.gradle中加入
implementation "androidx.navigation:navigation-fragment-ktx:2.1.0"
implementation "androidx.navigation:navigation-ui-ktx:2.1.0"
引入支持库之后,第二步在你的模块主布局中加入需要动态变化的Frament
对,就是上面这个东西,注意一点最终实例化的Fragment是androidx.navigation.fragment.NavHostFragment类,实例化的Frament是扩展库中的类,注意 app:navGraph="@navigation/mobile_navigation" 这个属性是真正的填充Fragment的类。第三步,在res下新建navigation文件夹,xml内容如下:
其中app:startDestination="@+id/home_dest"属性用来声明第一个显示的Fragment,argument标签用来声明传递的属性,action标签是用来跳转的,这和activity的action有异曲同工之妙,跳转到新的Fragment的时候调用Navigation.findNavController(view).navigate(),回到上一个Fragment的时候调Navigation.findNavController(view).navigateUp()
方法。好了,这就是Navigation的基本使用了,大家可以在官网下载demo看一下,接下来来探究一下navigation的原理。
前面介绍了navigation使用的入口就是
androidx.navigation.fragment.NavHostFragment这个类
public class NavHostFragment extends Fragment implements NavHost
NavHostFragment 就是一个Fragment并实现了NavHost接口,这个接口只有一个方法
public interface NavHost {
/**
* Returns the {@link NavController navigation controller} for this navigation host.
*
* @return this host's navigation controller
*/
@NonNull
NavController getNavController();
}
在它的onCreateView中,它只是填充了简单的帧布局FrameLayout,它的用处就是最终用来替换布局的
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
FrameLayout frameLayout = new FrameLayout(inflater.getContext());
// When added via XML, this has no effect (since this FrameLayout 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
frameLayout.setId(getId());
return frameLayout;
}
onCreateView执行完以后会执行onViewCreated,看一下
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (!(view instanceof ViewGroup)) {
throw new IllegalStateException("created host view " + view + " is not a ViewGroup");
}
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) {
View rootView = (View) view.getParent();
if (rootView.getId() == getId()) {
Navigation.setViewNavController(rootView, mNavController);
}
}
}
很简单就是为当前Fragment设置当前的根view设置一个tag,这个tag的值是NavHostController,这个类实现了对Fragment的控制的封装。而onInflate是在fragment标签被解析的时候调用的,它早于fragment的任何生命周期方法
public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs,
@Nullable Bundle savedInstanceState) {
super.onInflate(context, attrs, savedInstanceState);
final TypedArray navHost = context.obtainStyledAttributes(attrs, R.styleable.NavHost);
final int graphId = navHost.getResourceId(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();
}
这里根据属性解析出app:navGraph引用的xml的id
然后在onCreate方法通过 mNavController.setGraph(mGraphId)方法解析出来第一个需要显示的Fragment
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(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;
requireFragmentManager().beginTransaction()
.setPrimaryNavigationFragment(this)
.commit();
}
}
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);
}
}
}
继续看
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);
}
如果mGraph 不为null就证明栈中已经存放了当前的Fragment,直接让它出栈就好了,接着看onGraphCreated方法
private void onGraphCreated(@Nullable Bundle startDestinationArgs) {
if (mNavigatorStateToRestore != null) {
ArrayList 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 (mBackStackUUIDsToRestore != null) {
for (int index = 0; index < mBackStackUUIDsToRestore.length; index++) {
UUID uuid = UUID.fromString(mBackStackUUIDsToRestore[index]);
int destinationId = mBackStackIdsToRestore[index];
Bundle args = (Bundle) mBackStackArgsToRestore[index];
NavDestination node = findDestination(destinationId);
if (node == null) {
throw new IllegalStateException("unknown destination during restore: "
+ mContext.getResources().getResourceName(destinationId));
}
if (args != null) {
args.setClassLoader(mContext.getClassLoader());
}
mBackStack.add(new NavBackStackEntry(uuid, node, args, mViewModel));
}
updateOnBackPressedCallbackEnabled();
mBackStackUUIDsToRestore = null;
mBackStackIdsToRestore = null;
mBackStackArgsToRestore = 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
navigate(mGraph, startDestinationArgs, null, null);
}
}
}
和流程有关的方法是navigate,继续进入
private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
boolean popped = false;
if (navOptions != null) {
if (navOptions.getPopUpTo() != -1) {
popped = popBackStackInternal(navOptions.getPopUpTo(),
navOptions.isPopUpToInclusive());
}
}
Navigator navigator = mNavigatorProvider.getNavigator(
node.getNavigatorName());
Bundle finalArgs = node.addInDefaultArgs(args);
NavDestination newDest = navigator.navigate(node, finalArgs,
navOptions, navigatorExtras);
if (newDest != null) {
if (!(newDest instanceof FloatingWindow)) {
// We've successfully navigating to the new destination, which means
// we should pop any FloatingWindow destination off the back stack
// before updating the back stack with our new destination
//noinspection StatementWithEmptyBody
while (!mBackStack.isEmpty()
&& mBackStack.peekLast().getDestination() instanceof FloatingWindow
&& popBackStackInternal(
mBackStack.peekLast().getDestination().getId(), true)) {
// Keep popping
}
}
// The mGraph should always be on the back stack after you navigate()
if (mBackStack.isEmpty()) {
mBackStack.add(new NavBackStackEntry(mGraph, finalArgs, mViewModel));
}
// Now ensure all intermediate NavGraphs are put on the back stack
// to ensure that global actions work.
ArrayDeque hierarchy = new ArrayDeque<>();
NavDestination destination = newDest;
while (destination != null && findDestination(destination.getId()) == null) {
NavGraph parent = destination.getParent();
if (parent != null) {
hierarchy.addFirst(new NavBackStackEntry(parent, finalArgs, mViewModel));
}
destination = parent;
}
mBackStack.addAll(hierarchy);
// And finally, add the new destination with its default args
NavBackStackEntry newBackStackEntry = new NavBackStackEntry(newDest,
newDest.addInDefaultArgs(finalArgs), mViewModel);
mBackStack.add(newBackStackEntry);
}
updateOnBackPressedCallbackEnabled();
if (popped || newDest != null) {
dispatchOnDestinationChanged();
}
}
这个方法有点长,大部分代码都是保存当前需要显示的Fragment类的封装类NavDestination ,保存到栈中。
navigator.navigate(node, finalArgs,
navOptions, navigatorExtras);
而navigator.navigate是真正实现Fragment切换的,真正实现类是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;
}
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 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;
}
}
这个方法最重要的就是这个代码 ft.replace(mContainerId, frag),ft是FragmentTransaction 类,而mContainerId就是NavHostFragment的布局View的id如下:
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
FrameLayout frameLayout = new FrameLayout(inflater.getContext());
// When added via XML, this has no effect (since this FrameLayout 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
frameLayout.setId(getId());
return frameLayout;
}
所以Frament的切换都是通过NavHostFragment的FrameLayout 来切换的