前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android--NavigationView基本使用及源码分析

Android--NavigationView基本使用及源码分析

作者头像
aruba
发布2020-07-02 15:27:30
1.1K0
发布2020-07-02 15:27:30
举报
文章被收录于专栏:android技术
NavigationView也是design包下一个组件,一般用来和DrawerLayout配合使用,基本使用方法也很简单,直接在xml中使用就可以
代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="Hello World!" />

    <android.support.design.widget.NavigationView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/layout_header"
        app:menu="@menu/menu"></android.support.design.widget.NavigationView>

</android.support.v4.widget.DrawerLayout>
app:headerLayout用来加载头部,app:menu用来加载目录

layout_header.xml

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <ImageView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@drawable/ic_account_circle_black_24dp" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="登录" />

</LinearLayout>

menu.xml

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:icon="@drawable/ic_account_circle_black_24dp"
        android:title="我的" />
    <item
        android:icon="@drawable/ic_account_circle_black_24dp"
        android:title="安全" />
    <item
        android:icon="@drawable/ic_account_circle_black_24dp"
        android:title="收藏" />
    <item
        android:icon="@drawable/ic_account_circle_black_24dp"
        android:title="设置" />
</menu>
运行后的效果:

NavigationView.gif

目录中想要加分割线的话,可以在menu.xml中添加group节点
代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:icon="@drawable/ic_account_circle_black_24dp"
        android:title="我的" />
    <item
        android:icon="@drawable/ic_account_circle_black_24dp"
        android:title="安全" />

    <group android:id="@+id/gp">
        <item
            android:icon="@drawable/ic_account_circle_black_24dp"
            android:title="收藏" />
        <item
            android:icon="@drawable/ic_account_circle_black_24dp"
            android:title="设置" />
    </group>
</menu>
注意要给group一个id,否则不会显示分割线

NavigationView.gif

NavigationView会默认的给目录下的每个item的icon设置成灰色,如果我们想要原始图的颜色,需要在代码中调用
代码语言:javascript
复制
nv_slide.setItemIconTintList(null);

NavigationView.gif

接下来分析NavigationView的源码,它采用了MVP设计模式,写的非常好,首先看它的构造方法
代码语言:javascript
复制
    private final NavigationMenu mMenu;
    private final NavigationMenuPresenter mPresenter = new NavigationMenuPresenter();

    public NavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        ThemeUtils.checkAppCompatTheme(context);

        // Create the menu
        mMenu = new NavigationMenu(context);

        // Custom attributes
        TintTypedArray 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 color
            itemTextColor = createDefaultColorStateList(android.R.attr.textColorPrimary);
        }

        final Drawable itemBackground = a.getDrawable(R.styleable.NavigationView_itemBackground);

        mMenu.setCallback(new MenuBuilder.Callback() {
            @Override
            public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
                return mListener != null && mListener.onNavigationItemSelected(item);
            }

            @Override
            public 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);
        addView((View) mPresenter.getMenuView(this));

        if (a.hasValue(R.styleable.NavigationView_menu)) {
            inflateMenu(a.getResourceId(R.styleable.NavigationView_menu, 0));
        }

        if (a.hasValue(R.styleable.NavigationView_headerLayout)) {
            inflateHeaderView(a.getResourceId(R.styleable.NavigationView_headerLayout, 0));
        }

        a.recycle();
    }
其中NavigationMenu作为Model层,NavigationMenuPresenter 作为Presenter层,NavigationView作为View层,首先构造方法创建了一个Model,并通过mPresenter.initForMenu(context, mMenu);把它传给Presenter
代码语言:javascript
复制
    @Override
    public 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);
    }
NavigationView作为View层,它的代码量不多,基本全是丢给NavigationMenuPresenter 处理,在initForMenu方法调用之后,Presenter与Model建立联系,然后又调用了addView((View) mPresenter.getMenuView(this));这个方法从Presenter层获取View并添加到了View层
代码语言:javascript
复制
    @Override
    public MenuView getMenuView(ViewGroup root) {
        if (mMenuView == null) {
            mMenuView = (NavigationMenuView) mLayoutInflater.inflate(
                    R.layout.design_navigation_menu, root, false);
            if (mAdapter == null) {
                mAdapter = new NavigationMenuAdapter();
            }
            mHeaderLayout = (LinearLayout) mLayoutInflater
                    .inflate(R.layout.design_navigation_item_header,
                            mMenuView, false);
            mMenuView.setAdapter(mAdapter);
        }
        return mMenuView;
    }
追踪NavigationMenuView发现,它就是一个RecyclerView
代码语言:javascript
复制
public class NavigationMenuView extends RecyclerView implements MenuView {

    public NavigationMenuView(Context context) {
        this(context, null);
    }

    public NavigationMenuView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public NavigationMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false));
    }

    @Override
    public void initialize(MenuBuilder menu) {

    }

    @Override
    public int getWindowAnimations() {
        return 0;
    }

}
getMenuView方法又对mHeaderLayout 进行了赋值,我们查看design_navigation_item_header布局文件
代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2015 The Android Open Source Project
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~      http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:id="@+id/navigation_header_container"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="vertical"
      android:paddingBottom="@dimen/design_navigation_separator_vertical_padding" />
就是一个LinearLayout,之后getMenuView方法又给RecyclerView设置了Adapter,这个设配器我们回头再看,回到NavigationView的构造方法
代码语言:javascript
复制
    public NavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
        ...

        if (a.hasValue(R.styleable.NavigationView_menu)) {
            inflateMenu(a.getResourceId(R.styleable.NavigationView_menu, 0));
        }

        if (a.hasValue(R.styleable.NavigationView_headerLayout)) {
            inflateHeaderView(a.getResourceId(R.styleable.NavigationView_headerLayout, 0));
        }

        a.recycle();
    }
判断了下我们有没有在xml中设置menu和headerLayout,再看它对这两个自定义属性的处理,首先看inflateMenu方法
代码语言:javascript
复制
    /**
     * 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);
    }
其中将menu的资源文件id和mMenu(Model层),传给了xml解析器
代码语言:javascript
复制
    /**
     * Inflate a menu hierarchy from the specified XML resource. Throws
     * {@link InflateException} if there is an error.
     *
     * @param menuRes Resource ID for an XML layout resource to load (e.g.,
     *            <code>R.menu.main_activity</code>)
     * @param menu The Menu to inflate into. The items and submenus will be
     *            added to this Menu.
     */
    public void inflate(@MenuRes int menuRes, Menu menu) {
        XmlResourceParser parser = null;
        try {
            parser = mContext.getResources().getLayout(menuRes);
            AttributeSet attrs = Xml.asAttributeSet(parser);

            parseMenu(parser, attrs, menu);
        } catch (XmlPullParserException e) {
            throw new InflateException("Error inflating menu XML", e);
        } catch (IOException e) {
            throw new InflateException("Error inflating menu XML", e);
        } finally {
            if (parser != null) parser.close();
        }
    }

    /**
     * Called internally to fill the given menu. If a sub menu is seen, it will
     * call this recursively.
     */
    private void parseMenu(XmlPullParser parser, AttributeSet attrs, Menu menu)
            throws XmlPullParserException, IOException {
        MenuState menuState = new MenuState(menu);

        int eventType = parser.getEventType();
        String tagName;
        boolean lookingForEndOfUnknownTag = false;
        String unknownTagName = null;

        // This loop will skip to the menu start tag
        do {
            if (eventType == XmlPullParser.START_TAG) {
                tagName = parser.getName();
                if (tagName.equals(XML_MENU)) {
                    // Go to next tag
                    eventType = parser.next();
                    break;
                }

                throw new RuntimeException("Expecting menu, got " + tagName);
            }
            eventType = parser.next();
        } while (eventType != XmlPullParser.END_DOCUMENT);

        boolean reachedEndOfMenu = false;
        while (!reachedEndOfMenu) {
            switch (eventType) {
                case XmlPullParser.START_TAG:
                    if (lookingForEndOfUnknownTag) {
                        break;
                    }

                    tagName = parser.getName();
                    if (tagName.equals(XML_GROUP)) {
                        menuState.readGroup(attrs);
                    } else if (tagName.equals(XML_ITEM)) {
                        menuState.readItem(attrs);
                    } else if (tagName.equals(XML_MENU)) {
                        // A menu start tag denotes a submenu for an item
                        SubMenu subMenu = menuState.addSubMenuItem();
                        registerMenu(subMenu, attrs);

                        // Parse the submenu into returned SubMenu
                        parseMenu(parser, attrs, subMenu);
                    } else {
                        lookingForEndOfUnknownTag = true;
                        unknownTagName = tagName;
                    }
                    break;

                case XmlPullParser.END_TAG:
                    tagName = parser.getName();
                    if (lookingForEndOfUnknownTag && tagName.equals(unknownTagName)) {
                        lookingForEndOfUnknownTag = false;
                        unknownTagName = null;
                    } else if (tagName.equals(XML_GROUP)) {
                        menuState.resetGroup();
                    } else if (tagName.equals(XML_ITEM)) {
                        // Add the item if it hasn't been added (if the item was
                        // a submenu, it would have been added already)
                        if (!menuState.hasAddedItem()) {
                            if (menuState.itemActionProvider != null &&
                                    menuState.itemActionProvider.hasSubMenu()) {
                                registerMenu(menuState.addSubMenuItem(), attrs);
                            } else {
                                registerMenu(menuState.addItem(), attrs);
                            }
                        }
                    } else if (tagName.equals(XML_MENU)) {
                        reachedEndOfMenu = true;
                    }
                    break;

                case XmlPullParser.END_DOCUMENT:
                    throw new RuntimeException("Unexpected end of document");
            }

            eventType = parser.next();
        }
    }
MenuState是MenuInflater的一个内部类,最终会将所有item都添加给mMenu,实现数据的绑定,inflateMenu方法接下来又调用了mPresenter.updateMenuView(false);
代码语言:javascript
复制
    @Override
    public void updateMenuView(boolean cleared) {
        if (mAdapter != null) {
            mAdapter.update();
        }
    }
暂时Adapter先不看,我们在构造方法中继续往下看
代码语言:javascript
复制
    public NavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
       ...

        if (a.hasValue(R.styleable.NavigationView_headerLayout)) {
            inflateHeaderView(a.getResourceId(R.styleable.NavigationView_headerLayout, 0));
        }

        a.recycle();
    }

    /**
     * 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);
    }
又调用了Presenter的inflateHeaderView方法,将我们headerLayout的资源id传入
代码语言:javascript
复制
    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());
    }
最终我们的headerLayout被添加到mHeaderLayout对象中
我们现在来看这个Adapter,它就是一个内部类,我们核心关注onCreateViewHolder方法和update方法
代码语言:javascript
复制
    private class NavigationMenuAdapter extends RecyclerView.Adapter<ViewHolder> {

        private static final String STATE_CHECKED_ITEM = "android:menu:checked";

        private static final String STATE_ACTION_VIEWS = "android:menu:action_views";
        private 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;

        private final ArrayList<NavigationMenuItem> mItems = new ArrayList<>();
        private MenuItemImpl mCheckedItem;
        private boolean mUpdateSuspended;

        NavigationMenuAdapter() {
            prepareMenuItems();
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public int getItemCount() {
            return mItems.size();
        }

        @Override
        public 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.");
        }

        @Override
        public 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;
        }

        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            switch (getItemViewType(position)) {
                case VIEW_TYPE_NORMAL: {
                    NavigationMenuItemView itemView = (NavigationMenuItemView) holder.itemView;
                    itemView.setIconTintList(mIconTintList);
                    if (mTextAppearanceSet) {
                        itemView.setTextAppearance(mTextAppearance);
                    }
                    if (mTextColor != null) {
                        itemView.setTextColor(mTextColor);
                    }
                    ViewCompat.setBackground(itemView, mItemBackground != null ?
                            mItemBackground.getConstantState().newDrawable() : null);
                    NavigationMenuTextItem item = (NavigationMenuTextItem) mItems.get(position);
                    itemView.setNeedsEmptyIcon(item.needsEmptyIcon);
                    itemView.initialize(item.getMenuItem(), 0);
                    break;
                }
                case VIEW_TYPE_SUBHEADER: {
                    TextView subHeader = (TextView) holder.itemView;
                    NavigationMenuTextItem item = (NavigationMenuTextItem) mItems.get(position);
                    subHeader.setText(item.getMenuItem().getTitle());
                    break;
                }
                case VIEW_TYPE_SEPARATOR: {
                    NavigationMenuSeparatorItem item =
                            (NavigationMenuSeparatorItem) mItems.get(position);
                    holder.itemView.setPadding(0, item.getPaddingTop(), 0,
                            item.getPaddingBottom());
                    break;
                }
                case VIEW_TYPE_HEADER: {
                    break;
                }
            }

        }

        @Override
        public void onViewRecycled(ViewHolder holder) {
            if (holder instanceof NormalViewHolder) {
                ((NavigationMenuItemView) holder.itemView).recycle();
            }
        }

        public void update() {
            prepareMenuItems();
            notifyDataSetChanged();
        }

        /**
         * Flattens the visible menu items of {@link #mMenu} into {@link #mItems},
         * while inserting separators between items when necessary.
         */
        private void prepareMenuItems() {
            if (mUpdateSuspended) {
                return;
            }
            mUpdateSuspended = true;
            mItems.clear();
            mItems.add(new NavigationMenuHeaderItem());

            int currentGroupId = -1;
            int currentGroupStart = 0;
            boolean currentGroupHasIcon = false;
            for (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 group
                        currentGroupStart = 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;
        }

        private void appendTransparentIconIfMissing(int startIndex, int endIndex) {
            for (int i = startIndex; i < endIndex; i++) {
                NavigationMenuTextItem textItem = (NavigationMenuTextItem) mItems.get(i);
                textItem.needsEmptyIcon = true;
            }
        }

        public void setCheckedItem(MenuItemImpl checkedItem) {
            if (mCheckedItem == checkedItem || !checkedItem.isCheckable()) {
                return;
            }
            if (mCheckedItem != null) {
                mCheckedItem.setChecked(false);
            }
            mCheckedItem = checkedItem;
            checkedItem.setChecked(true);
        }

        public Bundle createInstanceState() {
            Bundle state = new Bundle();
            if (mCheckedItem != null) {
                state.putInt(STATE_CHECKED_ITEM, mCheckedItem.getItemId());
            }
            // Store the states of the action views.
            SparseArray<ParcelableSparseArray> actionViewStates = new SparseArray<>();
            for (int i = 0, size = mItems.size(); i < size; i++) {
                NavigationMenuItem navigationMenuItem = mItems.get(i);
                if (navigationMenuItem instanceof NavigationMenuTextItem) {
                    MenuItemImpl item = ((NavigationMenuTextItem) navigationMenuItem).getMenuItem();
                    View actionView = item != null ? item.getActionView() : null;
                    if (actionView != null) {
                        ParcelableSparseArray container = new ParcelableSparseArray();
                        actionView.saveHierarchyState(container);
                        actionViewStates.put(item.getItemId(), container);
                    }
                }
            }
            state.putSparseParcelableArray(STATE_ACTION_VIEWS, actionViewStates);
            return state;
        }

        public void restoreInstanceState(Bundle state) {
            int checkedItem = state.getInt(STATE_CHECKED_ITEM, 0);
            if (checkedItem != 0) {
                mUpdateSuspended = true;
                for (int i = 0, size = mItems.size(); i < size; i++) {
                    NavigationMenuItem item = mItems.get(i);
                    if (item instanceof NavigationMenuTextItem) {
                        MenuItemImpl menuItem = ((NavigationMenuTextItem) item).getMenuItem();
                        if (menuItem != null && menuItem.getItemId() == checkedItem) {
                            setCheckedItem(menuItem);
                            break;
                        }
                    }
                }
                mUpdateSuspended = false;
                prepareMenuItems();
            }
            // Restore the states of the action views.
            SparseArray<ParcelableSparseArray> actionViewStates = state
                    .getSparseParcelableArray(STATE_ACTION_VIEWS);
            if (actionViewStates != null) {
                for (int i = 0, size = mItems.size(); i < size; i++) {
                    NavigationMenuItem navigationMenuItem = mItems.get(i);
                    if (!(navigationMenuItem instanceof NavigationMenuTextItem)) {
                        continue;
                    }
                    MenuItemImpl item = ((NavigationMenuTextItem) navigationMenuItem).getMenuItem();
                    if (item == null) {
                        continue;
                    }
                    View actionView = item.getActionView();
                    if (actionView == null) {
                        continue;
                    }
                    ParcelableSparseArray container = actionViewStates.get(item.getItemId());
                    if (container == null) {
                        continue;
                    }
                    actionView.restoreHierarchyState(container);
                }
            }
        }

        public void setUpdateSuspended(boolean updateSuspended) {
            mUpdateSuspended = updateSuspended;
        }

    }
这边只把onCreateViewHolder方法和update方法单独贴出来
代码语言:javascript
复制
        @Override
        public 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;
        }
当viewType是VIEW_TYPE_HEADER,显示mHeaderLayout,也就是头部布局,上面分析的时候,在解析menu布局时,最后调用了adpater的update方法,我们来看下update方法
代码语言:javascript
复制
        public void update() {
            prepareMenuItems();
            notifyDataSetChanged();
        }

        /**
         * Flattens the visible menu items of {@link #mMenu} into {@link #mItems},
         * while inserting separators between items when necessary.
         */
        private void prepareMenuItems() {
            if (mUpdateSuspended) {
                return;
            }
            mUpdateSuspended = true;
            mItems.clear();
            mItems.add(new NavigationMenuHeaderItem());

            int currentGroupId = -1;
            int currentGroupStart = 0;
            boolean currentGroupHasIcon = false;
            for (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 group
                        currentGroupStart = 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;
        }
prepareMenuItems方法中通过MenuItemImpl item = mMenu.getVisibleItems().get(i);将数据从mMenu(Model层)中取出,放到了mItems集合中,就是我们常用的RecyclerView的套路,到此我们对NavigationView的布局结构就很清晰了,如下图:

NavigationView布局结构.png

NavigationView类结构图.png

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • NavigationView也是design包下一个组件,一般用来和DrawerLayout配合使用,基本使用方法也很简单,直接在xml中使用就可以
  • app:headerLayout用来加载头部,app:menu用来加载目录
  • 运行后的效果:
  • 目录中想要加分割线的话,可以在menu.xml中添加group节点
  • 注意要给group一个id,否则不会显示分割线
  • NavigationView会默认的给目录下的每个item的icon设置成灰色,如果我们想要原始图的颜色,需要在代码中调用
  • 接下来分析NavigationView的源码,它采用了MVP设计模式,写的非常好,首先看它的构造方法
  • 其中NavigationMenu作为Model层,NavigationMenuPresenter 作为Presenter层,NavigationView作为View层,首先构造方法创建了一个Model,并通过mPresenter.initForMenu(context, mMenu);把它传给Presenter
  • NavigationView作为View层,它的代码量不多,基本全是丢给NavigationMenuPresenter 处理,在initForMenu方法调用之后,Presenter与Model建立联系,然后又调用了addView((View) mPresenter.getMenuView(this));这个方法从Presenter层获取View并添加到了View层
  • 追踪NavigationMenuView发现,它就是一个RecyclerView
  • getMenuView方法又对mHeaderLayout 进行了赋值,我们查看design_navigation_item_header布局文件
  • 就是一个LinearLayout,之后getMenuView方法又给RecyclerView设置了Adapter,这个设配器我们回头再看,回到NavigationView的构造方法
  • 判断了下我们有没有在xml中设置menu和headerLayout,再看它对这两个自定义属性的处理,首先看inflateMenu方法
  • 其中将menu的资源文件id和mMenu(Model层),传给了xml解析器
  • MenuState是MenuInflater的一个内部类,最终会将所有item都添加给mMenu,实现数据的绑定,inflateMenu方法接下来又调用了mPresenter.updateMenuView(false);
  • 暂时Adapter先不看,我们在构造方法中继续往下看
  • 又调用了Presenter的inflateHeaderView方法,将我们headerLayout的资源id传入
  • 最终我们的headerLayout被添加到mHeaderLayout对象中
  • 我们现在来看这个Adapter,它就是一个内部类,我们核心关注onCreateViewHolder方法和update方法
  • 这边只把onCreateViewHolder方法和update方法单独贴出来
  • 当viewType是VIEW_TYPE_HEADER,显示mHeaderLayout,也就是头部布局,上面分析的时候,在解析menu布局时,最后调用了adpater的update方法,我们来看下update方法
  • prepareMenuItems方法中通过MenuItemImpl item = mMenu.getVisibleItems().get(i);将数据从mMenu(Model层)中取出,放到了mItems集合中,就是我们常用的RecyclerView的套路,到此我们对NavigationView的布局结构就很清晰了,如下图:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档