blob: 4d0a1c86fd927da92ccb5e1af671df990b4631a4 [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;
Adam Powell96675b12010-06-10 18:58:59 -070021import android.content.Context;
Adam Powell8028dd32010-07-15 10:16:33 -070022import android.content.res.Configuration;
Chris Banesa41b7892015-06-09 13:36:44 +000023import android.graphics.drawable.Drawable;
Adam Powell96675b12010-06-10 18:58:59 -070024import android.util.AttributeSet;
Alan Viverette3d0f21d2014-07-10 16:15:01 -070025import android.view.ContextThemeWrapper;
Adam Powell6b336f82010-08-10 20:13:01 -070026import android.view.Gravity;
Adam Powellfa18d182014-01-07 15:56:59 -080027import android.view.Menu;
Adam Powelle43340c2014-03-17 19:10:43 -070028import android.view.MenuItem;
Adam Powellcf78b3e2010-09-12 18:25:23 -070029import android.view.View;
Adam Powell640a66e2011-04-29 10:18:53 -070030import android.view.ViewDebug;
Adam Powell7ade1be2010-06-17 12:51:21 -070031import android.view.ViewGroup;
Siva Velusamy94a6d152015-05-05 15:07:00 -070032import android.view.ViewHierarchyEncoder;
Adam Powell7bc3ca02011-08-26 18:29:58 -070033import android.view.accessibility.AccessibilityEvent;
Adam Powellfa18d182014-01-07 15:56:59 -080034import com.android.internal.view.menu.ActionMenuItemView;
35import com.android.internal.view.menu.MenuBuilder;
36import com.android.internal.view.menu.MenuItemImpl;
Adam Powell04c0d462014-09-04 16:10:24 -070037import com.android.internal.view.menu.MenuPresenter;
Adam Powellfa18d182014-01-07 15:56:59 -080038import com.android.internal.view.menu.MenuView;
Adam Powell96675b12010-06-10 18:58:59 -070039
Adam Powell96675b12010-06-10 18:58:59 -070040/**
Adam Powellfa18d182014-01-07 15:56:59 -080041 * ActionMenuView is a presentation of a series of menu options as a View. It provides
42 * several top level options as action buttons while spilling remaining options over as
43 * items in an overflow menu. This allows applications to present packs of actions inline with
44 * specific or repeating content.
Adam Powell96675b12010-06-10 18:58:59 -070045 */
46public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView {
47 private static final String TAG = "ActionMenuView";
48
Adam Powell35aecd52011-07-01 13:43:49 -070049 static final int MIN_CELL_SIZE = 56; // dips
Adam Powellbe3c3292011-08-24 12:52:28 -070050 static final int GENERATED_ITEM_PADDING = 4; // dips
Adam Powell35aecd52011-07-01 13:43:49 -070051
Adam Powell96675b12010-06-10 18:58:59 -070052 private MenuBuilder mMenu;
Adam Powell7ade1be2010-06-17 12:51:21 -070053
Alan Viverette3d0f21d2014-07-10 16:15:01 -070054 /** Context against which to inflate popup menus. */
55 private Context mPopupContext;
56
57 /** Theme resource against which to inflate popup menus. */
58 private int mPopupTheme;
59
Adam Powell8028dd32010-07-15 10:16:33 -070060 private boolean mReserveOverflow;
Adam Powell696cba52011-03-29 10:38:16 -070061 private ActionMenuPresenter mPresenter;
Adam Powell04c0d462014-09-04 16:10:24 -070062 private MenuPresenter.Callback mActionMenuPresenterCallback;
Adam Powellc4612502014-09-04 19:16:39 -070063 private MenuBuilder.Callback mMenuBuilderCallback;
Adam Powell640a66e2011-04-29 10:18:53 -070064 private boolean mFormatItems;
Adam Powell89b09da2011-07-27 11:55:29 -070065 private int mFormatItemsWidth;
Adam Powell35aecd52011-07-01 13:43:49 -070066 private int mMinCellSize;
Adam Powellbe3c3292011-08-24 12:52:28 -070067 private int mGeneratedItemPadding;
Adam Powell8515ee82010-11-30 14:09:55 -080068
Adam Powelle43340c2014-03-17 19:10:43 -070069 private OnMenuItemClickListener mOnMenuItemClickListener;
70
Adam Powell96675b12010-06-10 18:58:59 -070071 public ActionMenuView(Context context) {
72 this(context, null);
73 }
74
75 public ActionMenuView(Context context, AttributeSet attrs) {
76 super(context, attrs);
Adam Powellf16888f2010-10-11 17:05:29 -070077 setBaselineAligned(false);
Adam Powellbe3c3292011-08-24 12:52:28 -070078 final float density = context.getResources().getDisplayMetrics().density;
79 mMinCellSize = (int) (MIN_CELL_SIZE * density);
80 mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
Alan Viverette3d0f21d2014-07-10 16:15:01 -070081 mPopupContext = context;
82 mPopupTheme = 0;
Adam Powell773b1b92010-08-20 15:45:24 -070083 }
84
Alan Viverette3d0f21d2014-07-10 16:15:01 -070085 /**
86 * Specifies the theme to use when inflating popup menus. By default, uses
87 * the same theme as the action menu view itself.
88 *
89 * @param resId theme used to inflate popup menus
90 * @see #getPopupTheme()
91 */
Tor Norbye417ee5b2015-03-10 20:57:37 -070092 public void setPopupTheme(@StyleRes int resId) {
Alan Viverette3d0f21d2014-07-10 16:15:01 -070093 if (mPopupTheme != resId) {
94 mPopupTheme = resId;
95 if (resId == 0) {
96 mPopupContext = mContext;
97 } else {
98 mPopupContext = new ContextThemeWrapper(mContext, resId);
99 }
100 }
101 }
102
103 /**
104 * @return resource identifier of the theme used to inflate popup menus, or
105 * 0 if menus are inflated against the action menu view theme
106 * @see #setPopupTheme(int)
107 */
108 public int getPopupTheme() {
109 return mPopupTheme;
110 }
111
112 /**
113 * @param presenter Menu presenter used to display popup menu
114 * @hide
115 */
Adam Powell696cba52011-03-29 10:38:16 -0700116 public void setPresenter(ActionMenuPresenter presenter) {
117 mPresenter = presenter;
Adam Powelle021e6e2014-05-23 17:27:24 -0700118 mPresenter.setMenuView(this);
Adam Powell696cba52011-03-29 10:38:16 -0700119 }
120
Adam Powell6c6f5752010-08-20 18:34:46 -0700121 @Override
Adam Powell773b1b92010-08-20 15:45:24 -0700122 public void onConfigurationChanged(Configuration newConfig) {
Adam Powell8515ee82010-11-30 14:09:55 -0800123 super.onConfigurationChanged(newConfig);
Adam Powell6c6f5752010-08-20 18:34:46 -0700124
Chris Banes5cd13262015-01-19 12:01:46 +0000125 if (mPresenter != null) {
126 mPresenter.updateMenuView(false);
127
128 if (mPresenter.isOverflowMenuShowing()) {
129 mPresenter.hideOverflowMenu();
130 mPresenter.showOverflowMenu();
131 }
Adam Powell6c6f5752010-08-20 18:34:46 -0700132 }
Adam Powell773b1b92010-08-20 15:45:24 -0700133 }
134
Adam Powelle43340c2014-03-17 19:10:43 -0700135 public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
136 mOnMenuItemClickListener = listener;
137 }
138
Adam Powell8515ee82010-11-30 14:09:55 -0800139 @Override
Adam Powell640a66e2011-04-29 10:18:53 -0700140 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Adam Powell35aecd52011-07-01 13:43:49 -0700141 // If we've been given an exact size to match, apply special formatting during layout.
Adam Powell89b09da2011-07-27 11:55:29 -0700142 final boolean wasFormatted = mFormatItems;
Adam Powell35aecd52011-07-01 13:43:49 -0700143 mFormatItems = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
Adam Powell89b09da2011-07-27 11:55:29 -0700144
145 if (wasFormatted != mFormatItems) {
146 mFormatItemsWidth = 0; // Reset this when switching modes
147 }
148
149 // Special formatting can change whether items can fit as action buttons.
150 // Kick the menu and update presenters when this changes.
Adam Powellda971082013-10-03 18:21:58 -0700151 final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
Adam Powell89b09da2011-07-27 11:55:29 -0700152 if (mFormatItems && mMenu != null && widthSize != mFormatItemsWidth) {
153 mFormatItemsWidth = widthSize;
Adam Powell640a66e2011-04-29 10:18:53 -0700154 mMenu.onItemsChanged(true);
Adam Powell640a66e2011-04-29 10:18:53 -0700155 }
Adam Powell35aecd52011-07-01 13:43:49 -0700156
Adam Powelle43340c2014-03-17 19:10:43 -0700157 final int childCount = getChildCount();
158 if (mFormatItems && childCount > 0) {
Adam Powell35aecd52011-07-01 13:43:49 -0700159 onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec);
160 } else {
Adam Powell75d022a2012-03-06 12:04:07 -0800161 // Previous measurement at exact format may have set margins - reset them.
Adam Powell75d022a2012-03-06 12:04:07 -0800162 for (int i = 0; i < childCount; i++) {
163 final View child = getChildAt(i);
164 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
165 lp.leftMargin = lp.rightMargin = 0;
166 }
Adam Powell35aecd52011-07-01 13:43:49 -0700167 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
168 }
169 }
170
171 private void onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec) {
172 // We already know the width mode is EXACTLY if we're here.
173 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
174 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
175 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
176
177 final int widthPadding = getPaddingLeft() + getPaddingRight();
178 final int heightPadding = getPaddingTop() + getPaddingBottom();
179
Adam Powellfa18d182014-01-07 15:56:59 -0800180 final int itemHeightSpec = getChildMeasureSpec(heightMeasureSpec, heightPadding,
181 ViewGroup.LayoutParams.WRAP_CONTENT);
Adam Powell367ee322012-05-06 18:32:33 -0700182
Adam Powell35aecd52011-07-01 13:43:49 -0700183 widthSize -= widthPadding;
184
185 // Divide the view into cells.
186 final int cellCount = widthSize / mMinCellSize;
187 final int cellSizeRemaining = widthSize % mMinCellSize;
Adam Powell3bb421d2011-08-16 15:04:53 -0700188
189 if (cellCount == 0) {
190 // Give up, nothing fits.
191 setMeasuredDimension(widthSize, 0);
192 return;
193 }
194
Adam Powell35aecd52011-07-01 13:43:49 -0700195 final int cellSize = mMinCellSize + cellSizeRemaining / cellCount;
196
197 int cellsRemaining = cellCount;
198 int maxChildHeight = 0;
199 int maxCellsUsed = 0;
Adam Powell160bb7f2011-07-07 10:22:27 -0700200 int expandableItemCount = 0;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700201 int visibleItemCount = 0;
202 boolean hasOverflow = false;
Adam Powell35aecd52011-07-01 13:43:49 -0700203
Adam Powell14b7e2c2011-08-12 11:11:50 -0700204 // This is used as a bitfield to locate the smallest items present. Assumes childCount < 64.
205 long smallestItemsAt = 0;
Adam Powell35aecd52011-07-01 13:43:49 -0700206
207 final int childCount = getChildCount();
208 for (int i = 0; i < childCount; i++) {
209 final View child = getChildAt(i);
Adam Powell14b7e2c2011-08-12 11:11:50 -0700210 if (child.getVisibility() == GONE) continue;
211
Adam Powellbe3c3292011-08-24 12:52:28 -0700212 final boolean isGeneratedItem = child instanceof ActionMenuItemView;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700213 visibleItemCount++;
214
Adam Powellbe3c3292011-08-24 12:52:28 -0700215 if (isGeneratedItem) {
216 // Reset padding for generated menu item views; it may change below
217 // and views are recycled.
218 child.setPadding(mGeneratedItemPadding, 0, mGeneratedItemPadding, 0);
219 }
220
Adam Powell35aecd52011-07-01 13:43:49 -0700221 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
222 lp.expanded = false;
223 lp.extraPixels = 0;
224 lp.cellsUsed = 0;
Adam Powell160bb7f2011-07-07 10:22:27 -0700225 lp.expandable = false;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700226 lp.leftMargin = 0;
227 lp.rightMargin = 0;
Adam Powellbe3c3292011-08-24 12:52:28 -0700228 lp.preventEdgeOffset = isGeneratedItem && ((ActionMenuItemView) child).hasText();
Adam Powell35aecd52011-07-01 13:43:49 -0700229
230 // Overflow always gets 1 cell. No more, no less.
231 final int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining;
232
233 final int cellsUsed = measureChildForCells(child, cellSize, cellsAvailable,
Adam Powell367ee322012-05-06 18:32:33 -0700234 itemHeightSpec, heightPadding);
Adam Powell35aecd52011-07-01 13:43:49 -0700235
236 maxCellsUsed = Math.max(maxCellsUsed, cellsUsed);
Adam Powell160bb7f2011-07-07 10:22:27 -0700237 if (lp.expandable) expandableItemCount++;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700238 if (lp.isOverflowButton) hasOverflow = true;
Adam Powell35aecd52011-07-01 13:43:49 -0700239
240 cellsRemaining -= cellsUsed;
241 maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
Adam Powell14b7e2c2011-08-12 11:11:50 -0700242 if (cellsUsed == 1) smallestItemsAt |= (1 << i);
Adam Powell35aecd52011-07-01 13:43:49 -0700243 }
244
Adam Powellbe3c3292011-08-24 12:52:28 -0700245 // When we have overflow and a single expanded (text) item, we want to try centering it
246 // visually in the available space even though overflow consumes some of it.
247 final boolean centerSingleExpandedItem = hasOverflow && visibleItemCount == 2;
248
Adam Powell35aecd52011-07-01 13:43:49 -0700249 // Divide space for remaining cells if we have items that can expand.
250 // Try distributing whole leftover cells to smaller items first.
251
252 boolean needsExpansion = false;
Adam Powell160bb7f2011-07-07 10:22:27 -0700253 while (expandableItemCount > 0 && cellsRemaining > 0) {
Adam Powell35aecd52011-07-01 13:43:49 -0700254 int minCells = Integer.MAX_VALUE;
255 long minCellsAt = 0; // Bit locations are indices of relevant child views
256 int minCellsItemCount = 0;
257 for (int i = 0; i < childCount; i++) {
258 final View child = getChildAt(i);
259 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
260
261 // Don't try to expand items that shouldn't.
Adam Powell160bb7f2011-07-07 10:22:27 -0700262 if (!lp.expandable) continue;
Adam Powell35aecd52011-07-01 13:43:49 -0700263
264 // Mark indices of children that can receive an extra cell.
265 if (lp.cellsUsed < minCells) {
266 minCells = lp.cellsUsed;
267 minCellsAt = 1 << i;
268 minCellsItemCount = 1;
269 } else if (lp.cellsUsed == minCells) {
270 minCellsAt |= 1 << i;
271 minCellsItemCount++;
272 }
273 }
274
Adam Powell35aecd52011-07-01 13:43:49 -0700275 // Items that get expanded will always be in the set of smallest items when we're done.
Adam Powell14b7e2c2011-08-12 11:11:50 -0700276 smallestItemsAt |= minCellsAt;
Adam Powell35aecd52011-07-01 13:43:49 -0700277
Adam Powellbe3c3292011-08-24 12:52:28 -0700278 if (minCellsItemCount > cellsRemaining) break; // Couldn't expand anything evenly. Stop.
Adam Powell35aecd52011-07-01 13:43:49 -0700279
Adam Powellbe3c3292011-08-24 12:52:28 -0700280 // We have enough cells, all minimum size items will be incremented.
281 minCells++;
282
283 for (int i = 0; i < childCount; i++) {
Adam Powell35aecd52011-07-01 13:43:49 -0700284 final View child = getChildAt(i);
285 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Powellbe3c3292011-08-24 12:52:28 -0700286 if ((minCellsAt & (1 << i)) == 0) {
287 // If this item is already at our small item count, mark it for later.
288 if (lp.cellsUsed == minCells) smallestItemsAt |= 1 << i;
289 continue;
290 }
291
292 if (centerSingleExpandedItem && lp.preventEdgeOffset && cellsRemaining == 1) {
293 // Add padding to this item such that it centers.
294 child.setPadding(mGeneratedItemPadding + cellSize, 0, mGeneratedItemPadding, 0);
295 }
Adam Powell35aecd52011-07-01 13:43:49 -0700296 lp.cellsUsed++;
297 lp.expanded = true;
298 cellsRemaining--;
299 }
300
301 needsExpansion = true;
302 }
303
304 // Divide any space left that wouldn't divide along cell boundaries
Adam Powell14b7e2c2011-08-12 11:11:50 -0700305 // evenly among the smallest items
Adam Powell35aecd52011-07-01 13:43:49 -0700306
Adam Powell14b7e2c2011-08-12 11:11:50 -0700307 final boolean singleItem = !hasOverflow && visibleItemCount == 1;
308 if (cellsRemaining > 0 && smallestItemsAt != 0 &&
Adam Powellbe3c3292011-08-24 12:52:28 -0700309 (cellsRemaining < visibleItemCount - 1 || singleItem || maxCellsUsed > 1)) {
Adam Powell14b7e2c2011-08-12 11:11:50 -0700310 float expandCount = Long.bitCount(smallestItemsAt);
311
312 if (!singleItem) {
313 // The items at the far edges may only expand by half in order to pin to either side.
314 if ((smallestItemsAt & 1) != 0) {
Adam Powellbe3c3292011-08-24 12:52:28 -0700315 LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams();
316 if (!lp.preventEdgeOffset) expandCount -= 0.5f;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700317 }
318 if ((smallestItemsAt & (1 << (childCount - 1))) != 0) {
Adam Powellbe3c3292011-08-24 12:52:28 -0700319 LayoutParams lp = ((LayoutParams) getChildAt(childCount - 1).getLayoutParams());
320 if (!lp.preventEdgeOffset) expandCount -= 0.5f;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700321 }
322 }
323
Adam Powell3bb421d2011-08-16 15:04:53 -0700324 final int extraPixels = expandCount > 0 ?
325 (int) (cellsRemaining * cellSize / expandCount) : 0;
Adam Powell35aecd52011-07-01 13:43:49 -0700326
327 for (int i = 0; i < childCount; i++) {
Adam Powell14b7e2c2011-08-12 11:11:50 -0700328 if ((smallestItemsAt & (1 << i)) == 0) continue;
Adam Powell35aecd52011-07-01 13:43:49 -0700329
330 final View child = getChildAt(i);
331 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Powell14b7e2c2011-08-12 11:11:50 -0700332 if (child instanceof ActionMenuItemView) {
333 // If this is one of our views, expand and measure at the larger size.
334 lp.extraPixels = extraPixels;
335 lp.expanded = true;
Adam Powellbe3c3292011-08-24 12:52:28 -0700336 if (i == 0 && !lp.preventEdgeOffset) {
Adam Powell14b7e2c2011-08-12 11:11:50 -0700337 // First item gets part of its new padding pushed out of sight.
338 // The last item will get this implicitly from layout.
339 lp.leftMargin = -extraPixels / 2;
340 }
341 needsExpansion = true;
342 } else if (lp.isOverflowButton) {
343 lp.extraPixels = extraPixels;
344 lp.expanded = true;
345 lp.rightMargin = -extraPixels / 2;
346 needsExpansion = true;
347 } else {
348 // If we don't know what it is, give it some margins instead
349 // and let it center within its space. We still want to pin
350 // against the edges.
351 if (i != 0) {
352 lp.leftMargin = extraPixels / 2;
353 }
354 if (i != childCount - 1) {
355 lp.rightMargin = extraPixels / 2;
356 }
357 }
Adam Powell35aecd52011-07-01 13:43:49 -0700358 }
359
Adam Powell35aecd52011-07-01 13:43:49 -0700360 cellsRemaining = 0;
361 }
362
363 // Remeasure any items that have had extra space allocated to them.
364 if (needsExpansion) {
Adam Powell35aecd52011-07-01 13:43:49 -0700365 for (int i = 0; i < childCount; i++) {
366 final View child = getChildAt(i);
367 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
368
369 if (!lp.expanded) continue;
370
371 final int width = lp.cellsUsed * cellSize + lp.extraPixels;
Adam Powell367ee322012-05-06 18:32:33 -0700372 child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
373 itemHeightSpec);
Adam Powell35aecd52011-07-01 13:43:49 -0700374 }
375 }
376
377 if (heightMode != MeasureSpec.EXACTLY) {
378 heightSize = maxChildHeight;
379 }
380
381 setMeasuredDimension(widthSize, heightSize);
Adam Powell35aecd52011-07-01 13:43:49 -0700382 }
383
384 /**
385 * Measure a child view to fit within cell-based formatting. The child's width
386 * will be measured to a whole multiple of cellSize.
387 *
Adam Powell160bb7f2011-07-07 10:22:27 -0700388 * <p>Sets the expandable and cellsUsed fields of LayoutParams.
Adam Powell35aecd52011-07-01 13:43:49 -0700389 *
390 * @param child Child to measure
391 * @param cellSize Size of one cell
392 * @param cellsRemaining Number of cells remaining that this view can expand to fill
393 * @param parentHeightMeasureSpec MeasureSpec used by the parent view
394 * @param parentHeightPadding Padding present in the parent view
395 * @return Number of cells this child was measured to occupy
396 */
397 static int measureChildForCells(View child, int cellSize, int cellsRemaining,
398 int parentHeightMeasureSpec, int parentHeightPadding) {
399 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Powell35aecd52011-07-01 13:43:49 -0700400
401 final int childHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec) -
402 parentHeightPadding;
403 final int childHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec);
404 final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode);
405
Adam Powella7dec6d2012-04-09 15:54:01 -0700406 final ActionMenuItemView itemView = child instanceof ActionMenuItemView ?
407 (ActionMenuItemView) child : null;
408 final boolean hasText = itemView != null && itemView.hasText();
409
Adam Powell160bb7f2011-07-07 10:22:27 -0700410 int cellsUsed = 0;
Adam Powella7dec6d2012-04-09 15:54:01 -0700411 if (cellsRemaining > 0 && (!hasText || cellsRemaining >= 2)) {
Adam Powell35aecd52011-07-01 13:43:49 -0700412 final int childWidthSpec = MeasureSpec.makeMeasureSpec(
413 cellSize * cellsRemaining, MeasureSpec.AT_MOST);
414 child.measure(childWidthSpec, childHeightSpec);
415
416 final int measuredWidth = child.getMeasuredWidth();
417 cellsUsed = measuredWidth / cellSize;
418 if (measuredWidth % cellSize != 0) cellsUsed++;
Adam Powella7dec6d2012-04-09 15:54:01 -0700419 if (hasText && cellsUsed < 2) cellsUsed = 2;
Adam Powell35aecd52011-07-01 13:43:49 -0700420 }
Adam Powell160bb7f2011-07-07 10:22:27 -0700421
Adam Powella7dec6d2012-04-09 15:54:01 -0700422 final boolean expandable = !lp.isOverflowButton && hasText;
Adam Powell160bb7f2011-07-07 10:22:27 -0700423 lp.expandable = expandable;
424
Adam Powell35aecd52011-07-01 13:43:49 -0700425 lp.cellsUsed = cellsUsed;
426 final int targetWidth = cellsUsed * cellSize;
427 child.measure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
428 childHeightSpec);
429 return cellsUsed;
Adam Powell640a66e2011-04-29 10:18:53 -0700430 }
431
432 @Override
433 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
434 if (!mFormatItems) {
435 super.onLayout(changed, left, top, right, bottom);
436 return;
437 }
438
439 final int childCount = getChildCount();
Chris Banes1970cfd42014-10-29 11:04:49 +0000440 final int midVertical = (bottom - top) / 2;
Adam Powell640a66e2011-04-29 10:18:53 -0700441 final int dividerWidth = getDividerWidth();
Adam Powell640a66e2011-04-29 10:18:53 -0700442 int overflowWidth = 0;
443 int nonOverflowWidth = 0;
444 int nonOverflowCount = 0;
445 int widthRemaining = right - left - getPaddingRight() - getPaddingLeft();
Adam Powell35aecd52011-07-01 13:43:49 -0700446 boolean hasOverflow = false;
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700447 final boolean isLayoutRtl = isLayoutRtl();
Adam Powell640a66e2011-04-29 10:18:53 -0700448 for (int i = 0; i < childCount; i++) {
449 final View v = getChildAt(i);
450 if (v.getVisibility() == GONE) {
451 continue;
452 }
453
454 LayoutParams p = (LayoutParams) v.getLayoutParams();
455 if (p.isOverflowButton) {
Adam Powell640a66e2011-04-29 10:18:53 -0700456 overflowWidth = v.getMeasuredWidth();
457 if (hasDividerBeforeChildAt(i)) {
458 overflowWidth += dividerWidth;
459 }
460
461 int height = v.getMeasuredHeight();
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700462 int r;
463 int l;
464 if (isLayoutRtl) {
465 l = getPaddingLeft() + p.leftMargin;
466 r = l + overflowWidth;
467 } else {
468 r = getWidth() - getPaddingRight() - p.rightMargin;
469 l = r - overflowWidth;
470 }
Adam Powell640a66e2011-04-29 10:18:53 -0700471 int t = midVertical - (height / 2);
472 int b = t + height;
473 v.layout(l, t, r, b);
474
475 widthRemaining -= overflowWidth;
Adam Powell35aecd52011-07-01 13:43:49 -0700476 hasOverflow = true;
Adam Powell640a66e2011-04-29 10:18:53 -0700477 } else {
Adam Powell35aecd52011-07-01 13:43:49 -0700478 final int size = v.getMeasuredWidth() + p.leftMargin + p.rightMargin;
479 nonOverflowWidth += size;
480 widthRemaining -= size;
Adam Powell640a66e2011-04-29 10:18:53 -0700481 if (hasDividerBeforeChildAt(i)) {
482 nonOverflowWidth += dividerWidth;
483 }
484 nonOverflowCount++;
485 }
486 }
487
Adam Powell14b7e2c2011-08-12 11:11:50 -0700488 if (childCount == 1 && !hasOverflow) {
489 // Center a single child
490 final View v = getChildAt(0);
491 final int width = v.getMeasuredWidth();
492 final int height = v.getMeasuredHeight();
493 final int midHorizontal = (right - left) / 2;
494 final int l = midHorizontal - width / 2;
495 final int t = midVertical - height / 2;
496 v.layout(l, t, l + width, t + height);
497 return;
498 }
499
Adam Powell35aecd52011-07-01 13:43:49 -0700500 final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1);
Adam Powell14b7e2c2011-08-12 11:11:50 -0700501 final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0);
Adam Powell640a66e2011-04-29 10:18:53 -0700502
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700503 if (isLayoutRtl) {
504 int startRight = getWidth() - getPaddingRight();
505 for (int i = 0; i < childCount; i++) {
506 final View v = getChildAt(i);
507 final LayoutParams lp = (LayoutParams) v.getLayoutParams();
508 if (v.getVisibility() == GONE || lp.isOverflowButton) {
509 continue;
510 }
Adam Powell640a66e2011-04-29 10:18:53 -0700511
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700512 startRight -= lp.rightMargin;
513 int width = v.getMeasuredWidth();
514 int height = v.getMeasuredHeight();
515 int t = midVertical - height / 2;
516 v.layout(startRight - width, t, startRight, t + height);
517 startRight -= width + lp.leftMargin + spacerSize;
518 }
519 } else {
520 int startLeft = getPaddingLeft();
521 for (int i = 0; i < childCount; i++) {
522 final View v = getChildAt(i);
523 final LayoutParams lp = (LayoutParams) v.getLayoutParams();
524 if (v.getVisibility() == GONE || lp.isOverflowButton) {
525 continue;
526 }
527
528 startLeft += lp.leftMargin;
529 int width = v.getMeasuredWidth();
530 int height = v.getMeasuredHeight();
531 int t = midVertical - height / 2;
532 v.layout(startLeft, t, startLeft + width, t + height);
533 startLeft += width + lp.rightMargin + spacerSize;
534 }
Adam Powell640a66e2011-04-29 10:18:53 -0700535 }
536 }
537
538 @Override
Adam Powell8515ee82010-11-30 14:09:55 -0800539 public void onDetachedFromWindow() {
540 super.onDetachedFromWindow();
Adam Powelle021e6e2014-05-23 17:27:24 -0700541 dismissPopupMenus();
Adam Powell8028dd32010-07-15 10:16:33 -0700542 }
543
Chris Banesa41b7892015-06-09 13:36:44 +0000544 /**
545 * Set the icon to use for the overflow button.
546 *
547 * @param icon Drawable to set, may be null to clear the icon
548 */
549 public void setOverflowIcon(@Nullable Drawable icon) {
550 getMenu();
551 mPresenter.setOverflowIcon(icon);
552 }
553
554 /**
555 * Return the current drawable used as the overflow icon.
556 *
557 * @return The overflow icon drawable
558 */
559 @Nullable
560 public Drawable getOverflowIcon() {
561 getMenu();
562 return mPresenter.getOverflowIcon();
563 }
564
Adam Powellfa18d182014-01-07 15:56:59 -0800565 /** @hide */
Adam Powell8028dd32010-07-15 10:16:33 -0700566 public boolean isOverflowReserved() {
567 return mReserveOverflow;
Adam Powell7ade1be2010-06-17 12:51:21 -0700568 }
Adam Powellfa18d182014-01-07 15:56:59 -0800569
570 /** @hide */
Adam Powellb366bba2010-07-20 14:26:38 -0700571 public void setOverflowReserved(boolean reserveOverflow) {
572 mReserveOverflow = reserveOverflow;
573 }
Adam Powellf0ad6e62011-01-10 17:14:06 -0800574
Adam Powell7ade1be2010-06-17 12:51:21 -0700575 @Override
576 protected LayoutParams generateDefaultLayoutParams() {
577 LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
578 LayoutParams.WRAP_CONTENT);
Adam Powell6b336f82010-08-10 20:13:01 -0700579 params.gravity = Gravity.CENTER_VERTICAL;
Adam Powell7ade1be2010-06-17 12:51:21 -0700580 return params;
581 }
582
583 @Override
Adam Powell35aecd52011-07-01 13:43:49 -0700584 public LayoutParams generateLayoutParams(AttributeSet attrs) {
585 return new LayoutParams(getContext(), attrs);
586 }
587
588 @Override
Adam Powell7ade1be2010-06-17 12:51:21 -0700589 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
Adam Powell8c1b02e2012-07-16 17:58:18 -0700590 if (p != null) {
591 final LayoutParams result = p instanceof LayoutParams
592 ? new LayoutParams((LayoutParams) p)
593 : new LayoutParams(p);
Adam Powell3f476b32011-01-03 19:25:36 -0800594 if (result.gravity <= Gravity.NO_GRAVITY) {
595 result.gravity = Gravity.CENTER_VERTICAL;
596 }
597 return result;
598 }
Adam Powell7ade1be2010-06-17 12:51:21 -0700599 return generateDefaultLayoutParams();
600 }
Adam Powell96675b12010-06-10 18:58:59 -0700601
Adam Powell696cba52011-03-29 10:38:16 -0700602 @Override
603 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
Adam Powell35aecd52011-07-01 13:43:49 -0700604 return p != null && p instanceof LayoutParams;
Adam Powell696cba52011-03-29 10:38:16 -0700605 }
606
Adam Powellfa18d182014-01-07 15:56:59 -0800607 /** @hide */
Adam Powell640a66e2011-04-29 10:18:53 -0700608 public LayoutParams generateOverflowButtonLayoutParams() {
609 LayoutParams result = generateDefaultLayoutParams();
610 result.isOverflowButton = true;
611 return result;
612 }
613
Adam Powellfa18d182014-01-07 15:56:59 -0800614 /** @hide */
Adam Powell96675b12010-06-10 18:58:59 -0700615 public boolean invokeItem(MenuItemImpl item) {
616 return mMenu.performItemAction(item, 0);
617 }
618
Adam Powellfa18d182014-01-07 15:56:59 -0800619 /** @hide */
Adam Powell96675b12010-06-10 18:58:59 -0700620 public int getWindowAnimations() {
621 return 0;
622 }
623
Adam Powellfa18d182014-01-07 15:56:59 -0800624 /** @hide */
Alan Viverette28a84682016-01-04 13:43:23 -0500625 public void initialize(@Nullable MenuBuilder menu) {
Adam Powell96675b12010-06-10 18:58:59 -0700626 mMenu = menu;
Adam Powell96675b12010-06-10 18:58:59 -0700627 }
628
Adam Powellfa18d182014-01-07 15:56:59 -0800629 /**
630 * Returns the Menu object that this ActionMenuView is currently presenting.
631 *
632 * <p>Applications should use this method to obtain the ActionMenuView's Menu object
633 * and inflate or add content to it as necessary.</p>
634 *
635 * @return the Menu presented by this view
636 */
637 public Menu getMenu() {
638 if (mMenu == null) {
639 final Context context = getContext();
640 mMenu = new MenuBuilder(context);
Adam Powelle43340c2014-03-17 19:10:43 -0700641 mMenu.setCallback(new MenuBuilderCallback());
Adam Powellfa18d182014-01-07 15:56:59 -0800642 mPresenter = new ActionMenuPresenter(context);
Adam Powelle8d15362014-09-09 13:51:16 -0700643 mPresenter.setReserveOverflow(true);
Adam Powell04c0d462014-09-04 16:10:24 -0700644 mPresenter.setCallback(mActionMenuPresenterCallback != null
645 ? mActionMenuPresenterCallback : new ActionMenuPresenterCallback());
Alan Viverette3d0f21d2014-07-10 16:15:01 -0700646 mMenu.addMenuPresenter(mPresenter, mPopupContext);
Adam Powell07a74542014-05-30 15:52:44 -0700647 mPresenter.setMenuView(this);
Adam Powellfa18d182014-01-07 15:56:59 -0800648 }
649
650 return mMenu;
651 }
652
653 /**
Adam Powell04c0d462014-09-04 16:10:24 -0700654 * Must be called before the first call to getMenu()
655 * @hide
656 */
Adam Powellc4612502014-09-04 19:16:39 -0700657 public void setMenuCallbacks(MenuPresenter.Callback pcb, MenuBuilder.Callback mcb) {
658 mActionMenuPresenterCallback = pcb;
659 mMenuBuilderCallback = mcb;
Adam Powell04c0d462014-09-04 16:10:24 -0700660 }
661
662 /**
Adam Powelle021e6e2014-05-23 17:27:24 -0700663 * Returns the current menu or null if one has not yet been configured.
664 * @hide Internal use only for action bar integration
665 */
666 public MenuBuilder peekMenu() {
667 return mMenu;
668 }
669
670 /**
671 * Show the overflow items from the associated menu.
672 *
673 * @return true if the menu was able to be shown, false otherwise
674 */
675 public boolean showOverflowMenu() {
676 return mPresenter != null && mPresenter.showOverflowMenu();
677 }
678
679 /**
680 * Hide the overflow items from the associated menu.
681 *
682 * @return true if the menu was able to be hidden, false otherwise
683 */
684 public boolean hideOverflowMenu() {
685 return mPresenter != null && mPresenter.hideOverflowMenu();
686 }
687
688 /**
689 * Check whether the overflow menu is currently showing. This may not reflect
690 * a pending show operation in progress.
691 *
692 * @return true if the overflow menu is currently showing
693 */
694 public boolean isOverflowMenuShowing() {
695 return mPresenter != null && mPresenter.isOverflowMenuShowing();
696 }
697
698 /** @hide */
699 public boolean isOverflowMenuShowPending() {
700 return mPresenter != null && mPresenter.isOverflowMenuShowPending();
701 }
702
703 /**
704 * Dismiss any popups associated with this menu view.
705 */
706 public void dismissPopupMenus() {
707 if (mPresenter != null) {
708 mPresenter.dismissPopupMenus();
709 }
710 }
711
712 /**
Adam Powellfa18d182014-01-07 15:56:59 -0800713 * @hide Private LinearLayout (superclass) API. Un-hide if LinearLayout API is made public.
714 */
Adam Powell696cba52011-03-29 10:38:16 -0700715 @Override
716 protected boolean hasDividerBeforeChildAt(int childIndex) {
Jake Wharton825992f2012-07-28 21:31:51 -0700717 if (childIndex == 0) {
718 return false;
719 }
Adam Powell696cba52011-03-29 10:38:16 -0700720 final View childBefore = getChildAt(childIndex - 1);
721 final View child = getChildAt(childIndex);
722 boolean result = false;
723 if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
724 result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
Adam Powell96675b12010-06-10 18:58:59 -0700725 }
Adam Powell696cba52011-03-29 10:38:16 -0700726 if (childIndex > 0 && child instanceof ActionMenuChildView) {
727 result |= ((ActionMenuChildView) child).needsDividerBefore();
Adam Powell8028dd32010-07-15 10:16:33 -0700728 }
Adam Powellbe4d68e2010-10-08 18:16:34 -0700729 return result;
730 }
731
Alan Viverettea54956a2015-01-07 16:05:02 -0800732 /** @hide */
733 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
Adam Powell7bc3ca02011-08-26 18:29:58 -0700734 return false;
735 }
736
Adam Powell07a74542014-05-30 15:52:44 -0700737 /** @hide */
738 public void setExpandedActionViewsExclusive(boolean exclusive) {
739 mPresenter.setExpandedActionViewsExclusive(exclusive);
740 }
741
Adam Powelle43340c2014-03-17 19:10:43 -0700742 /**
743 * Interface responsible for receiving menu item click events if the items themselves
744 * do not have individual item click listeners.
745 */
746 public interface OnMenuItemClickListener {
747 /**
748 * This method will be invoked when a menu item is clicked if the item itself did
749 * not already handle the event.
750 *
751 * @param item {@link MenuItem} that was clicked
752 * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
753 */
754 public boolean onMenuItemClick(MenuItem item);
755 }
756
757 private class MenuBuilderCallback implements MenuBuilder.Callback {
758 @Override
759 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
760 return mOnMenuItemClickListener != null &&
761 mOnMenuItemClickListener.onMenuItemClick(item);
762 }
763
764 @Override
765 public void onMenuModeChange(MenuBuilder menu) {
Adam Powellc4612502014-09-04 19:16:39 -0700766 if (mMenuBuilderCallback != null) {
767 mMenuBuilderCallback.onMenuModeChange(menu);
768 }
Adam Powelle43340c2014-03-17 19:10:43 -0700769 }
770 }
771
772 private class ActionMenuPresenterCallback implements ActionMenuPresenter.Callback {
773 @Override
774 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
775 }
776
777 @Override
778 public boolean onOpenSubMenu(MenuBuilder subMenu) {
779 return false;
780 }
781 }
782
Adam Powellfa18d182014-01-07 15:56:59 -0800783 /** @hide */
Adam Powell696cba52011-03-29 10:38:16 -0700784 public interface ActionMenuChildView {
785 public boolean needsDividerBefore();
786 public boolean needsDividerAfter();
Adam Powell8515ee82010-11-30 14:09:55 -0800787 }
Adam Powell640a66e2011-04-29 10:18:53 -0700788
789 public static class LayoutParams extends LinearLayout.LayoutParams {
Adam Powellfa18d182014-01-07 15:56:59 -0800790 /** @hide */
Adam Powell640a66e2011-04-29 10:18:53 -0700791 @ViewDebug.ExportedProperty(category = "layout")
792 public boolean isOverflowButton;
Adam Powellfa18d182014-01-07 15:56:59 -0800793
794 /** @hide */
Adam Powell35aecd52011-07-01 13:43:49 -0700795 @ViewDebug.ExportedProperty(category = "layout")
796 public int cellsUsed;
Adam Powellfa18d182014-01-07 15:56:59 -0800797
798 /** @hide */
Adam Powell35aecd52011-07-01 13:43:49 -0700799 @ViewDebug.ExportedProperty(category = "layout")
Adam Powell35aecd52011-07-01 13:43:49 -0700800 public int extraPixels;
Adam Powellfa18d182014-01-07 15:56:59 -0800801
802 /** @hide */
Adam Powell160bb7f2011-07-07 10:22:27 -0700803 @ViewDebug.ExportedProperty(category = "layout")
804 public boolean expandable;
Adam Powellfa18d182014-01-07 15:56:59 -0800805
806 /** @hide */
Adam Powellbe3c3292011-08-24 12:52:28 -0700807 @ViewDebug.ExportedProperty(category = "layout")
808 public boolean preventEdgeOffset;
Adam Powell160bb7f2011-07-07 10:22:27 -0700809
Adam Powellfa18d182014-01-07 15:56:59 -0800810 /** @hide */
Adam Powell35aecd52011-07-01 13:43:49 -0700811 public boolean expanded;
Adam Powell640a66e2011-04-29 10:18:53 -0700812
813 public LayoutParams(Context c, AttributeSet attrs) {
814 super(c, attrs);
815 }
816
Adam Powell8c1b02e2012-07-16 17:58:18 -0700817 public LayoutParams(ViewGroup.LayoutParams other) {
818 super(other);
819 }
820
Adam Powell640a66e2011-04-29 10:18:53 -0700821 public LayoutParams(LayoutParams other) {
822 super((LinearLayout.LayoutParams) other);
823 isOverflowButton = other.isOverflowButton;
824 }
825
826 public LayoutParams(int width, int height) {
827 super(width, height);
828 isOverflowButton = false;
829 }
830
Adam Powellfa18d182014-01-07 15:56:59 -0800831 /** @hide */
Adam Powell640a66e2011-04-29 10:18:53 -0700832 public LayoutParams(int width, int height, boolean isOverflowButton) {
833 super(width, height);
834 this.isOverflowButton = isOverflowButton;
835 }
Siva Velusamy94a6d152015-05-05 15:07:00 -0700836
837 /** @hide */
838 @Override
839 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
840 super.encodeProperties(encoder);
841
842 encoder.addProperty("layout:overFlowButton", isOverflowButton);
843 encoder.addProperty("layout:cellsUsed", cellsUsed);
844 encoder.addProperty("layout:extraPixels", extraPixels);
845 encoder.addProperty("layout:expandable", expandable);
846 encoder.addProperty("layout:preventEdgeOffset", preventEdgeOffset);
847 }
Adam Powell640a66e2011-04-29 10:18:53 -0700848 }
Adam Powell96675b12010-06-10 18:58:59 -0700849}