什么是Navigation?
在没有Navigation之前我们切换Fragment是通过FragmentManager的add、commit、replace等方法操作(网上有很多传统的Fragment的切换方法,可以自行查资料学习),在有了Navigation之后我们的切换逻辑就简单了,甚至可以通过IDEA的视图都可以配置切换跳转的功能。
新建一个项目、选择下图所示的模板:
该模板创建完成后 运行项目看到如下的效果图,并打开activity_main.xml
我们关注一下几个信息:
- BottomNavigationView
BottomNavigationView是一个底部导航栏控件,一般和fragment一起使用。
- app:menu=”@menu/bottom_nav_menu”
这个menu文件就是底部显示的icon和文字.
- android:name=”androidx.navigation.fragment.NavHostFragment”
NavHostFragment是一个Fragment,其作用有两个,一个是给要显示的Fragment提供一个载体,还有就是控制页面导航行为
它的onCreateView中创建了一个Fragment的布局容器
- app:defaultNavHost=”true”
是否加入回退栈
- app:navGraph=”@navigation/mobile_navigation”
关联navigation文件,
看完了这些基础的配置,就需要了解它们是怎么实现导航功能的呢?
还记得上面提到过的NavHostFragment吗?下面来看看它的源码.
这里先给出一个结论:在NavHostFragment中实例化了四个导航器,它们分别是:
- NavGraphNavigator
- ActivityNavigator
- FragmentNavigator
- DialogFragmentNavigator
它们的父类都是”Navigator”
看看Navigator的类结构:
NavHostFragment中onCreate函数
@CallSuper
@Override
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);
......
在NavHostFragment的onCreate中创建了一个NavHostController
mNavController = new NavHostController(context);
NavController的构造函数如下:
public NavController(@NonNull Context context) {
mContext = context;
while (context instanceof ContextWrapper) {
if (context instanceof Activity) {
mActivity = (Activity) context;
break;
}
context = ((ContextWrapper) context).getBaseContext();
}
mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider));
mNavigatorProvider.addNavigator(new ActivityNavigator(mContext));
}
在看NavHostFragment类中onCreate方法里面的 onCreateNavController(mNavController);
@CallSuper
protected void onCreateNavController(@NonNull NavController navController) {
navController.getNavigatorProvider().addNavigator(
new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}
从上面的代码就可以看出,通过NavHostFragment的onCreate 完成了上面提到的四个导航器的创建。
这四个导航器分别是为Fragment、Activity、DialogFragment导航的,NavGraphNavigator有点特殊我们放在最后讲。
接下来我们看看ActivityNavigator源码:
可以看到,ActivityNavigator继承了Navigator,并有一个Name注解。这几个注解就代表是为Activity提供导航的,注解的用途官方是这么解释的:
/**
* This annotation should be added to each Navigator subclass to denote the default name used
* to register the Navigator with a {@link NavigatorProvider}.
*
* @see NavigatorProvider#addNavigator(Navigator)
* @see NavigatorProvider#getNavigator(Class)
*/
每个Navigator子类都应该提供name注解,用于注册到NavigatorProvider中,这里可以把NavigatorProvider理解成HashMap,把name作为key,Navigator实例作为value存储起来,通过NavigatorProvider向外提供。
倒不妨去看看NavigatorProvider#addNavigator(Navigator)源码
getNameForNavigator(navigator.getClass())返回了name
addNavigator(name, navigator);完成了存储。
@CallSuper
@Nullable
public Navigator extends NavDestination> addNavigator(@NonNull String name,
@NonNull Navigator extends NavDestination> navigator) {
if (!validateName(name)) {
throw new IllegalArgumentException("navigator name cannot be an empty string");
}
return mNavigators.put(name, navigator);
}
跟进方法getNameForNavigator(navigator.getClass())
@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;
}
这里就可以把name注解的值提取出来了。
接下来我们看ActivityNavigator中的createDestination方法。
@NonNull
@Override
public Destination createDestination() {
return new Destination(this);
}
Destination里面最终是将ActivityNavigator的注解name存起来。后面我们可以看他他的用处。
比较核心的还是在navigate方法,接下来我们看它是怎么实现导航的:
@Nullable
@Override
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
if (destination.getIntent() == null) {
throw new IllegalStateException("Destination " + destination.getId()
+ " does not have an Intent set.");
}
Intent intent = new Intent(destination.getIntent());
// 参数 flagsd等配置
mContext.startActivity(intent);
//动画配置
// You can't pop the back stack from the caller of a new Activity,
// so we don't add this navigator to the controller's back stack
return null;
}
删了打断代码,增加了两个注释。其实功能很简单,就是通过Intent来实现跳转的,中间做了一下参数设置,flags的添加,和动画等。让后调用 mContext.startActivity(intent);启动activity,
也就说说我们只要通过调用navigate方法就能实现Activity的启动.
说完了ActivityNavigate后我们来说NavGraphNavigator.,它不同于ActivityNavigator和FragmentNavigator.
接下来我们就看看它怎么个不同。
在NavGraphNavigator的createDestination方法中,它并不像其他几个Navigator是创建了Destination对象,
它创建的是NavGraph对象
进入NavController中:
接着进入inflate里面
@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();
}
}
传进来的id就是 mobile_navigation.xml这个资源布局
.........
inflate方法中就是解析xml文件,把每个节点信息解析出来存到final SparseArrayCompat
在回到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 navigator = mNavigatorProvider.getNavigator(
startDestination.getNavigatorName());
return navigator.navigate(startDestination, startDestination.addInDefaultArgs(args),
navOptions, navigatorExtras);
}
可以看到NavGraphNavigator的navigate并没有真正的实现导航,而是通过 mNavigatorProvider.getNavigator()得到对应的导航器,做了一个对应多态调用,最后由对应的FragmentNavigator和AcitivtyNavigator等去实现导航,前面我们已经介绍了Actitivi的导航实现,下面在来看看FragmentNavigator怎么实现的导航。
@SuppressWarnings("deprecation") /* Using instantiateFragment for forward compatibility */
@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();
//动画代码
ft.replace(mContainerId, frag);
ft.setPrimaryNavigationFragment(frag);
}
可以看到instantiateFragment(mContext, mFragmentManager,
className, args); 通过反射实例化一个Fragment,然后调用replace方法显示出来了,
这里使用replace导致每次切换都会重新创建Framgnt,所以效率比较低。后面我会自定义一个Fragment导航器,通过show方法控制fragment的显示。
BottomNavigationView navView = findViewById(R.id.nav_view);
final NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
navView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
navController.navigate(item.getItemId(), null);
Log.d("navController", item.getTitle().toString());
return true;
}
});
上面代码就是通过导航器的navigate方法实现了导航功能。
基本的导航器的使用和原理已经很清晰了,后面会讲一下如何自定Fragment的导航器。
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/archives/19578,转载请注明出处。
评论0