blob: 3a743562110f7de6993e448cb47c90ea1b73716c [file] [log] [blame]
Adam Powell96675b12010-06-10 18:58:59 -07001/*
2 * Copyright (C) 2010 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 */
Adam Powellfa18d182014-01-07 15:56:59 -080016package android.widget;
Adam Powell96675b12010-06-10 18:58:59 -070017
Siva Velusamy94a6d152015-05-05 15:07:00 -070018import android.annotation.NonNull;
Chris Banesa41b7892015-06-09 13:36:44 +000019import android.annotation.Nullable;
Tor Norbye417ee5b2015-03-10 20:57:37 -070020import android.annotation.StyleRes;
Artur Satayevad9254c2019-12-10 17:47:54 +000021import android.compat.annotation.UnsupportedAppUsage;
Adam Powell96675b12010-06-10 18:58:59 -070022import android.content.Context;
Adam Powell8028dd32010-07-15 10:16:33 -070023import android.content.res.Configuration;
Chris Banesa41b7892015-06-09 13:36:44 +000024import android.graphics.drawable.Drawable;
Adam Powell96675b12010-06-10 18:58:59 -070025import android.util.AttributeSet;
Alan Viverette3d0f21d2014-07-10 16:15:01 -070026import android.view.ContextThemeWrapper;
Adam Powell6b336f82010-08-10 20:13:01 -070027import android.view.Gravity;
Adam Powellfa18d182014-01-07 15:56:59 -080028import android.view.Menu;
Adam Powelle43340c2014-03-17 19:10:43 -070029import android.view.MenuItem;
Adam Powellcf78b3e2010-09-12 18:25:23 -070030import android.view.View;
Adam Powell640a66e2011-04-29 10:18:53 -070031import android.view.ViewDebug;
Adam Powell7ade1be2010-06-17 12:51:21 -070032import android.view.ViewGroup;
Siva Velusamy94a6d152015-05-05 15:07:00 -070033import android.view.ViewHierarchyEncoder;
Adam Powell7bc3ca02011-08-26 18:29:58 -070034import android.view.accessibility.AccessibilityEvent;
Aurimas Liutikas99441c52016-10-11 16:48:32 -070035
Adam Powellfa18d182014-01-07 15:56:59 -080036import com.android.internal.view.menu.ActionMenuItemView;
37import com.android.internal.view.menu.MenuBuilder;
38import com.android.internal.view.menu.MenuItemImpl;
Adam Powell04c0d462014-09-04 16:10:24 -070039import com.android.internal.view.menu.MenuPresenter;
Adam Powellfa18d182014-01-07 15:56:59 -080040import com.android.internal.view.menu.MenuView;
Adam Powell96675b12010-06-10 18:58:59 -070041
Adam Powell96675b12010-06-10 18:58:59 -070042/**
Adam Powellfa18d182014-01-07 15:56:59 -080043 * ActionMenuView is a presentation of a series of menu options as a View. It provides
44 * several top level options as action buttons while spilling remaining options over as
45 * items in an overflow menu. This allows applications to present packs of actions inline with
46 * specific or repeating content.
Adam Powell96675b12010-06-10 18:58:59 -070047 */
48public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView {
49 private static final String TAG = "ActionMenuView";
Aurimas Liutikas99441c52016-10-11 16:48:32 -070050
Adam Powell35aecd52011-07-01 13:43:49 -070051 static final int MIN_CELL_SIZE = 56; // dips
Adam Powellbe3c3292011-08-24 12:52:28 -070052 static final int GENERATED_ITEM_PADDING = 4; // dips
Adam Powell35aecd52011-07-01 13:43:49 -070053
Adam Powell96675b12010-06-10 18:58:59 -070054 private MenuBuilder mMenu;
Adam Powell7ade1be2010-06-17 12:51:21 -070055
Alan Viverette3d0f21d2014-07-10 16:15:01 -070056 /** Context against which to inflate popup menus. */
57 private Context mPopupContext;
58
59 /** Theme resource against which to inflate popup menus. */
60 private int mPopupTheme;
61
Adam Powell8028dd32010-07-15 10:16:33 -070062 private boolean mReserveOverflow;
Adam Powell696cba52011-03-29 10:38:16 -070063 private ActionMenuPresenter mPresenter;
Adam Powell04c0d462014-09-04 16:10:24 -070064 private MenuPresenter.Callback mActionMenuPresenterCallback;
Adam Powellc4612502014-09-04 19:16:39 -070065 private MenuBuilder.Callback mMenuBuilderCallback;
Adam Powell640a66e2011-04-29 10:18:53 -070066 private boolean mFormatItems;
Adam Powell89b09da2011-07-27 11:55:29 -070067 private int mFormatItemsWidth;
Adam Powell35aecd52011-07-01 13:43:49 -070068 private int mMinCellSize;
Adam Powellbe3c3292011-08-24 12:52:28 -070069 private int mGeneratedItemPadding;
Adam Powell8515ee82010-11-30 14:09:55 -080070
Adam Powelle43340c2014-03-17 19:10:43 -070071 private OnMenuItemClickListener mOnMenuItemClickListener;
72
Adam Powell96675b12010-06-10 18:58:59 -070073 public ActionMenuView(Context context) {
74 this(context, null);
75 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -070076
Adam Powell96675b12010-06-10 18:58:59 -070077 public ActionMenuView(Context context, AttributeSet attrs) {
78 super(context, attrs);
Adam Powellf16888f2010-10-11 17:05:29 -070079 setBaselineAligned(false);
Adam Powellbe3c3292011-08-24 12:52:28 -070080 final float density = context.getResources().getDisplayMetrics().density;
81 mMinCellSize = (int) (MIN_CELL_SIZE * density);
82 mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
Alan Viverette3d0f21d2014-07-10 16:15:01 -070083 mPopupContext = context;
84 mPopupTheme = 0;
Adam Powell773b1b92010-08-20 15:45:24 -070085 }
86
Alan Viverette3d0f21d2014-07-10 16:15:01 -070087 /**
88 * Specifies the theme to use when inflating popup menus. By default, uses
89 * the same theme as the action menu view itself.
90 *
91 * @param resId theme used to inflate popup menus
92 * @see #getPopupTheme()
93 */
Tor Norbye417ee5b2015-03-10 20:57:37 -070094 public void setPopupTheme(@StyleRes int resId) {
Alan Viverette3d0f21d2014-07-10 16:15:01 -070095 if (mPopupTheme != resId) {
96 mPopupTheme = resId;
97 if (resId == 0) {
98 mPopupContext = mContext;
99 } else {
100 mPopupContext = new ContextThemeWrapper(mContext, resId);
101 }
102 }
103 }
104
105 /**
106 * @return resource identifier of the theme used to inflate popup menus, or
107 * 0 if menus are inflated against the action menu view theme
108 * @see #setPopupTheme(int)
109 */
110 public int getPopupTheme() {
111 return mPopupTheme;
112 }
113
114 /**
115 * @param presenter Menu presenter used to display popup menu
116 * @hide
117 */
Adam Powell696cba52011-03-29 10:38:16 -0700118 public void setPresenter(ActionMenuPresenter presenter) {
119 mPresenter = presenter;
Adam Powelle021e6e2014-05-23 17:27:24 -0700120 mPresenter.setMenuView(this);
Adam Powell696cba52011-03-29 10:38:16 -0700121 }
122
Adam Powell6c6f5752010-08-20 18:34:46 -0700123 @Override
Adam Powell773b1b92010-08-20 15:45:24 -0700124 public void onConfigurationChanged(Configuration newConfig) {
Adam Powell8515ee82010-11-30 14:09:55 -0800125 super.onConfigurationChanged(newConfig);
Adam Powell6c6f5752010-08-20 18:34:46 -0700126
Chris Banes5cd13262015-01-19 12:01:46 +0000127 if (mPresenter != null) {
128 mPresenter.updateMenuView(false);
129
130 if (mPresenter.isOverflowMenuShowing()) {
131 mPresenter.hideOverflowMenu();
132 mPresenter.showOverflowMenu();
133 }
Adam Powell6c6f5752010-08-20 18:34:46 -0700134 }
Adam Powell773b1b92010-08-20 15:45:24 -0700135 }
136
Adam Powelle43340c2014-03-17 19:10:43 -0700137 public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
138 mOnMenuItemClickListener = listener;
139 }
140
Adam Powell8515ee82010-11-30 14:09:55 -0800141 @Override
Adam Powell640a66e2011-04-29 10:18:53 -0700142 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Adam Powell35aecd52011-07-01 13:43:49 -0700143 // If we've been given an exact size to match, apply special formatting during layout.
Adam Powell89b09da2011-07-27 11:55:29 -0700144 final boolean wasFormatted = mFormatItems;
Adam Powell35aecd52011-07-01 13:43:49 -0700145 mFormatItems = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
Adam Powell89b09da2011-07-27 11:55:29 -0700146
147 if (wasFormatted != mFormatItems) {
148 mFormatItemsWidth = 0; // Reset this when switching modes
149 }
150
151 // Special formatting can change whether items can fit as action buttons.
152 // Kick the menu and update presenters when this changes.
Adam Powellda971082013-10-03 18:21:58 -0700153 final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
Adam Powell89b09da2011-07-27 11:55:29 -0700154 if (mFormatItems && mMenu != null && widthSize != mFormatItemsWidth) {
155 mFormatItemsWidth = widthSize;
Adam Powell640a66e2011-04-29 10:18:53 -0700156 mMenu.onItemsChanged(true);
Adam Powell640a66e2011-04-29 10:18:53 -0700157 }
Adam Powell35aecd52011-07-01 13:43:49 -0700158
Adam Powelle43340c2014-03-17 19:10:43 -0700159 final int childCount = getChildCount();
160 if (mFormatItems && childCount > 0) {
Adam Powell35aecd52011-07-01 13:43:49 -0700161 onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec);
162 } else {
Adam Powell75d022a2012-03-06 12:04:07 -0800163 // Previous measurement at exact format may have set margins - reset them.
Adam Powell75d022a2012-03-06 12:04:07 -0800164 for (int i = 0; i < childCount; i++) {
165 final View child = getChildAt(i);
166 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
167 lp.leftMargin = lp.rightMargin = 0;
168 }
Adam Powell35aecd52011-07-01 13:43:49 -0700169 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
170 }
171 }
172
173 private void onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec) {
174 // We already know the width mode is EXACTLY if we're here.
175 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
176 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
177 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
178
179 final int widthPadding = getPaddingLeft() + getPaddingRight();
180 final int heightPadding = getPaddingTop() + getPaddingBottom();
181
Adam Powellfa18d182014-01-07 15:56:59 -0800182 final int itemHeightSpec = getChildMeasureSpec(heightMeasureSpec, heightPadding,
183 ViewGroup.LayoutParams.WRAP_CONTENT);
Adam Powell367ee322012-05-06 18:32:33 -0700184
Adam Powell35aecd52011-07-01 13:43:49 -0700185 widthSize -= widthPadding;
186
187 // Divide the view into cells.
188 final int cellCount = widthSize / mMinCellSize;
189 final int cellSizeRemaining = widthSize % mMinCellSize;
Adam Powell3bb421d2011-08-16 15:04:53 -0700190
191 if (cellCount == 0) {
192 // Give up, nothing fits.
193 setMeasuredDimension(widthSize, 0);
194 return;
195 }
196
Adam Powell35aecd52011-07-01 13:43:49 -0700197 final int cellSize = mMinCellSize + cellSizeRemaining / cellCount;
198
199 int cellsRemaining = cellCount;
200 int maxChildHeight = 0;
201 int maxCellsUsed = 0;
Adam Powell160bb7f2011-07-07 10:22:27 -0700202 int expandableItemCount = 0;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700203 int visibleItemCount = 0;
204 boolean hasOverflow = false;
Adam Powell35aecd52011-07-01 13:43:49 -0700205
Adam Powell14b7e2c2011-08-12 11:11:50 -0700206 // This is used as a bitfield to locate the smallest items present. Assumes childCount < 64.
207 long smallestItemsAt = 0;
Adam Powell35aecd52011-07-01 13:43:49 -0700208
209 final int childCount = getChildCount();
210 for (int i = 0; i < childCount; i++) {
211 final View child = getChildAt(i);
Adam Powell14b7e2c2011-08-12 11:11:50 -0700212 if (child.getVisibility() == GONE) continue;
213
Adam Powellbe3c3292011-08-24 12:52:28 -0700214 final boolean isGeneratedItem = child instanceof ActionMenuItemView;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700215 visibleItemCount++;
216
Adam Powellbe3c3292011-08-24 12:52:28 -0700217 if (isGeneratedItem) {
218 // Reset padding for generated menu item views; it may change below
219 // and views are recycled.
220 child.setPadding(mGeneratedItemPadding, 0, mGeneratedItemPadding, 0);
221 }
222
Adam Powell35aecd52011-07-01 13:43:49 -0700223 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
224 lp.expanded = false;
225 lp.extraPixels = 0;
226 lp.cellsUsed = 0;
Adam Powell160bb7f2011-07-07 10:22:27 -0700227 lp.expandable = false;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700228 lp.leftMargin = 0;
229 lp.rightMargin = 0;
Adam Powellbe3c3292011-08-24 12:52:28 -0700230 lp.preventEdgeOffset = isGeneratedItem && ((ActionMenuItemView) child).hasText();
Adam Powell35aecd52011-07-01 13:43:49 -0700231
232 // Overflow always gets 1 cell. No more, no less.
233 final int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining;
234
235 final int cellsUsed = measureChildForCells(child, cellSize, cellsAvailable,
Adam Powell367ee322012-05-06 18:32:33 -0700236 itemHeightSpec, heightPadding);
Adam Powell35aecd52011-07-01 13:43:49 -0700237
238 maxCellsUsed = Math.max(maxCellsUsed, cellsUsed);
Adam Powell160bb7f2011-07-07 10:22:27 -0700239 if (lp.expandable) expandableItemCount++;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700240 if (lp.isOverflowButton) hasOverflow = true;
Adam Powell35aecd52011-07-01 13:43:49 -0700241
242 cellsRemaining -= cellsUsed;
243 maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
Adam Powell14b7e2c2011-08-12 11:11:50 -0700244 if (cellsUsed == 1) smallestItemsAt |= (1 << i);
Adam Powell35aecd52011-07-01 13:43:49 -0700245 }
246
Adam Powellbe3c3292011-08-24 12:52:28 -0700247 // When we have overflow and a single expanded (text) item, we want to try centering it
248 // visually in the available space even though overflow consumes some of it.
249 final boolean centerSingleExpandedItem = hasOverflow && visibleItemCount == 2;
250
Adam Powell35aecd52011-07-01 13:43:49 -0700251 // Divide space for remaining cells if we have items that can expand.
252 // Try distributing whole leftover cells to smaller items first.
253
254 boolean needsExpansion = false;
Adam Powell160bb7f2011-07-07 10:22:27 -0700255 while (expandableItemCount > 0 && cellsRemaining > 0) {
Adam Powell35aecd52011-07-01 13:43:49 -0700256 int minCells = Integer.MAX_VALUE;
257 long minCellsAt = 0; // Bit locations are indices of relevant child views
258 int minCellsItemCount = 0;
259 for (int i = 0; i < childCount; i++) {
260 final View child = getChildAt(i);
261 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
262
263 // Don't try to expand items that shouldn't.
Adam Powell160bb7f2011-07-07 10:22:27 -0700264 if (!lp.expandable) continue;
Adam Powell35aecd52011-07-01 13:43:49 -0700265
266 // Mark indices of children that can receive an extra cell.
267 if (lp.cellsUsed < minCells) {
268 minCells = lp.cellsUsed;
269 minCellsAt = 1 << i;
270 minCellsItemCount = 1;
271 } else if (lp.cellsUsed == minCells) {
272 minCellsAt |= 1 << i;
273 minCellsItemCount++;
274 }
275 }
276
Adam Powell35aecd52011-07-01 13:43:49 -0700277 // Items that get expanded will always be in the set of smallest items when we're done.
Adam Powell14b7e2c2011-08-12 11:11:50 -0700278 smallestItemsAt |= minCellsAt;
Adam Powell35aecd52011-07-01 13:43:49 -0700279
Adam Powellbe3c3292011-08-24 12:52:28 -0700280 if (minCellsItemCount > cellsRemaining) break; // Couldn't expand anything evenly. Stop.
Adam Powell35aecd52011-07-01 13:43:49 -0700281
Adam Powellbe3c3292011-08-24 12:52:28 -0700282 // We have enough cells, all minimum size items will be incremented.
283 minCells++;
284
285 for (int i = 0; i < childCount; i++) {
Adam Powell35aecd52011-07-01 13:43:49 -0700286 final View child = getChildAt(i);
287 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Powellbe3c3292011-08-24 12:52:28 -0700288 if ((minCellsAt & (1 << i)) == 0) {
289 // If this item is already at our small item count, mark it for later.
290 if (lp.cellsUsed == minCells) smallestItemsAt |= 1 << i;
291 continue;
292 }
293
294 if (centerSingleExpandedItem && lp.preventEdgeOffset && cellsRemaining == 1) {
295 // Add padding to this item such that it centers.
296 child.setPadding(mGeneratedItemPadding + cellSize, 0, mGeneratedItemPadding, 0);
297 }
Adam Powell35aecd52011-07-01 13:43:49 -0700298 lp.cellsUsed++;
299 lp.expanded = true;
300 cellsRemaining--;
301 }
302
303 needsExpansion = true;
304 }
305
306 // Divide any space left that wouldn't divide along cell boundaries
Adam Powell14b7e2c2011-08-12 11:11:50 -0700307 // evenly among the smallest items
Adam Powell35aecd52011-07-01 13:43:49 -0700308
Adam Powell14b7e2c2011-08-12 11:11:50 -0700309 final boolean singleItem = !hasOverflow && visibleItemCount == 1;
310 if (cellsRemaining > 0 && smallestItemsAt != 0 &&
Adam Powellbe3c3292011-08-24 12:52:28 -0700311 (cellsRemaining < visibleItemCount - 1 || singleItem || maxCellsUsed > 1)) {
Adam Powell14b7e2c2011-08-12 11:11:50 -0700312 float expandCount = Long.bitCount(smallestItemsAt);
313
314 if (!singleItem) {
315 // The items at the far edges may only expand by half in order to pin to either side.
316 if ((smallestItemsAt & 1) != 0) {
Adam Powellbe3c3292011-08-24 12:52:28 -0700317 LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams();
318 if (!lp.preventEdgeOffset) expandCount -= 0.5f;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700319 }
320 if ((smallestItemsAt & (1 << (childCount - 1))) != 0) {
Adam Powellbe3c3292011-08-24 12:52:28 -0700321 LayoutParams lp = ((LayoutParams) getChildAt(childCount - 1).getLayoutParams());
322 if (!lp.preventEdgeOffset) expandCount -= 0.5f;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700323 }
324 }
325
Adam Powell3bb421d2011-08-16 15:04:53 -0700326 final int extraPixels = expandCount > 0 ?
327 (int) (cellsRemaining * cellSize / expandCount) : 0;
Adam Powell35aecd52011-07-01 13:43:49 -0700328
329 for (int i = 0; i < childCount; i++) {
Adam Powell14b7e2c2011-08-12 11:11:50 -0700330 if ((smallestItemsAt & (1 << i)) == 0) continue;
Adam Powell35aecd52011-07-01 13:43:49 -0700331
332 final View child = getChildAt(i);
333 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Powell14b7e2c2011-08-12 11:11:50 -0700334 if (child instanceof ActionMenuItemView) {
335 // If this is one of our views, expand and measure at the larger size.
336 lp.extraPixels = extraPixels;
337 lp.expanded = true;
Adam Powellbe3c3292011-08-24 12:52:28 -0700338 if (i == 0 && !lp.preventEdgeOffset) {
Adam Powell14b7e2c2011-08-12 11:11:50 -0700339 // First item gets part of its new padding pushed out of sight.
340 // The last item will get this implicitly from layout.
341 lp.leftMargin = -extraPixels / 2;
342 }
343 needsExpansion = true;
344 } else if (lp.isOverflowButton) {
345 lp.extraPixels = extraPixels;
346 lp.expanded = true;
347 lp.rightMargin = -extraPixels / 2;
348 needsExpansion = true;
349 } else {
350 // If we don't know what it is, give it some margins instead
351 // and let it center within its space. We still want to pin
352 // against the edges.
353 if (i != 0) {
354 lp.leftMargin = extraPixels / 2;
355 }
356 if (i != childCount - 1) {
357 lp.rightMargin = extraPixels / 2;
358 }
359 }
Adam Powell35aecd52011-07-01 13:43:49 -0700360 }
361
Adam Powell35aecd52011-07-01 13:43:49 -0700362 cellsRemaining = 0;
363 }
364
365 // Remeasure any items that have had extra space allocated to them.
366 if (needsExpansion) {
Adam Powell35aecd52011-07-01 13:43:49 -0700367 for (int i = 0; i < childCount; i++) {
368 final View child = getChildAt(i);
369 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
370
371 if (!lp.expanded) continue;
372
373 final int width = lp.cellsUsed * cellSize + lp.extraPixels;
Adam Powell367ee322012-05-06 18:32:33 -0700374 child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
375 itemHeightSpec);
Adam Powell35aecd52011-07-01 13:43:49 -0700376 }
377 }
378
379 if (heightMode != MeasureSpec.EXACTLY) {
380 heightSize = maxChildHeight;
381 }
382
383 setMeasuredDimension(widthSize, heightSize);
Adam Powell35aecd52011-07-01 13:43:49 -0700384 }
385
386 /**
387 * Measure a child view to fit within cell-based formatting. The child's width
388 * will be measured to a whole multiple of cellSize.
389 *
Adam Powell160bb7f2011-07-07 10:22:27 -0700390 * <p>Sets the expandable and cellsUsed fields of LayoutParams.
Adam Powell35aecd52011-07-01 13:43:49 -0700391 *
392 * @param child Child to measure
393 * @param cellSize Size of one cell
394 * @param cellsRemaining Number of cells remaining that this view can expand to fill
395 * @param parentHeightMeasureSpec MeasureSpec used by the parent view
396 * @param parentHeightPadding Padding present in the parent view
397 * @return Number of cells this child was measured to occupy
398 */
399 static int measureChildForCells(View child, int cellSize, int cellsRemaining,
400 int parentHeightMeasureSpec, int parentHeightPadding) {
401 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Powell35aecd52011-07-01 13:43:49 -0700402
403 final int childHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec) -
404 parentHeightPadding;
405 final int childHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec);
406 final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode);
407
Adam Powella7dec6d2012-04-09 15:54:01 -0700408 final ActionMenuItemView itemView = child instanceof ActionMenuItemView ?
409 (ActionMenuItemView) child : null;
410 final boolean hasText = itemView != null && itemView.hasText();
411
Adam Powell160bb7f2011-07-07 10:22:27 -0700412 int cellsUsed = 0;
Adam Powella7dec6d2012-04-09 15:54:01 -0700413 if (cellsRemaining > 0 && (!hasText || cellsRemaining >= 2)) {
Adam Powell35aecd52011-07-01 13:43:49 -0700414 final int childWidthSpec = MeasureSpec.makeMeasureSpec(
415 cellSize * cellsRemaining, MeasureSpec.AT_MOST);
416 child.measure(childWidthSpec, childHeightSpec);
417
418 final int measuredWidth = child.getMeasuredWidth();
419 cellsUsed = measuredWidth / cellSize;
420 if (measuredWidth % cellSize != 0) cellsUsed++;
Adam Powella7dec6d2012-04-09 15:54:01 -0700421 if (hasText && cellsUsed < 2) cellsUsed = 2;
Adam Powell35aecd52011-07-01 13:43:49 -0700422 }
Adam Powell160bb7f2011-07-07 10:22:27 -0700423
Adam Powella7dec6d2012-04-09 15:54:01 -0700424 final boolean expandable = !lp.isOverflowButton && hasText;
Adam Powell160bb7f2011-07-07 10:22:27 -0700425 lp.expandable = expandable;
426
Adam Powell35aecd52011-07-01 13:43:49 -0700427 lp.cellsUsed = cellsUsed;
428 final int targetWidth = cellsUsed * cellSize;
429 child.measure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
430 childHeightSpec);
431 return cellsUsed;
Adam Powell640a66e2011-04-29 10:18:53 -0700432 }
433
434 @Override
435 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
436 if (!mFormatItems) {
437 super.onLayout(changed, left, top, right, bottom);
438 return;
439 }
440
441 final int childCount = getChildCount();
Chris Banes1970cfd42014-10-29 11:04:49 +0000442 final int midVertical = (bottom - top) / 2;
Adam Powell640a66e2011-04-29 10:18:53 -0700443 final int dividerWidth = getDividerWidth();
Adam Powell640a66e2011-04-29 10:18:53 -0700444 int overflowWidth = 0;
445 int nonOverflowWidth = 0;
446 int nonOverflowCount = 0;
447 int widthRemaining = right - left - getPaddingRight() - getPaddingLeft();
Adam Powell35aecd52011-07-01 13:43:49 -0700448 boolean hasOverflow = false;
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700449 final boolean isLayoutRtl = isLayoutRtl();
Adam Powell640a66e2011-04-29 10:18:53 -0700450 for (int i = 0; i < childCount; i++) {
451 final View v = getChildAt(i);
452 if (v.getVisibility() == GONE) {
453 continue;
454 }
455
456 LayoutParams p = (LayoutParams) v.getLayoutParams();
457 if (p.isOverflowButton) {
Adam Powell640a66e2011-04-29 10:18:53 -0700458 overflowWidth = v.getMeasuredWidth();
459 if (hasDividerBeforeChildAt(i)) {
460 overflowWidth += dividerWidth;
461 }
462
463 int height = v.getMeasuredHeight();
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700464 int r;
465 int l;
466 if (isLayoutRtl) {
467 l = getPaddingLeft() + p.leftMargin;
468 r = l + overflowWidth;
469 } else {
470 r = getWidth() - getPaddingRight() - p.rightMargin;
471 l = r - overflowWidth;
472 }
Adam Powell640a66e2011-04-29 10:18:53 -0700473 int t = midVertical - (height / 2);
474 int b = t + height;
475 v.layout(l, t, r, b);
476
477 widthRemaining -= overflowWidth;
Adam Powell35aecd52011-07-01 13:43:49 -0700478 hasOverflow = true;
Adam Powell640a66e2011-04-29 10:18:53 -0700479 } else {
Adam Powell35aecd52011-07-01 13:43:49 -0700480 final int size = v.getMeasuredWidth() + p.leftMargin + p.rightMargin;
481 nonOverflowWidth += size;
482 widthRemaining -= size;
Adam Powell640a66e2011-04-29 10:18:53 -0700483 if (hasDividerBeforeChildAt(i)) {
484 nonOverflowWidth += dividerWidth;
485 }
486 nonOverflowCount++;
487 }
488 }
489
Adam Powell14b7e2c2011-08-12 11:11:50 -0700490 if (childCount == 1 && !hasOverflow) {
491 // Center a single child
492 final View v = getChildAt(0);
493 final int width = v.getMeasuredWidth();
494 final int height = v.getMeasuredHeight();
495 final int midHorizontal = (right - left) / 2;
496 final int l = midHorizontal - width / 2;
497 final int t = midVertical - height / 2;
498 v.layout(l, t, l + width, t + height);
499 return;
500 }
501
Adam Powell35aecd52011-07-01 13:43:49 -0700502 final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1);
Adam Powell14b7e2c2011-08-12 11:11:50 -0700503 final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0);
Adam Powell640a66e2011-04-29 10:18:53 -0700504
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700505 if (isLayoutRtl) {
506 int startRight = getWidth() - getPaddingRight();
507 for (int i = 0; i < childCount; i++) {
508 final View v = getChildAt(i);
509 final LayoutParams lp = (LayoutParams) v.getLayoutParams();
510 if (v.getVisibility() == GONE || lp.isOverflowButton) {
511 continue;
512 }
Adam Powell640a66e2011-04-29 10:18:53 -0700513
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700514 startRight -= lp.rightMargin;
515 int width = v.getMeasuredWidth();
516 int height = v.getMeasuredHeight();
517 int t = midVertical - height / 2;
518 v.layout(startRight - width, t, startRight, t + height);
519 startRight -= width + lp.leftMargin + spacerSize;
520 }
521 } else {
522 int startLeft = getPaddingLeft();
523 for (int i = 0; i < childCount; i++) {
524 final View v = getChildAt(i);
525 final LayoutParams lp = (LayoutParams) v.getLayoutParams();
526 if (v.getVisibility() == GONE || lp.isOverflowButton) {
527 continue;
528 }
529
530 startLeft += lp.leftMargin;
531 int width = v.getMeasuredWidth();
532 int height = v.getMeasuredHeight();
533 int t = midVertical - height / 2;
534 v.layout(startLeft, t, startLeft + width, t + height);
535 startLeft += width + lp.rightMargin + spacerSize;
536 }
Adam Powell640a66e2011-04-29 10:18:53 -0700537 }
538 }
539
540 @Override
Adam Powell8515ee82010-11-30 14:09:55 -0800541 public void onDetachedFromWindow() {
542 super.onDetachedFromWindow();
Adam Powelle021e6e2014-05-23 17:27:24 -0700543 dismissPopupMenus();
Adam Powell8028dd32010-07-15 10:16:33 -0700544 }
545
Chris Banesa41b7892015-06-09 13:36:44 +0000546 /**
547 * Set the icon to use for the overflow button.
548 *
549 * @param icon Drawable to set, may be null to clear the icon
550 */
551 public void setOverflowIcon(@Nullable Drawable icon) {
552 getMenu();
553 mPresenter.setOverflowIcon(icon);
554 }
555
556 /**
557 * Return the current drawable used as the overflow icon.
558 *
559 * @return The overflow icon drawable
560 */
561 @Nullable
562 public Drawable getOverflowIcon() {
563 getMenu();
564 return mPresenter.getOverflowIcon();
565 }
566
Adam Powellfa18d182014-01-07 15:56:59 -0800567 /** @hide */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100568 @UnsupportedAppUsage
Adam Powell8028dd32010-07-15 10:16:33 -0700569 public boolean isOverflowReserved() {
570 return mReserveOverflow;
Adam Powell7ade1be2010-06-17 12:51:21 -0700571 }
Adam Powellfa18d182014-01-07 15:56:59 -0800572
573 /** @hide */
Adam Powellb366bba2010-07-20 14:26:38 -0700574 public void setOverflowReserved(boolean reserveOverflow) {
575 mReserveOverflow = reserveOverflow;
576 }
Adam Powellf0ad6e62011-01-10 17:14:06 -0800577
Adam Powell7ade1be2010-06-17 12:51:21 -0700578 @Override
579 protected LayoutParams generateDefaultLayoutParams() {
580 LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
581 LayoutParams.WRAP_CONTENT);
Adam Powell6b336f82010-08-10 20:13:01 -0700582 params.gravity = Gravity.CENTER_VERTICAL;
Adam Powell7ade1be2010-06-17 12:51:21 -0700583 return params;
584 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700585
Adam Powell7ade1be2010-06-17 12:51:21 -0700586 @Override
Adam Powell35aecd52011-07-01 13:43:49 -0700587 public LayoutParams generateLayoutParams(AttributeSet attrs) {
588 return new LayoutParams(getContext(), attrs);
589 }
590
591 @Override
Adam Powell7ade1be2010-06-17 12:51:21 -0700592 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
Adam Powell8c1b02e2012-07-16 17:58:18 -0700593 if (p != null) {
594 final LayoutParams result = p instanceof LayoutParams
595 ? new LayoutParams((LayoutParams) p)
596 : new LayoutParams(p);
Adam Powell3f476b32011-01-03 19:25:36 -0800597 if (result.gravity <= Gravity.NO_GRAVITY) {
598 result.gravity = Gravity.CENTER_VERTICAL;
599 }
600 return result;
601 }
Adam Powell7ade1be2010-06-17 12:51:21 -0700602 return generateDefaultLayoutParams();
603 }
Adam Powell96675b12010-06-10 18:58:59 -0700604
Adam Powell696cba52011-03-29 10:38:16 -0700605 @Override
606 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
Adam Powell35aecd52011-07-01 13:43:49 -0700607 return p != null && p instanceof LayoutParams;
Adam Powell696cba52011-03-29 10:38:16 -0700608 }
609
Adam Powellfa18d182014-01-07 15:56:59 -0800610 /** @hide */
Adam Powell640a66e2011-04-29 10:18:53 -0700611 public LayoutParams generateOverflowButtonLayoutParams() {
612 LayoutParams result = generateDefaultLayoutParams();
613 result.isOverflowButton = true;
614 return result;
615 }
616
Adam Powellfa18d182014-01-07 15:56:59 -0800617 /** @hide */
Adam Powell96675b12010-06-10 18:58:59 -0700618 public boolean invokeItem(MenuItemImpl item) {
619 return mMenu.performItemAction(item, 0);
620 }
621
Adam Powellfa18d182014-01-07 15:56:59 -0800622 /** @hide */
Adam Powell96675b12010-06-10 18:58:59 -0700623 public int getWindowAnimations() {
624 return 0;
625 }
626
Adam Powellfa18d182014-01-07 15:56:59 -0800627 /** @hide */
Alan Viverette28a84682016-01-04 13:43:23 -0500628 public void initialize(@Nullable MenuBuilder menu) {
Adam Powell96675b12010-06-10 18:58:59 -0700629 mMenu = menu;
Adam Powell96675b12010-06-10 18:58:59 -0700630 }
631
Adam Powellfa18d182014-01-07 15:56:59 -0800632 /**
633 * Returns the Menu object that this ActionMenuView is currently presenting.
634 *
635 * <p>Applications should use this method to obtain the ActionMenuView's Menu object
636 * and inflate or add content to it as necessary.</p>
637 *
638 * @return the Menu presented by this view
639 */
640 public Menu getMenu() {
641 if (mMenu == null) {
642 final Context context = getContext();
643 mMenu = new MenuBuilder(context);
Adam Powelle43340c2014-03-17 19:10:43 -0700644 mMenu.setCallback(new MenuBuilderCallback());
Adam Powellfa18d182014-01-07 15:56:59 -0800645 mPresenter = new ActionMenuPresenter(context);
Adam Powelle8d15362014-09-09 13:51:16 -0700646 mPresenter.setReserveOverflow(true);
Adam Powell04c0d462014-09-04 16:10:24 -0700647 mPresenter.setCallback(mActionMenuPresenterCallback != null
648 ? mActionMenuPresenterCallback : new ActionMenuPresenterCallback());
Alan Viverette3d0f21d2014-07-10 16:15:01 -0700649 mMenu.addMenuPresenter(mPresenter, mPopupContext);
Adam Powell07a74542014-05-30 15:52:44 -0700650 mPresenter.setMenuView(this);
Adam Powellfa18d182014-01-07 15:56:59 -0800651 }
652
653 return mMenu;
654 }
655
656 /**
Adam Powell04c0d462014-09-04 16:10:24 -0700657 * Must be called before the first call to getMenu()
658 * @hide
659 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100660 @UnsupportedAppUsage
Adam Powellc4612502014-09-04 19:16:39 -0700661 public void setMenuCallbacks(MenuPresenter.Callback pcb, MenuBuilder.Callback mcb) {
662 mActionMenuPresenterCallback = pcb;
663 mMenuBuilderCallback = mcb;
Adam Powell04c0d462014-09-04 16:10:24 -0700664 }
665
666 /**
Adam Powelle021e6e2014-05-23 17:27:24 -0700667 * Returns the current menu or null if one has not yet been configured.
668 * @hide Internal use only for action bar integration
669 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100670 @UnsupportedAppUsage
Adam Powelle021e6e2014-05-23 17:27:24 -0700671 public MenuBuilder peekMenu() {
672 return mMenu;
673 }
674
675 /**
676 * Show the overflow items from the associated menu.
677 *
678 * @return true if the menu was able to be shown, false otherwise
679 */
680 public boolean showOverflowMenu() {
681 return mPresenter != null && mPresenter.showOverflowMenu();
682 }
683
684 /**
685 * Hide the overflow items from the associated menu.
686 *
687 * @return true if the menu was able to be hidden, false otherwise
688 */
689 public boolean hideOverflowMenu() {
690 return mPresenter != null && mPresenter.hideOverflowMenu();
691 }
692
693 /**
694 * Check whether the overflow menu is currently showing. This may not reflect
695 * a pending show operation in progress.
696 *
697 * @return true if the overflow menu is currently showing
698 */
699 public boolean isOverflowMenuShowing() {
700 return mPresenter != null && mPresenter.isOverflowMenuShowing();
701 }
702
703 /** @hide */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100704 @UnsupportedAppUsage
Adam Powelle021e6e2014-05-23 17:27:24 -0700705 public boolean isOverflowMenuShowPending() {
706 return mPresenter != null && mPresenter.isOverflowMenuShowPending();
707 }
708
709 /**
710 * Dismiss any popups associated with this menu view.
711 */
712 public void dismissPopupMenus() {
713 if (mPresenter != null) {
714 mPresenter.dismissPopupMenus();
715 }
716 }
717
718 /**
Adam Powellfa18d182014-01-07 15:56:59 -0800719 * @hide Private LinearLayout (superclass) API. Un-hide if LinearLayout API is made public.
720 */
Adam Powell696cba52011-03-29 10:38:16 -0700721 @Override
Mathew Inwood978c6e22018-08-21 15:58:55 +0100722 @UnsupportedAppUsage
Adam Powell696cba52011-03-29 10:38:16 -0700723 protected boolean hasDividerBeforeChildAt(int childIndex) {
Jake Wharton825992f2012-07-28 21:31:51 -0700724 if (childIndex == 0) {
725 return false;
726 }
Adam Powell696cba52011-03-29 10:38:16 -0700727 final View childBefore = getChildAt(childIndex - 1);
728 final View child = getChildAt(childIndex);
729 boolean result = false;
730 if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
731 result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
Adam Powell96675b12010-06-10 18:58:59 -0700732 }
Adam Powell696cba52011-03-29 10:38:16 -0700733 if (childIndex > 0 && child instanceof ActionMenuChildView) {
734 result |= ((ActionMenuChildView) child).needsDividerBefore();
Adam Powell8028dd32010-07-15 10:16:33 -0700735 }
Adam Powellbe4d68e2010-10-08 18:16:34 -0700736 return result;
737 }
738
Alan Viverettea54956a2015-01-07 16:05:02 -0800739 /** @hide */
740 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
Adam Powell7bc3ca02011-08-26 18:29:58 -0700741 return false;
742 }
743
Adam Powell07a74542014-05-30 15:52:44 -0700744 /** @hide */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100745 @UnsupportedAppUsage
Adam Powell07a74542014-05-30 15:52:44 -0700746 public void setExpandedActionViewsExclusive(boolean exclusive) {
747 mPresenter.setExpandedActionViewsExclusive(exclusive);
748 }
749
Adam Powelle43340c2014-03-17 19:10:43 -0700750 /**
751 * Interface responsible for receiving menu item click events if the items themselves
752 * do not have individual item click listeners.
753 */
754 public interface OnMenuItemClickListener {
755 /**
756 * This method will be invoked when a menu item is clicked if the item itself did
757 * not already handle the event.
758 *
759 * @param item {@link MenuItem} that was clicked
760 * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
761 */
762 public boolean onMenuItemClick(MenuItem item);
763 }
764
765 private class MenuBuilderCallback implements MenuBuilder.Callback {
766 @Override
767 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
768 return mOnMenuItemClickListener != null &&
769 mOnMenuItemClickListener.onMenuItemClick(item);
770 }
771
772 @Override
773 public void onMenuModeChange(MenuBuilder menu) {
Adam Powellc4612502014-09-04 19:16:39 -0700774 if (mMenuBuilderCallback != null) {
775 mMenuBuilderCallback.onMenuModeChange(menu);
776 }
Adam Powelle43340c2014-03-17 19:10:43 -0700777 }
778 }
779
780 private class ActionMenuPresenterCallback implements ActionMenuPresenter.Callback {
781 @Override
782 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
783 }
784
785 @Override
786 public boolean onOpenSubMenu(MenuBuilder subMenu) {
787 return false;
788 }
789 }
790
Adam Powellfa18d182014-01-07 15:56:59 -0800791 /** @hide */
Adam Powell696cba52011-03-29 10:38:16 -0700792 public interface ActionMenuChildView {
Mathew Inwood978c6e22018-08-21 15:58:55 +0100793 @UnsupportedAppUsage
Adam Powell696cba52011-03-29 10:38:16 -0700794 public boolean needsDividerBefore();
795 public boolean needsDividerAfter();
Adam Powell8515ee82010-11-30 14:09:55 -0800796 }
Adam Powell640a66e2011-04-29 10:18:53 -0700797
798 public static class LayoutParams extends LinearLayout.LayoutParams {
Adam Powellfa18d182014-01-07 15:56:59 -0800799 /** @hide */
Adam Powell640a66e2011-04-29 10:18:53 -0700800 @ViewDebug.ExportedProperty(category = "layout")
Mathew Inwood978c6e22018-08-21 15:58:55 +0100801 @UnsupportedAppUsage
Adam Powell640a66e2011-04-29 10:18:53 -0700802 public boolean isOverflowButton;
Adam Powellfa18d182014-01-07 15:56:59 -0800803
804 /** @hide */
Adam Powell35aecd52011-07-01 13:43:49 -0700805 @ViewDebug.ExportedProperty(category = "layout")
Mathew Inwood978c6e22018-08-21 15:58:55 +0100806 @UnsupportedAppUsage
Adam Powell35aecd52011-07-01 13:43:49 -0700807 public int cellsUsed;
Adam Powellfa18d182014-01-07 15:56:59 -0800808
809 /** @hide */
Adam Powell35aecd52011-07-01 13:43:49 -0700810 @ViewDebug.ExportedProperty(category = "layout")
Mathew Inwood978c6e22018-08-21 15:58:55 +0100811 @UnsupportedAppUsage
Adam Powell35aecd52011-07-01 13:43:49 -0700812 public int extraPixels;
Adam Powellfa18d182014-01-07 15:56:59 -0800813
814 /** @hide */
Adam Powell160bb7f2011-07-07 10:22:27 -0700815 @ViewDebug.ExportedProperty(category = "layout")
Mathew Inwood978c6e22018-08-21 15:58:55 +0100816 @UnsupportedAppUsage
Adam Powell160bb7f2011-07-07 10:22:27 -0700817 public boolean expandable;
Adam Powellfa18d182014-01-07 15:56:59 -0800818
819 /** @hide */
Adam Powellbe3c3292011-08-24 12:52:28 -0700820 @ViewDebug.ExportedProperty(category = "layout")
Mathew Inwood978c6e22018-08-21 15:58:55 +0100821 @UnsupportedAppUsage
Adam Powellbe3c3292011-08-24 12:52:28 -0700822 public boolean preventEdgeOffset;
Adam Powell160bb7f2011-07-07 10:22:27 -0700823
Adam Powellfa18d182014-01-07 15:56:59 -0800824 /** @hide */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100825 @UnsupportedAppUsage
Adam Powell35aecd52011-07-01 13:43:49 -0700826 public boolean expanded;
Adam Powell640a66e2011-04-29 10:18:53 -0700827
828 public LayoutParams(Context c, AttributeSet attrs) {
829 super(c, attrs);
830 }
831
Adam Powell8c1b02e2012-07-16 17:58:18 -0700832 public LayoutParams(ViewGroup.LayoutParams other) {
833 super(other);
834 }
835
Adam Powell640a66e2011-04-29 10:18:53 -0700836 public LayoutParams(LayoutParams other) {
837 super((LinearLayout.LayoutParams) other);
838 isOverflowButton = other.isOverflowButton;
839 }
840
841 public LayoutParams(int width, int height) {
842 super(width, height);
843 isOverflowButton = false;
844 }
845
Adam Powellfa18d182014-01-07 15:56:59 -0800846 /** @hide */
Adam Powell640a66e2011-04-29 10:18:53 -0700847 public LayoutParams(int width, int height, boolean isOverflowButton) {
848 super(width, height);
849 this.isOverflowButton = isOverflowButton;
850 }
Siva Velusamy94a6d152015-05-05 15:07:00 -0700851
852 /** @hide */
853 @Override
854 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
855 super.encodeProperties(encoder);
856
857 encoder.addProperty("layout:overFlowButton", isOverflowButton);
858 encoder.addProperty("layout:cellsUsed", cellsUsed);
859 encoder.addProperty("layout:extraPixels", extraPixels);
860 encoder.addProperty("layout:expandable", expandable);
861 encoder.addProperty("layout:preventEdgeOffset", preventEdgeOffset);
862 }
Adam Powell640a66e2011-04-29 10:18:53 -0700863 }
Adam Powell96675b12010-06-10 18:58:59 -0700864}