NavigationView实际上是一个FrameLayout,这个FrameLayout中又包含了一个RecyclerView。如果用户设置了Header布局,那么NavigationView就把这个Header作为这个RecyclerView的第一个Item View,在Header的下面就是菜单列表。通过这种封装使得构建用户菜单变得非常简单,而不需要用户每次都通过RecyclerView手动设置header和菜单,提高了工程师的开发效率。

NavigationView就是一个MVP设计模式,Toolbar的菜单解析也遵循MVP设计模式。由于Toolbar的MVP比较复杂,我们就通过剖析NavigationView的案例来学习MVP的运用。

NavigationView的构造方法:

NavigationMenuPresenter的getMenuView方法:

NavigationMenuPresenter加载菜单方法:inflateMenu

NavigationMenuPresenter加载head view的方法:addHeaderView

NavigationView中MVP的使用:

NavigationView的OnNavigationItemSelectedListener的作用:

其实NavigatioNmenuPresenter持有NavigationMenu,NavigationMenu选中会通知触发OnNavigationItemSelectedListener的onNavigationItemSelected方法,其实也类似于NavigatioNmenuPresenter在通知OnNavigationItemSelectedListener执行onNavigationItemSelected方法。

NavigationView中的Model层NavigationItem

在使用NavigationView的时候,app:menu设置菜单项;app:headerLayout设置菜单Header。

NavigationView实际上是一个FrameLayout,确切的说它继承自FrameLayout。

public class ScrimInsetsFrameLayout extends FrameLayout {}public class NavigationView extends ScrimInsetsFrameLayout {//菜单Presenter
private final NavigationMenu mMenu;
private final NavigationMenuPresenter mPresenter = new NavigationMenuPresenter();OnNavigationItemSelectedListener mListener;
private int mMaxWidth;
//菜单解析的Inflater
private MenuInflater mMenuInflater;
}
  public NavigationView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);ThemeUtils.checkAppCompatTheme(context);// Create the menumMenu = new NavigationMenu(context);
//其他初始化操作// Custom attributesTintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,R.styleable.NavigationView, defStyleAttr,R.style.Widget_Design_NavigationView);ViewCompat.setBackground(this, a.getDrawable(R.styleable.NavigationView_android_background));if (a.hasValue(R.styleable.NavigationView_elevation)) {ViewCompat.setElevation(this, a.getDimensionPixelSize(R.styleable.NavigationView_elevation, 0));}ViewCompat.setFitsSystemWindows(this,a.getBoolean(R.styleable.NavigationView_android_fitsSystemWindows, false));mMaxWidth = a.getDimensionPixelSize(R.styleable.NavigationView_android_maxWidth, 0);final ColorStateList itemIconTint;if (a.hasValue(R.styleable.NavigationView_itemIconTint)) {itemIconTint = a.getColorStateList(R.styleable.NavigationView_itemIconTint);} else {itemIconTint = createDefaultColorStateList(android.R.attr.textColorSecondary);}boolean textAppearanceSet = false;int textAppearance = 0;if (a.hasValue(R.styleable.NavigationView_itemTextAppearance)) {textAppearance = a.getResourceId(R.styleable.NavigationView_itemTextAppearance, 0);textAppearanceSet = true;}ColorStateList itemTextColor = null;if (a.hasValue(R.styleable.NavigationView_itemTextColor)) {itemTextColor = a.getColorStateList(R.styleable.NavigationView_itemTextColor);}if (!textAppearanceSet && itemTextColor == null) {// If there isn't a text appearance set, we'll use a default text coloritemTextColor = createDefaultColorStateList(android.R.attr.textColorPrimary);}final Drawable itemBackground = a.getDrawable(R.styleable.NavigationView_itemBackground);mMenu.setCallback(new MenuBuilder.Callback() {@Overridepublic boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {return mListener != null && mListener.onNavigationItemSelected(item);}@Overridepublic void onMenuModeChange(MenuBuilder menu) {}});mPresenter.setId(PRESENTER_NAVIGATION_VIEW_ID);//1.初始化一些资源mPresenter.initForMenu(context, mMenu);mPresenter.setItemIconTintList(itemIconTint);if (textAppearanceSet) {mPresenter.setItemTextAppearance(textAppearance);}mPresenter.setItemTextColor(itemTextColor);mPresenter.setItemBackground(itemBackground);mMenu.addMenuPresenter(mPresenter);
//2.构建整个菜单视图并且添加到当前视图中addView((View) mPresenter.getMenuView(this));//3.初始化菜单资源menu目录if (a.hasValue(R.styleable.NavigationView_menu)) {inflateMenu(a.getResourceId(R.styleable.NavigationView_menu, 0));}//4.初始化header layoutif (a.hasValue(R.styleable.NavigationView_headerLayout)) {inflateHeaderView(a.getResourceId(R.styleable.NavigationView_headerLayout, 0));}a.recycle();}
#NavigationView/*** Inflate a menu resource into this navigation view.*(将菜单资源扩展到此导航视图中。)* <p>Existing items in the menu will not be modified or removed.</p>*(菜单中的现有项目不会被修改或删除。)* @param resId ID of a menu resource to inflate*/public void inflateMenu(int resId) {mPresenter.setUpdateSuspended(true);getMenuInflater().inflate(resId, mMenu);mPresenter.setUpdateSuspended(false);mPresenter.updateMenuView(false);}#NavigationView/*** Inflates a View and add it as a header of the navigation menu.*(加载视图并将其添加为导航菜单的标题。)* @param res The layout resource ID.* @return a newly inflated View.*/public View inflateHeaderView(@LayoutRes int res) {return mPresenter.inflateHeaderView(res);}

在NavigationView中我们可以看到熟悉的Presenter字眼,即NavigationMenuPresenter。

在NavigattionView的构造函数有几个比较重要的步骤:

1.初始化资源;

2.构建菜单和Header视图根布局;

3.解析、显示菜单项;

4.解析和显示Header视图。

在这四个步骤中,我们可以看到这四步基本上都是通过Presenter实现的,Presenter承担了几乎所有的业务逻辑。

public class NavigationMenuPresenter implements MenuPresenter {//菜单视图,也就是一个RecyclerViewprivate NavigationMenuView mMenuView;//菜单的Header布局LinearLayout mHeaderLayout;private Callback mCallback;MenuBuilder mMenu;private int mId;NavigationMenuAdapter mAdapter;LayoutInflater mLayoutInflater;}
   /*** Padding for separators between items*/int mPaddingSeparator;//1.初始化mLayoutInflater和MenuBuilder@Overridepublic void initForMenu(Context context, MenuBuilder menu) {mLayoutInflater = LayoutInflater.from(context);mMenu = menu;Resources res = context.getResources();mPaddingSeparator = res.getDimensionPixelOffset(R.dimen.design_navigation_separator_vertical_padding);}
2.构建菜单和Header视图@Overridepublic MenuView getMenuView(ViewGroup root) {if (mMenuView == null) {//加载菜单NavigationViewmMenuView = (NavigationMenuView) mLayoutInflater.inflate(R.layout.design_navigation_menu, root, false);if (mAdapter == null) {mAdapter = new NavigationMenuAdapter();}
//加载菜单的HeadermHeaderLayout = (LinearLayout) mLayoutInflater.inflate(R.layout.design_navigation_item_header,mMenuView, false);mMenuView.setAdapter(mAdapter);}return mMenuView;}
//3.更新菜单项
@Overridepublic void updateMenuView(boolean cleared) {if (mAdapter != null) {mAdapter.update();}}
//4.加载Header布局public View inflateHeaderView(@LayoutRes int res) {View view = mLayoutInflater.inflate(res, mHeaderLayout, false);addHeaderView(view);return view;}
 public void addHeaderView(@NonNull View view) {mHeaderLayout.addView(view);// The padding on top should be cleared.mMenuView.setPadding(0, 0, 0, mMenuView.getPaddingBottom());}

NvaigationView构造函数的第一个重要的函数是Presnter的initForMenu,在这个函数中只是进行简单的初始化操作,将mMenu对象指向构造函数传递进来的MenuBuilder,并且初始化了一些padding值。

第二个重要的函数是NavigationMenuPresenter的getMenuView函数,该函数中构造了菜单NavigationMenuView、菜单项Adapter、和Header视图。在前文中,我们提到过,NavigationMenuView本质上是一个RecyclerView,它是以数值的布局方式显示的菜单项。

public class NavigationMenuView extends RecyclerView implements MenuView {
}public NavigationMenuView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);//布局方式是竖直线性布局setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false));}

构造完NavigationMenuView和Header视图,最后将NavigationMenuAdapter设置为NavigationMenuView的Adapter。这个NavigationMenuAdapter就负责根据视图类型来解析、绑定、展示不同非菜单项视图,比如Header视图、普通菜单项、子菜单项等。

  public NavigationView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);ThemeUtils.checkAppCompatTheme(context);// Create the menumMenu = new NavigationMenu(context);// Custom attributesTintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,R.styleable.NavigationView, defStyleAttr,R.style.Widget_Design_NavigationView);ViewCompat.setBackground(this, a.getDrawable(R.styleable.NavigationView_android_background));if (a.hasValue(R.styleable.NavigationView_elevation)) {ViewCompat.setElevation(this, a.getDimensionPixelSize(R.styleable.NavigationView_elevation, 0));}ViewCompat.setFitsSystemWindows(this,a.getBoolean(R.styleable.NavigationView_android_fitsSystemWindows, false));mMaxWidth = a.getDimensionPixelSize(R.styleable.NavigationView_android_maxWidth, 0);final ColorStateList itemIconTint;if (a.hasValue(R.styleable.NavigationView_itemIconTint)) {itemIconTint = a.getColorStateList(R.styleable.NavigationView_itemIconTint);} else {itemIconTint = createDefaultColorStateList(android.R.attr.textColorSecondary);}boolean textAppearanceSet = false;int textAppearance = 0;if (a.hasValue(R.styleable.NavigationView_itemTextAppearance)) {textAppearance = a.getResourceId(R.styleable.NavigationView_itemTextAppearance, 0);textAppearanceSet = true;}ColorStateList itemTextColor = null;if (a.hasValue(R.styleable.NavigationView_itemTextColor)) {itemTextColor = a.getColorStateList(R.styleable.NavigationView_itemTextColor);}if (!textAppearanceSet && itemTextColor == null) {// If there isn't a text appearance set, we'll use a default text coloritemTextColor = createDefaultColorStateList(android.R.attr.textColorPrimary);}final Drawable itemBackground = a.getDrawable(R.styleable.NavigationView_itemBackground);mMenu.setCallback(new MenuBuilder.Callback() {@Overridepublic boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {return mListener != null && mListener.onNavigationItemSelected(item);}@Overridepublic void onMenuModeChange(MenuBuilder menu) {}});mPresenter.setId(PRESENTER_NAVIGATION_VIEW_ID);mPresenter.initForMenu(context, mMenu);mPresenter.setItemIconTintList(itemIconTint);if (textAppearanceSet) {mPresenter.setItemTextAppearance(textAppearance);}mPresenter.setItemTextColor(itemTextColor);mPresenter.setItemBackground(itemBackground);mMenu.addMenuPresenter(mPresenter);//2.构建整个菜单视图并添加到当前视图中addView((View) mPresenter.getMenuView(this));//3.初始化菜单资源menu目录if (a.hasValue(R.styleable.NavigationView_menu)) {inflateMenu(a.getResourceId(R.styleable.NavigationView_menu, 0));}//4. 初始化 header layoutif (a.hasValue(R.styleable.NavigationView_headerLayout)) {inflateHeaderView(a.getResourceId(R.styleable.NavigationView_headerLayout, 0));}a.recycle();}/*** Inflate a menu resource into this navigation view.*(将菜单资源扩展到此导航视图中。)* <p>Existing items in the menu will not be modified or removed.</p>** @param resId ID of a menu resource to inflate*/public void inflateMenu(int resId) {mPresenter.setUpdateSuspended(true);//解析菜单项资源getMenuInflater().inflate(resId, mMenu);mPresenter.setUpdateSuspended(false);//更新菜单视图mPresenter.updateMenuView(false);}

在注释3中的inflateMenu函数中,我们获取到用户设置的menu项资源,然后解析该menu资源。这个资源就是我们在前文的示例中的app:menu属性设置的menu资源,即res/menu/slide_menu.xml。里面的资源会被解析为相应的Java对象,最后添加到NavigationMenuAdapter中。

解析完菜单之后,我们再通过NavigationAdapter来更新整个菜单视图。菜单项对象会存储在mMenu对象中,然后通过Presenter的updateMenuView函数更新视图。

#NavigationMenuPresenter@Overridepublic void updateMenuView(boolean cleared) {if (mAdapter != null) {mAdapter.update();}}NavigationMenuAdapter是NavigationMenuPresenter的内部类private class NavigationMenuAdapter extends RecyclerView.Adapter<ViewHolder> {//视图类型,分别为菜单、子菜单、分割视图、headerprivate static final int VIEW_TYPE_NORMAL = 0;private static final int VIEW_TYPE_SUBHEADER = 1;private static final int VIEW_TYPE_SEPARATOR = 2;private static final int VIEW_TYPE_HEADER = 3;}
#NavigationMenuAdapter
/*** Flattens the visible menu items of {@link #mMenu} into {@link #mItems},* while inserting separators between items when necessary.*/(将{@link #mMenu}的可见菜单项展平为{@link #mItems},同时在必要时在项之间插入分隔符。)private void prepareMenuItems() {if (mUpdateSuspended) {return;}mUpdateSuspended = true;mItems.clear();
//1.添加header视图,放在第一项mItems.add(new NavigationMenuHeaderItem());int currentGroupId = -1;int currentGroupStart = 0;boolean currentGroupHasIcon = false;
//2.从Menu中解析、添加菜单itemfor (int i = 0, totalSize = mMenu.getVisibleItems().size(); i < totalSize; i++) {MenuItemImpl item = mMenu.getVisibleItems().get(i);if (item.isChecked()) {setCheckedItem(item);}if (item.isCheckable()) {item.setExclusiveCheckable(false);}if (item.hasSubMenu()) {SubMenu subMenu = item.getSubMenu();if (subMenu.hasVisibleItems()) {if (i != 0) {mItems.add(new NavigationMenuSeparatorItem(mPaddingSeparator, 0));}
//添加菜单以及子菜单mItems.add(new NavigationMenuTextItem(item));boolean subMenuHasIcon = false;int subMenuStart = mItems.size();for (int j = 0, size = subMenu.size(); j < size; j++) {MenuItemImpl subMenuItem = (MenuItemImpl) subMenu.getItem(j);if (subMenuItem.isVisible()) {if (!subMenuHasIcon && subMenuItem.getIcon() != null) {subMenuHasIcon = true;}if (subMenuItem.isCheckable()) {subMenuItem.setExclusiveCheckable(false);}if (item.isChecked()) {setCheckedItem(item);}mItems.add(new NavigationMenuTextItem(subMenuItem));}}if (subMenuHasIcon) {appendTransparentIconIfMissing(subMenuStart, mItems.size());}}} else {
//添加菜单int groupId = item.getGroupId();if (groupId != currentGroupId) { // first item in groupcurrentGroupStart = mItems.size();currentGroupHasIcon = item.getIcon() != null;if (i != 0) {currentGroupStart++;mItems.add(new NavigationMenuSeparatorItem(mPaddingSeparator, mPaddingSeparator));}} else if (!currentGroupHasIcon && item.getIcon() != null) {currentGroupHasIcon = true;appendTransparentIconIfMissing(currentGroupStart, mItems.size());}NavigationMenuTextItem textItem = new NavigationMenuTextItem(item);textItem.needsEmptyIcon = currentGroupHasIcon;mItems.add(textItem);currentGroupId = groupId;}}mUpdateSuspended = false;}
#NavigationMenuAdapter@Overridepublic int getItemViewType(int position) {NavigationMenuItem item = mItems.get(position);if (item instanceof NavigationMenuSeparatorItem) {return VIEW_TYPE_SEPARATOR;} else if (item instanceof NavigationMenuHeaderItem) {return VIEW_TYPE_HEADER;} else if (item instanceof NavigationMenuTextItem) {NavigationMenuTextItem textItem = (NavigationMenuTextItem) item;if (textItem.getMenuItem().hasSubMenu()) {return VIEW_TYPE_SUBHEADER;} else {return VIEW_TYPE_NORMAL;}}throw new RuntimeException("Unknown item type.");}#NavigationMenuAdapter@Overridepublic ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {switch (viewType) {case VIEW_TYPE_NORMAL:return new NormalViewHolder(mLayoutInflater, parent, mOnClickListener);case VIEW_TYPE_SUBHEADER:return new SubheaderViewHolder(mLayoutInflater, parent);case VIEW_TYPE_SEPARATOR:return new SeparatorViewHolder(mLayoutInflater, parent);case VIEW_TYPE_HEADER:return new HeaderViewHolder(mHeaderLayout);}return null;}#NavigationMenuAdapterpublic void update() {prepareMenuItems();notifyDataSetChanged();}

从代码中可以看到,在update函数中我们首先会获取到mMenu中所有的MenuItemImpl对象,MenuItemImpl就是每个菜单Java对象。然后将这些对象转换为NavigationMenuItem对象,并且添加到列表中。最后调用notifyDataSetChanged函数更新NavigationMenuView。而不同的菜单类型会有不同的ViewHoler,最终表现为不同的视觉效果。例如,header与菜单项是完全不同一样的。

至此,通过NavigationMenuView(本质为RecyclerView)构建了整个菜单视图。总结这些组件的逻辑关系:NavigationView就是MVP中的View角色,它通过Presenter处理解析、构造各种类别菜单项的业务逻辑,将自身从复杂的逻辑中解耦出来,因此NavigationView的代码非常少,而Model就是NavigationMenuItem对象,它们只是简单的实体类,负责承载菜单项的数据。

参考《Android源码设计模式》

android NavigationView解析相关推荐

  1. Android混淆解析

    此文章转载来源https://www.jianshu.com/p/84114b7feb38点击打开链接 Android混淆解析 一.混淆的目的 一款发布到市场的软件原则上都应该做代码混淆. 通过代码混 ...

  2. Android中解析XML

    Android中解析XML 转载于:https://www.cnblogs.com/zhujiabin/p/5868993.html

  3. android 如何实现无限列表,在Android中解析和创建无限/无限级别的List /子列表中的XML...

    在我的Android Application的服务器端应用程序也由我开发.在这个应用程序Android应用程序从服务器请求一些XML并解析它. XML包含描述应用程序中应该有多少标签的信息,并且每个标 ...

  4. 在linux kernel或android中解析cmdline参数

    文章目录 ★★★ 友情链接 : 个人博客导读首页-点击此处 ★★★ Kernel command line: earlycon androidboot.selinux=permissive uart_ ...

  5. android最大json,Android:解析大型JSON文件

    我正在创建一个Android应用程序,该应用程序应该将Json从文件或网址解析为jsonarray和jsonobjects. 问题是,我的JSON是3.3 MB,当我使用一个简单的代码,如下所示:(现 ...

  6. android XMl 解析神奇xstream 六: 把集合list 转化为 XML文档

    前言:对xstream不理解的请看: android XMl 解析神奇xstream 一: 解析android项目中 asset 文件夹 下的 aa.xml 文件 android XMl 解析神奇xs ...

  7. android XMl 解析神奇xstream 五: 把复杂对象转换成 xml ,并写入SD卡中的xml文件

    前言:对xstream不理解的请看: android XMl 解析神奇xstream 一: 解析android项目中 asset 文件夹 下的 aa.xml 文件 android XMl 解析神奇xs ...

  8. android XMl 解析神奇xstream 四: 将复杂的xml文件解析为对象

    前言:对xstream不理解的请看: android XMl 解析神奇xstream 一: 解析android项目中 asset 文件夹 下的 aa.xml 文件 android XMl 解析神奇xs ...

  9. android XMl 解析神奇xstream 二: 把对象转换成xml

    前言:对xstream不理解的请看:android XMl 解析神奇xstream 一: 解析android项目中 asset 文件夹 下的 aa.xml 文件 1.Javabeen 代码 packa ...

最新文章

  1. Git的工作区与暂存区
  2. 从Uber微服务看最佳实践如何炼成?
  3. XXE Lab:1题解
  4. C++ Primer 5th笔记(chap 19 特殊工具与技术)定位 new 表达式
  5. JavaScript函数调用规则
  6. 如何在Mac上更改声音输出设置呢?
  7. 编程ING:人人都能学会程序设计
  8. H.264 NAL语法语意以及字节流的语法语意
  9. checksum计算方法
  10. Three 之 three.js (webgl)shader 中 Texture 贴图 uv 坐标的相关简单说明,并简单测试 UV 重复旋转偏移效果
  11. wincc c 语言改颜色,wincc常用c脚本小草设置
  12. java字符串同构_Java同构代码
  13. C#插件开发之带控件的插件开发(基础篇)
  14. 大唐移动android面试题,大唐移动面试经验
  15. 音视频播放器开发——实现变速播放
  16. 传奇GOM引擎-GEE引擎版本如何添加GM账号刷装备
  17. 菜鸟读财报,如何从上市公司财报中挖情报?--微博转载
  18. 程序员最该买的十本书
  19. 软件制作 asp.net sqlserver access
  20. iOS 入门开发踩坑实录

热门文章

  1. 有哪些好用的pdf编辑软件?简单途径一览
  2. Java API源码在哪里找_详解查看JAVA API及JAVA源码的方法
  3. 看数智平台如何助力企业实现产业互联
  4. 刚上路的苹果地图:急需提升用户体验
  5. 依赖注入(DI)的三种方式
  6. QML系列教程(9)-状态过渡Transitions
  7. 企业为什么需要UI快速开发框架
  8. IIS 服务器的安全设置
  9. JavaScript算法——冒泡排序
  10. 股票撑线与阻力线介绍