blob: c4bbdb005fe0974e32593cca02b091d026c79c06 [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;
Aurimas Liutikas99441c52016-10-11 16:48:32 -070034
Adam Powellfa18d182014-01-07 15:56:59 -080035import com.android.internal.view.menu.ActionMenuItemView;
36import com.android.internal.view.menu.MenuBuilder;
37import com.android.internal.view.menu.MenuItemImpl;
Adam Powell04c0d462014-09-04 16:10:24 -070038import com.android.internal.view.menu.MenuPresenter;
Adam Powellfa18d182014-01-07 15:56:59 -080039import com.android.internal.view.menu.MenuView;
Adam Powell96675b12010-06-10 18:58:59 -070040
Adam Powell96675b12010-06-10 18:58:59 -070041/**
Adam Powellfa18d182014-01-07 15:56:59 -080042 * ActionMenuView is a presentation of a series of menu options as a View. It provides
43 * several top level options as action buttons while spilling remaining options over as
44 * items in an overflow menu. This allows applications to present packs of actions inline with
45 * specific or repeating content.
Adam Powell96675b12010-06-10 18:58:59 -070046 */
47public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView {
48 private static final String TAG = "ActionMenuView";
Aurimas Liutikas99441c52016-10-11 16:48:32 -070049
Adam Powell35aecd52011-07-01 13:43:49 -070050 static final int MIN_CELL_SIZE = 56; // dips
Adam Powellbe3c3292011-08-24 12:52:28 -070051 static final int GENERATED_ITEM_PADDING = 4; // dips
Adam Powell35aecd52011-07-01 13:43:49 -070052
Adam Powell96675b12010-06-10 18:58:59 -070053 private MenuBuilder mMenu;
Adam Powell7ade1be2010-06-17 12:51:21 -070054
Alan Viverette3d0f21d2014-07-10 16:15:01 -070055 /** Context against which to inflate popup menus. */
56 private Context mPopupContext;
57
58 /** Theme resource against which to inflate popup menus. */
59 private int mPopupTheme;
60
Adam Powell8028dd32010-07-15 10:16:33 -070061 private boolean mReserveOverflow;
Adam Powell696cba52011-03-29 10:38:16 -070062 private ActionMenuPresenter mPresenter;
Adam Powell04c0d462014-09-04 16:10:24 -070063 private MenuPresenter.Callback mActionMenuPresenterCallback;
Adam Powellc4612502014-09-04 19:16:39 -070064 private MenuBuilder.Callback mMenuBuilderCallback;
Adam Powell640a66e2011-04-29 10:18:53 -070065 private boolean mFormatItems;
Adam Powell89b09da2011-07-27 11:55:29 -070066 private int mFormatItemsWidth;
Adam Powell35aecd52011-07-01 13:43:49 -070067 private int mMinCellSize;
Adam Powellbe3c3292011-08-24 12:52:28 -070068 private int mGeneratedItemPadding;
Adam Powell8515ee82010-11-30 14:09:55 -080069
Adam Powelle43340c2014-03-17 19:10:43 -070070 private OnMenuItemClickListener mOnMenuItemClickListener;
71
Adam Powell96675b12010-06-10 18:58:59 -070072 public ActionMenuView(Context context) {
73 this(context, null);
74 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -070075
Adam Powell96675b12010-06-10 18:58:59 -070076 public ActionMenuView(Context context, AttributeSet attrs) {
77 super(context, attrs);
Adam Powellf16888f2010-10-11 17:05:29 -070078 setBaselineAligned(false);
Adam Powellbe3c3292011-08-24 12:52:28 -070079 final float density = context.getResources().getDisplayMetrics().density;
80 mMinCellSize = (int) (MIN_CELL_SIZE * density);
81 mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
Alan Viverette3d0f21d2014-07-10 16:15:01 -070082 mPopupContext = context;
83 mPopupTheme = 0;
Adam Powell773b1b92010-08-20 15:45:24 -070084 }
85
Alan Viverette3d0f21d2014-07-10 16:15:01 -070086 /**
87 * Specifies the theme to use when inflating popup menus. By default, uses
88 * the same theme as the action menu view itself.
89 *
90 * @param resId theme used to inflate popup menus
91 * @see #getPopupTheme()
92 */
Tor Norbye417ee5b2015-03-10 20:57:37 -070093 public void setPopupTheme(@StyleRes int resId) {
Alan Viverette3d0f21d2014-07-10 16:15:01 -070094 if (mPopupTheme != resId) {
95 mPopupTheme = resId;
96 if (resId == 0) {
97 mPopupContext = mContext;
98 } else {
99 mPopupContext = new ContextThemeWrapper(mContext, resId);
100 }
101 }
102 }
103
104 /**
105 * @return resource identifier of the theme used to inflate popup menus, or
106 * 0 if menus are inflated against the action menu view theme
107 * @see #setPopupTheme(int)
108 */
109 public int getPopupTheme() {
110 return mPopupTheme;
111 }
112
113 /**
114 * @param presenter Menu presenter used to display popup menu
115 * @hide
116 */
Adam Powell696cba52011-03-29 10:38:16 -0700117 public void setPresenter(ActionMenuPresenter presenter) {
118 mPresenter = presenter;
Adam Powelle021e6e2014-05-23 17:27:24 -0700119 mPresenter.setMenuView(this);
Adam Powell696cba52011-03-29 10:38:16 -0700120 }
121
Adam Powell6c6f5752010-08-20 18:34:46 -0700122 @Override
Adam Powell773b1b92010-08-20 15:45:24 -0700123 public void onConfigurationChanged(Configuration newConfig) {
Adam Powell8515ee82010-11-30 14:09:55 -0800124 super.onConfigurationChanged(newConfig);
Adam Powell6c6f5752010-08-20 18:34:46 -0700125
Chris Banes5cd13262015-01-19 12:01:46 +0000126 if (mPresenter != null) {
127 mPresenter.updateMenuView(false);
128
129 if (mPresenter.isOverflowMenuShowing()) {
130 mPresenter.hideOverflowMenu();
131 mPresenter.showOverflowMenu();
132 }
Adam Powell6c6f5752010-08-20 18:34:46 -0700133 }
Adam Powell773b1b92010-08-20 15:45:24 -0700134 }
135
Adam Powelle43340c2014-03-17 19:10:43 -0700136 public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
137 mOnMenuItemClickListener = listener;
138 }
139
Adam Powell8515ee82010-11-30 14:09:55 -0800140 @Override
Adam Powell640a66e2011-04-29 10:18:53 -0700141 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Adam Powell35aecd52011-07-01 13:43:49 -0700142 // If we've been given an exact size to match, apply special formatting during layout.
Adam Powell89b09da2011-07-27 11:55:29 -0700143 final boolean wasFormatted = mFormatItems;
Adam Powell35aecd52011-07-01 13:43:49 -0700144 mFormatItems = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
Adam Powell89b09da2011-07-27 11:55:29 -0700145
146 if (wasFormatted != mFormatItems) {
147 mFormatItemsWidth = 0; // Reset this when switching modes
148 }
149
150 // Special formatting can change whether items can fit as action buttons.
151 // Kick the menu and update presenters when this changes.
Adam Powellda971082013-10-03 18:21:58 -0700152 final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
Adam Powell89b09da2011-07-27 11:55:29 -0700153 if (mFormatItems && mMenu != null && widthSize != mFormatItemsWidth) {
154 mFormatItemsWidth = widthSize;
Adam Powell640a66e2011-04-29 10:18:53 -0700155 mMenu.onItemsChanged(true);
Adam Powell640a66e2011-04-29 10:18:53 -0700156 }
Adam Powell35aecd52011-07-01 13:43:49 -0700157
Adam Powelle43340c2014-03-17 19:10:43 -0700158 final int childCount = getChildCount();
159 if (mFormatItems && childCount > 0) {
Adam Powell35aecd52011-07-01 13:43:49 -0700160 onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec);
161 } else {
Adam Powell75d022a2012-03-06 12:04:07 -0800162 // Previous measurement at exact format may have set margins - reset them.
Adam Powell75d022a2012-03-06 12:04:07 -0800163 for (int i = 0; i < childCount; i++) {
164 final View child = getChildAt(i);
165 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
166 lp.leftMargin = lp.rightMargin = 0;
167 }
Adam Powell35aecd52011-07-01 13:43:49 -0700168 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
169 }
170 }
171
172 private void onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec) {
173 // We already know the width mode is EXACTLY if we're here.
174 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
175 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
176 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
177
178 final int widthPadding = getPaddingLeft() + getPaddingRight();
179 final int heightPadding = getPaddingTop() + getPaddingBottom();
180
Adam Powellfa18d182014-01-07 15:56:59 -0800181 final int itemHeightSpec = getChildMeasureSpec(heightMeasureSpec, heightPadding,
182 ViewGroup.LayoutParams.WRAP_CONTENT);
Adam Powell367ee322012-05-06 18:32:33 -0700183
Adam Powell35aecd52011-07-01 13:43:49 -0700184 widthSize -= widthPadding;
185
186 // Divide the view into cells.
187 final int cellCount = widthSize / mMinCellSize;
188 final int cellSizeRemaining = widthSize % mMinCellSize;
Adam Powell3bb421d2011-08-16 15:04:53 -0700189
190 if (cellCount == 0) {
191 // Give up, nothing fits.
192 setMeasuredDimension(widthSize, 0);
193 return;
194 }
195
Adam Powell35aecd52011-07-01 13:43:49 -0700196 final int cellSize = mMinCellSize + cellSizeRemaining / cellCount;
197
198 int cellsRemaining = cellCount;
199 int maxChildHeight = 0;
200 int maxCellsUsed = 0;
Adam Powell160bb7f2011-07-07 10:22:27 -0700201 int expandableItemCount = 0;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700202 int visibleItemCount = 0;
203 boolean hasOverflow = false;
Adam Powell35aecd52011-07-01 13:43:49 -0700204
Adam Powell14b7e2c2011-08-12 11:11:50 -0700205 // This is used as a bitfield to locate the smallest items present. Assumes childCount < 64.
206 long smallestItemsAt = 0;
Adam Powell35aecd52011-07-01 13:43:49 -0700207
208 final int childCount = getChildCount();
209 for (int i = 0; i < childCount; i++) {
210 final View child = getChildAt(i);
Adam Powell14b7e2c2011-08-12 11:11:50 -0700211 if (child.getVisibility() == GONE) continue;
212
Adam Powellbe3c3292011-08-24 12:52:28 -0700213 final boolean isGeneratedItem = child instanceof ActionMenuItemView;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700214 visibleItemCount++;
215
Adam Powellbe3c3292011-08-24 12:52:28 -0700216 if (isGeneratedItem) {
217 // Reset padding for generated menu item views; it may change below
218 // and views are recycled.
219 child.setPadding(mGeneratedItemPadding, 0, mGeneratedItemPadding, 0);
220 }
221
Adam Powell35aecd52011-07-01 13:43:49 -0700222 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
223 lp.expanded = false;
224 lp.extraPixels = 0;
225 lp.cellsUsed = 0;
Adam Powell160bb7f2011-07-07 10:22:27 -0700226 lp.expandable = false;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700227 lp.leftMargin = 0;
228 lp.rightMargin = 0;
Adam Powellbe3c3292011-08-24 12:52:28 -0700229 lp.preventEdgeOffset = isGeneratedItem && ((ActionMenuItemView) child).hasText();
Adam Powell35aecd52011-07-01 13:43:49 -0700230
231 // Overflow always gets 1 cell. No more, no less.
232 final int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining;
233
234 final int cellsUsed = measureChildForCells(child, cellSize, cellsAvailable,
Adam Powell367ee322012-05-06 18:32:33 -0700235 itemHeightSpec, heightPadding);
Adam Powell35aecd52011-07-01 13:43:49 -0700236
237 maxCellsUsed = Math.max(maxCellsUsed, cellsUsed);
Adam Powell160bb7f2011-07-07 10:22:27 -0700238 if (lp.expandable) expandableItemCount++;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700239 if (lp.isOverflowButton) hasOverflow = true;
Adam Powell35aecd52011-07-01 13:43:49 -0700240
241 cellsRemaining -= cellsUsed;
242 maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
Adam Powell14b7e2c2011-08-12 11:11:50 -0700243 if (cellsUsed == 1) smallestItemsAt |= (1 << i);
Adam Powell35aecd52011-07-01 13:43:49 -0700244 }
245
Adam Powellbe3c3292011-08-24 12:52:28 -0700246 // When we have overflow and a single expanded (text) item, we want to try centering it
247 // visually in the available space even though overflow consumes some of it.
248 final boolean centerSingleExpandedItem = hasOverflow && visibleItemCount == 2;
249
Adam Powell35aecd52011-07-01 13:43:49 -0700250 // Divide space for remaining cells if we have items that can expand.
251 // Try distributing whole leftover cells to smaller items first.
252
253 boolean needsExpansion = false;
Adam Powell160bb7f2011-07-07 10:22:27 -0700254 while (expandableItemCount > 0 && cellsRemaining > 0) {
Adam Powell35aecd52011-07-01 13:43:49 -0700255 int minCells = Integer.MAX_VALUE;
256 long minCellsAt = 0; // Bit locations are indices of relevant child views
257 int minCellsItemCount = 0;
258 for (int i = 0; i < childCount; i++) {
259 final View child = getChildAt(i);
260 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
261
262 // Don't try to expand items that shouldn't.
Adam Powell160bb7f2011-07-07 10:22:27 -0700263 if (!lp.expandable) continue;
Adam Powell35aecd52011-07-01 13:43:49 -0700264
265 // Mark indices of children that can receive an extra cell.
266 if (lp.cellsUsed < minCells) {
267 minCells = lp.cellsUsed;
268 minCellsAt = 1 << i;
269 minCellsItemCount = 1;
270 } else if (lp.cellsUsed == minCells) {
271 minCellsAt |= 1 << i;
272 minCellsItemCount++;
273 }
274 }
275
Adam Powell35aecd52011-07-01 13:43:49 -0700276 // Items that get expanded will always be in the set of smallest items when we're done.
Adam Powell14b7e2c2011-08-12 11:11:50 -0700277 smallestItemsAt |= minCellsAt;
Adam Powell35aecd52011-07-01 13:43:49 -0700278
Adam Powellbe3c3292011-08-24 12:52:28 -0700279 if (minCellsItemCount > cellsRemaining) break; // Couldn't expand anything evenly. Stop.
Adam Powell35aecd52011-07-01 13:43:49 -0700280
Adam Powellbe3c3292011-08-24 12:52:28 -0700281 // We have enough cells, all minimum size items will be incremented.
282 minCells++;
283
284 for (int i = 0; i < childCount; i++) {
Adam Powell35aecd52011-07-01 13:43:49 -0700285 final View child = getChildAt(i);
286 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Powellbe3c3292011-08-24 12:52:28 -0700287 if ((minCellsAt & (1 << i)) == 0) {
288 // If this item is already at our small item count, mark it for later.
289 if (lp.cellsUsed == minCells) smallestItemsAt |= 1 << i;
290 continue;
291 }
292
293 if (centerSingleExpandedItem && lp.preventEdgeOffset && cellsRemaining == 1) {
294 // Add padding to this item such that it centers.
295 child.setPadding(mGeneratedItemPadding + cellSize, 0, mGeneratedItemPadding, 0);
296 }
Adam Powell35aecd52011-07-01 13:43:49 -0700297 lp.cellsUsed++;
298 lp.expanded = true;
299 cellsRemaining--;
300 }
301
302 needsExpansion = true;
303 }
304
305 // Divide any space left that wouldn't divide along cell boundaries
Adam Powell14b7e2c2011-08-12 11:11:50 -0700306 // evenly among the smallest items
Adam Powell35aecd52011-07-01 13:43:49 -0700307
Adam Powell14b7e2c2011-08-12 11:11:50 -0700308 final boolean singleItem = !hasOverflow && visibleItemCount == 1;
309 if (cellsRemaining > 0 && smallestItemsAt != 0 &&
Adam Powellbe3c3292011-08-24 12:52:28 -0700310 (cellsRemaining < visibleItemCount - 1 || singleItem || maxCellsUsed > 1)) {
Adam Powell14b7e2c2011-08-12 11:11:50 -0700311 float expandCount = Long.bitCount(smallestItemsAt);
312
313 if (!singleItem) {
314 // The items at the far edges may only expand by half in order to pin to either side.
315 if ((smallestItemsAt & 1) != 0) {
Adam Powellbe3c3292011-08-24 12:52:28 -0700316 LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams();
317 if (!lp.preventEdgeOffset) expandCount -= 0.5f;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700318 }
319 if ((smallestItemsAt & (1 << (childCount - 1))) != 0) {
Adam Powellbe3c3292011-08-24 12:52:28 -0700320 LayoutParams lp = ((LayoutParams) getChildAt(childCount - 1).getLayoutParams());
321 if (!lp.preventEdgeOffset) expandCount -= 0.5f;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700322 }
323 }
324
Adam Powell3bb421d2011-08-16 15:04:53 -0700325 final int extraPixels = expandCount > 0 ?
326 (int) (cellsRemaining * cellSize / expandCount) : 0;
Adam Powell35aecd52011-07-01 13:43:49 -0700327
328 for (int i = 0; i < childCount; i++) {
Adam Powell14b7e2c2011-08-12 11:11:50 -0700329 if ((smallestItemsAt & (1 << i)) == 0) continue;
Adam Powell35aecd52011-07-01 13:43:49 -0700330
331 final View child = getChildAt(i);
332 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Powell14b7e2c2011-08-12 11:11:50 -0700333 if (child instanceof ActionMenuItemView) {
334 // If this is one of our views, expand and measure at the larger size.
335 lp.extraPixels = extraPixels;
336 lp.expanded = true;
Adam Powellbe3c3292011-08-24 12:52:28 -0700337 if (i == 0 && !lp.preventEdgeOffset) {
Adam Powell14b7e2c2011-08-12 11:11:50 -0700338 // First item gets part of its new padding pushed out of sight.
339 // The last item will get this implicitly from layout.
340 lp.leftMargin = -extraPixels / 2;
341 }
342 needsExpansion = true;
343 } else if (lp.isOverflowButton) {
344 lp.extraPixels = extraPixels;
345 lp.expanded = true;
346 lp.rightMargin = -extraPixels / 2;
347 needsExpansion = true;
348 } else {
349 // If we don't know what it is, give it some margins instead
350 // and let it center within its space. We still want to pin
351 // against the edges.
352 if (i != 0) {
353 lp.leftMargin = extraPixels / 2;
354 }
355 if (i != childCount - 1) {
356 lp.rightMargin = extraPixels / 2;
357 }
358 }
Adam Powell35aecd52011-07-01 13:43:49 -0700359 }
360
Adam Powell35aecd52011-07-01 13:43:49 -0700361 cellsRemaining = 0;
362 }
363
364 // Remeasure any items that have had extra space allocated to them.
365 if (needsExpansion) {
Adam Powell35aecd52011-07-01 13:43:49 -0700366 for (int i = 0; i < childCount; i++) {
367 final View child = getChildAt(i);
368 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
369
370 if (!lp.expanded) continue;
371
372 final int width = lp.cellsUsed * cellSize + lp.extraPixels;
Adam Powell367ee322012-05-06 18:32:33 -0700373 child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
374 itemHeightSpec);
Adam Powell35aecd52011-07-01 13:43:49 -0700375 }
376 }
377
378 if (heightMode != MeasureSpec.EXACTLY) {
379 heightSize = maxChildHeight;
380 }
381
382 setMeasuredDimension(widthSize, heightSize);
Adam Powell35aecd52011-07-01 13:43:49 -0700383 }
384
385 /**
386 * Measure a child view to fit within cell-based formatting. The child's width
387 * will be measured to a whole multiple of cellSize.
388 *
Adam Powell160bb7f2011-07-07 10:22:27 -0700389 * <p>Sets the expandable and cellsUsed fields of LayoutParams.
Adam Powell35aecd52011-07-01 13:43:49 -0700390 *
391 * @param child Child to measure
392 * @param cellSize Size of one cell
393 * @param cellsRemaining Number of cells remaining that this view can expand to fill
394 * @param parentHeightMeasureSpec MeasureSpec used by the parent view
395 * @param parentHeightPadding Padding present in the parent view
396 * @return Number of cells this child was measured to occupy
397 */
398 static int measureChildForCells(View child, int cellSize, int cellsRemaining,
399 int parentHeightMeasureSpec, int parentHeightPadding) {
400 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Powell35aecd52011-07-01 13:43:49 -0700401
402 final int childHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec) -
403 parentHeightPadding;
404 final int childHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec);
405 final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode);
406
Adam Powella7dec6d2012-04-09 15:54:01 -0700407 final ActionMenuItemView itemView = child instanceof ActionMenuItemView ?
408 (ActionMenuItemView) child : null;
409 final boolean hasText = itemView != null && itemView.hasText();
410
Adam Powell160bb7f2011-07-07 10:22:27 -0700411 int cellsUsed = 0;
Adam Powella7dec6d2012-04-09 15:54:01 -0700412 if (cellsRemaining > 0 && (!hasText || cellsRemaining >= 2)) {
Adam Powell35aecd52011-07-01 13:43:49 -0700413 final int childWidthSpec = MeasureSpec.makeMeasureSpec(
414 cellSize * cellsRemaining, MeasureSpec.AT_MOST);
415 child.measure(childWidthSpec, childHeightSpec);
416
417 final int measuredWidth = child.getMeasuredWidth();
418 cellsUsed = measuredWidth / cellSize;
419 if (measuredWidth % cellSize != 0) cellsUsed++;
Adam Powella7dec6d2012-04-09 15:54:01 -0700420 if (hasText && cellsUsed < 2) cellsUsed = 2;
Adam Powell35aecd52011-07-01 13:43:49 -0700421 }
Adam Powell160bb7f2011-07-07 10:22:27 -0700422
Adam Powella7dec6d2012-04-09 15:54:01 -0700423 final boolean expandable = !lp.isOverflowButton && hasText;
Adam Powell160bb7f2011-07-07 10:22:27 -0700424 lp.expandable = expandable;
425
Adam Powell35aecd52011-07-01 13:43:49 -0700426 lp.cellsUsed = cellsUsed;
427 final int targetWidth = cellsUsed * cellSize;
428 child.measure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
429 childHeightSpec);
430 return cellsUsed;
Adam Powell640a66e2011-04-29 10:18:53 -0700431 }
432
433 @Override
434 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
435 if (!mFormatItems) {
436 super.onLayout(changed, left, top, right, bottom);
437 return;
438 }
439
440 final int childCount = getChildCount();
Chris Banes1970cfd42014-10-29 11:04:49 +0000441 final int midVertical = (bottom - top) / 2;
Adam Powell640a66e2011-04-29 10:18:53 -0700442 final int dividerWidth = getDividerWidth();
Adam Powell640a66e2011-04-29 10:18:53 -0700443 int overflowWidth = 0;
444 int nonOverflowWidth = 0;
445 int nonOverflowCount = 0;
446 int widthRemaining = right - left - getPaddingRight() - getPaddingLeft();
Adam Powell35aecd52011-07-01 13:43:49 -0700447 boolean hasOverflow = false;
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700448 final boolean isLayoutRtl = isLayoutRtl();
Adam Powell640a66e2011-04-29 10:18:53 -0700449 for (int i = 0; i < childCount; i++) {
450 final View v = getChildAt(i);
451 if (v.getVisibility() == GONE) {
452 continue;
453 }
454
455 LayoutParams p = (LayoutParams) v.getLayoutParams();
456 if (p.isOverflowButton) {
Adam Powell640a66e2011-04-29 10:18:53 -0700457 overflowWidth = v.getMeasuredWidth();
458 if (hasDividerBeforeChildAt(i)) {
459 overflowWidth += dividerWidth;
460 }
461
462 int height = v.getMeasuredHeight();
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700463 int r;
464 int l;
465 if (isLayoutRtl) {
466 l = getPaddingLeft() + p.leftMargin;
467 r = l + overflowWidth;
468 } else {
469 r = getWidth() - getPaddingRight() - p.rightMargin;
470 l = r - overflowWidth;
471 }
Adam Powell640a66e2011-04-29 10:18:53 -0700472 int t = midVertical - (height / 2);
473 int b = t + height;
474 v.layout(l, t, r, b);
475
476 widthRemaining -= overflowWidth;
Adam Powell35aecd52011-07-01 13:43:49 -0700477 hasOverflow = true;
Adam Powell640a66e2011-04-29 10:18:53 -0700478 } else {
Adam Powell35aecd52011-07-01 13:43:49 -0700479 final int size = v.getMeasuredWidth() + p.leftMargin + p.rightMargin;
480 nonOverflowWidth += size;
481 widthRemaining -= size;
Adam Powell640a66e2011-04-29 10:18:53 -0700482 if (hasDividerBeforeChildAt(i)) {
483 nonOverflowWidth += dividerWidth;
484 }
485 nonOverflowCount++;
486 }
487 }
488
Adam Powell14b7e2c2011-08-12 11:11:50 -0700489 if (childCount == 1 && !hasOverflow) {
490 // Center a single child
491 final View v = getChildAt(0);
492 final int width = v.getMeasuredWidth();
493 final int height = v.getMeasuredHeight();
494 final int midHorizontal = (right - left) / 2;
495 final int l = midHorizontal - width / 2;
496 final int t = midVertical - height / 2;
497 v.layout(l, t, l + width, t + height);
498 return;
499 }
500
Adam Powell35aecd52011-07-01 13:43:49 -0700501 final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1);
Adam Powell14b7e2c2011-08-12 11:11:50 -0700502 final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0);
Adam Powell640a66e2011-04-29 10:18:53 -0700503
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700504 if (isLayoutRtl) {
505 int startRight = getWidth() - getPaddingRight();
506 for (int i = 0; i < childCount; i++) {
507 final View v = getChildAt(i);
508 final LayoutParams lp = (LayoutParams) v.getLayoutParams();
509 if (v.getVisibility() == GONE || lp.isOverflowButton) {
510 continue;
511 }
Adam Powell640a66e2011-04-29 10:18:53 -0700512
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700513 startRight -= lp.rightMargin;
514 int width = v.getMeasuredWidth();
515 int height = v.getMeasuredHeight();
516 int t = midVertical - height / 2;
517 v.layout(startRight - width, t, startRight, t + height);
518 startRight -= width + lp.leftMargin + spacerSize;
519 }
520 } else {
521 int startLeft = getPaddingLeft();
522 for (int i = 0; i < childCount; i++) {
523 final View v = getChildAt(i);
524 final LayoutParams lp = (LayoutParams) v.getLayoutParams();
525 if (v.getVisibility() == GONE || lp.isOverflowButton) {
526 continue;
527 }
528
529 startLeft += lp.leftMargin;
530 int width = v.getMeasuredWidth();
531 int height = v.getMeasuredHeight();
532 int t = midVertical - height / 2;
533 v.layout(startLeft, t, startLeft + width, t + height);
534 startLeft += width + lp.rightMargin + spacerSize;
535 }
Adam Powell640a66e2011-04-29 10:18:53 -0700536 }
537 }
538
539 @Override
Adam Powell8515ee82010-11-30 14:09:55 -0800540 public void onDetachedFromWindow() {
541 super.onDetachedFromWindow();
Adam Powelle021e6e2014-05-23 17:27:24 -0700542 dismissPopupMenus();
Adam Powell8028dd32010-07-15 10:16:33 -0700543 }
544
Chris Banesa41b7892015-06-09 13:36:44 +0000545 /**
546 * Set the icon to use for the overflow button.
547 *
548 * @param icon Drawable to set, may be null to clear the icon
549 */
550 public void setOverflowIcon(@Nullable Drawable icon) {
551 getMenu();
552 mPresenter.setOverflowIcon(icon);
553 }
554
555 /**
556 * Return the current drawable used as the overflow icon.
557 *
558 * @return The overflow icon drawable
559 */
560 @Nullable
561 public Drawable getOverflowIcon() {
562 getMenu();
563 return mPresenter.getOverflowIcon();
564 }
565
Adam Powellfa18d182014-01-07 15:56:59 -0800566 /** @hide */
Adam Powell8028dd32010-07-15 10:16:33 -0700567 public boolean isOverflowReserved() {
568 return mReserveOverflow;
Adam Powell7ade1be2010-06-17 12:51:21 -0700569 }
Adam Powellfa18d182014-01-07 15:56:59 -0800570
571 /** @hide */
Adam Powellb366bba2010-07-20 14:26:38 -0700572 public void setOverflowReserved(boolean reserveOverflow) {
573 mReserveOverflow = reserveOverflow;
574 }
Adam Powellf0ad6e62011-01-10 17:14:06 -0800575
Adam Powell7ade1be2010-06-17 12:51:21 -0700576 @Override
577 protected LayoutParams generateDefaultLayoutParams() {
578 LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
579 LayoutParams.WRAP_CONTENT);
Adam Powell6b336f82010-08-10 20:13:01 -0700580 params.gravity = Gravity.CENTER_VERTICAL;
Adam Powell7ade1be2010-06-17 12:51:21 -0700581 return params;
582 }
Aurimas Liutikas99441c52016-10-11 16:48:32 -0700583
Adam Powell7ade1be2010-06-17 12:51:21 -0700584 @Override
Adam Powell35aecd52011-07-01 13:43:49 -0700585 public LayoutParams generateLayoutParams(AttributeSet attrs) {
586 return new LayoutParams(getContext(), attrs);
587 }
588
589 @Override
Adam Powell7ade1be2010-06-17 12:51:21 -0700590 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
Adam Powell8c1b02e2012-07-16 17:58:18 -0700591 if (p != null) {
592 final LayoutParams result = p instanceof LayoutParams
593 ? new LayoutParams((LayoutParams) p)
594 : new LayoutParams(p);
Adam Powell3f476b32011-01-03 19:25:36 -0800595 if (result.gravity <= Gravity.NO_GRAVITY) {
596 result.gravity = Gravity.CENTER_VERTICAL;
597 }
598 return result;
599 }
Adam Powell7ade1be2010-06-17 12:51:21 -0700600 return generateDefaultLayoutParams();
601 }
Adam Powell96675b12010-06-10 18:58:59 -0700602
Adam Powell696cba52011-03-29 10:38:16 -0700603 @Override
604 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
Adam Powell35aecd52011-07-01 13:43:49 -0700605 return p != null && p instanceof LayoutParams;
Adam Powell696cba52011-03-29 10:38:16 -0700606 }
607
Adam Powellfa18d182014-01-07 15:56:59 -0800608 /** @hide */
Adam Powell640a66e2011-04-29 10:18:53 -0700609 public LayoutParams generateOverflowButtonLayoutParams() {
610 LayoutParams result = generateDefaultLayoutParams();
611 result.isOverflowButton = true;
612 return result;
613 }
614
Adam Powellfa18d182014-01-07 15:56:59 -0800615 /** @hide */
Adam Powell96675b12010-06-10 18:58:59 -0700616 public boolean invokeItem(MenuItemImpl item) {
617 return mMenu.performItemAction(item, 0);
618 }
619
Adam Powellfa18d182014-01-07 15:56:59 -0800620 /** @hide */
Adam Powell96675b12010-06-10 18:58:59 -0700621 public int getWindowAnimations() {
622 return 0;
623 }
624
Adam Powellfa18d182014-01-07 15:56:59 -0800625 /** @hide */
Alan Viverette28a84682016-01-04 13:43:23 -0500626 public void initialize(@Nullable MenuBuilder menu) {
Adam Powell96675b12010-06-10 18:58:59 -0700627 mMenu = menu;
Adam Powell96675b12010-06-10 18:58:59 -0700628 }
629
Adam Powellfa18d182014-01-07 15:56:59 -0800630 /**
631 * Returns the Menu object that this ActionMenuView is currently presenting.
632 *
633 * <p>Applications should use this method to obtain the ActionMenuView's Menu object
634 * and inflate or add content to it as necessary.</p>
635 *
636 * @return the Menu presented by this view
637 */
638 public Menu getMenu() {
639 if (mMenu == null) {
640 final Context context = getContext();
641 mMenu = new MenuBuilder(context);
Adam Powelle43340c2014-03-17 19:10:43 -0700642 mMenu.setCallback(new MenuBuilderCallback());
Adam Powellfa18d182014-01-07 15:56:59 -0800643 mPresenter = new ActionMenuPresenter(context);
Adam Powelle8d15362014-09-09 13:51:16 -0700644 mPresenter.setReserveOverflow(true);
Adam Powell04c0d462014-09-04 16:10:24 -0700645 mPresenter.setCallback(mActionMenuPresenterCallback != null
646 ? mActionMenuPresenterCallback : new ActionMenuPresenterCallback());
Alan Viverette3d0f21d2014-07-10 16:15:01 -0700647 mMenu.addMenuPresenter(mPresenter, mPopupContext);
Adam Powell07a74542014-05-30 15:52:44 -0700648 mPresenter.setMenuView(this);
Adam Powellfa18d182014-01-07 15:56:59 -0800649 }
650
651 return mMenu;
652 }
653
654 /**
Adam Powell04c0d462014-09-04 16:10:24 -0700655 * Must be called before the first call to getMenu()
656 * @hide
657 */
Adam Powellc4612502014-09-04 19:16:39 -0700658 public void setMenuCallbacks(MenuPresenter.Callback pcb, MenuBuilder.Callback mcb) {
659 mActionMenuPresenterCallback = pcb;
660 mMenuBuilderCallback = mcb;
Adam Powell04c0d462014-09-04 16:10:24 -0700661 }
662
663 /**
Adam Powelle021e6e2014-05-23 17:27:24 -0700664 * Returns the current menu or null if one has not yet been configured.
665 * @hide Internal use only for action bar integration
666 */
667 public MenuBuilder peekMenu() {
668 return mMenu;
669 }
670
671 /**
672 * Show the overflow items from the associated menu.
673 *
674 * @return true if the menu was able to be shown, false otherwise
675 */
676 public boolean showOverflowMenu() {
677 return mPresenter != null && mPresenter.showOverflowMenu();
678 }
679
680 /**
681 * Hide the overflow items from the associated menu.
682 *
683 * @return true if the menu was able to be hidden, false otherwise
684 */
685 public boolean hideOverflowMenu() {
686 return mPresenter != null && mPresenter.hideOverflowMenu();
687 }
688
689 /**
690 * Check whether the overflow menu is currently showing. This may not reflect
691 * a pending show operation in progress.
692 *
693 * @return true if the overflow menu is currently showing
694 */
695 public boolean isOverflowMenuShowing() {
696 return mPresenter != null && mPresenter.isOverflowMenuShowing();
697 }
698
699 /** @hide */
700 public boolean isOverflowMenuShowPending() {
701 return mPresenter != null && mPresenter.isOverflowMenuShowPending();
702 }
703
704 /**
705 * Dismiss any popups associated with this menu view.
706 */
707 public void dismissPopupMenus() {
708 if (mPresenter != null) {
709 mPresenter.dismissPopupMenus();
710 }
711 }
712
713 /**
Adam Powellfa18d182014-01-07 15:56:59 -0800714 * @hide Private LinearLayout (superclass) API. Un-hide if LinearLayout API is made public.
715 */
Adam Powell696cba52011-03-29 10:38:16 -0700716 @Override
717 protected boolean hasDividerBeforeChildAt(int childIndex) {
Jake Wharton825992f2012-07-28 21:31:51 -0700718 if (childIndex == 0) {
719 return false;
720 }
Adam Powell696cba52011-03-29 10:38:16 -0700721 final View childBefore = getChildAt(childIndex - 1);
722 final View child = getChildAt(childIndex);
723 boolean result = false;
724 if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
725 result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
Adam Powell96675b12010-06-10 18:58:59 -0700726 }
Adam Powell696cba52011-03-29 10:38:16 -0700727 if (childIndex > 0 && child instanceof ActionMenuChildView) {
728 result |= ((ActionMenuChildView) child).needsDividerBefore();
Adam Powell8028dd32010-07-15 10:16:33 -0700729 }
Adam Powellbe4d68e2010-10-08 18:16:34 -0700730 return result;
731 }
732
Alan Viverettea54956a2015-01-07 16:05:02 -0800733 /** @hide */
734 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
Adam Powell7bc3ca02011-08-26 18:29:58 -0700735 return false;
736 }
737
Adam Powell07a74542014-05-30 15:52:44 -0700738 /** @hide */
739 public void setExpandedActionViewsExclusive(boolean exclusive) {
740 mPresenter.setExpandedActionViewsExclusive(exclusive);
741 }
742
Adam Powelle43340c2014-03-17 19:10:43 -0700743 /**
744 * Interface responsible for receiving menu item click events if the items themselves
745 * do not have individual item click listeners.
746 */
747 public interface OnMenuItemClickListener {
748 /**
749 * This method will be invoked when a menu item is clicked if the item itself did
750 * not already handle the event.
751 *
752 * @param item {@link MenuItem} that was clicked
753 * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
754 */
755 public boolean onMenuItemClick(MenuItem item);
756 }
757
758 private class MenuBuilderCallback implements MenuBuilder.Callback {
759 @Override
760 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
761 return mOnMenuItemClickListener != null &&
762 mOnMenuItemClickListener.onMenuItemClick(item);
763 }
764
765 @Override
766 public void onMenuModeChange(MenuBuilder menu) {
Adam Powellc4612502014-09-04 19:16:39 -0700767 if (mMenuBuilderCallback != null) {
768 mMenuBuilderCallback.onMenuModeChange(menu);
769 }
Adam Powelle43340c2014-03-17 19:10:43 -0700770 }
771 }
772
773 private class ActionMenuPresenterCallback implements ActionMenuPresenter.Callback {
774 @Override
775 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
776 }
777
778 @Override
779 public boolean onOpenSubMenu(MenuBuilder subMenu) {
780 return false;
781 }
782 }
783
Adam Powellfa18d182014-01-07 15:56:59 -0800784 /** @hide */
Adam Powell696cba52011-03-29 10:38:16 -0700785 public interface ActionMenuChildView {
786 public boolean needsDividerBefore();
787 public boolean needsDividerAfter();
Adam Powell8515ee82010-11-30 14:09:55 -0800788 }
Adam Powell640a66e2011-04-29 10:18:53 -0700789
790 public static class LayoutParams extends LinearLayout.LayoutParams {
Adam Powellfa18d182014-01-07 15:56:59 -0800791 /** @hide */
Adam Powell640a66e2011-04-29 10:18:53 -0700792 @ViewDebug.ExportedProperty(category = "layout")
793 public boolean isOverflowButton;
Adam Powellfa18d182014-01-07 15:56:59 -0800794
795 /** @hide */
Adam Powell35aecd52011-07-01 13:43:49 -0700796 @ViewDebug.ExportedProperty(category = "layout")
797 public int cellsUsed;
Adam Powellfa18d182014-01-07 15:56:59 -0800798
799 /** @hide */
Adam Powell35aecd52011-07-01 13:43:49 -0700800 @ViewDebug.ExportedProperty(category = "layout")
Adam Powell35aecd52011-07-01 13:43:49 -0700801 public int extraPixels;
Adam Powellfa18d182014-01-07 15:56:59 -0800802
803 /** @hide */
Adam Powell160bb7f2011-07-07 10:22:27 -0700804 @ViewDebug.ExportedProperty(category = "layout")
805 public boolean expandable;
Adam Powellfa18d182014-01-07 15:56:59 -0800806
807 /** @hide */
Adam Powellbe3c3292011-08-24 12:52:28 -0700808 @ViewDebug.ExportedProperty(category = "layout")
809 public boolean preventEdgeOffset;
Adam Powell160bb7f2011-07-07 10:22:27 -0700810
Adam Powellfa18d182014-01-07 15:56:59 -0800811 /** @hide */
Adam Powell35aecd52011-07-01 13:43:49 -0700812 public boolean expanded;
Adam Powell640a66e2011-04-29 10:18:53 -0700813
814 public LayoutParams(Context c, AttributeSet attrs) {
815 super(c, attrs);
816 }
817
Adam Powell8c1b02e2012-07-16 17:58:18 -0700818 public LayoutParams(ViewGroup.LayoutParams other) {
819 super(other);
820 }
821
Adam Powell640a66e2011-04-29 10:18:53 -0700822 public LayoutParams(LayoutParams other) {
823 super((LinearLayout.LayoutParams) other);
824 isOverflowButton = other.isOverflowButton;
825 }
826
827 public LayoutParams(int width, int height) {
828 super(width, height);
829 isOverflowButton = false;
830 }
831
Adam Powellfa18d182014-01-07 15:56:59 -0800832 /** @hide */
Adam Powell640a66e2011-04-29 10:18:53 -0700833 public LayoutParams(int width, int height, boolean isOverflowButton) {
834 super(width, height);
835 this.isOverflowButton = isOverflowButton;
836 }
Siva Velusamy94a6d152015-05-05 15:07:00 -0700837
838 /** @hide */
839 @Override
840 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
841 super.encodeProperties(encoder);
842
843 encoder.addProperty("layout:overFlowButton", isOverflowButton);
844 encoder.addProperty("layout:cellsUsed", cellsUsed);
845 encoder.addProperty("layout:extraPixels", extraPixels);
846 encoder.addProperty("layout:expandable", expandable);
847 encoder.addProperty("layout:preventEdgeOffset", preventEdgeOffset);
848 }
Adam Powell640a66e2011-04-29 10:18:53 -0700849 }
Adam Powell96675b12010-06-10 18:58:59 -0700850}