16142a54baae3289f734947c6b5375b12eb0fb722Chris Banes/* 26142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Copyright (C) 2016 The Android Open Source Project 36142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * 46142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Licensed under the Apache License, Version 2.0 (the "License"); 56142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * you may not use this file except in compliance with the License. 66142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * You may obtain a copy of the License at 76142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * 86142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * http://www.apache.org/licenses/LICENSE-2.0 96142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * 106142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Unless required by applicable law or agreed to in writing, software 116142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * distributed under the License is distributed on an "AS IS" BASIS, 126142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 136142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * See the License for the specific language governing permissions and 146142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * limitations under the License. 156142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */ 166142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 176142a54baae3289f734947c6b5375b12eb0fb722Chris Banespackage android.support.v7.view.menu; 186142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 196142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.content.Context; 206142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.graphics.Rect; 216142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.support.annotation.NonNull; 226142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.support.annotation.Nullable; 236142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.view.MenuItem; 246142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.view.View; 256142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.view.View.MeasureSpec; 266142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.view.ViewGroup; 276142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.widget.AdapterView; 286142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.widget.FrameLayout; 296142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.widget.HeaderViewListAdapter; 306142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.widget.ListAdapter; 316142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.widget.PopupWindow; 326142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 336142a54baae3289f734947c6b5375b12eb0fb722Chris Banes/** 346142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Base class for a menu popup abstraction - i.e., some type of menu, housed in a popup window 356142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * environment. 366142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */ 376142a54baae3289f734947c6b5375b12eb0fb722Chris Banesabstract class MenuPopup implements ShowableListMenu, MenuPresenter, 386142a54baae3289f734947c6b5375b12eb0fb722Chris Banes AdapterView.OnItemClickListener { 396142a54baae3289f734947c6b5375b12eb0fb722Chris Banes private Rect mEpicenterBounds; 406142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 416142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public abstract void setForceShowIcon(boolean forceShow); 426142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 436142a54baae3289f734947c6b5375b12eb0fb722Chris Banes /** 446142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Adds the given menu to the popup, if it is capable of displaying submenus within itself. 456142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * If menu is the first menu shown, it won't be displayed until show() is called. 466142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * If the popup was already showing, adding a submenu via this method will cause that new 476142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * submenu to be shown immediately (that is, if this MenuPopup implementation is capable of 486142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * showing its own submenus). 496142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * 506142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * @param menu 516142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */ 526142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public abstract void addMenu(MenuBuilder menu); 536142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 546142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public abstract void setGravity(int dropDownGravity); 556142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 566142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public abstract void setAnchorView(View anchor); 576142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 586142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public abstract void setHorizontalOffset(int x); 596142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 606142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public abstract void setVerticalOffset(int y); 616142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 626142a54baae3289f734947c6b5375b12eb0fb722Chris Banes /** 636142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Specifies the anchor-relative bounds of the popup's transition 646142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * epicenter. 656142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * 666142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * @param bounds anchor-relative bounds 676142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */ 686142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public void setEpicenterBounds(Rect bounds) { 696142a54baae3289f734947c6b5375b12eb0fb722Chris Banes mEpicenterBounds = bounds; 706142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 716142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 726142a54baae3289f734947c6b5375b12eb0fb722Chris Banes /** 736142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * @return anchor-relative bounds of the popup's transition epicenter 746142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */ 756142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public Rect getEpicenterBounds() { 766142a54baae3289f734947c6b5375b12eb0fb722Chris Banes return mEpicenterBounds; 776142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 786142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 796142a54baae3289f734947c6b5375b12eb0fb722Chris Banes /** 806142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Set whether a title entry should be shown in the popup menu (if a title exists for the 816142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * menu). 826142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * 836142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * @param showTitle 846142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */ 856142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public abstract void setShowTitle(boolean showTitle); 866142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 876142a54baae3289f734947c6b5375b12eb0fb722Chris Banes /** 886142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Set a listener to receive a callback when the popup is dismissed. 896142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * 906142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * @param listener Listener that will be notified when the popup is dismissed. 916142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */ 926142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public abstract void setOnDismissListener(PopupWindow.OnDismissListener listener); 936142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 946142a54baae3289f734947c6b5375b12eb0fb722Chris Banes @Override 956142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) { 966142a54baae3289f734947c6b5375b12eb0fb722Chris Banes // Don't need to do anything; we added as a presenter in the constructor. 976142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 986142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 996142a54baae3289f734947c6b5375b12eb0fb722Chris Banes @Override 1006142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public MenuView getMenuView(ViewGroup root) { 1016142a54baae3289f734947c6b5375b12eb0fb722Chris Banes throw new UnsupportedOperationException("MenuPopups manage their own views"); 1026142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1036142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1046142a54baae3289f734947c6b5375b12eb0fb722Chris Banes @Override 1056142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { 1066142a54baae3289f734947c6b5375b12eb0fb722Chris Banes return false; 1076142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1086142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1096142a54baae3289f734947c6b5375b12eb0fb722Chris Banes @Override 1106142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { 1116142a54baae3289f734947c6b5375b12eb0fb722Chris Banes return false; 1126142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1136142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1146142a54baae3289f734947c6b5375b12eb0fb722Chris Banes @Override 1156142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public int getId() { 1166142a54baae3289f734947c6b5375b12eb0fb722Chris Banes return 0; 1176142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1186142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1196142a54baae3289f734947c6b5375b12eb0fb722Chris Banes @Override 1206142a54baae3289f734947c6b5375b12eb0fb722Chris Banes public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1216142a54baae3289f734947c6b5375b12eb0fb722Chris Banes ListAdapter outerAdapter = (ListAdapter) parent.getAdapter(); 1226142a54baae3289f734947c6b5375b12eb0fb722Chris Banes MenuAdapter wrappedAdapter = toMenuAdapter(outerAdapter); 1236142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1246142a54baae3289f734947c6b5375b12eb0fb722Chris Banes // Use the position from the outer adapter so that if a header view was added, we don't get 1256142a54baae3289f734947c6b5375b12eb0fb722Chris Banes // an off-by-1 error in position. 1266142a54baae3289f734947c6b5375b12eb0fb722Chris Banes wrappedAdapter.mAdapterMenu.performItemAction((MenuItem) outerAdapter.getItem(position), 0); 1276142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1286142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1296142a54baae3289f734947c6b5375b12eb0fb722Chris Banes /** 1306142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Measures the width of the given menu view. 1316142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * 1326142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * @param view The view to measure. 1336142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * @return The width. 1346142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */ 1356142a54baae3289f734947c6b5375b12eb0fb722Chris Banes protected static int measureIndividualMenuWidth(ListAdapter adapter, ViewGroup parent, 1366142a54baae3289f734947c6b5375b12eb0fb722Chris Banes Context context, int maxAllowedWidth) { 1376142a54baae3289f734947c6b5375b12eb0fb722Chris Banes // Menus don't tend to be long, so this is more sane than it looks. 1386142a54baae3289f734947c6b5375b12eb0fb722Chris Banes int maxWidth = 0; 1396142a54baae3289f734947c6b5375b12eb0fb722Chris Banes View itemView = null; 1406142a54baae3289f734947c6b5375b12eb0fb722Chris Banes int itemType = 0; 1416142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1426142a54baae3289f734947c6b5375b12eb0fb722Chris Banes final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 1436142a54baae3289f734947c6b5375b12eb0fb722Chris Banes final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 1446142a54baae3289f734947c6b5375b12eb0fb722Chris Banes final int count = adapter.getCount(); 1456142a54baae3289f734947c6b5375b12eb0fb722Chris Banes for (int i = 0; i < count; i++) { 1466142a54baae3289f734947c6b5375b12eb0fb722Chris Banes final int positionType = adapter.getItemViewType(i); 1476142a54baae3289f734947c6b5375b12eb0fb722Chris Banes if (positionType != itemType) { 1486142a54baae3289f734947c6b5375b12eb0fb722Chris Banes itemType = positionType; 1496142a54baae3289f734947c6b5375b12eb0fb722Chris Banes itemView = null; 1506142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1516142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1526142a54baae3289f734947c6b5375b12eb0fb722Chris Banes if (parent == null) { 1536142a54baae3289f734947c6b5375b12eb0fb722Chris Banes parent = new FrameLayout(context); 1546142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1556142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1566142a54baae3289f734947c6b5375b12eb0fb722Chris Banes itemView = adapter.getView(i, itemView, parent); 1576142a54baae3289f734947c6b5375b12eb0fb722Chris Banes itemView.measure(widthMeasureSpec, heightMeasureSpec); 1586142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1596142a54baae3289f734947c6b5375b12eb0fb722Chris Banes final int itemWidth = itemView.getMeasuredWidth(); 1606142a54baae3289f734947c6b5375b12eb0fb722Chris Banes if (itemWidth >= maxAllowedWidth) { 1616142a54baae3289f734947c6b5375b12eb0fb722Chris Banes return maxAllowedWidth; 1626142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } else if (itemWidth > maxWidth) { 1636142a54baae3289f734947c6b5375b12eb0fb722Chris Banes maxWidth = itemWidth; 1646142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1656142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1666142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1676142a54baae3289f734947c6b5375b12eb0fb722Chris Banes return maxWidth; 1686142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1696142a54baae3289f734947c6b5375b12eb0fb722Chris Banes 1706142a54baae3289f734947c6b5375b12eb0fb722Chris Banes /** 1716142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Converts the given ListAdapter originating from a menu, to a MenuAdapter, accounting for 1726142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * the possibility of the parameter adapter actually wrapping the MenuAdapter. (That could 1736142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * happen if a header view was added on the menu.) 1746142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * 1756142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * @param adapter 1766142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * @return 1776142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */ 1786142a54baae3289f734947c6b5375b12eb0fb722Chris Banes protected static MenuAdapter toMenuAdapter(ListAdapter adapter) { 1796142a54baae3289f734947c6b5375b12eb0fb722Chris Banes if (adapter instanceof HeaderViewListAdapter) { 1806142a54baae3289f734947c6b5375b12eb0fb722Chris Banes return (MenuAdapter) ((HeaderViewListAdapter) adapter).getWrappedAdapter(); 1816142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 1826142a54baae3289f734947c6b5375b12eb0fb722Chris Banes return (MenuAdapter) adapter; 1836142a54baae3289f734947c6b5375b12eb0fb722Chris Banes } 184d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes 185d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes /** 186d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes * Returns whether icon spacing needs to be preserved for the given menu, based on whether any 187d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes * of its items contains an icon. 188d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes * 189d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes * NOTE: This should only be used for non-overflow-only menus, because this method does not 190d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes * take into account whether the menu items are being shown as part of the popup or or being 191d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes * shown as actions in the action bar. 192d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes * 193d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes * @param menu 194d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes * @return Whether to preserve icon spacing. 195d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes */ 196d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes protected static boolean shouldPreserveIconSpacing(MenuBuilder menu) { 197d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes boolean preserveIconSpacing = false; 198d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes final int count = menu.size(); 199d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes 200d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes for (int i = 0; i < count; i++) { 201d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes MenuItem childItem = menu.getItem(i); 202d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes if (childItem.isVisible() && childItem.getIcon() != null) { 203d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes preserveIconSpacing = true; 204d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes break; 205d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes } 206d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes } 207d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes 208d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes return preserveIconSpacing; 209d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes } 210d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes} 211