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