blob: 32c7086b70e89fcda4f2b804d29baa57d83da25c [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;
Adam Powell6b336f82010-08-10 20:13:01 -070021import android.view.Gravity;
Adam Powellfa18d182014-01-07 15:56:59 -080022import android.view.Menu;
Adam Powellcf78b3e2010-09-12 18:25:23 -070023import android.view.View;
Adam Powell640a66e2011-04-29 10:18:53 -070024import android.view.ViewDebug;
Adam Powell7ade1be2010-06-17 12:51:21 -070025import android.view.ViewGroup;
Adam Powell7bc3ca02011-08-26 18:29:58 -070026import android.view.accessibility.AccessibilityEvent;
Adam Powellfa18d182014-01-07 15:56:59 -080027import com.android.internal.view.menu.ActionMenuItemView;
28import com.android.internal.view.menu.MenuBuilder;
29import com.android.internal.view.menu.MenuItemImpl;
30import com.android.internal.view.menu.MenuView;
Adam Powell96675b12010-06-10 18:58:59 -070031
Adam Powell96675b12010-06-10 18:58:59 -070032/**
Adam Powellfa18d182014-01-07 15:56:59 -080033 * ActionMenuView is a presentation of a series of menu options as a View. It provides
34 * several top level options as action buttons while spilling remaining options over as
35 * items in an overflow menu. This allows applications to present packs of actions inline with
36 * specific or repeating content.
Adam Powell96675b12010-06-10 18:58:59 -070037 */
38public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView {
39 private static final String TAG = "ActionMenuView";
40
Adam Powell35aecd52011-07-01 13:43:49 -070041 static final int MIN_CELL_SIZE = 56; // dips
Adam Powellbe3c3292011-08-24 12:52:28 -070042 static final int GENERATED_ITEM_PADDING = 4; // dips
Adam Powell35aecd52011-07-01 13:43:49 -070043
Adam Powell96675b12010-06-10 18:58:59 -070044 private MenuBuilder mMenu;
Adam Powell7ade1be2010-06-17 12:51:21 -070045
Adam Powell8028dd32010-07-15 10:16:33 -070046 private boolean mReserveOverflow;
Adam Powell696cba52011-03-29 10:38:16 -070047 private ActionMenuPresenter mPresenter;
Adam Powell640a66e2011-04-29 10:18:53 -070048 private boolean mFormatItems;
Adam Powell89b09da2011-07-27 11:55:29 -070049 private int mFormatItemsWidth;
Adam Powell35aecd52011-07-01 13:43:49 -070050 private int mMinCellSize;
Adam Powellbe3c3292011-08-24 12:52:28 -070051 private int mGeneratedItemPadding;
Adam Powell8515ee82010-11-30 14:09:55 -080052
Adam Powell96675b12010-06-10 18:58:59 -070053 public ActionMenuView(Context context) {
54 this(context, null);
55 }
56
57 public ActionMenuView(Context context, AttributeSet attrs) {
58 super(context, attrs);
Adam Powellf16888f2010-10-11 17:05:29 -070059 setBaselineAligned(false);
Adam Powellbe3c3292011-08-24 12:52:28 -070060 final float density = context.getResources().getDisplayMetrics().density;
61 mMinCellSize = (int) (MIN_CELL_SIZE * density);
62 mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
Adam Powell773b1b92010-08-20 15:45:24 -070063 }
64
Adam Powellfa18d182014-01-07 15:56:59 -080065 /** @hide */
Adam Powell696cba52011-03-29 10:38:16 -070066 public void setPresenter(ActionMenuPresenter presenter) {
67 mPresenter = presenter;
68 }
69
Adam Powell6c6f5752010-08-20 18:34:46 -070070 @Override
Adam Powell773b1b92010-08-20 15:45:24 -070071 public void onConfigurationChanged(Configuration newConfig) {
Adam Powell8515ee82010-11-30 14:09:55 -080072 super.onConfigurationChanged(newConfig);
Adam Powell696cba52011-03-29 10:38:16 -070073 mPresenter.updateMenuView(false);
Adam Powell6c6f5752010-08-20 18:34:46 -070074
Adam Powell696cba52011-03-29 10:38:16 -070075 if (mPresenter != null && mPresenter.isOverflowMenuShowing()) {
76 mPresenter.hideOverflowMenu();
77 mPresenter.showOverflowMenu();
Adam Powell6c6f5752010-08-20 18:34:46 -070078 }
Adam Powell773b1b92010-08-20 15:45:24 -070079 }
80
Adam Powell8515ee82010-11-30 14:09:55 -080081 @Override
Adam Powell640a66e2011-04-29 10:18:53 -070082 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Adam Powell35aecd52011-07-01 13:43:49 -070083 // If we've been given an exact size to match, apply special formatting during layout.
Adam Powell89b09da2011-07-27 11:55:29 -070084 final boolean wasFormatted = mFormatItems;
Adam Powell35aecd52011-07-01 13:43:49 -070085 mFormatItems = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
Adam Powell89b09da2011-07-27 11:55:29 -070086
87 if (wasFormatted != mFormatItems) {
88 mFormatItemsWidth = 0; // Reset this when switching modes
89 }
90
91 // Special formatting can change whether items can fit as action buttons.
92 // Kick the menu and update presenters when this changes.
Adam Powellda971082013-10-03 18:21:58 -070093 final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
Adam Powell89b09da2011-07-27 11:55:29 -070094 if (mFormatItems && mMenu != null && widthSize != mFormatItemsWidth) {
95 mFormatItemsWidth = widthSize;
Adam Powell640a66e2011-04-29 10:18:53 -070096 mMenu.onItemsChanged(true);
Adam Powell640a66e2011-04-29 10:18:53 -070097 }
Adam Powell35aecd52011-07-01 13:43:49 -070098
99 if (mFormatItems) {
100 onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec);
101 } else {
Adam Powell75d022a2012-03-06 12:04:07 -0800102 // Previous measurement at exact format may have set margins - reset them.
103 final int childCount = getChildCount();
104 for (int i = 0; i < childCount; i++) {
105 final View child = getChildAt(i);
106 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
107 lp.leftMargin = lp.rightMargin = 0;
108 }
Adam Powell35aecd52011-07-01 13:43:49 -0700109 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
110 }
111 }
112
113 private void onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec) {
114 // We already know the width mode is EXACTLY if we're here.
115 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
116 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
117 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
118
119 final int widthPadding = getPaddingLeft() + getPaddingRight();
120 final int heightPadding = getPaddingTop() + getPaddingBottom();
121
Adam Powellfa18d182014-01-07 15:56:59 -0800122 final int itemHeightSpec = getChildMeasureSpec(heightMeasureSpec, heightPadding,
123 ViewGroup.LayoutParams.WRAP_CONTENT);
Adam Powell367ee322012-05-06 18:32:33 -0700124
Adam Powell35aecd52011-07-01 13:43:49 -0700125 widthSize -= widthPadding;
126
127 // Divide the view into cells.
128 final int cellCount = widthSize / mMinCellSize;
129 final int cellSizeRemaining = widthSize % mMinCellSize;
Adam Powell3bb421d2011-08-16 15:04:53 -0700130
131 if (cellCount == 0) {
132 // Give up, nothing fits.
133 setMeasuredDimension(widthSize, 0);
134 return;
135 }
136
Adam Powell35aecd52011-07-01 13:43:49 -0700137 final int cellSize = mMinCellSize + cellSizeRemaining / cellCount;
138
139 int cellsRemaining = cellCount;
140 int maxChildHeight = 0;
141 int maxCellsUsed = 0;
Adam Powell160bb7f2011-07-07 10:22:27 -0700142 int expandableItemCount = 0;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700143 int visibleItemCount = 0;
144 boolean hasOverflow = false;
Adam Powell35aecd52011-07-01 13:43:49 -0700145
Adam Powell14b7e2c2011-08-12 11:11:50 -0700146 // This is used as a bitfield to locate the smallest items present. Assumes childCount < 64.
147 long smallestItemsAt = 0;
Adam Powell35aecd52011-07-01 13:43:49 -0700148
149 final int childCount = getChildCount();
150 for (int i = 0; i < childCount; i++) {
151 final View child = getChildAt(i);
Adam Powell14b7e2c2011-08-12 11:11:50 -0700152 if (child.getVisibility() == GONE) continue;
153
Adam Powellbe3c3292011-08-24 12:52:28 -0700154 final boolean isGeneratedItem = child instanceof ActionMenuItemView;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700155 visibleItemCount++;
156
Adam Powellbe3c3292011-08-24 12:52:28 -0700157 if (isGeneratedItem) {
158 // Reset padding for generated menu item views; it may change below
159 // and views are recycled.
160 child.setPadding(mGeneratedItemPadding, 0, mGeneratedItemPadding, 0);
161 }
162
Adam Powell35aecd52011-07-01 13:43:49 -0700163 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
164 lp.expanded = false;
165 lp.extraPixels = 0;
166 lp.cellsUsed = 0;
Adam Powell160bb7f2011-07-07 10:22:27 -0700167 lp.expandable = false;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700168 lp.leftMargin = 0;
169 lp.rightMargin = 0;
Adam Powellbe3c3292011-08-24 12:52:28 -0700170 lp.preventEdgeOffset = isGeneratedItem && ((ActionMenuItemView) child).hasText();
Adam Powell35aecd52011-07-01 13:43:49 -0700171
172 // Overflow always gets 1 cell. No more, no less.
173 final int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining;
174
175 final int cellsUsed = measureChildForCells(child, cellSize, cellsAvailable,
Adam Powell367ee322012-05-06 18:32:33 -0700176 itemHeightSpec, heightPadding);
Adam Powell35aecd52011-07-01 13:43:49 -0700177
178 maxCellsUsed = Math.max(maxCellsUsed, cellsUsed);
Adam Powell160bb7f2011-07-07 10:22:27 -0700179 if (lp.expandable) expandableItemCount++;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700180 if (lp.isOverflowButton) hasOverflow = true;
Adam Powell35aecd52011-07-01 13:43:49 -0700181
182 cellsRemaining -= cellsUsed;
183 maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
Adam Powell14b7e2c2011-08-12 11:11:50 -0700184 if (cellsUsed == 1) smallestItemsAt |= (1 << i);
Adam Powell35aecd52011-07-01 13:43:49 -0700185 }
186
Adam Powellbe3c3292011-08-24 12:52:28 -0700187 // When we have overflow and a single expanded (text) item, we want to try centering it
188 // visually in the available space even though overflow consumes some of it.
189 final boolean centerSingleExpandedItem = hasOverflow && visibleItemCount == 2;
190
Adam Powell35aecd52011-07-01 13:43:49 -0700191 // Divide space for remaining cells if we have items that can expand.
192 // Try distributing whole leftover cells to smaller items first.
193
194 boolean needsExpansion = false;
Adam Powell160bb7f2011-07-07 10:22:27 -0700195 while (expandableItemCount > 0 && cellsRemaining > 0) {
Adam Powell35aecd52011-07-01 13:43:49 -0700196 int minCells = Integer.MAX_VALUE;
197 long minCellsAt = 0; // Bit locations are indices of relevant child views
198 int minCellsItemCount = 0;
199 for (int i = 0; i < childCount; i++) {
200 final View child = getChildAt(i);
201 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
202
203 // Don't try to expand items that shouldn't.
Adam Powell160bb7f2011-07-07 10:22:27 -0700204 if (!lp.expandable) continue;
Adam Powell35aecd52011-07-01 13:43:49 -0700205
206 // Mark indices of children that can receive an extra cell.
207 if (lp.cellsUsed < minCells) {
208 minCells = lp.cellsUsed;
209 minCellsAt = 1 << i;
210 minCellsItemCount = 1;
211 } else if (lp.cellsUsed == minCells) {
212 minCellsAt |= 1 << i;
213 minCellsItemCount++;
214 }
215 }
216
Adam Powell35aecd52011-07-01 13:43:49 -0700217 // Items that get expanded will always be in the set of smallest items when we're done.
Adam Powell14b7e2c2011-08-12 11:11:50 -0700218 smallestItemsAt |= minCellsAt;
Adam Powell35aecd52011-07-01 13:43:49 -0700219
Adam Powellbe3c3292011-08-24 12:52:28 -0700220 if (minCellsItemCount > cellsRemaining) break; // Couldn't expand anything evenly. Stop.
Adam Powell35aecd52011-07-01 13:43:49 -0700221
Adam Powellbe3c3292011-08-24 12:52:28 -0700222 // We have enough cells, all minimum size items will be incremented.
223 minCells++;
224
225 for (int i = 0; i < childCount; i++) {
Adam Powell35aecd52011-07-01 13:43:49 -0700226 final View child = getChildAt(i);
227 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Powellbe3c3292011-08-24 12:52:28 -0700228 if ((minCellsAt & (1 << i)) == 0) {
229 // If this item is already at our small item count, mark it for later.
230 if (lp.cellsUsed == minCells) smallestItemsAt |= 1 << i;
231 continue;
232 }
233
234 if (centerSingleExpandedItem && lp.preventEdgeOffset && cellsRemaining == 1) {
235 // Add padding to this item such that it centers.
236 child.setPadding(mGeneratedItemPadding + cellSize, 0, mGeneratedItemPadding, 0);
237 }
Adam Powell35aecd52011-07-01 13:43:49 -0700238 lp.cellsUsed++;
239 lp.expanded = true;
240 cellsRemaining--;
241 }
242
243 needsExpansion = true;
244 }
245
246 // Divide any space left that wouldn't divide along cell boundaries
Adam Powell14b7e2c2011-08-12 11:11:50 -0700247 // evenly among the smallest items
Adam Powell35aecd52011-07-01 13:43:49 -0700248
Adam Powell14b7e2c2011-08-12 11:11:50 -0700249 final boolean singleItem = !hasOverflow && visibleItemCount == 1;
250 if (cellsRemaining > 0 && smallestItemsAt != 0 &&
Adam Powellbe3c3292011-08-24 12:52:28 -0700251 (cellsRemaining < visibleItemCount - 1 || singleItem || maxCellsUsed > 1)) {
Adam Powell14b7e2c2011-08-12 11:11:50 -0700252 float expandCount = Long.bitCount(smallestItemsAt);
253
254 if (!singleItem) {
255 // The items at the far edges may only expand by half in order to pin to either side.
256 if ((smallestItemsAt & 1) != 0) {
Adam Powellbe3c3292011-08-24 12:52:28 -0700257 LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams();
258 if (!lp.preventEdgeOffset) expandCount -= 0.5f;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700259 }
260 if ((smallestItemsAt & (1 << (childCount - 1))) != 0) {
Adam Powellbe3c3292011-08-24 12:52:28 -0700261 LayoutParams lp = ((LayoutParams) getChildAt(childCount - 1).getLayoutParams());
262 if (!lp.preventEdgeOffset) expandCount -= 0.5f;
Adam Powell14b7e2c2011-08-12 11:11:50 -0700263 }
264 }
265
Adam Powell3bb421d2011-08-16 15:04:53 -0700266 final int extraPixels = expandCount > 0 ?
267 (int) (cellsRemaining * cellSize / expandCount) : 0;
Adam Powell35aecd52011-07-01 13:43:49 -0700268
269 for (int i = 0; i < childCount; i++) {
Adam Powell14b7e2c2011-08-12 11:11:50 -0700270 if ((smallestItemsAt & (1 << i)) == 0) continue;
Adam Powell35aecd52011-07-01 13:43:49 -0700271
272 final View child = getChildAt(i);
273 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Powell14b7e2c2011-08-12 11:11:50 -0700274 if (child instanceof ActionMenuItemView) {
275 // If this is one of our views, expand and measure at the larger size.
276 lp.extraPixels = extraPixels;
277 lp.expanded = true;
Adam Powellbe3c3292011-08-24 12:52:28 -0700278 if (i == 0 && !lp.preventEdgeOffset) {
Adam Powell14b7e2c2011-08-12 11:11:50 -0700279 // First item gets part of its new padding pushed out of sight.
280 // The last item will get this implicitly from layout.
281 lp.leftMargin = -extraPixels / 2;
282 }
283 needsExpansion = true;
284 } else if (lp.isOverflowButton) {
285 lp.extraPixels = extraPixels;
286 lp.expanded = true;
287 lp.rightMargin = -extraPixels / 2;
288 needsExpansion = true;
289 } else {
290 // If we don't know what it is, give it some margins instead
291 // and let it center within its space. We still want to pin
292 // against the edges.
293 if (i != 0) {
294 lp.leftMargin = extraPixels / 2;
295 }
296 if (i != childCount - 1) {
297 lp.rightMargin = extraPixels / 2;
298 }
299 }
Adam Powell35aecd52011-07-01 13:43:49 -0700300 }
301
Adam Powell35aecd52011-07-01 13:43:49 -0700302 cellsRemaining = 0;
303 }
304
305 // Remeasure any items that have had extra space allocated to them.
306 if (needsExpansion) {
Adam Powell35aecd52011-07-01 13:43:49 -0700307 for (int i = 0; i < childCount; i++) {
308 final View child = getChildAt(i);
309 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
310
311 if (!lp.expanded) continue;
312
313 final int width = lp.cellsUsed * cellSize + lp.extraPixels;
Adam Powell367ee322012-05-06 18:32:33 -0700314 child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
315 itemHeightSpec);
Adam Powell35aecd52011-07-01 13:43:49 -0700316 }
317 }
318
319 if (heightMode != MeasureSpec.EXACTLY) {
320 heightSize = maxChildHeight;
321 }
322
323 setMeasuredDimension(widthSize, heightSize);
Adam Powell35aecd52011-07-01 13:43:49 -0700324 }
325
326 /**
327 * Measure a child view to fit within cell-based formatting. The child's width
328 * will be measured to a whole multiple of cellSize.
329 *
Adam Powell160bb7f2011-07-07 10:22:27 -0700330 * <p>Sets the expandable and cellsUsed fields of LayoutParams.
Adam Powell35aecd52011-07-01 13:43:49 -0700331 *
332 * @param child Child to measure
333 * @param cellSize Size of one cell
334 * @param cellsRemaining Number of cells remaining that this view can expand to fill
335 * @param parentHeightMeasureSpec MeasureSpec used by the parent view
336 * @param parentHeightPadding Padding present in the parent view
337 * @return Number of cells this child was measured to occupy
338 */
339 static int measureChildForCells(View child, int cellSize, int cellsRemaining,
340 int parentHeightMeasureSpec, int parentHeightPadding) {
341 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Powell35aecd52011-07-01 13:43:49 -0700342
343 final int childHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec) -
344 parentHeightPadding;
345 final int childHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec);
346 final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode);
347
Adam Powella7dec6d2012-04-09 15:54:01 -0700348 final ActionMenuItemView itemView = child instanceof ActionMenuItemView ?
349 (ActionMenuItemView) child : null;
350 final boolean hasText = itemView != null && itemView.hasText();
351
Adam Powell160bb7f2011-07-07 10:22:27 -0700352 int cellsUsed = 0;
Adam Powella7dec6d2012-04-09 15:54:01 -0700353 if (cellsRemaining > 0 && (!hasText || cellsRemaining >= 2)) {
Adam Powell35aecd52011-07-01 13:43:49 -0700354 final int childWidthSpec = MeasureSpec.makeMeasureSpec(
355 cellSize * cellsRemaining, MeasureSpec.AT_MOST);
356 child.measure(childWidthSpec, childHeightSpec);
357
358 final int measuredWidth = child.getMeasuredWidth();
359 cellsUsed = measuredWidth / cellSize;
360 if (measuredWidth % cellSize != 0) cellsUsed++;
Adam Powella7dec6d2012-04-09 15:54:01 -0700361 if (hasText && cellsUsed < 2) cellsUsed = 2;
Adam Powell35aecd52011-07-01 13:43:49 -0700362 }
Adam Powell160bb7f2011-07-07 10:22:27 -0700363
Adam Powella7dec6d2012-04-09 15:54:01 -0700364 final boolean expandable = !lp.isOverflowButton && hasText;
Adam Powell160bb7f2011-07-07 10:22:27 -0700365 lp.expandable = expandable;
366
Adam Powell35aecd52011-07-01 13:43:49 -0700367 lp.cellsUsed = cellsUsed;
368 final int targetWidth = cellsUsed * cellSize;
369 child.measure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
370 childHeightSpec);
371 return cellsUsed;
Adam Powell640a66e2011-04-29 10:18:53 -0700372 }
373
374 @Override
375 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
376 if (!mFormatItems) {
377 super.onLayout(changed, left, top, right, bottom);
378 return;
379 }
380
381 final int childCount = getChildCount();
382 final int midVertical = (top + bottom) / 2;
383 final int dividerWidth = getDividerWidth();
Adam Powell640a66e2011-04-29 10:18:53 -0700384 int overflowWidth = 0;
385 int nonOverflowWidth = 0;
386 int nonOverflowCount = 0;
387 int widthRemaining = right - left - getPaddingRight() - getPaddingLeft();
Adam Powell35aecd52011-07-01 13:43:49 -0700388 boolean hasOverflow = false;
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700389 final boolean isLayoutRtl = isLayoutRtl();
Adam Powell640a66e2011-04-29 10:18:53 -0700390 for (int i = 0; i < childCount; i++) {
391 final View v = getChildAt(i);
392 if (v.getVisibility() == GONE) {
393 continue;
394 }
395
396 LayoutParams p = (LayoutParams) v.getLayoutParams();
397 if (p.isOverflowButton) {
Adam Powell640a66e2011-04-29 10:18:53 -0700398 overflowWidth = v.getMeasuredWidth();
399 if (hasDividerBeforeChildAt(i)) {
400 overflowWidth += dividerWidth;
401 }
402
403 int height = v.getMeasuredHeight();
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700404 int r;
405 int l;
406 if (isLayoutRtl) {
407 l = getPaddingLeft() + p.leftMargin;
408 r = l + overflowWidth;
409 } else {
410 r = getWidth() - getPaddingRight() - p.rightMargin;
411 l = r - overflowWidth;
412 }
Adam Powell640a66e2011-04-29 10:18:53 -0700413 int t = midVertical - (height / 2);
414 int b = t + height;
415 v.layout(l, t, r, b);
416
417 widthRemaining -= overflowWidth;
Adam Powell35aecd52011-07-01 13:43:49 -0700418 hasOverflow = true;
Adam Powell640a66e2011-04-29 10:18:53 -0700419 } else {
Adam Powell35aecd52011-07-01 13:43:49 -0700420 final int size = v.getMeasuredWidth() + p.leftMargin + p.rightMargin;
421 nonOverflowWidth += size;
422 widthRemaining -= size;
Adam Powell640a66e2011-04-29 10:18:53 -0700423 if (hasDividerBeforeChildAt(i)) {
424 nonOverflowWidth += dividerWidth;
425 }
426 nonOverflowCount++;
427 }
428 }
429
Adam Powell14b7e2c2011-08-12 11:11:50 -0700430 if (childCount == 1 && !hasOverflow) {
431 // Center a single child
432 final View v = getChildAt(0);
433 final int width = v.getMeasuredWidth();
434 final int height = v.getMeasuredHeight();
435 final int midHorizontal = (right - left) / 2;
436 final int l = midHorizontal - width / 2;
437 final int t = midVertical - height / 2;
438 v.layout(l, t, l + width, t + height);
439 return;
440 }
441
Adam Powell35aecd52011-07-01 13:43:49 -0700442 final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1);
Adam Powell14b7e2c2011-08-12 11:11:50 -0700443 final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0);
Adam Powell640a66e2011-04-29 10:18:53 -0700444
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700445 if (isLayoutRtl) {
446 int startRight = getWidth() - getPaddingRight();
447 for (int i = 0; i < childCount; i++) {
448 final View v = getChildAt(i);
449 final LayoutParams lp = (LayoutParams) v.getLayoutParams();
450 if (v.getVisibility() == GONE || lp.isOverflowButton) {
451 continue;
452 }
Adam Powell640a66e2011-04-29 10:18:53 -0700453
Fabrice Di Meglio0762cec2012-09-05 19:17:20 -0700454 startRight -= lp.rightMargin;
455 int width = v.getMeasuredWidth();
456 int height = v.getMeasuredHeight();
457 int t = midVertical - height / 2;
458 v.layout(startRight - width, t, startRight, t + height);
459 startRight -= width + lp.leftMargin + spacerSize;
460 }
461 } else {
462 int startLeft = getPaddingLeft();
463 for (int i = 0; i < childCount; i++) {
464 final View v = getChildAt(i);
465 final LayoutParams lp = (LayoutParams) v.getLayoutParams();
466 if (v.getVisibility() == GONE || lp.isOverflowButton) {
467 continue;
468 }
469
470 startLeft += lp.leftMargin;
471 int width = v.getMeasuredWidth();
472 int height = v.getMeasuredHeight();
473 int t = midVertical - height / 2;
474 v.layout(startLeft, t, startLeft + width, t + height);
475 startLeft += width + lp.rightMargin + spacerSize;
476 }
Adam Powell640a66e2011-04-29 10:18:53 -0700477 }
478 }
479
480 @Override
Adam Powell8515ee82010-11-30 14:09:55 -0800481 public void onDetachedFromWindow() {
482 super.onDetachedFromWindow();
Adam Powell696cba52011-03-29 10:38:16 -0700483 mPresenter.dismissPopupMenus();
Adam Powell8028dd32010-07-15 10:16:33 -0700484 }
485
Adam Powellfa18d182014-01-07 15:56:59 -0800486 /** @hide */
Adam Powell8028dd32010-07-15 10:16:33 -0700487 public boolean isOverflowReserved() {
488 return mReserveOverflow;
Adam Powell7ade1be2010-06-17 12:51:21 -0700489 }
Adam Powellfa18d182014-01-07 15:56:59 -0800490
491 /** @hide */
Adam Powellb366bba2010-07-20 14:26:38 -0700492 public void setOverflowReserved(boolean reserveOverflow) {
493 mReserveOverflow = reserveOverflow;
494 }
Adam Powellf0ad6e62011-01-10 17:14:06 -0800495
Adam Powell7ade1be2010-06-17 12:51:21 -0700496 @Override
497 protected LayoutParams generateDefaultLayoutParams() {
498 LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
499 LayoutParams.WRAP_CONTENT);
Adam Powell6b336f82010-08-10 20:13:01 -0700500 params.gravity = Gravity.CENTER_VERTICAL;
Adam Powell7ade1be2010-06-17 12:51:21 -0700501 return params;
502 }
503
504 @Override
Adam Powell35aecd52011-07-01 13:43:49 -0700505 public LayoutParams generateLayoutParams(AttributeSet attrs) {
506 return new LayoutParams(getContext(), attrs);
507 }
508
509 @Override
Adam Powell7ade1be2010-06-17 12:51:21 -0700510 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
Adam Powell8c1b02e2012-07-16 17:58:18 -0700511 if (p != null) {
512 final LayoutParams result = p instanceof LayoutParams
513 ? new LayoutParams((LayoutParams) p)
514 : new LayoutParams(p);
Adam Powell3f476b32011-01-03 19:25:36 -0800515 if (result.gravity <= Gravity.NO_GRAVITY) {
516 result.gravity = Gravity.CENTER_VERTICAL;
517 }
518 return result;
519 }
Adam Powell7ade1be2010-06-17 12:51:21 -0700520 return generateDefaultLayoutParams();
521 }
Adam Powell96675b12010-06-10 18:58:59 -0700522
Adam Powell696cba52011-03-29 10:38:16 -0700523 @Override
524 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
Adam Powell35aecd52011-07-01 13:43:49 -0700525 return p != null && p instanceof LayoutParams;
Adam Powell696cba52011-03-29 10:38:16 -0700526 }
527
Adam Powellfa18d182014-01-07 15:56:59 -0800528 /** @hide */
Adam Powell640a66e2011-04-29 10:18:53 -0700529 public LayoutParams generateOverflowButtonLayoutParams() {
530 LayoutParams result = generateDefaultLayoutParams();
531 result.isOverflowButton = true;
532 return result;
533 }
534
Adam Powellfa18d182014-01-07 15:56:59 -0800535 /** @hide */
Adam Powell96675b12010-06-10 18:58:59 -0700536 public boolean invokeItem(MenuItemImpl item) {
537 return mMenu.performItemAction(item, 0);
538 }
539
Adam Powellfa18d182014-01-07 15:56:59 -0800540 /** @hide */
Adam Powell96675b12010-06-10 18:58:59 -0700541 public int getWindowAnimations() {
542 return 0;
543 }
544
Adam Powellfa18d182014-01-07 15:56:59 -0800545 /** @hide */
Adam Powell696cba52011-03-29 10:38:16 -0700546 public void initialize(MenuBuilder menu) {
Adam Powell96675b12010-06-10 18:58:59 -0700547 mMenu = menu;
Adam Powell96675b12010-06-10 18:58:59 -0700548 }
549
Adam Powellfa18d182014-01-07 15:56:59 -0800550 /**
551 * Returns the Menu object that this ActionMenuView is currently presenting.
552 *
553 * <p>Applications should use this method to obtain the ActionMenuView's Menu object
554 * and inflate or add content to it as necessary.</p>
555 *
556 * @return the Menu presented by this view
557 */
558 public Menu getMenu() {
559 if (mMenu == null) {
560 final Context context = getContext();
561 mMenu = new MenuBuilder(context);
562 mPresenter = new ActionMenuPresenter(context);
563 mPresenter.initForMenu(context, mMenu);
564 mPresenter.setMenuView(this);
565 }
566
567 return mMenu;
568 }
569
570 /**
571 * @hide Private LinearLayout (superclass) API. Un-hide if LinearLayout API is made public.
572 */
Adam Powell696cba52011-03-29 10:38:16 -0700573 @Override
574 protected boolean hasDividerBeforeChildAt(int childIndex) {
Jake Wharton825992f2012-07-28 21:31:51 -0700575 if (childIndex == 0) {
576 return false;
577 }
Adam Powell696cba52011-03-29 10:38:16 -0700578 final View childBefore = getChildAt(childIndex - 1);
579 final View child = getChildAt(childIndex);
580 boolean result = false;
581 if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
582 result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
Adam Powell96675b12010-06-10 18:58:59 -0700583 }
Adam Powell696cba52011-03-29 10:38:16 -0700584 if (childIndex > 0 && child instanceof ActionMenuChildView) {
585 result |= ((ActionMenuChildView) child).needsDividerBefore();
Adam Powell8028dd32010-07-15 10:16:33 -0700586 }
Adam Powellbe4d68e2010-10-08 18:16:34 -0700587 return result;
588 }
589
Adam Powell7bc3ca02011-08-26 18:29:58 -0700590 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
591 return false;
592 }
593
Adam Powellfa18d182014-01-07 15:56:59 -0800594 /** @hide */
Adam Powell696cba52011-03-29 10:38:16 -0700595 public interface ActionMenuChildView {
596 public boolean needsDividerBefore();
597 public boolean needsDividerAfter();
Adam Powell8515ee82010-11-30 14:09:55 -0800598 }
Adam Powell640a66e2011-04-29 10:18:53 -0700599
600 public static class LayoutParams extends LinearLayout.LayoutParams {
Adam Powellfa18d182014-01-07 15:56:59 -0800601 /** @hide */
Adam Powell640a66e2011-04-29 10:18:53 -0700602 @ViewDebug.ExportedProperty(category = "layout")
603 public boolean isOverflowButton;
Adam Powellfa18d182014-01-07 15:56:59 -0800604
605 /** @hide */
Adam Powell35aecd52011-07-01 13:43:49 -0700606 @ViewDebug.ExportedProperty(category = "layout")
607 public int cellsUsed;
Adam Powellfa18d182014-01-07 15:56:59 -0800608
609 /** @hide */
Adam Powell35aecd52011-07-01 13:43:49 -0700610 @ViewDebug.ExportedProperty(category = "layout")
Adam Powell35aecd52011-07-01 13:43:49 -0700611 public int extraPixels;
Adam Powellfa18d182014-01-07 15:56:59 -0800612
613 /** @hide */
Adam Powell160bb7f2011-07-07 10:22:27 -0700614 @ViewDebug.ExportedProperty(category = "layout")
615 public boolean expandable;
Adam Powellfa18d182014-01-07 15:56:59 -0800616
617 /** @hide */
Adam Powellbe3c3292011-08-24 12:52:28 -0700618 @ViewDebug.ExportedProperty(category = "layout")
619 public boolean preventEdgeOffset;
Adam Powell160bb7f2011-07-07 10:22:27 -0700620
Adam Powellfa18d182014-01-07 15:56:59 -0800621 /** @hide */
Adam Powell35aecd52011-07-01 13:43:49 -0700622 public boolean expanded;
Adam Powell640a66e2011-04-29 10:18:53 -0700623
624 public LayoutParams(Context c, AttributeSet attrs) {
625 super(c, attrs);
626 }
627
Adam Powell8c1b02e2012-07-16 17:58:18 -0700628 public LayoutParams(ViewGroup.LayoutParams other) {
629 super(other);
630 }
631
Adam Powell640a66e2011-04-29 10:18:53 -0700632 public LayoutParams(LayoutParams other) {
633 super((LinearLayout.LayoutParams) other);
634 isOverflowButton = other.isOverflowButton;
635 }
636
637 public LayoutParams(int width, int height) {
638 super(width, height);
639 isOverflowButton = false;
640 }
641
Adam Powellfa18d182014-01-07 15:56:59 -0800642 /** @hide */
Adam Powell640a66e2011-04-29 10:18:53 -0700643 public LayoutParams(int width, int height, boolean isOverflowButton) {
644 super(width, height);
645 this.isOverflowButton = isOverflowButton;
646 }
647 }
Adam Powell96675b12010-06-10 18:58:59 -0700648}