diff --git a/Android.mk b/Android.mk index 0b0b2a843..f2fc797ee 100644 --- a/Android.mk +++ b/Android.mk @@ -23,10 +23,12 @@ LOCAL_MODULE := android-support-design-res LOCAL_SDK_VERSION := current LOCAL_SRC_FILES := $(call all-java-files-under, dummy) LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \ - frameworks/support/v7/appcompat/res + frameworks/support/v7/appcompat/res \ + frameworks/support/v7/recyclerview/res LOCAL_AAPT_FLAGS := \ --auto-add-overlay \ - --extra-packages android.support.v7.appcompat + --extra-packages android.support.v7.appcompat \ + --extra-packages android.support.v7.recyclerview LOCAL_JAR_EXCLUDE_FILES := none include $(BUILD_STATIC_JAVA_LIBRARY) @@ -38,7 +40,8 @@ LOCAL_SDK_VERSION := 7 LOCAL_SRC_FILES := $(call all-java-files-under, base) LOCAL_JAVA_LIBRARIES := android-support-design-res \ android-support-v4 \ - android-support-v7-appcompat + android-support-v7-appcompat \ + android-support-v7-recyclerview include $(BUILD_STATIC_JAVA_LIBRARY) # A helper sub-library that makes direct use of Eclair MR1 APIs @@ -49,7 +52,8 @@ LOCAL_SRC_FILES := $(call all-java-files-under, eclair-mr1) LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-base LOCAL_JAVA_LIBRARIES := android-support-design-res \ android-support-v4 \ - android-support-v7-appcompat + android-support-v7-appcompat \ + android-support-v7-recyclerview include $(BUILD_STATIC_JAVA_LIBRARY) # A helper sub-library that makes direct use of Honeycomb APIs @@ -60,7 +64,8 @@ LOCAL_SRC_FILES := $(call all-java-files-under, honeycomb) LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-eclair-mr1 LOCAL_JAVA_LIBRARIES := android-support-design-res \ android-support-v4 \ - android-support-v7-appcompat + android-support-v7-appcompat \ + android-support-v7-recyclerview include $(BUILD_STATIC_JAVA_LIBRARY) # A helper sub-library that makes direct use of Honeycomb MR1 APIs @@ -71,7 +76,8 @@ LOCAL_SRC_FILES := $(call all-java-files-under, honeycomb-mr1) LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-honeycomb LOCAL_JAVA_LIBRARIES := android-support-design-res \ android-support-v4 \ - android-support-v7-appcompat + android-support-v7-appcompat \ + android-support-v7-recyclerview include $(BUILD_STATIC_JAVA_LIBRARY) # A helper sub-library that makes direct use of Lollipop APIs @@ -82,7 +88,8 @@ LOCAL_SRC_FILES := $(call all-java-files-under, lollipop) LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-honeycomb-mr1 LOCAL_JAVA_LIBRARIES := android-support-design-res \ android-support-v4 \ - android-support-v7-appcompat + android-support-v7-appcompat \ + android-support-v7-recyclerview include $(BUILD_STATIC_JAVA_LIBRARY) # Here is the final static library that apps can link against. @@ -96,7 +103,8 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-lollipop LOCAL_JAVA_LIBRARIES := android-support-design-res \ android-support-v4 \ - android-support-v7-appcompat + android-support-v7-appcompat \ + android-support-v7-recyclerview include $(BUILD_STATIC_JAVA_LIBRARY) # API Check diff --git a/build.gradle b/build.gradle index 5f5482f51..e785f83c5 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,7 @@ archivesBaseName = 'design' dependencies { compile project(':support-v4') compile project(':support-appcompat-v7') + compile project(':support-recyclerview-v7') } android { diff --git a/res/layout/design_navigation_item.xml b/res/layout/design_navigation_item.xml index 14c1be3a0..3a6a90c6f 100644 --- a/res/layout/design_navigation_item.xml +++ b/res/layout/design_navigation_item.xml @@ -19,4 +19,5 @@ android:layout_width="match_parent" android:layout_height="?attr/listPreferredItemHeightSmall" android:paddingLeft="?attr/listPreferredItemPaddingLeft" - android:paddingRight="?attr/listPreferredItemPaddingRight"/> + android:paddingRight="?attr/listPreferredItemPaddingRight" + android:foreground="?attr/selectableItemBackground"/> diff --git a/res/layout/design_navigation_menu.xml b/res/layout/design_navigation_menu.xml index 02b7aad1e..2a3bbf058 100644 --- a/res/layout/design_navigation_menu.xml +++ b/res/layout/design_navigation_menu.xml @@ -16,6 +16,7 @@ --> + android:listSelector="?attr/selectableItemBackground" + app:layoutManager="android.support.v7.widget.LinearLayoutManager"/> diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 785308a55..478c07b09 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -56,6 +56,12 @@ + + + + + + diff --git a/src/android/support/design/internal/ForegroundLinearLayout.java b/src/android/support/design/internal/ForegroundLinearLayout.java new file mode 100644 index 000000000..373ba4806 --- /dev/null +++ b/src/android/support/design/internal/ForegroundLinearLayout.java @@ -0,0 +1,229 @@ +/* + * 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. + */ + +package android.support.design.internal; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.design.R; +import android.support.v7.widget.LinearLayoutCompat; +import android.util.AttributeSet; +import android.view.Gravity; + +/** + * @hide + */ +public class ForegroundLinearLayout extends LinearLayoutCompat { + + private Drawable mForeground; + + private final Rect mSelfBounds = new Rect(); + + private final Rect mOverlayBounds = new Rect(); + + private int mForegroundGravity = Gravity.FILL; + + protected boolean mForegroundInPadding = true; + + boolean mForegroundBoundsChanged = false; + + public ForegroundLinearLayout(Context context) { + super(context); + } + + public ForegroundLinearLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ForegroundLinearLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundLinearLayout, + defStyle, 0); + + mForegroundGravity = a.getInt( + R.styleable.ForegroundLinearLayout_android_foregroundGravity, mForegroundGravity); + + final Drawable d = a.getDrawable(R.styleable.ForegroundLinearLayout_android_foreground); + if (d != null) { + setForeground(d); + } + + mForegroundInPadding = a.getBoolean( + R.styleable.ForegroundLinearLayout_foregroundInsidePadding, true); + + a.recycle(); + } + + /** + * Describes how the foreground is positioned. + * + * @return foreground gravity. + * @see #setForegroundGravity(int) + */ + public int getForegroundGravity() { + return mForegroundGravity; + } + + /** + * Describes how the foreground is positioned. Defaults to START and TOP. + * + * @param foregroundGravity See {@link android.view.Gravity} + * @see #getForegroundGravity() + */ + public void setForegroundGravity(int foregroundGravity) { + if (mForegroundGravity != foregroundGravity) { + if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { + foregroundGravity |= Gravity.START; + } + + if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { + foregroundGravity |= Gravity.TOP; + } + + mForegroundGravity = foregroundGravity; + + if (mForegroundGravity == Gravity.FILL && mForeground != null) { + Rect padding = new Rect(); + mForeground.getPadding(padding); + } + + requestLayout(); + } + } + + @Override + protected boolean verifyDrawable(Drawable who) { + return super.verifyDrawable(who) || (who == mForeground); + } + + @Override + public void jumpDrawablesToCurrentState() { + super.jumpDrawablesToCurrentState(); + if (mForeground != null) { + mForeground.jumpToCurrentState(); + } + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + if (mForeground != null && mForeground.isStateful()) { + mForeground.setState(getDrawableState()); + } + } + + /** + * Supply a Drawable that is to be rendered on top of all of the child + * views in the frame layout. Any padding in the Drawable will be taken + * into account by ensuring that the children are inset to be placed + * inside of the padding area. + * + * @param drawable The Drawable to be drawn on top of the children. + */ + public void setForeground(Drawable drawable) { + if (mForeground != drawable) { + if (mForeground != null) { + mForeground.setCallback(null); + unscheduleDrawable(mForeground); + } + + mForeground = drawable; + + if (drawable != null) { + setWillNotDraw(false); + drawable.setCallback(this); + if (drawable.isStateful()) { + drawable.setState(getDrawableState()); + } + if (mForegroundGravity == Gravity.FILL) { + Rect padding = new Rect(); + drawable.getPadding(padding); + } + } else { + setWillNotDraw(true); + } + requestLayout(); + invalidate(); + } + } + + /** + * Returns the drawable used as the foreground of this FrameLayout. The + * foreground drawable, if non-null, is always drawn on top of the children. + * + * @return A Drawable or null if no foreground was set. + */ + public Drawable getForeground() { + return mForeground; + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + mForegroundBoundsChanged |= changed; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mForegroundBoundsChanged = true; + } + + @Override + public void draw(@NonNull Canvas canvas) { + super.draw(canvas); + + if (mForeground != null) { + final Drawable foreground = mForeground; + + if (mForegroundBoundsChanged) { + mForegroundBoundsChanged = false; + final Rect selfBounds = mSelfBounds; + final Rect overlayBounds = mOverlayBounds; + + final int w = getRight() - getLeft(); + final int h = getBottom() - getTop(); + + if (mForegroundInPadding) { + selfBounds.set(0, 0, w, h); + } else { + selfBounds.set(getPaddingLeft(), getPaddingTop(), + w - getPaddingRight(), h - getPaddingBottom()); + } + + Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(), + foreground.getIntrinsicHeight(), selfBounds, overlayBounds); + foreground.setBounds(overlayBounds); + } + + foreground.draw(canvas); + } + } + + @Override + public void drawableHotspotChanged(float x, float y) { + super.drawableHotspotChanged(x, y); + if (mForeground != null) { + mForeground.setHotspot(x, y); + } + } + +} diff --git a/src/android/support/design/internal/NavigationMenuItemView.java b/src/android/support/design/internal/NavigationMenuItemView.java index 1d819df3d..1809789bc 100644 --- a/src/android/support/design/internal/NavigationMenuItemView.java +++ b/src/android/support/design/internal/NavigationMenuItemView.java @@ -27,7 +27,6 @@ import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v4.widget.TextViewCompat; import android.support.v7.internal.view.menu.MenuItemImpl; import android.support.v7.internal.view.menu.MenuView; -import android.support.v7.widget.LinearLayoutCompat; import android.util.AttributeSet; import android.util.TypedValue; import android.view.LayoutInflater; @@ -39,7 +38,7 @@ import android.widget.FrameLayout; /** * @hide */ -public class NavigationMenuItemView extends LinearLayoutCompat implements MenuView.ItemView { +public class NavigationMenuItemView extends ForegroundLinearLayout implements MenuView.ItemView { private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked}; @@ -69,8 +68,6 @@ public class NavigationMenuItemView extends LinearLayoutCompat implements MenuVi R.dimen.design_navigation_icon_size); mTextView = (CheckedTextView) findViewById(R.id.design_menu_item_text); mTextView.setDuplicateParentStateEnabled(true); - // Prevent the action view from stealing the event on the item row. - setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS); } @Override @@ -91,6 +88,13 @@ public class NavigationMenuItemView extends LinearLayoutCompat implements MenuVi setActionView(itemData.getActionView()); } + public void recycle() { + if (mActionArea != null) { + mActionArea.removeAllViews(); + } + mTextView.setCompoundDrawables(null, null, null, null); + } + private void setActionView(View actionView) { if (mActionArea == null) { mActionArea = (FrameLayout) ((ViewStub) findViewById( diff --git a/src/android/support/design/internal/NavigationMenuPresenter.java b/src/android/support/design/internal/NavigationMenuPresenter.java index e79a421b7..f238bcb2c 100644 --- a/src/android/support/design/internal/NavigationMenuPresenter.java +++ b/src/android/support/design/internal/NavigationMenuPresenter.java @@ -33,14 +33,13 @@ import android.support.v7.internal.view.menu.MenuItemImpl; import android.support.v7.internal.view.menu.MenuPresenter; import android.support.v7.internal.view.menu.MenuView; import android.support.v7.internal.view.menu.SubMenuBuilder; +import android.support.v7.widget.RecyclerView; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.SubMenu; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.BaseAdapter; import android.widget.LinearLayout; import android.widget.TextView; @@ -49,13 +48,13 @@ import java.util.ArrayList; /** * @hide */ -public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnItemClickListener { +public class NavigationMenuPresenter implements MenuPresenter { private static final String STATE_HIERARCHY = "android:menu:list"; private static final String STATE_ADAPTER = "android:menu:adapter"; private NavigationMenuView mMenuView; - private LinearLayout mHeader; + private LinearLayout mHeaderLayout; private Callback mCallback; private MenuBuilder mMenu; @@ -100,11 +99,10 @@ public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnIte if (mAdapter == null) { mAdapter = new NavigationMenuAdapter(); } - mHeader = (LinearLayout) mLayoutInflater.inflate(R.layout.design_navigation_item_header, - mMenuView, false); - mMenuView.addHeaderView(mHeader, null, false); + mHeaderLayout = (LinearLayout) mLayoutInflater + .inflate(R.layout.design_navigation_item_header, + mMenuView, false); mMenuView.setAdapter(mAdapter); - mMenuView.setOnItemClickListener(this); } return mMenuView; } @@ -112,7 +110,7 @@ public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnIte @Override public void updateMenuView(boolean cleared) { if (mAdapter != null) { - mAdapter.notifyDataSetChanged(); + mAdapter.update(); } } @@ -184,40 +182,25 @@ public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnIte } } - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - int positionInAdapter = position - mMenuView.getHeaderViewsCount(); - if (positionInAdapter >= 0) { - setUpdateSuspended(true); - MenuItemImpl item = mAdapter.getItem(positionInAdapter).getMenuItem(); - boolean result = mMenu.performItemAction(item, this, 0); - if (item != null && item.isCheckable() && result) { - mAdapter.setCheckedItem(item); - } - setUpdateSuspended(false); - updateMenuView(false); - } - } - public void setCheckedItem(MenuItemImpl item) { mAdapter.setCheckedItem(item); } public View inflateHeaderView(@LayoutRes int res) { - View view = mLayoutInflater.inflate(res, mHeader, false); + View view = mLayoutInflater.inflate(res, mHeaderLayout, false); addHeaderView(view); return view; } public void addHeaderView(@NonNull View view) { - mHeader.addView(view); + mHeaderLayout.addView(view); // The padding on top should be cleared. mMenuView.setPadding(0, 0, 0, mMenuView.getPaddingBottom()); } public void removeHeaderView(@NonNull View view) { - mHeader.removeView(view); - if (mHeader.getChildCount() == 0) { + mHeaderLayout.removeView(view); + if (mHeaderLayout.getChildCount() == 0) { mMenuView.setPadding(0, mPaddingTopDefault, 0, mMenuView.getPaddingBottom()); } } @@ -262,7 +245,69 @@ public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnIte } } - private class NavigationMenuAdapter extends BaseAdapter { + private abstract static class ViewHolder extends RecyclerView.ViewHolder { + + public ViewHolder(View itemView) { + super(itemView); + } + + } + + private static class NormalViewHolder extends ViewHolder { + + public NormalViewHolder(LayoutInflater inflater, ViewGroup parent, + View.OnClickListener listener) { + super(inflater.inflate(R.layout.design_navigation_item, parent, false)); + itemView.setOnClickListener(listener); + } + + } + + private static class SubheaderViewHolder extends ViewHolder { + + public SubheaderViewHolder(LayoutInflater inflater, ViewGroup parent) { + super(inflater.inflate(R.layout.design_navigation_item_subheader, parent, false)); + } + + } + + private static class SeparatorViewHolder extends ViewHolder { + + public SeparatorViewHolder(LayoutInflater inflater, ViewGroup parent) { + super(inflater.inflate(R.layout.design_navigation_item_separator, parent, false)); + } + + } + + private static class HeaderViewHolder extends ViewHolder { + + public HeaderViewHolder(View itemView) { + super(itemView); + } + + } + + /** + * Handles click events for the menu items. The items has to be {@link NavigationMenuItemView}. + */ + private final View.OnClickListener mOnClickListener = new View.OnClickListener() { + + @Override + public void onClick(View v) { + NavigationMenuItemView itemView = (NavigationMenuItemView) v; + setUpdateSuspended(true); + MenuItemImpl item = itemView.getItemData(); + boolean result = mMenu.performItemAction(item, NavigationMenuPresenter.this, 0); + if (item != null && item.isCheckable() && result) { + mAdapter.setCheckedItem(item); + } + setUpdateSuspended(false); + updateMenuView(false); + } + + }; + + private class NavigationMenuAdapter extends RecyclerView.Adapter { private static final String STATE_CHECKED_ITEM = "android:menu:checked"; @@ -270,6 +315,7 @@ public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnIte 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 mItems = new ArrayList<>(); private MenuItemImpl mCheckedItem; @@ -280,49 +326,54 @@ public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnIte prepareMenuItems(); } - @Override - public int getCount() { - return mItems.size(); - } - - @Override - public NavigationMenuItem getItem(int position) { - return mItems.get(position); - } - @Override public long getItemId(int position) { return position; } @Override - public int getViewTypeCount() { - return 3; + public int getItemCount() { + return mItems.size(); } @Override public int getItemViewType(int position) { - NavigationMenuItem item = getItem(position); - if (item.isSeparator()) { + NavigationMenuItem item = mItems.get(position); + if (item instanceof NavigationMenuSeparatorItem) { return VIEW_TYPE_SEPARATOR; - } else if (item.getMenuItem().hasSubMenu()) { - return VIEW_TYPE_SUBHEADER; - } else { - return VIEW_TYPE_NORMAL; + } 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 View getView(int position, View convertView, ViewGroup parent) { - NavigationMenuItem item = getItem(position); - int viewType = getItemViewType(position); + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType) { case VIEW_TYPE_NORMAL: - if (convertView == null) { - convertView = mLayoutInflater.inflate(R.layout.design_navigation_item, - parent, false); - } - NavigationMenuItemView itemView = (NavigationMenuItemView) convertView; + 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(itemView.getContext(), mTextAppearance); @@ -332,42 +383,40 @@ public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnIte } itemView.setBackgroundDrawable(mItemBackground != null ? mItemBackground.getConstantState().newDrawable() : null); + NavigationMenuTextItem item = (NavigationMenuTextItem) mItems.get(position); itemView.initialize(item.getMenuItem(), 0); break; - case VIEW_TYPE_SUBHEADER: - if (convertView == null) { - convertView = mLayoutInflater.inflate( - R.layout.design_navigation_item_subheader, parent, false); - } - TextView subHeader = (TextView) convertView; + } + 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: - if (convertView == null) { - convertView = mLayoutInflater.inflate( - R.layout.design_navigation_item_separator, parent, false); - } - convertView.setPadding(0, item.getPaddingTop(), 0, + } + 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; + } } - return convertView; + } @Override - public boolean areAllItemsEnabled() { - return false; + public void onViewRecycled(ViewHolder holder) { + if (holder instanceof NormalViewHolder) { + ((NavigationMenuItemView) holder.itemView).recycle(); + } } - @Override - public boolean isEnabled(int position) { - return getItem(position).isEnabled(); - } - - @Override - public void notifyDataSetChanged() { + public void update() { prepareMenuItems(); - super.notifyDataSetChanged(); + notifyDataSetChanged(); } /** @@ -380,6 +429,8 @@ public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnIte } mUpdateSuspended = true; mItems.clear(); + mItems.add(new NavigationMenuHeaderItem()); + int currentGroupId = -1; int currentGroupStart = 0; boolean currentGroupHasIcon = false; @@ -395,9 +446,9 @@ public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnIte SubMenu subMenu = item.getSubMenu(); if (subMenu.hasVisibleItems()) { if (i != 0) { - mItems.add(NavigationMenuItem.separator(mPaddingSeparator, 0)); + mItems.add(new NavigationMenuSeparatorItem(mPaddingSeparator, 0)); } - mItems.add(NavigationMenuItem.of(item)); + mItems.add(new NavigationMenuTextItem(item)); boolean subMenuHasIcon = false; int subMenuStart = mItems.size(); for (int j = 0, size = subMenu.size(); j < size; j++) { @@ -412,7 +463,7 @@ public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnIte if (item.isChecked()) { setCheckedItem(item); } - mItems.add(NavigationMenuItem.of(subMenuItem)); + mItems.add(new NavigationMenuTextItem(subMenuItem)); } } if (subMenuHasIcon) { @@ -426,7 +477,7 @@ public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnIte currentGroupHasIcon = item.getIcon() != null; if (i != 0) { currentGroupStart++; - mItems.add(NavigationMenuItem.separator( + mItems.add(new NavigationMenuSeparatorItem( mPaddingSeparator, mPaddingSeparator)); } } else if (!currentGroupHasIcon && item.getIcon() != null) { @@ -436,7 +487,7 @@ public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnIte if (currentGroupHasIcon && item.getIcon() == null) { item.setIcon(android.R.color.transparent); } - mItems.add(NavigationMenuItem.of(item)); + mItems.add(new NavigationMenuTextItem(item)); currentGroupId = groupId; } } @@ -445,7 +496,8 @@ public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnIte private void appendTransparentIconIfMissing(int startIndex, int endIndex) { for (int i = startIndex; i < endIndex; i++) { - MenuItem item = mItems.get(i).getMenuItem(); + NavigationMenuTextItem textItem = (NavigationMenuTextItem) mItems.get(i); + MenuItem item = textItem.getMenuItem(); if (item.getIcon() == null) { if (mTransparentIcon == null) { mTransparentIcon = new ColorDrawable(android.R.color.transparent); @@ -474,12 +526,14 @@ public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnIte // Store the states of the action views. SparseArray actionViewStates = new SparseArray<>(); for (NavigationMenuItem navigationMenuItem : mItems) { - MenuItemImpl item = navigationMenuItem.getMenuItem(); - View actionView = item != null ? item.getActionView() : null; - if (actionView != null) { - ParcelableSparseArray container = new ParcelableSparseArray(); - actionView.saveHierarchyState(container); - actionViewStates.put(item.getItemId(), container); + 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); @@ -491,10 +545,12 @@ public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnIte if (checkedItem != 0) { mUpdateSuspended = true; for (NavigationMenuItem item : mItems) { - MenuItemImpl menuItem = item.getMenuItem(); - if (menuItem != null && menuItem.getItemId() == checkedItem) { - setCheckedItem(menuItem); - break; + if (item instanceof NavigationMenuTextItem) { + MenuItemImpl menuItem = ((NavigationMenuTextItem) item).getMenuItem(); + if (menuItem != null && menuItem.getItemId() == checkedItem) { + setCheckedItem(menuItem); + break; + } } } mUpdateSuspended = false; @@ -504,10 +560,12 @@ public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnIte SparseArray actionViewStates = state .getSparseParcelableArray(STATE_ACTION_VIEWS); for (NavigationMenuItem navigationMenuItem : mItems) { - MenuItemImpl item = navigationMenuItem.getMenuItem(); - View actionView = item != null ? item.getActionView() : null; - if (actionView != null) { - actionView.restoreHierarchyState(actionViewStates.get(item.getItemId())); + if (navigationMenuItem instanceof NavigationMenuTextItem) { + MenuItemImpl item = ((NavigationMenuTextItem) navigationMenuItem).getMenuItem(); + View actionView = item != null ? item.getActionView() : null; + if (actionView != null) { + actionView.restoreHierarchyState(actionViewStates.get(item.getItemId())); + } } } } @@ -519,37 +577,42 @@ public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnIte } /** - * Wraps {@link MenuItemImpl}. This allows separators to be counted as items in list. + * Unified data model for all sorts of navigation menu items. */ - private static class NavigationMenuItem { + private interface NavigationMenuItem { + } + + /** + * Normal or subheader items. + */ + private static class NavigationMenuTextItem implements NavigationMenuItem { - /** The item; null for separators */ private final MenuItemImpl mMenuItem; - /** Padding top; used only for separators */ + private NavigationMenuTextItem(MenuItemImpl item) { + mMenuItem = item; + } + + public MenuItemImpl getMenuItem() { + return mMenuItem; + } + + } + + /** + * Separator items. + */ + private static class NavigationMenuSeparatorItem implements NavigationMenuItem { + private final int mPaddingTop; - /** Padding bottom; used only for separators */ private final int mPaddingBottom; - private NavigationMenuItem(MenuItemImpl item, int paddingTop, int paddingBottom) { - mMenuItem = item; + public NavigationMenuSeparatorItem(int paddingTop, int paddingBottom) { mPaddingTop = paddingTop; mPaddingBottom = paddingBottom; } - public static NavigationMenuItem of(MenuItemImpl item) { - return new NavigationMenuItem(item, 0, 0); - } - - public static NavigationMenuItem separator(int paddingTop, int paddingBottom) { - return new NavigationMenuItem(null, paddingTop, paddingBottom); - } - - public boolean isSeparator() { - return mMenuItem == null; - } - public int getPaddingTop() { return mPaddingTop; } @@ -558,15 +621,13 @@ public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnIte return mPaddingBottom; } - public MenuItemImpl getMenuItem() { - return mMenuItem; - } - - public boolean isEnabled() { - // Separators and subheaders never respond to click - return mMenuItem != null && !mMenuItem.hasSubMenu() && mMenuItem.isEnabled(); - } + } + /** + * Header (not subheader) items. + */ + private static class NavigationMenuHeaderItem implements NavigationMenuItem { + // The actual content is hold by NavigationMenuPresenter#mHeaderLayout. } } diff --git a/src/android/support/design/internal/NavigationMenuView.java b/src/android/support/design/internal/NavigationMenuView.java index 054b800c7..50649b70a 100644 --- a/src/android/support/design/internal/NavigationMenuView.java +++ b/src/android/support/design/internal/NavigationMenuView.java @@ -19,13 +19,13 @@ package android.support.design.internal; import android.content.Context; import android.support.v7.internal.view.menu.MenuBuilder; import android.support.v7.internal.view.menu.MenuView; +import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; -import android.widget.ListView; /** * @hide */ -public class NavigationMenuView extends ListView implements MenuView { +public class NavigationMenuView extends RecyclerView implements MenuView { public NavigationMenuView(Context context) { this(context, null);