美文网首页
Android--NavigationView基本使用及源码分析

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

作者: aruba | 来源:发表于2020-03-12 10:29 被阅读0次
    NavigationView也是design包下一个组件,一般用来和DrawerLayout配合使用,基本使用方法也很简单,直接在xml中使用就可以
    <?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

    <?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

    <?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节点
    <?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设置成灰色,如果我们想要原始图的颜色,需要在代码中调用
    nv_slide.setItemIconTintList(null);
    
    NavigationView.gif
    接下来分析NavigationView的源码,它采用了MVP设计模式,写的非常好,首先看它的构造方法
        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
        @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层
        @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
    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布局文件
    <?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的构造方法
        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方法
        /**
         * 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解析器
        /**
         * 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);
        @Override
        public void updateMenuView(boolean cleared) {
            if (mAdapter != null) {
                mAdapter.update();
            }
        }
    
    暂时Adapter先不看,我们在构造方法中继续往下看
        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传入
        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方法
        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方法单独贴出来
            @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方法
            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

    相关文章

      网友评论

          本文标题:Android--NavigationView基本使用及源码分析

          本文链接:https://www.haomeiwen.com/subject/jvwcjhtx.html