1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.v7.view.menu; 18 19import android.content.Context; 20import android.graphics.Rect; 21import android.support.annotation.NonNull; 22import android.support.annotation.Nullable; 23import android.support.v4.internal.view.SupportMenu; 24import android.view.MenuItem; 25import android.view.View; 26import android.view.View.MeasureSpec; 27import android.view.ViewGroup; 28import android.widget.AdapterView; 29import android.widget.FrameLayout; 30import android.widget.HeaderViewListAdapter; 31import android.widget.ListAdapter; 32import android.widget.PopupWindow; 33 34/** 35 * Base class for a menu popup abstraction - i.e., some type of menu, housed in a popup window 36 * environment. 37 */ 38abstract class MenuPopup implements ShowableListMenu, MenuPresenter, 39 AdapterView.OnItemClickListener { 40 private Rect mEpicenterBounds; 41 42 public abstract void setForceShowIcon(boolean forceShow); 43 44 /** 45 * Adds the given menu to the popup, if it is capable of displaying submenus within itself. 46 * If menu is the first menu shown, it won't be displayed until show() is called. 47 * If the popup was already showing, adding a submenu via this method will cause that new 48 * submenu to be shown immediately (that is, if this MenuPopup implementation is capable of 49 * showing its own submenus). 50 * 51 * @param menu 52 */ 53 public abstract void addMenu(MenuBuilder menu); 54 55 public abstract void setGravity(int dropDownGravity); 56 57 public abstract void setAnchorView(View anchor); 58 59 public abstract void setHorizontalOffset(int x); 60 61 public abstract void setVerticalOffset(int y); 62 63 /** 64 * Specifies the anchor-relative bounds of the popup's transition 65 * epicenter. 66 * 67 * @param bounds anchor-relative bounds 68 */ 69 public void setEpicenterBounds(Rect bounds) { 70 mEpicenterBounds = bounds; 71 } 72 73 /** 74 * @return anchor-relative bounds of the popup's transition epicenter 75 */ 76 public Rect getEpicenterBounds() { 77 return mEpicenterBounds; 78 } 79 80 /** 81 * Set whether a title entry should be shown in the popup menu (if a title exists for the 82 * menu). 83 * 84 * @param showTitle 85 */ 86 public abstract void setShowTitle(boolean showTitle); 87 88 /** 89 * Set a listener to receive a callback when the popup is dismissed. 90 * 91 * @param listener Listener that will be notified when the popup is dismissed. 92 */ 93 public abstract void setOnDismissListener(PopupWindow.OnDismissListener listener); 94 95 @Override 96 public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) { 97 // Don't need to do anything; we added as a presenter in the constructor. 98 } 99 100 @Override 101 public MenuView getMenuView(ViewGroup root) { 102 throw new UnsupportedOperationException("MenuPopups manage their own views"); 103 } 104 105 @Override 106 public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { 107 return false; 108 } 109 110 @Override 111 public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { 112 return false; 113 } 114 115 @Override 116 public int getId() { 117 return 0; 118 } 119 120 @Override 121 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 122 ListAdapter outerAdapter = (ListAdapter) parent.getAdapter(); 123 MenuAdapter wrappedAdapter = toMenuAdapter(outerAdapter); 124 125 // Use the position from the outer adapter so that if a header view was added, we don't get 126 // an off-by-1 error in position. 127 wrappedAdapter.mAdapterMenu.performItemAction( 128 (MenuItem) outerAdapter.getItem(position), 129 this, // always make sure that we show the sub-menu 130 closeMenuOnSubMenuOpened() ? 0 : SupportMenu.FLAG_KEEP_OPEN_ON_SUBMENU_OPENED); 131 } 132 133 /** 134 * Measures the width of the given menu view. 135 * 136 * @param view The view to measure. 137 * @return The width. 138 */ 139 protected static int measureIndividualMenuWidth(ListAdapter adapter, ViewGroup parent, 140 Context context, int maxAllowedWidth) { 141 // Menus don't tend to be long, so this is more sane than it looks. 142 int maxWidth = 0; 143 View itemView = null; 144 int itemType = 0; 145 146 final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 147 final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 148 final int count = adapter.getCount(); 149 for (int i = 0; i < count; i++) { 150 final int positionType = adapter.getItemViewType(i); 151 if (positionType != itemType) { 152 itemType = positionType; 153 itemView = null; 154 } 155 156 if (parent == null) { 157 parent = new FrameLayout(context); 158 } 159 160 itemView = adapter.getView(i, itemView, parent); 161 itemView.measure(widthMeasureSpec, heightMeasureSpec); 162 163 final int itemWidth = itemView.getMeasuredWidth(); 164 if (itemWidth >= maxAllowedWidth) { 165 return maxAllowedWidth; 166 } else if (itemWidth > maxWidth) { 167 maxWidth = itemWidth; 168 } 169 } 170 171 return maxWidth; 172 } 173 174 /** 175 * Converts the given ListAdapter originating from a menu, to a MenuAdapter, accounting for 176 * the possibility of the parameter adapter actually wrapping the MenuAdapter. (That could 177 * happen if a header view was added on the menu.) 178 * 179 * @param adapter 180 * @return 181 */ 182 protected static MenuAdapter toMenuAdapter(ListAdapter adapter) { 183 if (adapter instanceof HeaderViewListAdapter) { 184 return (MenuAdapter) ((HeaderViewListAdapter) adapter).getWrappedAdapter(); 185 } 186 return (MenuAdapter) adapter; 187 } 188 189 /** 190 * Returns whether icon spacing needs to be preserved for the given menu, based on whether any 191 * of its items contains an icon. 192 * 193 * NOTE: This should only be used for non-overflow-only menus, because this method does not 194 * take into account whether the menu items are being shown as part of the popup or or being 195 * shown as actions in the action bar. 196 * 197 * @param menu 198 * @return Whether to preserve icon spacing. 199 */ 200 protected static boolean shouldPreserveIconSpacing(MenuBuilder menu) { 201 boolean preserveIconSpacing = false; 202 final int count = menu.size(); 203 204 for (int i = 0; i < count; i++) { 205 MenuItem childItem = menu.getItem(i); 206 if (childItem.isVisible() && childItem.getIcon() != null) { 207 preserveIconSpacing = true; 208 break; 209 } 210 } 211 212 return preserveIconSpacing; 213 } 214 215 protected boolean closeMenuOnSubMenuOpened() { 216 return true; 217 } 218} 219