0e3b1ae0d7095d638fcf62d922864aad3b4ff6dc
[pub/Android/ownCloud.git] / actionbarsherlock / src / com / actionbarsherlock / internal / view / menu / ActionMenuView.java
1 /*
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 */
16 package com.actionbarsherlock.internal.view.menu;
17
18 import android.content.Context;
19 import android.content.res.Configuration;
20 import android.graphics.Canvas;
21 import android.os.Build;
22 import android.util.AttributeSet;
23 import android.view.Gravity;
24 import android.view.View;
25 import android.view.ViewGroup;
26 import android.view.accessibility.AccessibilityEvent;
27 import android.widget.LinearLayout;
28 import com.actionbarsherlock.internal.widget.IcsLinearLayout;
29
30 /**
31 * @hide
32 */
33 public class ActionMenuView extends IcsLinearLayout implements MenuBuilder.ItemInvoker, MenuView {
34 //UNUSED private static final String TAG = "ActionMenuView";
35 private static final boolean IS_FROYO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
36
37 static final int MIN_CELL_SIZE = 56; // dips
38 static final int GENERATED_ITEM_PADDING = 4; // dips
39
40 private MenuBuilder mMenu;
41
42 private boolean mReserveOverflow;
43 private ActionMenuPresenter mPresenter;
44 private boolean mFormatItems;
45 private int mFormatItemsWidth;
46 private int mMinCellSize;
47 private int mGeneratedItemPadding;
48 //UNUSED private int mMeasuredExtraWidth;
49
50 private boolean mFirst = true;
51
52 public ActionMenuView(Context context) {
53 this(context, null);
54 }
55
56 public ActionMenuView(Context context, AttributeSet attrs) {
57 super(context, attrs);
58 setBaselineAligned(false);
59 final float density = context.getResources().getDisplayMetrics().density;
60 mMinCellSize = (int) (MIN_CELL_SIZE * density);
61 mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
62 }
63
64 public void setPresenter(ActionMenuPresenter presenter) {
65 mPresenter = presenter;
66 }
67
68 public boolean isExpandedFormat() {
69 return mFormatItems;
70 }
71
72 @Override
73 public void onConfigurationChanged(Configuration newConfig) {
74 if (IS_FROYO) {
75 super.onConfigurationChanged(newConfig);
76 }
77 mPresenter.updateMenuView(false);
78
79 if (mPresenter != null && mPresenter.isOverflowMenuShowing()) {
80 mPresenter.hideOverflowMenu();
81 mPresenter.showOverflowMenu();
82 }
83 }
84
85 @Override
86 protected void onDraw(Canvas canvas) {
87 //Need to trigger a relayout since we may have been added extremely
88 //late in the initial rendering (e.g., when contained in a ViewPager).
89 //See: https://github.com/JakeWharton/ActionBarSherlock/issues/272
90 if (!IS_FROYO && mFirst) {
91 mFirst = false;
92 requestLayout();
93 return;
94 }
95 super.onDraw(canvas);
96 }
97
98 @Override
99 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
100 // If we've been given an exact size to match, apply special formatting during layout.
101 final boolean wasFormatted = mFormatItems;
102 mFormatItems = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
103
104 if (wasFormatted != mFormatItems) {
105 mFormatItemsWidth = 0; // Reset this when switching modes
106 }
107
108 // Special formatting can change whether items can fit as action buttons.
109 // Kick the menu and update presenters when this changes.
110 final int widthSize = MeasureSpec.getMode(widthMeasureSpec);
111 if (mFormatItems && mMenu != null && widthSize != mFormatItemsWidth) {
112 mFormatItemsWidth = widthSize;
113 mMenu.onItemsChanged(true);
114 }
115
116 if (mFormatItems) {
117 onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec);
118 } else {
119 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
120 }
121 }
122
123 private void onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec) {
124 // We already know the width mode is EXACTLY if we're here.
125 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
126 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
127 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
128
129 final int widthPadding = getPaddingLeft() + getPaddingRight();
130 final int heightPadding = getPaddingTop() + getPaddingBottom();
131
132 widthSize -= widthPadding;
133
134 // Divide the view into cells.
135 final int cellCount = widthSize / mMinCellSize;
136 final int cellSizeRemaining = widthSize % mMinCellSize;
137
138 if (cellCount == 0) {
139 // Give up, nothing fits.
140 setMeasuredDimension(widthSize, 0);
141 return;
142 }
143
144 final int cellSize = mMinCellSize + cellSizeRemaining / cellCount;
145
146 int cellsRemaining = cellCount;
147 int maxChildHeight = 0;
148 int maxCellsUsed = 0;
149 int expandableItemCount = 0;
150 int visibleItemCount = 0;
151 boolean hasOverflow = false;
152
153 // This is used as a bitfield to locate the smallest items present. Assumes childCount < 64.
154 long smallestItemsAt = 0;
155
156 final int childCount = getChildCount();
157 for (int i = 0; i < childCount; i++) {
158 final View child = getChildAt(i);
159 if (child.getVisibility() == GONE) continue;
160
161 final boolean isGeneratedItem = child instanceof ActionMenuItemView;
162 visibleItemCount++;
163
164 if (isGeneratedItem) {
165 // Reset padding for generated menu item views; it may change below
166 // and views are recycled.
167 child.setPadding(mGeneratedItemPadding, 0, mGeneratedItemPadding, 0);
168 }
169
170 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
171 lp.expanded = false;
172 lp.extraPixels = 0;
173 lp.cellsUsed = 0;
174 lp.expandable = false;
175 lp.leftMargin = 0;
176 lp.rightMargin = 0;
177 lp.preventEdgeOffset = isGeneratedItem && ((ActionMenuItemView) child).hasText();
178
179 // Overflow always gets 1 cell. No more, no less.
180 final int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining;
181
182 final int cellsUsed = measureChildForCells(child, cellSize, cellsAvailable,
183 heightMeasureSpec, heightPadding);
184
185 maxCellsUsed = Math.max(maxCellsUsed, cellsUsed);
186 if (lp.expandable) expandableItemCount++;
187 if (lp.isOverflowButton) hasOverflow = true;
188
189 cellsRemaining -= cellsUsed;
190 maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
191 if (cellsUsed == 1) smallestItemsAt |= (1 << i);
192 }
193
194 // When we have overflow and a single expanded (text) item, we want to try centering it
195 // visually in the available space even though overflow consumes some of it.
196 final boolean centerSingleExpandedItem = hasOverflow && visibleItemCount == 2;
197
198 // Divide space for remaining cells if we have items that can expand.
199 // Try distributing whole leftover cells to smaller items first.
200
201 boolean needsExpansion = false;
202 while (expandableItemCount > 0 && cellsRemaining > 0) {
203 int minCells = Integer.MAX_VALUE;
204 long minCellsAt = 0; // Bit locations are indices of relevant child views
205 int minCellsItemCount = 0;
206 for (int i = 0; i < childCount; i++) {
207 final View child = getChildAt(i);
208 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
209
210 // Don't try to expand items that shouldn't.
211 if (!lp.expandable) continue;
212
213 // Mark indices of children that can receive an extra cell.
214 if (lp.cellsUsed < minCells) {
215 minCells = lp.cellsUsed;
216 minCellsAt = 1 << i;
217 minCellsItemCount = 1;
218 } else if (lp.cellsUsed == minCells) {
219 minCellsAt |= 1 << i;
220 minCellsItemCount++;
221 }
222 }
223
224 // Items that get expanded will always be in the set of smallest items when we're done.
225 smallestItemsAt |= minCellsAt;
226
227 if (minCellsItemCount > cellsRemaining) break; // Couldn't expand anything evenly. Stop.
228
229 // We have enough cells, all minimum size items will be incremented.
230 minCells++;
231
232 for (int i = 0; i < childCount; i++) {
233 final View child = getChildAt(i);
234 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
235 if ((minCellsAt & (1 << i)) == 0) {
236 // If this item is already at our small item count, mark it for later.
237 if (lp.cellsUsed == minCells) smallestItemsAt |= 1 << i;
238 continue;
239 }
240
241 if (centerSingleExpandedItem && lp.preventEdgeOffset && cellsRemaining == 1) {
242 // Add padding to this item such that it centers.
243 child.setPadding(mGeneratedItemPadding + cellSize, 0, mGeneratedItemPadding, 0);
244 }
245 lp.cellsUsed++;
246 lp.expanded = true;
247 cellsRemaining--;
248 }
249
250 needsExpansion = true;
251 }
252
253 // Divide any space left that wouldn't divide along cell boundaries
254 // evenly among the smallest items
255
256 final boolean singleItem = !hasOverflow && visibleItemCount == 1;
257 if (cellsRemaining > 0 && smallestItemsAt != 0 &&
258 (cellsRemaining < visibleItemCount - 1 || singleItem || maxCellsUsed > 1)) {
259 float expandCount = Long.bitCount(smallestItemsAt);
260
261 if (!singleItem) {
262 // The items at the far edges may only expand by half in order to pin to either side.
263 if ((smallestItemsAt & 1) != 0) {
264 LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams();
265 if (!lp.preventEdgeOffset) expandCount -= 0.5f;
266 }
267 if ((smallestItemsAt & (1 << (childCount - 1))) != 0) {
268 LayoutParams lp = ((LayoutParams) getChildAt(childCount - 1).getLayoutParams());
269 if (!lp.preventEdgeOffset) expandCount -= 0.5f;
270 }
271 }
272
273 final int extraPixels = expandCount > 0 ?
274 (int) (cellsRemaining * cellSize / expandCount) : 0;
275
276 for (int i = 0; i < childCount; i++) {
277 if ((smallestItemsAt & (1 << i)) == 0) continue;
278
279 final View child = getChildAt(i);
280 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
281 if (child instanceof ActionMenuItemView) {
282 // If this is one of our views, expand and measure at the larger size.
283 lp.extraPixels = extraPixels;
284 lp.expanded = true;
285 if (i == 0 && !lp.preventEdgeOffset) {
286 // First item gets part of its new padding pushed out of sight.
287 // The last item will get this implicitly from layout.
288 lp.leftMargin = -extraPixels / 2;
289 }
290 needsExpansion = true;
291 } else if (lp.isOverflowButton) {
292 lp.extraPixels = extraPixels;
293 lp.expanded = true;
294 lp.rightMargin = -extraPixels / 2;
295 needsExpansion = true;
296 } else {
297 // If we don't know what it is, give it some margins instead
298 // and let it center within its space. We still want to pin
299 // against the edges.
300 if (i != 0) {
301 lp.leftMargin = extraPixels / 2;
302 }
303 if (i != childCount - 1) {
304 lp.rightMargin = extraPixels / 2;
305 }
306 }
307 }
308
309 cellsRemaining = 0;
310 }
311
312 // Remeasure any items that have had extra space allocated to them.
313 if (needsExpansion) {
314 int heightSpec = MeasureSpec.makeMeasureSpec(heightSize - heightPadding, heightMode);
315 for (int i = 0; i < childCount; i++) {
316 final View child = getChildAt(i);
317 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
318
319 if (!lp.expanded) continue;
320
321 final int width = lp.cellsUsed * cellSize + lp.extraPixels;
322 child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), heightSpec);
323 }
324 }
325
326 if (heightMode != MeasureSpec.EXACTLY) {
327 heightSize = maxChildHeight;
328 }
329
330 setMeasuredDimension(widthSize, heightSize);
331 //UNUSED mMeasuredExtraWidth = cellsRemaining * cellSize;
332 }
333
334 /**
335 * Measure a child view to fit within cell-based formatting. The child's width
336 * will be measured to a whole multiple of cellSize.
337 *
338 * <p>Sets the expandable and cellsUsed fields of LayoutParams.
339 *
340 * @param child Child to measure
341 * @param cellSize Size of one cell
342 * @param cellsRemaining Number of cells remaining that this view can expand to fill
343 * @param parentHeightMeasureSpec MeasureSpec used by the parent view
344 * @param parentHeightPadding Padding present in the parent view
345 * @return Number of cells this child was measured to occupy
346 */
347 static int measureChildForCells(View child, int cellSize, int cellsRemaining,
348 int parentHeightMeasureSpec, int parentHeightPadding) {
349 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
350
351 final int childHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec) -
352 parentHeightPadding;
353 final int childHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec);
354 final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode);
355
356 int cellsUsed = 0;
357 if (cellsRemaining > 0) {
358 final int childWidthSpec = MeasureSpec.makeMeasureSpec(
359 cellSize * cellsRemaining, MeasureSpec.AT_MOST);
360 child.measure(childWidthSpec, childHeightSpec);
361
362 final int measuredWidth = child.getMeasuredWidth();
363 cellsUsed = measuredWidth / cellSize;
364 if (measuredWidth % cellSize != 0) cellsUsed++;
365 }
366
367 final ActionMenuItemView itemView = child instanceof ActionMenuItemView ?
368 (ActionMenuItemView) child : null;
369 final boolean expandable = !lp.isOverflowButton && itemView != null && itemView.hasText();
370 lp.expandable = expandable;
371
372 lp.cellsUsed = cellsUsed;
373 final int targetWidth = cellsUsed * cellSize;
374 child.measure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
375 childHeightSpec);
376 return cellsUsed;
377 }
378
379 @Override
380 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
381 if (!mFormatItems) {
382 super.onLayout(changed, left, top, right, bottom);
383 return;
384 }
385
386 final int childCount = getChildCount();
387 final int midVertical = (top + bottom) / 2;
388 final int dividerWidth = 0;//getDividerWidth();
389 int overflowWidth = 0;
390 //UNUSED int nonOverflowWidth = 0;
391 int nonOverflowCount = 0;
392 int widthRemaining = right - left - getPaddingRight() - getPaddingLeft();
393 boolean hasOverflow = false;
394 for (int i = 0; i < childCount; i++) {
395 final View v = getChildAt(i);
396 if (v.getVisibility() == GONE) {
397 continue;
398 }
399
400 LayoutParams p = (LayoutParams) v.getLayoutParams();
401 if (p.isOverflowButton) {
402 overflowWidth = v.getMeasuredWidth();
403 if (hasDividerBeforeChildAt(i)) {
404 overflowWidth += dividerWidth;
405 }
406
407 int height = v.getMeasuredHeight();
408 int r = getWidth() - getPaddingRight() - p.rightMargin;
409 int l = r - overflowWidth;
410 int t = midVertical - (height / 2);
411 int b = t + height;
412 v.layout(l, t, r, b);
413
414 widthRemaining -= overflowWidth;
415 hasOverflow = true;
416 } else {
417 final int size = v.getMeasuredWidth() + p.leftMargin + p.rightMargin;
418 //UNUSED nonOverflowWidth += size;
419 widthRemaining -= size;
420 //if (hasDividerBeforeChildAt(i)) {
421 //UNUSED nonOverflowWidth += dividerWidth;
422 //}
423 nonOverflowCount++;
424 }
425 }
426
427 if (childCount == 1 && !hasOverflow) {
428 // Center a single child
429 final View v = getChildAt(0);
430 final int width = v.getMeasuredWidth();
431 final int height = v.getMeasuredHeight();
432 final int midHorizontal = (right - left) / 2;
433 final int l = midHorizontal - width / 2;
434 final int t = midVertical - height / 2;
435 v.layout(l, t, l + width, t + height);
436 return;
437 }
438
439 final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1);
440 final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0);
441
442 int startLeft = getPaddingLeft();
443 for (int i = 0; i < childCount; i++) {
444 final View v = getChildAt(i);
445 final LayoutParams lp = (LayoutParams) v.getLayoutParams();
446 if (v.getVisibility() == GONE || lp.isOverflowButton) {
447 continue;
448 }
449
450 startLeft += lp.leftMargin;
451 int width = v.getMeasuredWidth();
452 int height = v.getMeasuredHeight();
453 int t = midVertical - height / 2;
454 v.layout(startLeft, t, startLeft + width, t + height);
455 startLeft += width + lp.rightMargin + spacerSize;
456 }
457 }
458
459 @Override
460 public void onDetachedFromWindow() {
461 super.onDetachedFromWindow();
462 mPresenter.dismissPopupMenus();
463 }
464
465 public boolean isOverflowReserved() {
466 return mReserveOverflow;
467 }
468
469 public void setOverflowReserved(boolean reserveOverflow) {
470 mReserveOverflow = reserveOverflow;
471 }
472
473 @Override
474 protected LayoutParams generateDefaultLayoutParams() {
475 LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
476 LayoutParams.WRAP_CONTENT);
477 params.gravity = Gravity.CENTER_VERTICAL;
478 return params;
479 }
480
481 @Override
482 public LayoutParams generateLayoutParams(AttributeSet attrs) {
483 return new LayoutParams(getContext(), attrs);
484 }
485
486 @Override
487 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
488 if (p instanceof LayoutParams) {
489 LayoutParams result = new LayoutParams((LayoutParams) p);
490 if (result.gravity <= Gravity.NO_GRAVITY) {
491 result.gravity = Gravity.CENTER_VERTICAL;
492 }
493 return result;
494 }
495 return generateDefaultLayoutParams();
496 }
497
498 @Override
499 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
500 return p != null && p instanceof LayoutParams;
501 }
502
503 public LayoutParams generateOverflowButtonLayoutParams() {
504 LayoutParams result = generateDefaultLayoutParams();
505 result.isOverflowButton = true;
506 return result;
507 }
508
509 public boolean invokeItem(MenuItemImpl item) {
510 return mMenu.performItemAction(item, 0);
511 }
512
513 public int getWindowAnimations() {
514 return 0;
515 }
516
517 public void initialize(MenuBuilder menu) {
518 mMenu = menu;
519 }
520
521 //@Override
522 protected boolean hasDividerBeforeChildAt(int childIndex) {
523 if (childIndex == 0) {
524 return false;
525 }
526 final View childBefore = getChildAt(childIndex - 1);
527 final View child = getChildAt(childIndex);
528 boolean result = false;
529 if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
530 result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
531 }
532 if (childIndex > 0 && child instanceof ActionMenuChildView) {
533 result |= ((ActionMenuChildView) child).needsDividerBefore();
534 }
535 return result;
536 }
537
538 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
539 return false;
540 }
541
542 public interface ActionMenuChildView {
543 public boolean needsDividerBefore();
544 public boolean needsDividerAfter();
545 }
546
547 public static class LayoutParams extends LinearLayout.LayoutParams {
548 public boolean isOverflowButton;
549 public int cellsUsed;
550 public int extraPixels;
551 public boolean expandable;
552 public boolean preventEdgeOffset;
553
554 public boolean expanded;
555
556 public LayoutParams(Context c, AttributeSet attrs) {
557 super(c, attrs);
558 }
559
560 public LayoutParams(LayoutParams other) {
561 super((LinearLayout.LayoutParams) other);
562 isOverflowButton = other.isOverflowButton;
563 }
564
565 public LayoutParams(int width, int height) {
566 super(width, height);
567 isOverflowButton = false;
568 }
569
570 public LayoutParams(int width, int height, boolean isOverflowButton) {
571 super(width, height);
572 this.isOverflowButton = isOverflowButton;
573 }
574 }
575 }