2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com
.actionbarsherlock
.internal
.view
.menu
;
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
;
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
;
37 static final int MIN_CELL_SIZE
= 56; // dips
38 static final int GENERATED_ITEM_PADDING
= 4; // dips
40 private MenuBuilder mMenu
;
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;
50 private boolean mFirst
= true
;
52 public ActionMenuView(Context context
) {
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
);
64 public void setPresenter(ActionMenuPresenter presenter
) {
65 mPresenter
= presenter
;
68 public boolean isExpandedFormat() {
73 public void onConfigurationChanged(Configuration newConfig
) {
75 super.onConfigurationChanged(newConfig
);
77 mPresenter
.updateMenuView(false
);
79 if (mPresenter
!= null
&& mPresenter
.isOverflowMenuShowing()) {
80 mPresenter
.hideOverflowMenu();
81 mPresenter
.showOverflowMenu();
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
) {
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
;
104 if (wasFormatted
!= mFormatItems
) {
105 mFormatItemsWidth
= 0; // Reset this when switching modes
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
);
117 onMeasureExactFormat(widthMeasureSpec
, heightMeasureSpec
);
119 super.onMeasure(widthMeasureSpec
, heightMeasureSpec
);
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
);
129 final int widthPadding
= getPaddingLeft() + getPaddingRight();
130 final int heightPadding
= getPaddingTop() + getPaddingBottom();
132 widthSize
-= widthPadding
;
134 // Divide the view into cells.
135 final int cellCount
= widthSize
/ mMinCellSize
;
136 final int cellSizeRemaining
= widthSize
% mMinCellSize
;
138 if (cellCount
== 0) {
139 // Give up, nothing fits.
140 setMeasuredDimension(widthSize
, 0);
144 final int cellSize
= mMinCellSize
+ cellSizeRemaining
/ cellCount
;
146 int cellsRemaining
= cellCount
;
147 int maxChildHeight
= 0;
148 int maxCellsUsed
= 0;
149 int expandableItemCount
= 0;
150 int visibleItemCount
= 0;
151 boolean hasOverflow
= false
;
153 // This is used as a bitfield to locate the smallest items present. Assumes childCount < 64.
154 long smallestItemsAt
= 0;
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;
161 final boolean isGeneratedItem
= child
instanceof ActionMenuItemView
;
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);
170 final LayoutParams lp
= (LayoutParams
) child
.getLayoutParams();
174 lp
.expandable
= false
;
177 lp
.preventEdgeOffset
= isGeneratedItem
&& ((ActionMenuItemView
) child
).hasText();
179 // Overflow always gets 1 cell. No more, no less.
180 final int cellsAvailable
= lp
.isOverflowButton ?
1 : cellsRemaining
;
182 final int cellsUsed
= measureChildForCells(child
, cellSize
, cellsAvailable
,
183 heightMeasureSpec
, heightPadding
);
185 maxCellsUsed
= Math
.max(maxCellsUsed
, cellsUsed
);
186 if (lp
.expandable
) expandableItemCount
++;
187 if (lp
.isOverflowButton
) hasOverflow
= true
;
189 cellsRemaining
-= cellsUsed
;
190 maxChildHeight
= Math
.max(maxChildHeight
, child
.getMeasuredHeight());
191 if (cellsUsed
== 1) smallestItemsAt
|= (1 << i
);
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;
198 // Divide space for remaining cells if we have items that can expand.
199 // Try distributing whole leftover cells to smaller items first.
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();
210 // Don't try to expand items that shouldn't.
211 if (!lp
.expandable
) continue;
213 // Mark indices of children that can receive an extra cell.
214 if (lp
.cellsUsed
< minCells
) {
215 minCells
= lp
.cellsUsed
;
217 minCellsItemCount
= 1;
218 } else if (lp
.cellsUsed
== minCells
) {
219 minCellsAt
|= 1 << i
;
224 // Items that get expanded will always be in the set of smallest items when we're done.
225 smallestItemsAt
|= minCellsAt
;
227 if (minCellsItemCount
> cellsRemaining
) break; // Couldn't expand anything evenly. Stop.
229 // We have enough cells, all minimum size items will be incremented.
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
;
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);
250 needsExpansion
= true
;
253 // Divide any space left that wouldn't divide along cell boundaries
254 // evenly among the smallest items
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
);
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
;
267 if ((smallestItemsAt
& (1 << (childCount
- 1))) != 0) {
268 LayoutParams lp
= ((LayoutParams
) getChildAt(childCount
- 1).getLayoutParams());
269 if (!lp
.preventEdgeOffset
) expandCount
-= 0.5f
;
273 final int extraPixels
= expandCount
> 0 ?
274 (int) (cellsRemaining
* cellSize
/ expandCount
) : 0;
276 for (int i
= 0; i
< childCount
; i
++) {
277 if ((smallestItemsAt
& (1 << i
)) == 0) continue;
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
;
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;
290 needsExpansion
= true
;
291 } else if (lp
.isOverflowButton
) {
292 lp
.extraPixels
= extraPixels
;
294 lp
.rightMargin
= -extraPixels
/ 2;
295 needsExpansion
= true
;
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.
301 lp
.leftMargin
= extraPixels
/ 2;
303 if (i
!= childCount
- 1) {
304 lp
.rightMargin
= extraPixels
/ 2;
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();
319 if (!lp
.expanded
) continue;
321 final int width
= lp
.cellsUsed
* cellSize
+ lp
.extraPixels
;
322 child
.measure(MeasureSpec
.makeMeasureSpec(width
, MeasureSpec
.EXACTLY
), heightSpec
);
326 if (heightMode
!= MeasureSpec
.EXACTLY
) {
327 heightSize
= maxChildHeight
;
330 setMeasuredDimension(widthSize
, heightSize
);
331 //UNUSED mMeasuredExtraWidth = cellsRemaining * cellSize;
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.
338 * <p>Sets the expandable and cellsUsed fields of LayoutParams.
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
347 static int measureChildForCells(View child
, int cellSize
, int cellsRemaining
,
348 int parentHeightMeasureSpec
, int parentHeightPadding
) {
349 final LayoutParams lp
= (LayoutParams
) child
.getLayoutParams();
351 final int childHeightSize
= MeasureSpec
.getSize(parentHeightMeasureSpec
) -
353 final int childHeightMode
= MeasureSpec
.getMode(parentHeightMeasureSpec
);
354 final int childHeightSpec
= MeasureSpec
.makeMeasureSpec(childHeightSize
, childHeightMode
);
357 if (cellsRemaining
> 0) {
358 final int childWidthSpec
= MeasureSpec
.makeMeasureSpec(
359 cellSize
* cellsRemaining
, MeasureSpec
.AT_MOST
);
360 child
.measure(childWidthSpec
, childHeightSpec
);
362 final int measuredWidth
= child
.getMeasuredWidth();
363 cellsUsed
= measuredWidth
/ cellSize
;
364 if (measuredWidth
% cellSize
!= 0) cellsUsed
++;
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
;
372 lp
.cellsUsed
= cellsUsed
;
373 final int targetWidth
= cellsUsed
* cellSize
;
374 child
.measure(MeasureSpec
.makeMeasureSpec(targetWidth
, MeasureSpec
.EXACTLY
),
380 protected void onLayout(boolean changed
, int left
, int top
, int right
, int bottom
) {
382 super.onLayout(changed
, left
, top
, right
, bottom
);
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
) {
400 LayoutParams p
= (LayoutParams
) v
.getLayoutParams();
401 if (p
.isOverflowButton
) {
402 overflowWidth
= v
.getMeasuredWidth();
403 if (hasDividerBeforeChildAt(i
)) {
404 overflowWidth
+= dividerWidth
;
407 int height
= v
.getMeasuredHeight();
408 int r
= getWidth() - getPaddingRight() - p
.rightMargin
;
409 int l
= r
- overflowWidth
;
410 int t
= midVertical
- (height
/ 2);
412 v
.layout(l
, t
, r
, b
);
414 widthRemaining
-= overflowWidth
;
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;
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
);
439 final int spacerCount
= nonOverflowCount
- (hasOverflow ?
0 : 1);
440 final int spacerSize
= Math
.max(0, spacerCount
> 0 ? widthRemaining
/ spacerCount
: 0);
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
) {
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
;
460 public void onDetachedFromWindow() {
461 super.onDetachedFromWindow();
462 mPresenter
.dismissPopupMenus();
465 public boolean isOverflowReserved() {
466 return mReserveOverflow
;
469 public void setOverflowReserved(boolean reserveOverflow
) {
470 mReserveOverflow
= reserveOverflow
;
474 protected LayoutParams
generateDefaultLayoutParams() {
475 LayoutParams params
= new LayoutParams(LayoutParams
.WRAP_CONTENT
,
476 LayoutParams
.WRAP_CONTENT
);
477 params
.gravity
= Gravity
.CENTER_VERTICAL
;
482 public LayoutParams
generateLayoutParams(AttributeSet attrs
) {
483 return new LayoutParams(getContext(), attrs
);
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
;
495 return generateDefaultLayoutParams();
499 protected boolean checkLayoutParams(ViewGroup
.LayoutParams p
) {
500 return p
!= null
&& p
instanceof LayoutParams
;
503 public LayoutParams
generateOverflowButtonLayoutParams() {
504 LayoutParams result
= generateDefaultLayoutParams();
505 result
.isOverflowButton
= true
;
509 public boolean invokeItem(MenuItemImpl item
) {
510 return mMenu
.performItemAction(item
, 0);
513 public int getWindowAnimations() {
517 public void initialize(MenuBuilder menu
) {
522 protected boolean hasDividerBeforeChildAt(int childIndex
) {
523 if (childIndex
== 0) {
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();
532 if (childIndex
> 0 && child
instanceof ActionMenuChildView
) {
533 result
|= ((ActionMenuChildView
) child
).needsDividerBefore();
538 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event
) {
542 public interface ActionMenuChildView
{
543 public boolean needsDividerBefore();
544 public boolean needsDividerAfter();
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
;
554 public boolean expanded
;
556 public LayoutParams(Context c
, AttributeSet attrs
) {
560 public LayoutParams(LayoutParams other
) {
561 super((LinearLayout
.LayoutParams
) other
);
562 isOverflowButton
= other
.isOverflowButton
;
565 public LayoutParams(int width
, int height
) {
566 super(width
, height
);
567 isOverflowButton
= false
;
570 public LayoutParams(int width
, int height
, boolean isOverflowButton
) {
571 super(width
, height
);
572 this.isOverflowButton
= isOverflowButton
;