android NavigationView解析
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解析相关推荐
- Android混淆解析
此文章转载来源https://www.jianshu.com/p/84114b7feb38点击打开链接 Android混淆解析 一.混淆的目的 一款发布到市场的软件原则上都应该做代码混淆. 通过代码混 ...
- Android中解析XML
Android中解析XML 转载于:https://www.cnblogs.com/zhujiabin/p/5868993.html
- android 如何实现无限列表,在Android中解析和创建无限/无限级别的List /子列表中的XML...
在我的Android Application的服务器端应用程序也由我开发.在这个应用程序Android应用程序从服务器请求一些XML并解析它. XML包含描述应用程序中应该有多少标签的信息,并且每个标 ...
- 在linux kernel或android中解析cmdline参数
文章目录 ★★★ 友情链接 : 个人博客导读首页-点击此处 ★★★ Kernel command line: earlycon androidboot.selinux=permissive uart_ ...
- android最大json,Android:解析大型JSON文件
我正在创建一个Android应用程序,该应用程序应该将Json从文件或网址解析为jsonarray和jsonobjects. 问题是,我的JSON是3.3 MB,当我使用一个简单的代码,如下所示:(现 ...
- android XMl 解析神奇xstream 六: 把集合list 转化为 XML文档
前言:对xstream不理解的请看: android XMl 解析神奇xstream 一: 解析android项目中 asset 文件夹 下的 aa.xml 文件 android XMl 解析神奇xs ...
- android XMl 解析神奇xstream 五: 把复杂对象转换成 xml ,并写入SD卡中的xml文件
前言:对xstream不理解的请看: android XMl 解析神奇xstream 一: 解析android项目中 asset 文件夹 下的 aa.xml 文件 android XMl 解析神奇xs ...
- android XMl 解析神奇xstream 四: 将复杂的xml文件解析为对象
前言:对xstream不理解的请看: android XMl 解析神奇xstream 一: 解析android项目中 asset 文件夹 下的 aa.xml 文件 android XMl 解析神奇xs ...
- android XMl 解析神奇xstream 二: 把对象转换成xml
前言:对xstream不理解的请看:android XMl 解析神奇xstream 一: 解析android项目中 asset 文件夹 下的 aa.xml 文件 1.Javabeen 代码 packa ...
最新文章
- Git的工作区与暂存区
- 从Uber微服务看最佳实践如何炼成?
- XXE Lab:1题解
- C++ Primer 5th笔记(chap 19 特殊工具与技术)定位 new 表达式
- JavaScript函数调用规则
- 如何在Mac上更改声音输出设置呢?
- 编程ING:人人都能学会程序设计
- H.264 NAL语法语意以及字节流的语法语意
- checksum计算方法
- Three 之 three.js (webgl)shader 中 Texture 贴图 uv 坐标的相关简单说明,并简单测试 UV 重复旋转偏移效果
- wincc c 语言改颜色,wincc常用c脚本小草设置
- java字符串同构_Java同构代码
- C#插件开发之带控件的插件开发(基础篇)
- 大唐移动android面试题,大唐移动面试经验
- 音视频播放器开发——实现变速播放
- 传奇GOM引擎-GEE引擎版本如何添加GM账号刷装备
- 菜鸟读财报,如何从上市公司财报中挖情报?--微博转载
- 程序员最该买的十本书
- 软件制作 asp.net sqlserver access
- iOS 入门开发踩坑实录