当前位置:   article > 正文

android NavigationView解析_android navigationview

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。

 

  1. public class ScrimInsetsFrameLayout extends FrameLayout {
  2. }
  3. public class NavigationView extends ScrimInsetsFrameLayout {
  4. //菜单Presenter
  5. private final NavigationMenu mMenu;
  6. private final NavigationMenuPresenter mPresenter = new NavigationMenuPresenter();
  7. OnNavigationItemSelectedListener mListener;
  8. private int mMaxWidth;
  9. //菜单解析的Inflater
  10. private MenuInflater mMenuInflater;
  11. }

 

  1. public NavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
  2. super(context, attrs, defStyleAttr);
  3. ThemeUtils.checkAppCompatTheme(context);
  4. // Create the menu
  5. mMenu = new NavigationMenu(context);
  6. //其他初始化操作
  7. // Custom attributes
  8. TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
  9. R.styleable.NavigationView, defStyleAttr,
  10. R.style.Widget_Design_NavigationView);
  11. ViewCompat.setBackground(
  12. this, a.getDrawable(R.styleable.NavigationView_android_background));
  13. if (a.hasValue(R.styleable.NavigationView_elevation)) {
  14. ViewCompat.setElevation(this, a.getDimensionPixelSize(
  15. R.styleable.NavigationView_elevation, 0));
  16. }
  17. ViewCompat.setFitsSystemWindows(this,
  18. a.getBoolean(R.styleable.NavigationView_android_fitsSystemWindows, false));
  19. mMaxWidth = a.getDimensionPixelSize(R.styleable.NavigationView_android_maxWidth, 0);
  20. final ColorStateList itemIconTint;
  21. if (a.hasValue(R.styleable.NavigationView_itemIconTint)) {
  22. itemIconTint = a.getColorStateList(R.styleable.NavigationView_itemIconTint);
  23. } else {
  24. itemIconTint = createDefaultColorStateList(android.R.attr.textColorSecondary);
  25. }
  26. boolean textAppearanceSet = false;
  27. int textAppearance = 0;
  28. if (a.hasValue(R.styleable.NavigationView_itemTextAppearance)) {
  29. textAppearance = a.getResourceId(R.styleable.NavigationView_itemTextAppearance, 0);
  30. textAppearanceSet = true;
  31. }
  32. ColorStateList itemTextColor = null;
  33. if (a.hasValue(R.styleable.NavigationView_itemTextColor)) {
  34. itemTextColor = a.getColorStateList(R.styleable.NavigationView_itemTextColor);
  35. }
  36. if (!textAppearanceSet && itemTextColor == null) {
  37. // If there isn't a text appearance set, we'll use a default text color
  38. itemTextColor = createDefaultColorStateList(android.R.attr.textColorPrimary);
  39. }
  40. final Drawable itemBackground = a.getDrawable(R.styleable.NavigationView_itemBackground);
  41. mMenu.setCallback(new MenuBuilder.Callback() {
  42. @Override
  43. public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
  44. return mListener != null && mListener.onNavigationItemSelected(item);
  45. }
  46. @Override
  47. public void onMenuModeChange(MenuBuilder menu) {}
  48. });
  49. mPresenter.setId(PRESENTER_NAVIGATION_VIEW_ID);
  50. //1.初始化一些资源
  51. mPresenter.initForMenu(context, mMenu);
  52. mPresenter.setItemIconTintList(itemIconTint);
  53. if (textAppearanceSet) {
  54. mPresenter.setItemTextAppearance(textAppearance);
  55. }
  56. mPresenter.setItemTextColor(itemTextColor);
  57. mPresenter.setItemBackground(itemBackground);
  58. mMenu.addMenuPresenter(mPresenter);
  59. //2.构建整个菜单视图并且添加到当前视图中
  60. addView((View) mPresenter.getMenuView(this));
  61. //3.初始化菜单资源menu目录
  62. if (a.hasValue(R.styleable.NavigationView_menu)) {
  63. inflateMenu(a.getResourceId(R.styleable.NavigationView_menu, 0));
  64. }
  65. //4.初始化header layout
  66. if (a.hasValue(R.styleable.NavigationView_headerLayout)) {
  67. inflateHeaderView(a.getResourceId(R.styleable.NavigationView_headerLayout, 0));
  68. }
  69. a.recycle();
  70. }
  1. #NavigationView
  2. /**
  3. * Inflate a menu resource into this navigation view.
  4. *(将菜单资源扩展到此导航视图中。)
  5. * <p>Existing items in the menu will not be modified or removed.</p>
  6. *(菜单中的现有项目不会被修改或删除。)
  7. * @param resId ID of a menu resource to inflate
  8. */
  9. public void inflateMenu(int resId) {
  10. mPresenter.setUpdateSuspended(true);
  11. getMenuInflater().inflate(resId, mMenu);
  12. mPresenter.setUpdateSuspended(false);
  13. mPresenter.updateMenuView(false);
  14. }
  15. #NavigationView
  16. /**
  17. * Inflates a View and add it as a header of the navigation menu.
  18. *(加载视图并将其添加为导航菜单的标题。)
  19. * @param res The layout resource ID.
  20. * @return a newly inflated View.
  21. */
  22. public View inflateHeaderView(@LayoutRes int res) {
  23. return mPresenter.inflateHeaderView(res);
  24. }

 

 

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

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

1.初始化资源;

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

3.解析、显示菜单项;

4.解析和显示Header视图。

 

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

 

  1. public class NavigationMenuPresenter implements MenuPresenter {
  2. //菜单视图,也就是一个RecyclerView
  3. private NavigationMenuView mMenuView;
  4. //菜单的Header布局
  5. LinearLayout mHeaderLayout;
  6. private Callback mCallback;
  7. MenuBuilder mMenu;
  8. private int mId;
  9. NavigationMenuAdapter mAdapter;
  10. LayoutInflater mLayoutInflater;
  11. }

 

  1. /**
  2. * Padding for separators between items
  3. */
  4. int mPaddingSeparator;
  5. //1.初始化mLayoutInflater和MenuBuilder
  6. @Override
  7. public void initForMenu(Context context, MenuBuilder menu) {
  8. mLayoutInflater = LayoutInflater.from(context);
  9. mMenu = menu;
  10. Resources res = context.getResources();
  11. mPaddingSeparator = res.getDimensionPixelOffset(
  12. R.dimen.design_navigation_separator_vertical_padding);
  13. }

 

 

  1. 2.构建菜单和Header视图
  2. @Override
  3. public MenuView getMenuView(ViewGroup root) {
  4. if (mMenuView == null) {
  5. //加载菜单NavigationView
  6. mMenuView = (NavigationMenuView) mLayoutInflater.inflate(
  7. R.layout.design_navigation_menu, root, false);
  8. if (mAdapter == null) {
  9. mAdapter = new NavigationMenuAdapter();
  10. }
  11. //加载菜单的Header
  12. mHeaderLayout = (LinearLayout) mLayoutInflater
  13. .inflate(R.layout.design_navigation_item_header,
  14. mMenuView, false);
  15. mMenuView.setAdapter(mAdapter);
  16. }
  17. return mMenuView;
  18. }

 

 

  1. //3.更新菜单项
  2. @Override
  3. public void updateMenuView(boolean cleared) {
  4. if (mAdapter != null) {
  5. mAdapter.update();
  6. }
  7. }
  1. //4.加载Header布局
  2. public View inflateHeaderView(@LayoutRes int res) {
  3. View view = mLayoutInflater.inflate(res, mHeaderLayout, false);
  4. addHeaderView(view);
  5. return view;
  6. }

 

 

  1. public void addHeaderView(@NonNull View view) {
  2. mHeaderLayout.addView(view);
  3. // The padding on top should be cleared.
  4. mMenuView.setPadding(0, 0, 0, mMenuView.getPaddingBottom());
  5. }

 

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

 

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

 

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

 

 

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

  1. public NavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
  2. super(context, attrs, defStyleAttr);
  3. ThemeUtils.checkAppCompatTheme(context);
  4. // Create the menu
  5. mMenu = new NavigationMenu(context);
  6. // Custom attributes
  7. TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
  8. R.styleable.NavigationView, defStyleAttr,
  9. R.style.Widget_Design_NavigationView);
  10. ViewCompat.setBackground(
  11. this, a.getDrawable(R.styleable.NavigationView_android_background));
  12. if (a.hasValue(R.styleable.NavigationView_elevation)) {
  13. ViewCompat.setElevation(this, a.getDimensionPixelSize(
  14. R.styleable.NavigationView_elevation, 0));
  15. }
  16. ViewCompat.setFitsSystemWindows(this,
  17. a.getBoolean(R.styleable.NavigationView_android_fitsSystemWindows, false));
  18. mMaxWidth = a.getDimensionPixelSize(R.styleable.NavigationView_android_maxWidth, 0);
  19. final ColorStateList itemIconTint;
  20. if (a.hasValue(R.styleable.NavigationView_itemIconTint)) {
  21. itemIconTint = a.getColorStateList(R.styleable.NavigationView_itemIconTint);
  22. } else {
  23. itemIconTint = createDefaultColorStateList(android.R.attr.textColorSecondary);
  24. }
  25. boolean textAppearanceSet = false;
  26. int textAppearance = 0;
  27. if (a.hasValue(R.styleable.NavigationView_itemTextAppearance)) {
  28. textAppearance = a.getResourceId(R.styleable.NavigationView_itemTextAppearance, 0);
  29. textAppearanceSet = true;
  30. }
  31. ColorStateList itemTextColor = null;
  32. if (a.hasValue(R.styleable.NavigationView_itemTextColor)) {
  33. itemTextColor = a.getColorStateList(R.styleable.NavigationView_itemTextColor);
  34. }
  35. if (!textAppearanceSet && itemTextColor == null) {
  36. // If there isn't a text appearance set, we'll use a default text color
  37. itemTextColor = createDefaultColorStateList(android.R.attr.textColorPrimary);
  38. }
  39. final Drawable itemBackground = a.getDrawable(R.styleable.NavigationView_itemBackground);
  40. mMenu.setCallback(new MenuBuilder.Callback() {
  41. @Override
  42. public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
  43. return mListener != null && mListener.onNavigationItemSelected(item);
  44. }
  45. @Override
  46. public void onMenuModeChange(MenuBuilder menu) {}
  47. });
  48. mPresenter.setId(PRESENTER_NAVIGATION_VIEW_ID);
  49. mPresenter.initForMenu(context, mMenu);
  50. mPresenter.setItemIconTintList(itemIconTint);
  51. if (textAppearanceSet) {
  52. mPresenter.setItemTextAppearance(textAppearance);
  53. }
  54. mPresenter.setItemTextColor(itemTextColor);
  55. mPresenter.setItemBackground(itemBackground);
  56. mMenu.addMenuPresenter(mPresenter);
  57. //2.构建整个菜单视图并添加到当前视图中
  58. addView((View) mPresenter.getMenuView(this));
  59. //3.初始化菜单资源menu目录
  60. if (a.hasValue(R.styleable.NavigationView_menu)) {
  61. inflateMenu(a.getResourceId(R.styleable.NavigationView_menu, 0));
  62. }
  63. //4. 初始化 header layout
  64. if (a.hasValue(R.styleable.NavigationView_headerLayout)) {
  65. inflateHeaderView(a.getResourceId(R.styleable.NavigationView_headerLayout, 0));
  66. }
  67. a.recycle();
  68. }
  69. /**
  70. * Inflate a menu resource into this navigation view.
  71. *(将菜单资源扩展到此导航视图中。)
  72. * <p>Existing items in the menu will not be modified or removed.</p>
  73. *
  74. * @param resId ID of a menu resource to inflate
  75. */
  76. public void inflateMenu(int resId) {
  77. mPresenter.setUpdateSuspended(true);
  78. //解析菜单项资源
  79. getMenuInflater().inflate(resId, mMenu);
  80. mPresenter.setUpdateSuspended(false);
  81. //更新菜单视图
  82. mPresenter.updateMenuView(false);
  83. }

 

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

 

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

 

  1. #NavigationMenuPresenter
  2. @Override
  3. public void updateMenuView(boolean cleared) {
  4. if (mAdapter != null) {
  5. mAdapter.update();
  6. }
  7. }
  8. NavigationMenuAdapter是NavigationMenuPresenter的内部类
  9. private class NavigationMenuAdapter extends RecyclerView.Adapter<ViewHolder> {
  10. //视图类型,分别为菜单、子菜单、分割视图、header
  11. private static final int VIEW_TYPE_NORMAL = 0;
  12. private static final int VIEW_TYPE_SUBHEADER = 1;
  13. private static final int VIEW_TYPE_SEPARATOR = 2;
  14. private static final int VIEW_TYPE_HEADER = 3;
  15. }

 

  1. #NavigationMenuAdapter
  2. /**
  3. * Flattens the visible menu items of {@link #mMenu} into {@link #mItems},
  4. * while inserting separators between items when necessary.
  5. */(将{@link #mMenu}的可见菜单项展平为{@link #mItems},同时在必要时在项之间插入分隔符。)
  6. private void prepareMenuItems() {
  7. if (mUpdateSuspended) {
  8. return;
  9. }
  10. mUpdateSuspended = true;
  11. mItems.clear();
  12. //1.添加header视图,放在第一项
  13. mItems.add(new NavigationMenuHeaderItem());
  14. int currentGroupId = -1;
  15. int currentGroupStart = 0;
  16. boolean currentGroupHasIcon = false;
  17. //2.从Menu中解析、添加菜单item
  18. for (int i = 0, totalSize = mMenu.getVisibleItems().size(); i < totalSize; i++) {
  19. MenuItemImpl item = mMenu.getVisibleItems().get(i);
  20. if (item.isChecked()) {
  21. setCheckedItem(item);
  22. }
  23. if (item.isCheckable()) {
  24. item.setExclusiveCheckable(false);
  25. }
  26. if (item.hasSubMenu()) {
  27. SubMenu subMenu = item.getSubMenu();
  28. if (subMenu.hasVisibleItems()) {
  29. if (i != 0) {
  30. mItems.add(new NavigationMenuSeparatorItem(mPaddingSeparator, 0));
  31. }
  32. //添加菜单以及子菜单
  33. mItems.add(new NavigationMenuTextItem(item));
  34. boolean subMenuHasIcon = false;
  35. int subMenuStart = mItems.size();
  36. for (int j = 0, size = subMenu.size(); j < size; j++) {
  37. MenuItemImpl subMenuItem = (MenuItemImpl) subMenu.getItem(j);
  38. if (subMenuItem.isVisible()) {
  39. if (!subMenuHasIcon && subMenuItem.getIcon() != null) {
  40. subMenuHasIcon = true;
  41. }
  42. if (subMenuItem.isCheckable()) {
  43. subMenuItem.setExclusiveCheckable(false);
  44. }
  45. if (item.isChecked()) {
  46. setCheckedItem(item);
  47. }
  48. mItems.add(new NavigationMenuTextItem(subMenuItem));
  49. }
  50. }
  51. if (subMenuHasIcon) {
  52. appendTransparentIconIfMissing(subMenuStart, mItems.size());
  53. }
  54. }
  55. } else {
  56. //添加菜单
  57. int groupId = item.getGroupId();
  58. if (groupId != currentGroupId) { // first item in group
  59. currentGroupStart = mItems.size();
  60. currentGroupHasIcon = item.getIcon() != null;
  61. if (i != 0) {
  62. currentGroupStart++;
  63. mItems.add(new NavigationMenuSeparatorItem(
  64. mPaddingSeparator, mPaddingSeparator));
  65. }
  66. } else if (!currentGroupHasIcon && item.getIcon() != null) {
  67. currentGroupHasIcon = true;
  68. appendTransparentIconIfMissing(currentGroupStart, mItems.size());
  69. }
  70. NavigationMenuTextItem textItem = new NavigationMenuTextItem(item);
  71. textItem.needsEmptyIcon = currentGroupHasIcon;
  72. mItems.add(textItem);
  73. currentGroupId = groupId;
  74. }
  75. }
  76. mUpdateSuspended = false;
  77. }
  1. #NavigationMenuAdapter
  2. @Override
  3. public int getItemViewType(int position) {
  4. NavigationMenuItem item = mItems.get(position);
  5. if (item instanceof NavigationMenuSeparatorItem) {
  6. return VIEW_TYPE_SEPARATOR;
  7. } else if (item instanceof NavigationMenuHeaderItem) {
  8. return VIEW_TYPE_HEADER;
  9. } else if (item instanceof NavigationMenuTextItem) {
  10. NavigationMenuTextItem textItem = (NavigationMenuTextItem) item;
  11. if (textItem.getMenuItem().hasSubMenu()) {
  12. return VIEW_TYPE_SUBHEADER;
  13. } else {
  14. return VIEW_TYPE_NORMAL;
  15. }
  16. }
  17. throw new RuntimeException("Unknown item type.");
  18. }
  19. #NavigationMenuAdapter
  20. @Override
  21. public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
  22. switch (viewType) {
  23. case VIEW_TYPE_NORMAL:
  24. return new NormalViewHolder(mLayoutInflater, parent, mOnClickListener);
  25. case VIEW_TYPE_SUBHEADER:
  26. return new SubheaderViewHolder(mLayoutInflater, parent);
  27. case VIEW_TYPE_SEPARATOR:
  28. return new SeparatorViewHolder(mLayoutInflater, parent);
  29. case VIEW_TYPE_HEADER:
  30. return new HeaderViewHolder(mHeaderLayout);
  31. }
  32. return null;
  33. }
  34. #NavigationMenuAdapter
  35. public void update() {
  36. prepareMenuItems();
  37. notifyDataSetChanged();
  38. }

 

 

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

 

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

 

参考《Android源码设计模式》

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/219221
推荐阅读
相关标签
  

闽ICP备14008679号