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