blob: 0a8a01f2dfe9d8c7ff93fcd6b1bffc5646513117 [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
18import android.content.Context;
Adam Powell8028dd32010-07-15 10:16:33 -070019import android.content.res.Configuration;
Adam Powell96675b12010-06-10 18:58:59 -070020import android.util.AttributeSet;
Alan Viverette3d0f21d2014-07-10 16:15:01 -070021import android.view.ContextThemeWrapper;
Adam Powell6b336f82010-08-10 20:13:01 -070022import android.view.Gravity;
Adam Powellfa18d182014-01-07 15:56:59 -080023import android.view.Menu;
Adam Powelle43340c2014-03-17 19:10:43 -070024import android.view.MenuItem;
Adam Powellcf78b3e2010-09-12 18:25:23 -070025import android.view.View;
Adam Powell640a66e2011-04-29 10:18:53 -070026import android.view.ViewDebug;
Adam Powell7ade1be2010-06-17 12:51:21 -070027import android.view.ViewGroup;
Adam Powell7bc3ca02011-08-26 18:29:58 -070028import android.view.accessibility.AccessibilityEvent;
Adam Powellfa18d182014-01-07 15:56:59 -080029import com.android.internal.view.menu.ActionMenuItemView;
30import com.android.internal.view.menu.MenuBuilder;
31import com.android.internal.view.menu.MenuItemImpl;
Adam Powell04c0d462014-09-04 16:10:24 -070032import com.android.internal.view.menu.MenuPresenter;
Adam Powellfa18d182014-01-07 15:56:59 -080033import com.android.internal.view.menu.MenuView;
Adam Powell96675b12010-06-10 18:58:59 -070034
Adam Powell96675b12010-06-10 18:58:59 -070035/**
Adam Powellfa18d182014-01-07 15:56:59 -080036 * ActionMenuView is a presentation of a series of menu options as a View. It provides
37 * several top level options as action buttons while spilling remaining options over as
38 * items in an overflow menu. This allows applications to present packs of actions inline with
39 * specific or repeating content.
Adam Powell96675b12010-06-10 18:58:59 -070040 */
41public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView {
42 private static final String TAG = "ActionMenuView";
43
Adam Powell35aecd52011-07-01 13:43:49 -070044 static final int MIN_CELL_SIZE = 56; // dips
Adam Powellbe3c3292011-08-24 12:52:28 -070045 static final int GENERATED_ITEM_PADDING = 4; // dips
Adam Powell35aecd52011-07-01 13:43:49 -070046
Adam Powell96675b12010-06-10 18:58:59 -070047 private MenuBuilder mMenu;
Adam Powell7ade1be2010-06-17 12:51:21 -070048
Alan Viverette3d0f21d2014-07-10 16:15:01 -070049 /** Context against which to inflate popup menus. */
50 private Context mPopupContext;
51
52 /** Theme resource against which to inflate popup menus. */
53 private int mPopupTheme;
54
Adam Powell8028dd32010-07-15 10:16:33 -070055 private boolean mReserveOverflow;
Adam Powell696cba52011-03-29 10:38:16 -070056 private ActionMenuPresenter mPresenter;
Adam Powell04c0d462014-09-04 16:10:24 -070057 private MenuPresenter.Callback mActionMenuPresenterCallback;
Adam Powellc4612502014-09-04 19:16:39 -070058 private MenuBuilder.Callback mMenuBuilderCallback;
Adam Powell640a66e2011-04-29 10:18:53 -070059 private boolean mFormatItems;
Adam Powell89b09da2011-07-27 11:55:29 -070060 private int mFormatItemsWidth;
Adam Powell35aecd52011-07-01 13:43:49 -070061 private int mMinCellSize;
Adam Powellbe3c3292011-08-24 12:52:28 -070062 private int mGeneratedItemPadding;
Adam Powell8515ee82010-11-30 14:09:55 -080063
Adam Powelle43340c2014-03-17 19:10:43 -070064 private OnMenuItemClickListener mOnMenuItemClickListener;
65
Adam Powell96675b12010-06-10 18:58:59 -070066 public ActionMenuView(Context context) {
67 this(context, null);
68 }
69
70 public ActionMenuView(Context context, AttributeSet attrs) {
71 super(context, attrs);
Adam Powellf16888f2010-10-11 17:05:29 -070072 setBaselineAligned(false);
Adam Powellbe3c3292011-08-24 12:52:28 -070073 final float density = context.getResources().getDisplayMetrics().density;
74 mMinCellSize = (int) (MIN_CELL_SIZE * density);
75 mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
Alan Viverette3d0f21d2014-07-10 16:15:01 -070076 mPopupContext = context;
77 mPopupTheme = 0;
Adam Powell773b1b92010-08-20 15:45:24 -070078 }
79
Alan Viverette3d0f21d2014-07-10 16:15:01 -070080 /**
81 * Specifies the theme to use when inflating popup menus. By default, uses
82 * the same theme as the action menu view itself.
83 *
84 * @param resId theme used to inflate popup menus
85 * @see #getPopupTheme()
86 */
87 public void setPopupTheme(int resId) {
88 if (mPopupTheme != resId) {
89 mPopupTheme = resId;
90 if (resId == 0) {
91 mPopupContext = mContext;
92 } else {
93 mPopupContext = new ContextThemeWrapper(mContext, resId);
94 }
95 }
96 }
97
98 /**
99 * @return resource identifier of the theme used to inflate popup menus, or
100 * 0 if menus are inflated against the action menu view theme
101 * @see #setPopupTheme(int)
102 */
103 public int getPopupTheme() {
104 return mPopupTheme;
105 }
106
107 /**
108 * @param presenter Menu presenter used to display popup menu
109 * @hide
110 */
Adam Powell696cba52011-03-29 10:38:16 -0700111 public void setPresenter(ActionMenuPresenter presenter) {
112 mPresenter = presenter;
Adam Powelle021e6e2014-05-23 17:27:24 -0700113 mPresenter.setMenuView(this);
Adam Powell696cba52011-03-29 10:38:16 -0700114 }
115
Adam Powell6c6f5752010-08-20 18:34:46 -0700116 @Override
Adam Powell773b1b92010-08-20 15:45:24 -0700117 public void onConfigurationChanged(Configuration newConfig) {
Adam Powell8515ee82010-11-30 14:09:55 -0800118 super.onConfigurationChanged(newConfig);
Adam Powell696cba52011-03-29 10:38:16 -0700119 mPresenter.updateMenuView(false);
Adam Powell6c6f5752010-08-20 18:34:46 -0700120
Adam Powell696cba52011-03-29 10:38:16 -0700121 if (mPresenter != null && mPresenter.isOverflowMenuShowing()) {
122 mPresenter.hideOverflowMenu();
123 mPresenter.showOverflowMenu();
Adam Powell6c6f5752010-08-20 18:34:46 -0700124 }
Adam Powell773b1b92010-08-20 15:45:24 -0700125 }
126
Adam Powelle43340c2014-03-17 19:10:43 -0700127 public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
128 mOnMenuItemClickListener = listener;
129 }
130
Adam Powell8515ee82010-11-30 14:09:55 -0800131 @Override
Adam Powell640a66e2011-04-29 10:18:53 -0700132 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Adam Powell35aecd52011-07-01 13:43:49 -0700133 // If we've been given an exact size to match, apply special formatting during layout.
Adam Powell89b09da2011-07-27 11:55:29 -0700134 final boolean wasFormatted = mFormatItems;
Adam Powell35aecd52011-07-01 13:43:49 -0700135 mFormatItems = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
Adam Powell89b09da2011-07-27 11:55:29 -0700136
137 if (wasFormatted != mFormatItems) {
138 mFormatItemsWidth = 0; // Reset this when switching modes
139 }
140
141 // Special formatting can change whether items can fit as action buttons.
142 // Kick the menu and update presenters when this changes.
Adam Powellda971082013-10-03 18:21:58 -0700143 final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
Adam Powell89b09da2011-07-27 11:55:29 -0700144 if (mFormatItems && mMenu != null && widthSize != mFormatItemsWidth) {
145 mFormatItemsWidth = widthSize;
Adam Powell640a66e2011-04-29 10:18:53 -0700146 mMenu.onItemsChanged(true);
Adam Powell640a66e2011-04-29 10:18:53 -0700147 }
Adam Powell35aecd52011-07-01 13:43:49 -0700148
Adam Powelle43340c2014-03-17 19:10:43 -0700149 final int childCount = getChildCount();
150 if (mFormatItems && childCount > 0) {
Adam Powell35aecd52011-07-01 13:43:49 -0700151 onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec);
152 } else {
Adam Powell75d022a2012-03-06 12:04:07 -0800153 // Previous measurement at exact format may have set margins - reset them.
Adam Powell75d022a2012-03-06 12:04:07 -0800154 for (int i = 0; i < childCount; i++) {
155 final View child = getChildAt(i);
156 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
157 lp.leftMargin = lp.rightMargin = 0;
158 }
Adam Powell35aecd52011-07-01 13:43:49 -0700159 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
160 }
161 }
162
163 private void onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec) {
164 // We already know the width mode is EXACTLY if we're here.
165 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
166 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
167 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
168
169 final int widthPadding = getPaddingLeft() + getPaddingRight();
170 final int heightPadding = getPaddingTop() + getPaddingBottom();
171
Adam Powellfa18d182014-01-07 15:56:59 -0800172 final int itemHeightSpec = getChildMeasureSpec(heightMeasureSpec, heightPadding,
173 ViewGroup.LayoutParams.WRAP_CONTENT);
Adam Powell367ee322012-05-06 18:32:33 -0700174
Adam Powell35aecd52011-07-01 13:43:49 -0700175 widthSize -= widthPadding;
176
177 // Divide the view into cells.
178 final int cellCount = widthSize / mMinCellSize;
179 final int cellSizeRemaining = widthSize % mMinCellSize;
Adam Powell3bb421d2011-08-16 15:04:53 -0700180
181 if (cellCount == 0) {
182 // Give up, nothing fits.
183 setMeasuredDimension(widthSize, 0);
184 return;
185 }
186
Adam Powell35aecd52011-07-01 13:43:49 -0700187 final int cellSize = mMinCellSize + cellSizeRemaining / cellCount;
188
189 int cellsRemaining = cellCount;
190 int maxChildHeight = 0;
191 int maxCellsUsed = 0;
Adam Powell160bb7f2011-07-07 10:22:27 -0700192 int expandableItemCount = 0;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700193 int visibleItemCount = 0;
194 boolean hasOverflow = false;
Adam Powell35aecd52011-07-01 13:43:49 -0700195
Adam Powell14b7e2c2011-08-12 11:11:50 -0700196 // This is used as a bitfield to locate the smallest items present. Assumes childCount < 64.
197 long smallestItemsAt = 0;
Adam Powell35aecd52011-07-01 13:43:49 -0700198
199 final int childCount = getChildCount();
200 for (int i = 0; i < childCount; i++) {
201 final View child = getChildAt(i);
Adam Powell14b7e2c2011-08-12 11:11:50 -0700202 if (child.getVisibility() == GONE) continue;
203
Adam Powellbe3c3292011-08-24 12:52:28 -0700204 final boolean isGeneratedItem = child instanceof ActionMenuItemView;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700205 visibleItemCount++;
206
Adam Powellbe3c3292011-08-24 12:52:28 -0700207 if (isGeneratedItem) {
208 // Reset padding for generated menu item views; it may change below
209 // and views are recycled.
210 child.setPadding(mGeneratedItemPadding, 0, mGeneratedItemPadding, 0);
211 }
212
Adam Powell35aecd52011-07-01 13:43:49 -0700213 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
214 lp.expanded = false;
215 lp.extraPixels = 0;
216 lp.cellsUsed = 0;
Adam Powell160bb7f2011-07-07 10:22:27 -0700217 lp.expandable = false;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700218 lp.leftMargin = 0;
219 lp.rightMargin = 0;
Adam Powellbe3c3292011-08-24 12:52:28 -0700220 lp.preventEdgeOffset = isGeneratedItem && ((ActionMenuItemView) child).hasText();
Adam Powell35aecd52011-07-01 13:43:49 -0700221
222 // Overflow always gets 1 cell. No more, no less.
223 final int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining;
224
225 final int cellsUsed = measureChildForCells(child, cellSize, cellsAvailable,
Adam Powell367ee322012-05-06 18:32:33 -0700226 itemHeightSpec, heightPadding);
Adam Powell35aecd52011-07-01 13:43:49 -0700227
228 maxCellsUsed = Math.max(maxCellsUsed, cellsUsed);
Adam Powell160bb7f2011-07-07 10:22:27 -0700229 if (lp.expandable) expandableItemCount++;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700230 if (lp.isOverflowButton) hasOverflow = true;
Adam Powell35aecd52011-07-01 13:43:49 -0700231
232 cellsRemaining -= cellsUsed;
233 maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
Adam Powell14b7e2c2011-08-12 11:11:50 -0700234 if (cellsUsed == 1) smallestItemsAt |= (1 << i);
Adam Powell35aecd52011-07-01 13:43:49 -0700235 }
236
Adam Powellbe3c3292011-08-24 12:52:28 -0700237 // When we have overflow and a single expanded (text) item, we want to try centering it
238 // visually in the available space even though overflow consumes some of it.
239 final boolean centerSingleExpandedItem = hasOverflow && visibleItemCount == 2;
240
Adam Powell35aecd52011-07-01 13:43:49 -0700241 // Divide space for remaining cells if we have items that can expand.
242 // Try distributing whole leftover cells to smaller items first.
243
244 boolean needsExpansion = false;
Adam Powell160bb7f2011-07-07 10:22:27 -0700245 while (expandableItemCount > 0 && cellsRemaining > 0) {
Adam Powell35aecd52011-07-01 13:43:49 -0700246 int minCells = Integer.MAX_VALUE;
247 long minCellsAt = 0; // Bit locations are indices of relevant child views
248 int minCellsItemCount = 0;
249 for (int i = 0; i < childCount; i++) {
250 final View child = getChildAt(i);
251 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
252
253 // Don't try to expand items that shouldn't.
Adam Powell160bb7f2011-07-07 10:22:27 -0700254 if (!lp.expandable) continue;
Adam Powell35aecd52011-07-01 13:43:49 -0700255
256 // Mark indices of children that can receive an extra cell.
257 if (lp.cellsUsed < minCells) {
258 minCells = lp.cellsUsed;
259 minCellsAt = 1 << i;
260 minCellsItemCount = 1;
261 } else if (lp.cellsUsed == minCells) {
262 minCellsAt |= 1 << i;
263 minCellsItemCount++;
264 }
265 }
266
Adam Powell35aecd52011-07-01 13:43:49 -0700267 // Items that get expanded will always be in the set of smallest items when we're done.
Adam Powell14b7e2c2011-08-12 11:11:50 -0700268 smallestItemsAt |= minCellsAt;
Adam Powell35aecd52011-07-01 13:43:49 -0700269
Adam Powellbe3c3292011-08-24 12:52:28 -0700270 if (minCellsItemCount > cellsRemaining) break; // Couldn't expand anything evenly. Stop.
Adam Powell35aecd52011-07-01 13:43:49 -0700271
Adam Powellbe3c3292011-08-24 12:52:28 -0700272 // We have enough cells, all minimum size items will be incremented.
273 minCells++;
274
275 for (int i = 0; i < childCount; i++) {
Adam Powell35aecd52011-07-01 13:43:49 -0700276 final View child = getChildAt(i);
277 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Powellbe3c3292011-08-24 12:52:28 -0700278 if ((minCellsAt & (1 << i)) == 0) {
279 // If this item is already at our small item count, mark it for later.
280 if (lp.cellsUsed == minCells) smallestItemsAt |= 1 << i;
281 continue;
282 }
283
284 if (centerSingleExpandedItem && lp.preventEdgeOffset && cellsRemaining == 1) {
285 // Add padding to this item such that it centers.
286 child.setPadding(mGeneratedItemPadding + cellSize, 0, mGeneratedItemPadding, 0);
287 }
Adam Powell35aecd52011-07-01 13:43:49 -0700288 lp.cellsUsed++;
289 lp.expanded = true;
290 cellsRemaining--;
291 }
292
293 needsExpansion = true;
294 }
295
296 // Divide any space left that wouldn't divide along cell boundaries
Adam Powell14b7e2c2011-08-12 11:11:50 -0700297 // evenly among the smallest items
Adam Powell35aecd52011-07-01 13:43:49 -0700298
Adam Powell14b7e2c2011-08-12 11:11:50 -0700299 final boolean singleItem = !hasOverflow && visibleItemCount == 1;
300 if (cellsRemaining > 0 && smallestItemsAt != 0 &&
Adam Powellbe3c3292011-08-24 12:52:28 -0700301 (cellsRemaining < visibleItemCount - 1 || singleItem || maxCellsUsed > 1)) {
Adam Powell14b7e2c2011-08-12 11:11:50 -0700302 float expandCount = Long.bitCount(smallestItemsAt);
303
304 if (!singleItem) {
305 // The items at the far edges may only expand by half in order to pin to either side.
306 if ((smallestItemsAt & 1) != 0) {
Adam Powellbe3c3292011-08-24 12:52:28 -0700307 LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams();
308 if (!lp.preventEdgeOffset) expandCount -= 0.5f;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700309 }
310 if ((smallestItemsAt & (1 << (childCount - 1))) != 0) {
Adam Powellbe3c3292011-08-24 12:52:28 -0700311 LayoutParams lp = ((LayoutParams) getChildAt(childCount - 1).getLayoutParams());
312 if (!lp.preventEdgeOffset) expandCount -= 0.5f;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700313 }
314 }
315
Adam Powell3bb421d2011-08-16 15:04:53 -0700316 final int extraPixels = expandCount > 0 ?
317 (int) (cellsRemaining * cellSize / expandCount) : 0;
Adam Powell35aecd52011-07-01 13:43:49 -0700318
319 for (int i = 0; i < childCount; i++) {
Adam Powell14b7e2c2011-08-12 11:11:50 -0700320 if ((smallestItemsAt & (1 << i)) == 0) continue;
Adam Powell35aecd52011-07-01 13:43:49 -0700321
322 final View child = getChildAt(i);
323 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Powell14b7e2c2011-08-12 11:11:50 -0700324 if (child instanceof ActionMenuItemView) {
325 // If this is one of our views, expand and measure at the larger size.
326 lp.extraPixels = extraPixels;
327 lp.expanded = true;
Adam Powellbe3c3292011-08-24 12:52:28 -0700328 if (i == 0 && !lp.preventEdgeOffset) {
Adam Powell14b7e2c2011-08-12 11:11:50 -0700329 // First item gets part of its new padding pushed out of sight.
330 // The last item will get this implicitly from layout.
331 lp.leftMargin = -extraPixels / 2;
332 }
333 needsExpansion = true;
334 } else if (lp.isOverflowButton) {
335 lp.extraPixels = extraPixels;
336 lp.expanded = true;
337 lp.rightMargin = -extraPixels / 2;
338 needsExpansion = true;
339 } else {
340 // If we don't know what it is, give it some margins instead
341 // and let it center within its space. We still want to pin
342 // against the edges.
343 if (i != 0) {
344 lp.leftMargin = extraPixels / 2;
345 }
346 if (i != childCount - 1) {
347 lp.rightMargin = extraPixels / 2;
348 }
349 }
Adam Powell35aecd52011-07-01 13:43:49 -0700350 }
351
Adam Powell35aecd52011-07-01 13:43:49 -0700352 cellsRemaining = 0;
353 }
354
355 // Remeasure any items that have had extra space allocated to them.
356 if (needsExpansion) {
Adam Powell35aecd52011-07-01 13:43:49 -0700357 for (int i = 0; i < childCount; i++) {
358 final View child = getChildAt(i);
359 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
360
361 if (!lp.expanded) continue;
362
363 final int width = lp.cellsUsed * cellSize + lp.extraPixels;
Adam Powell367ee322012-05-06 18:32:33 -0700364 child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
365 itemHeightSpec);
Adam Powell35aecd52011-07-01 13:43:49 -0700366 }
367 }
368
369 if (heightMode != MeasureSpec.EXACTLY) {
370 heightSize = maxChildHeight;
371 }
372
373 setMeasuredDimension(widthSize, heightSize);
Adam Powell35aecd52011-07-01 13:43:49 -0700374 }
375
376 /**
377 * Measure a child view to fit within cell-based formatting. The child's width
378 * will be measured to a whole multiple of cellSize.
379 *
Adam Powell160bb7f2011-07-07 10:22:27 -0700380 * <p>Sets the expandable and cellsUsed fields of LayoutParams.
Adam Powell35aecd52011-07-01 13:43:49 -0700381 *
382 * @param child Child to measure
383 * @param cellSize Size of one cell
384 * @param cellsRemaining Number of cells remaining that this view can expand to fill
385 * @param parentHeightMeasureSpec MeasureSpec used by the parent view
386 * @param parentHeightPadding Padding present in the parent view
387 * @return Number of cells this child was measured to occupy
388 */
389 static int measureChildForCells(View child, int cellSize, int cellsRemaining,
390 int parentHeightMeasureSpec, int parentHeightPadding) {
391 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Powell35aecd52011-07-01 13:43:49 -0700392
393 final int childHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec) -
394 parentHeightPadding;
395 final int childHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec);
396 final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode);
397
Adam Powella7dec6d2012-04-09 15:54:01 -0700398 final ActionMenuItemView itemView = child instanceof ActionMenuItemView ?
399 (ActionMenuItemView) child : null;
400 final boolean hasText = itemView != null && itemView.hasText();
401
Adam Powell160bb7f2011-07-07 10:22:27 -0700402 int cellsUsed = 0;
Adam Powella7dec6d2012-04-09 15:54:01 -0700403 if (cellsRemaining > 0 && (!hasText || cellsRemaining >= 2)) {
Adam Powell35aecd52011-07-01 13:43:49 -0700404 final int childWidthSpec = MeasureSpec.makeMeasureSpec(
405 cellSize * cellsRemaining, MeasureSpec.AT_MOST);
406 child.measure(childWidthSpec, childHeightSpec);
407
408 final int measuredWidth = child.getMeasuredWidth();
409 cellsUsed = measuredWidth / cellSize;
410 if (measuredWidth % cellSize != 0) cellsUsed++;
Adam Powella7dec6d2012-04-09 15:54:01 -0700411 if (hasText && cellsUsed < 2) cellsUsed = 2;
Adam Powell35aecd52011-07-01 13:43:49 -0700412 }
Adam Powell160bb7f2011-07-07 10:22:27 -0700413
Adam Powella7dec6d2012-04-09 15:54:01 -0700414 final boolean expandable = !lp.isOverflowButton && hasText;
Adam Powell160bb7f2011-07-07 10:22:27 -0700415 lp.expandable = expandable;
416
Adam Powell35aecd52011-07-01 13:43:49 -0700417 lp.cellsUsed = cellsUsed;
418 final int targetWidth = cellsUsed * cellSize;
419 child.measure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
420 childHeightSpec);
421 return cellsUsed;
Adam Powell640a66e2011-04-29 10:18:53 -0700422 }
423
424 @Override
425 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
426 if (!mFormatItems) {
427 super.onLayout(changed, left, top, right, bottom);
428 return;
429 }
430
431 final int childCount = getChildCount();
Chris Banes1970cfd42014-10-29 11:04:49 +0000432 final int midVertical = (bottom - top) / 2;
Adam Powell640a66e2011-04-29 10:18:53 -0700433 final int dividerWidth = getDividerWidth();
Adam Powell640a66e2011-04-29 10:18:53 -0700434 int overflowWidth = 0;
435 int nonOverflowWidth = 0;
436 int nonOverflowCount = 0;
437 int widthRemaining = right - left - getPaddingRight() - getPaddingLeft();
Adam Powell35aecd52011-07-01 13:43:49 -0700438 boolean hasOverflow = false;
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700439 final boolean isLayoutRtl = isLayoutRtl();
Adam Powell640a66e2011-04-29 10:18:53 -0700440 for (int i = 0; i < childCount; i++) {
441 final View v = getChildAt(i);
442 if (v.getVisibility() == GONE) {
443 continue;
444 }
445
446 LayoutParams p = (LayoutParams) v.getLayoutParams();
447 if (p.isOverflowButton) {
Adam Powell640a66e2011-04-29 10:18:53 -0700448 overflowWidth = v.getMeasuredWidth();
449 if (hasDividerBeforeChildAt(i)) {
450 overflowWidth += dividerWidth;
451 }
452
453 int height = v.getMeasuredHeight();
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700454 int r;
455 int l;
456 if (isLayoutRtl) {
457 l = getPaddingLeft() + p.leftMargin;
458 r = l + overflowWidth;
459 } else {
460 r = getWidth() - getPaddingRight() - p.rightMargin;
461 l = r - overflowWidth;
462 }
Adam Powell640a66e2011-04-29 10:18:53 -0700463 int t = midVertical - (height / 2);
464 int b = t + height;
465 v.layout(l, t, r, b);
466
467 widthRemaining -= overflowWidth;
Adam Powell35aecd52011-07-01 13:43:49 -0700468 hasOverflow = true;
Adam Powell640a66e2011-04-29 10:18:53 -0700469 } else {
Adam Powell35aecd52011-07-01 13:43:49 -0700470 final int size = v.getMeasuredWidth() + p.leftMargin + p.rightMargin;
471 nonOverflowWidth += size;
472 widthRemaining -= size;
Adam Powell640a66e2011-04-29 10:18:53 -0700473 if (hasDividerBeforeChildAt(i)) {
474 nonOverflowWidth += dividerWidth;
475 }
476 nonOverflowCount++;
477 }
478 }
479
Adam Powell14b7e2c2011-08-12 11:11:50 -0700480 if (childCount == 1 && !hasOverflow) {
481 // Center a single child
482 final View v = getChildAt(0);
483 final int width = v.getMeasuredWidth();
484 final int height = v.getMeasuredHeight();
485 final int midHorizontal = (right - left) / 2;
486 final int l = midHorizontal - width / 2;
487 final int t = midVertical - height / 2;
488 v.layout(l, t, l + width, t + height);
489 return;
490 }
491
Adam Powell35aecd52011-07-01 13:43:49 -0700492 final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1);
Adam Powell14b7e2c2011-08-12 11:11:50 -0700493 final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0);
Adam Powell640a66e2011-04-29 10:18:53 -0700494
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700495 if (isLayoutRtl) {
496 int startRight = getWidth() - getPaddingRight();
497 for (int i = 0; i < childCount; i++) {
498 final View v = getChildAt(i);
499 final LayoutParams lp = (LayoutParams) v.getLayoutParams();
500 if (v.getVisibility() == GONE || lp.isOverflowButton) {
501 continue;
502 }
Adam Powell640a66e2011-04-29 10:18:53 -0700503
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700504 startRight -= lp.rightMargin;
505 int width = v.getMeasuredWidth();
506 int height = v.getMeasuredHeight();
507 int t = midVertical - height / 2;
508 v.layout(startRight - width, t, startRight, t + height);
509 startRight -= width + lp.leftMargin + spacerSize;
510 }
511 } else {
512 int startLeft = getPaddingLeft();
513 for (int i = 0; i < childCount; i++) {
514 final View v = getChildAt(i);
515 final LayoutParams lp = (LayoutParams) v.getLayoutParams();
516 if (v.getVisibility() == GONE || lp.isOverflowButton) {
517 continue;
518 }
519
520 startLeft += lp.leftMargin;
521 int width = v.getMeasuredWidth();
522 int height = v.getMeasuredHeight();
523 int t = midVertical - height / 2;
524 v.layout(startLeft, t, startLeft + width, t + height);
525 startLeft += width + lp.rightMargin + spacerSize;
526 }
Adam Powell640a66e2011-04-29 10:18:53 -0700527 }
528 }
529
530 @Override
Adam Powell8515ee82010-11-30 14:09:55 -0800531 public void onDetachedFromWindow() {
532 super.onDetachedFromWindow();
Adam Powelle021e6e2014-05-23 17:27:24 -0700533 dismissPopupMenus();
Adam Powell8028dd32010-07-15 10:16:33 -0700534 }
535
Adam Powellfa18d182014-01-07 15:56:59 -0800536 /** @hide */
Adam Powell8028dd32010-07-15 10:16:33 -0700537 public boolean isOverflowReserved() {
538 return mReserveOverflow;
Adam Powell7ade1be2010-06-17 12:51:21 -0700539 }
Adam Powellfa18d182014-01-07 15:56:59 -0800540
541 /** @hide */
Adam Powellb366bba2010-07-20 14:26:38 -0700542 public void setOverflowReserved(boolean reserveOverflow) {
543 mReserveOverflow = reserveOverflow;
544 }
Adam Powellf0ad6e62011-01-10 17:14:06 -0800545
Adam Powell7ade1be2010-06-17 12:51:21 -0700546 @Override
547 protected LayoutParams generateDefaultLayoutParams() {
548 LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
549 LayoutParams.WRAP_CONTENT);
Adam Powell6b336f82010-08-10 20:13:01 -0700550 params.gravity = Gravity.CENTER_VERTICAL;
Adam Powell7ade1be2010-06-17 12:51:21 -0700551 return params;
552 }
553
554 @Override
Adam Powell35aecd52011-07-01 13:43:49 -0700555 public LayoutParams generateLayoutParams(AttributeSet attrs) {
556 return new LayoutParams(getContext(), attrs);
557 }
558
559 @Override
Adam Powell7ade1be2010-06-17 12:51:21 -0700560 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
Adam Powell8c1b02e2012-07-16 17:58:18 -0700561 if (p != null) {
562 final LayoutParams result = p instanceof LayoutParams
563 ? new LayoutParams((LayoutParams) p)
564 : new LayoutParams(p);
Adam Powell3f476b32011-01-03 19:25:36 -0800565 if (result.gravity <= Gravity.NO_GRAVITY) {
566 result.gravity = Gravity.CENTER_VERTICAL;
567 }
568 return result;
569 }
Adam Powell7ade1be2010-06-17 12:51:21 -0700570 return generateDefaultLayoutParams();
571 }
Adam Powell96675b12010-06-10 18:58:59 -0700572
Adam Powell696cba52011-03-29 10:38:16 -0700573 @Override
574 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
Adam Powell35aecd52011-07-01 13:43:49 -0700575 return p != null && p instanceof LayoutParams;
Adam Powell696cba52011-03-29 10:38:16 -0700576 }
577
Adam Powellfa18d182014-01-07 15:56:59 -0800578 /** @hide */
Adam Powell640a66e2011-04-29 10:18:53 -0700579 public LayoutParams generateOverflowButtonLayoutParams() {
580 LayoutParams result = generateDefaultLayoutParams();
581 result.isOverflowButton = true;
582 return result;
583 }
584
Adam Powellfa18d182014-01-07 15:56:59 -0800585 /** @hide */
Adam Powell96675b12010-06-10 18:58:59 -0700586 public boolean invokeItem(MenuItemImpl item) {
587 return mMenu.performItemAction(item, 0);
588 }
589
Adam Powellfa18d182014-01-07 15:56:59 -0800590 /** @hide */
Adam Powell96675b12010-06-10 18:58:59 -0700591 public int getWindowAnimations() {
592 return 0;
593 }
594
Adam Powellfa18d182014-01-07 15:56:59 -0800595 /** @hide */
Adam Powell696cba52011-03-29 10:38:16 -0700596 public void initialize(MenuBuilder menu) {
Adam Powell96675b12010-06-10 18:58:59 -0700597 mMenu = menu;
Adam Powell96675b12010-06-10 18:58:59 -0700598 }
599
Adam Powellfa18d182014-01-07 15:56:59 -0800600 /**
601 * Returns the Menu object that this ActionMenuView is currently presenting.
602 *
603 * <p>Applications should use this method to obtain the ActionMenuView's Menu object
604 * and inflate or add content to it as necessary.</p>
605 *
606 * @return the Menu presented by this view
607 */
608 public Menu getMenu() {
609 if (mMenu == null) {
610 final Context context = getContext();
611 mMenu = new MenuBuilder(context);
Adam Powelle43340c2014-03-17 19:10:43 -0700612 mMenu.setCallback(new MenuBuilderCallback());
Adam Powellfa18d182014-01-07 15:56:59 -0800613 mPresenter = new ActionMenuPresenter(context);
Adam Powelle8d15362014-09-09 13:51:16 -0700614 mPresenter.setReserveOverflow(true);
Adam Powell04c0d462014-09-04 16:10:24 -0700615 mPresenter.setCallback(mActionMenuPresenterCallback != null
616 ? mActionMenuPresenterCallback : new ActionMenuPresenterCallback());
Alan Viverette3d0f21d2014-07-10 16:15:01 -0700617 mMenu.addMenuPresenter(mPresenter, mPopupContext);
Adam Powell07a74542014-05-30 15:52:44 -0700618 mPresenter.setMenuView(this);
Adam Powellfa18d182014-01-07 15:56:59 -0800619 }
620
621 return mMenu;
622 }
623
624 /**
Adam Powell04c0d462014-09-04 16:10:24 -0700625 * Must be called before the first call to getMenu()
626 * @hide
627 */
Adam Powellc4612502014-09-04 19:16:39 -0700628 public void setMenuCallbacks(MenuPresenter.Callback pcb, MenuBuilder.Callback mcb) {
629 mActionMenuPresenterCallback = pcb;
630 mMenuBuilderCallback = mcb;
Adam Powell04c0d462014-09-04 16:10:24 -0700631 }
632
633 /**
Adam Powelle021e6e2014-05-23 17:27:24 -0700634 * Returns the current menu or null if one has not yet been configured.
635 * @hide Internal use only for action bar integration
636 */
637 public MenuBuilder peekMenu() {
638 return mMenu;
639 }
640
641 /**
642 * Show the overflow items from the associated menu.
643 *
644 * @return true if the menu was able to be shown, false otherwise
645 */
646 public boolean showOverflowMenu() {
647 return mPresenter != null && mPresenter.showOverflowMenu();
648 }
649
650 /**
651 * Hide the overflow items from the associated menu.
652 *
653 * @return true if the menu was able to be hidden, false otherwise
654 */
655 public boolean hideOverflowMenu() {
656 return mPresenter != null && mPresenter.hideOverflowMenu();
657 }
658
659 /**
660 * Check whether the overflow menu is currently showing. This may not reflect
661 * a pending show operation in progress.
662 *
663 * @return true if the overflow menu is currently showing
664 */
665 public boolean isOverflowMenuShowing() {
666 return mPresenter != null && mPresenter.isOverflowMenuShowing();
667 }
668
669 /** @hide */
670 public boolean isOverflowMenuShowPending() {
671 return mPresenter != null && mPresenter.isOverflowMenuShowPending();
672 }
673
674 /**
675 * Dismiss any popups associated with this menu view.
676 */
677 public void dismissPopupMenus() {
678 if (mPresenter != null) {
679 mPresenter.dismissPopupMenus();
680 }
681 }
682
683 /**
Adam Powellfa18d182014-01-07 15:56:59 -0800684 * @hide Private LinearLayout (superclass) API. Un-hide if LinearLayout API is made public.
685 */
Adam Powell696cba52011-03-29 10:38:16 -0700686 @Override
687 protected boolean hasDividerBeforeChildAt(int childIndex) {
Jake Wharton825992f2012-07-28 21:31:51 -0700688 if (childIndex == 0) {
689 return false;
690 }
Adam Powell696cba52011-03-29 10:38:16 -0700691 final View childBefore = getChildAt(childIndex - 1);
692 final View child = getChildAt(childIndex);
693 boolean result = false;
694 if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
695 result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
Adam Powell96675b12010-06-10 18:58:59 -0700696 }
Adam Powell696cba52011-03-29 10:38:16 -0700697 if (childIndex > 0 && child instanceof ActionMenuChildView) {
698 result |= ((ActionMenuChildView) child).needsDividerBefore();
Adam Powell8028dd32010-07-15 10:16:33 -0700699 }
Adam Powellbe4d68e2010-10-08 18:16:34 -0700700 return result;
701 }
702
Adam Powell7bc3ca02011-08-26 18:29:58 -0700703 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
704 return false;
705 }
706
Adam Powell07a74542014-05-30 15:52:44 -0700707 /** @hide */
708 public void setExpandedActionViewsExclusive(boolean exclusive) {
709 mPresenter.setExpandedActionViewsExclusive(exclusive);
710 }
711
Adam Powelle43340c2014-03-17 19:10:43 -0700712 /**
713 * Interface responsible for receiving menu item click events if the items themselves
714 * do not have individual item click listeners.
715 */
716 public interface OnMenuItemClickListener {
717 /**
718 * This method will be invoked when a menu item is clicked if the item itself did
719 * not already handle the event.
720 *
721 * @param item {@link MenuItem} that was clicked
722 * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
723 */
724 public boolean onMenuItemClick(MenuItem item);
725 }
726
727 private class MenuBuilderCallback implements MenuBuilder.Callback {
728 @Override
729 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
730 return mOnMenuItemClickListener != null &&
731 mOnMenuItemClickListener.onMenuItemClick(item);
732 }
733
734 @Override
735 public void onMenuModeChange(MenuBuilder menu) {
Adam Powellc4612502014-09-04 19:16:39 -0700736 if (mMenuBuilderCallback != null) {
737 mMenuBuilderCallback.onMenuModeChange(menu);
738 }
Adam Powelle43340c2014-03-17 19:10:43 -0700739 }
740 }
741
742 private class ActionMenuPresenterCallback implements ActionMenuPresenter.Callback {
743 @Override
744 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
745 }
746
747 @Override
748 public boolean onOpenSubMenu(MenuBuilder subMenu) {
749 return false;
750 }
751 }
752
Adam Powellfa18d182014-01-07 15:56:59 -0800753 /** @hide */
Adam Powell696cba52011-03-29 10:38:16 -0700754 public interface ActionMenuChildView {
755 public boolean needsDividerBefore();
756 public boolean needsDividerAfter();
Adam Powell8515ee82010-11-30 14:09:55 -0800757 }
Adam Powell640a66e2011-04-29 10:18:53 -0700758
759 public static class LayoutParams extends LinearLayout.LayoutParams {
Adam Powellfa18d182014-01-07 15:56:59 -0800760 /** @hide */
Adam Powell640a66e2011-04-29 10:18:53 -0700761 @ViewDebug.ExportedProperty(category = "layout")
762 public boolean isOverflowButton;
Adam Powellfa18d182014-01-07 15:56:59 -0800763
764 /** @hide */
Adam Powell35aecd52011-07-01 13:43:49 -0700765 @ViewDebug.ExportedProperty(category = "layout")
766 public int cellsUsed;
Adam Powellfa18d182014-01-07 15:56:59 -0800767
768 /** @hide */
Adam Powell35aecd52011-07-01 13:43:49 -0700769 @ViewDebug.ExportedProperty(category = "layout")
Adam Powell35aecd52011-07-01 13:43:49 -0700770 public int extraPixels;
Adam Powellfa18d182014-01-07 15:56:59 -0800771
772 /** @hide */
Adam Powell160bb7f2011-07-07 10:22:27 -0700773 @ViewDebug.ExportedProperty(category = "layout")
774 public boolean expandable;
Adam Powellfa18d182014-01-07 15:56:59 -0800775
776 /** @hide */
Adam Powellbe3c3292011-08-24 12:52:28 -0700777 @ViewDebug.ExportedProperty(category = "layout")
778 public boolean preventEdgeOffset;
Adam Powell160bb7f2011-07-07 10:22:27 -0700779
Adam Powellfa18d182014-01-07 15:56:59 -0800780 /** @hide */
Adam Powell35aecd52011-07-01 13:43:49 -0700781 public boolean expanded;
Adam Powell640a66e2011-04-29 10:18:53 -0700782
783 public LayoutParams(Context c, AttributeSet attrs) {
784 super(c, attrs);
785 }
786
Adam Powell8c1b02e2012-07-16 17:58:18 -0700787 public LayoutParams(ViewGroup.LayoutParams other) {
788 super(other);
789 }
790
Adam Powell640a66e2011-04-29 10:18:53 -0700791 public LayoutParams(LayoutParams other) {
792 super((LinearLayout.LayoutParams) other);
793 isOverflowButton = other.isOverflowButton;
794 }
795
796 public LayoutParams(int width, int height) {
797 super(width, height);
798 isOverflowButton = false;
799 }
800
Adam Powellfa18d182014-01-07 15:56:59 -0800801 /** @hide */
Adam Powell640a66e2011-04-29 10:18:53 -0700802 public LayoutParams(int width, int height, boolean isOverflowButton) {
803 super(width, height);
804 this.isOverflowButton = isOverflowButton;
805 }
806 }
Adam Powell96675b12010-06-10 18:58:59 -0700807}