• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.widget;
17 
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 import android.annotation.StyleRes;
21 import android.content.Context;
22 import android.content.res.Configuration;
23 import android.graphics.drawable.Drawable;
24 import android.util.AttributeSet;
25 import android.view.ContextThemeWrapper;
26 import android.view.Gravity;
27 import android.view.Menu;
28 import android.view.MenuItem;
29 import android.view.View;
30 import android.view.ViewDebug;
31 import android.view.ViewGroup;
32 import android.view.ViewHierarchyEncoder;
33 import android.view.accessibility.AccessibilityEvent;
34 
35 import com.android.internal.view.menu.ActionMenuItemView;
36 import com.android.internal.view.menu.MenuBuilder;
37 import com.android.internal.view.menu.MenuItemImpl;
38 import com.android.internal.view.menu.MenuPresenter;
39 import com.android.internal.view.menu.MenuView;
40 
41 /**
42  * ActionMenuView is a presentation of a series of menu options as a View. It provides
43  * several top level options as action buttons while spilling remaining options over as
44  * items in an overflow menu. This allows applications to present packs of actions inline with
45  * specific or repeating content.
46  */
47 public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView {
48     private static final String TAG = "ActionMenuView";
49 
50     static final int MIN_CELL_SIZE = 56; // dips
51     static final int GENERATED_ITEM_PADDING = 4; // dips
52 
53     private MenuBuilder mMenu;
54 
55     /** Context against which to inflate popup menus. */
56     private Context mPopupContext;
57 
58     /** Theme resource against which to inflate popup menus. */
59     private int mPopupTheme;
60 
61     private boolean mReserveOverflow;
62     private ActionMenuPresenter mPresenter;
63     private MenuPresenter.Callback mActionMenuPresenterCallback;
64     private MenuBuilder.Callback mMenuBuilderCallback;
65     private boolean mFormatItems;
66     private int mFormatItemsWidth;
67     private int mMinCellSize;
68     private int mGeneratedItemPadding;
69 
70     private OnMenuItemClickListener mOnMenuItemClickListener;
71 
ActionMenuView(Context context)72     public ActionMenuView(Context context) {
73         this(context, null);
74     }
75 
ActionMenuView(Context context, AttributeSet attrs)76     public ActionMenuView(Context context, AttributeSet attrs) {
77         super(context, attrs);
78         setBaselineAligned(false);
79         final float density = context.getResources().getDisplayMetrics().density;
80         mMinCellSize = (int) (MIN_CELL_SIZE * density);
81         mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
82         mPopupContext = context;
83         mPopupTheme = 0;
84     }
85 
86     /**
87      * Specifies the theme to use when inflating popup menus. By default, uses
88      * the same theme as the action menu view itself.
89      *
90      * @param resId theme used to inflate popup menus
91      * @see #getPopupTheme()
92      */
setPopupTheme(@tyleRes int resId)93     public void setPopupTheme(@StyleRes int resId) {
94         if (mPopupTheme != resId) {
95             mPopupTheme = resId;
96             if (resId == 0) {
97                 mPopupContext = mContext;
98             } else {
99                 mPopupContext = new ContextThemeWrapper(mContext, resId);
100             }
101         }
102     }
103 
104     /**
105      * @return resource identifier of the theme used to inflate popup menus, or
106      *         0 if menus are inflated against the action menu view theme
107      * @see #setPopupTheme(int)
108      */
getPopupTheme()109     public int getPopupTheme() {
110         return mPopupTheme;
111     }
112 
113     /**
114      * @param presenter Menu presenter used to display popup menu
115      * @hide
116      */
setPresenter(ActionMenuPresenter presenter)117     public void setPresenter(ActionMenuPresenter presenter) {
118         mPresenter = presenter;
119         mPresenter.setMenuView(this);
120     }
121 
122     @Override
onConfigurationChanged(Configuration newConfig)123     public void onConfigurationChanged(Configuration newConfig) {
124         super.onConfigurationChanged(newConfig);
125 
126         if (mPresenter != null) {
127             mPresenter.updateMenuView(false);
128 
129             if (mPresenter.isOverflowMenuShowing()) {
130                 mPresenter.hideOverflowMenu();
131                 mPresenter.showOverflowMenu();
132             }
133         }
134     }
135 
setOnMenuItemClickListener(OnMenuItemClickListener listener)136     public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
137         mOnMenuItemClickListener = listener;
138     }
139 
140     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)141     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
142         // If we've been given an exact size to match, apply special formatting during layout.
143         final boolean wasFormatted = mFormatItems;
144         mFormatItems = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
145 
146         if (wasFormatted != mFormatItems) {
147             mFormatItemsWidth = 0; // Reset this when switching modes
148         }
149 
150         // Special formatting can change whether items can fit as action buttons.
151         // Kick the menu and update presenters when this changes.
152         final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
153         if (mFormatItems && mMenu != null && widthSize != mFormatItemsWidth) {
154             mFormatItemsWidth = widthSize;
155             mMenu.onItemsChanged(true);
156         }
157 
158         final int childCount = getChildCount();
159         if (mFormatItems && childCount > 0) {
160             onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec);
161         } else {
162             // Previous measurement at exact format may have set margins - reset them.
163             for (int i = 0; i < childCount; i++) {
164                 final View child = getChildAt(i);
165                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
166                 lp.leftMargin = lp.rightMargin = 0;
167             }
168             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
169         }
170     }
171 
onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec)172     private void onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec) {
173         // We already know the width mode is EXACTLY if we're here.
174         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
175         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
176         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
177 
178         final int widthPadding = getPaddingLeft() + getPaddingRight();
179         final int heightPadding = getPaddingTop() + getPaddingBottom();
180 
181         final int itemHeightSpec = getChildMeasureSpec(heightMeasureSpec, heightPadding,
182                 ViewGroup.LayoutParams.WRAP_CONTENT);
183 
184         widthSize -= widthPadding;
185 
186         // Divide the view into cells.
187         final int cellCount = widthSize / mMinCellSize;
188         final int cellSizeRemaining = widthSize % mMinCellSize;
189 
190         if (cellCount == 0) {
191             // Give up, nothing fits.
192             setMeasuredDimension(widthSize, 0);
193             return;
194         }
195 
196         final int cellSize = mMinCellSize + cellSizeRemaining / cellCount;
197 
198         int cellsRemaining = cellCount;
199         int maxChildHeight = 0;
200         int maxCellsUsed = 0;
201         int expandableItemCount = 0;
202         int visibleItemCount = 0;
203         boolean hasOverflow = false;
204 
205         // This is used as a bitfield to locate the smallest items present. Assumes childCount < 64.
206         long smallestItemsAt = 0;
207 
208         final int childCount = getChildCount();
209         for (int i = 0; i < childCount; i++) {
210             final View child = getChildAt(i);
211             if (child.getVisibility() == GONE) continue;
212 
213             final boolean isGeneratedItem = child instanceof ActionMenuItemView;
214             visibleItemCount++;
215 
216             if (isGeneratedItem) {
217                 // Reset padding for generated menu item views; it may change below
218                 // and views are recycled.
219                 child.setPadding(mGeneratedItemPadding, 0, mGeneratedItemPadding, 0);
220             }
221 
222             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
223             lp.expanded = false;
224             lp.extraPixels = 0;
225             lp.cellsUsed = 0;
226             lp.expandable = false;
227             lp.leftMargin = 0;
228             lp.rightMargin = 0;
229             lp.preventEdgeOffset = isGeneratedItem && ((ActionMenuItemView) child).hasText();
230 
231             // Overflow always gets 1 cell. No more, no less.
232             final int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining;
233 
234             final int cellsUsed = measureChildForCells(child, cellSize, cellsAvailable,
235                     itemHeightSpec, heightPadding);
236 
237             maxCellsUsed = Math.max(maxCellsUsed, cellsUsed);
238             if (lp.expandable) expandableItemCount++;
239             if (lp.isOverflowButton) hasOverflow = true;
240 
241             cellsRemaining -= cellsUsed;
242             maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
243             if (cellsUsed == 1) smallestItemsAt |= (1 << i);
244         }
245 
246         // When we have overflow and a single expanded (text) item, we want to try centering it
247         // visually in the available space even though overflow consumes some of it.
248         final boolean centerSingleExpandedItem = hasOverflow && visibleItemCount == 2;
249 
250         // Divide space for remaining cells if we have items that can expand.
251         // Try distributing whole leftover cells to smaller items first.
252 
253         boolean needsExpansion = false;
254         while (expandableItemCount > 0 && cellsRemaining > 0) {
255             int minCells = Integer.MAX_VALUE;
256             long minCellsAt = 0; // Bit locations are indices of relevant child views
257             int minCellsItemCount = 0;
258             for (int i = 0; i < childCount; i++) {
259                 final View child = getChildAt(i);
260                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
261 
262                 // Don't try to expand items that shouldn't.
263                 if (!lp.expandable) continue;
264 
265                 // Mark indices of children that can receive an extra cell.
266                 if (lp.cellsUsed < minCells) {
267                     minCells = lp.cellsUsed;
268                     minCellsAt = 1 << i;
269                     minCellsItemCount = 1;
270                 } else if (lp.cellsUsed == minCells) {
271                     minCellsAt |= 1 << i;
272                     minCellsItemCount++;
273                 }
274             }
275 
276             // Items that get expanded will always be in the set of smallest items when we're done.
277             smallestItemsAt |= minCellsAt;
278 
279             if (minCellsItemCount > cellsRemaining) break; // Couldn't expand anything evenly. Stop.
280 
281             // We have enough cells, all minimum size items will be incremented.
282             minCells++;
283 
284             for (int i = 0; i < childCount; i++) {
285                 final View child = getChildAt(i);
286                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
287                 if ((minCellsAt & (1 << i)) == 0) {
288                     // If this item is already at our small item count, mark it for later.
289                     if (lp.cellsUsed == minCells) smallestItemsAt |= 1 << i;
290                     continue;
291                 }
292 
293                 if (centerSingleExpandedItem && lp.preventEdgeOffset && cellsRemaining == 1) {
294                     // Add padding to this item such that it centers.
295                     child.setPadding(mGeneratedItemPadding + cellSize, 0, mGeneratedItemPadding, 0);
296                 }
297                 lp.cellsUsed++;
298                 lp.expanded = true;
299                 cellsRemaining--;
300             }
301 
302             needsExpansion = true;
303         }
304 
305         // Divide any space left that wouldn't divide along cell boundaries
306         // evenly among the smallest items
307 
308         final boolean singleItem = !hasOverflow && visibleItemCount == 1;
309         if (cellsRemaining > 0 && smallestItemsAt != 0 &&
310                 (cellsRemaining < visibleItemCount - 1 || singleItem || maxCellsUsed > 1)) {
311             float expandCount = Long.bitCount(smallestItemsAt);
312 
313             if (!singleItem) {
314                 // The items at the far edges may only expand by half in order to pin to either side.
315                 if ((smallestItemsAt & 1) != 0) {
316                     LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams();
317                     if (!lp.preventEdgeOffset) expandCount -= 0.5f;
318                 }
319                 if ((smallestItemsAt & (1 << (childCount - 1))) != 0) {
320                     LayoutParams lp = ((LayoutParams) getChildAt(childCount - 1).getLayoutParams());
321                     if (!lp.preventEdgeOffset) expandCount -= 0.5f;
322                 }
323             }
324 
325             final int extraPixels = expandCount > 0 ?
326                     (int) (cellsRemaining * cellSize / expandCount) : 0;
327 
328             for (int i = 0; i < childCount; i++) {
329                 if ((smallestItemsAt & (1 << i)) == 0) continue;
330 
331                 final View child = getChildAt(i);
332                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
333                 if (child instanceof ActionMenuItemView) {
334                     // If this is one of our views, expand and measure at the larger size.
335                     lp.extraPixels = extraPixels;
336                     lp.expanded = true;
337                     if (i == 0 && !lp.preventEdgeOffset) {
338                         // First item gets part of its new padding pushed out of sight.
339                         // The last item will get this implicitly from layout.
340                         lp.leftMargin = -extraPixels / 2;
341                     }
342                     needsExpansion = true;
343                 } else if (lp.isOverflowButton) {
344                     lp.extraPixels = extraPixels;
345                     lp.expanded = true;
346                     lp.rightMargin = -extraPixels / 2;
347                     needsExpansion = true;
348                 } else {
349                     // If we don't know what it is, give it some margins instead
350                     // and let it center within its space. We still want to pin
351                     // against the edges.
352                     if (i != 0) {
353                         lp.leftMargin = extraPixels / 2;
354                     }
355                     if (i != childCount - 1) {
356                         lp.rightMargin = extraPixels / 2;
357                     }
358                 }
359             }
360 
361             cellsRemaining = 0;
362         }
363 
364         // Remeasure any items that have had extra space allocated to them.
365         if (needsExpansion) {
366             for (int i = 0; i < childCount; i++) {
367                 final View child = getChildAt(i);
368                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
369 
370                 if (!lp.expanded) continue;
371 
372                 final int width = lp.cellsUsed * cellSize + lp.extraPixels;
373                 child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
374                         itemHeightSpec);
375             }
376         }
377 
378         if (heightMode != MeasureSpec.EXACTLY) {
379             heightSize = maxChildHeight;
380         }
381 
382         setMeasuredDimension(widthSize, heightSize);
383     }
384 
385     /**
386      * Measure a child view to fit within cell-based formatting. The child's width
387      * will be measured to a whole multiple of cellSize.
388      *
389      * <p>Sets the expandable and cellsUsed fields of LayoutParams.
390      *
391      * @param child Child to measure
392      * @param cellSize Size of one cell
393      * @param cellsRemaining Number of cells remaining that this view can expand to fill
394      * @param parentHeightMeasureSpec MeasureSpec used by the parent view
395      * @param parentHeightPadding Padding present in the parent view
396      * @return Number of cells this child was measured to occupy
397      */
measureChildForCells(View child, int cellSize, int cellsRemaining, int parentHeightMeasureSpec, int parentHeightPadding)398     static int measureChildForCells(View child, int cellSize, int cellsRemaining,
399             int parentHeightMeasureSpec, int parentHeightPadding) {
400         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
401 
402         final int childHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec) -
403                 parentHeightPadding;
404         final int childHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec);
405         final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode);
406 
407         final ActionMenuItemView itemView = child instanceof ActionMenuItemView ?
408                 (ActionMenuItemView) child : null;
409         final boolean hasText = itemView != null && itemView.hasText();
410 
411         int cellsUsed = 0;
412         if (cellsRemaining > 0 && (!hasText || cellsRemaining >= 2)) {
413             final int childWidthSpec = MeasureSpec.makeMeasureSpec(
414                     cellSize * cellsRemaining, MeasureSpec.AT_MOST);
415             child.measure(childWidthSpec, childHeightSpec);
416 
417             final int measuredWidth = child.getMeasuredWidth();
418             cellsUsed = measuredWidth / cellSize;
419             if (measuredWidth % cellSize != 0) cellsUsed++;
420             if (hasText && cellsUsed < 2) cellsUsed = 2;
421         }
422 
423         final boolean expandable = !lp.isOverflowButton && hasText;
424         lp.expandable = expandable;
425 
426         lp.cellsUsed = cellsUsed;
427         final int targetWidth = cellsUsed * cellSize;
428         child.measure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
429                 childHeightSpec);
430         return cellsUsed;
431     }
432 
433     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)434     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
435         if (!mFormatItems) {
436             super.onLayout(changed, left, top, right, bottom);
437             return;
438         }
439 
440         final int childCount = getChildCount();
441         final int midVertical = (bottom - top) / 2;
442         final int dividerWidth = getDividerWidth();
443         int overflowWidth = 0;
444         int nonOverflowWidth = 0;
445         int nonOverflowCount = 0;
446         int widthRemaining = right - left - getPaddingRight() - getPaddingLeft();
447         boolean hasOverflow = false;
448         final boolean isLayoutRtl = isLayoutRtl();
449         for (int i = 0; i < childCount; i++) {
450             final View v = getChildAt(i);
451             if (v.getVisibility() == GONE) {
452                 continue;
453             }
454 
455             LayoutParams p = (LayoutParams) v.getLayoutParams();
456             if (p.isOverflowButton) {
457                 overflowWidth = v.getMeasuredWidth();
458                 if (hasDividerBeforeChildAt(i)) {
459                     overflowWidth += dividerWidth;
460                 }
461 
462                 int height = v.getMeasuredHeight();
463                 int r;
464                 int l;
465                 if (isLayoutRtl) {
466                     l = getPaddingLeft() + p.leftMargin;
467                     r = l + overflowWidth;
468                 } else {
469                     r = getWidth() - getPaddingRight() - p.rightMargin;
470                     l = r - overflowWidth;
471                 }
472                 int t = midVertical - (height / 2);
473                 int b = t + height;
474                 v.layout(l, t, r, b);
475 
476                 widthRemaining -= overflowWidth;
477                 hasOverflow = true;
478             } else {
479                 final int size = v.getMeasuredWidth() + p.leftMargin + p.rightMargin;
480                 nonOverflowWidth += size;
481                 widthRemaining -= size;
482                 if (hasDividerBeforeChildAt(i)) {
483                     nonOverflowWidth += dividerWidth;
484                 }
485                 nonOverflowCount++;
486             }
487         }
488 
489         if (childCount == 1 && !hasOverflow) {
490             // Center a single child
491             final View v = getChildAt(0);
492             final int width = v.getMeasuredWidth();
493             final int height = v.getMeasuredHeight();
494             final int midHorizontal = (right - left) / 2;
495             final int l = midHorizontal - width / 2;
496             final int t = midVertical - height / 2;
497             v.layout(l, t, l + width, t + height);
498             return;
499         }
500 
501         final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1);
502         final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0);
503 
504         if (isLayoutRtl) {
505             int startRight = getWidth() - getPaddingRight();
506             for (int i = 0; i < childCount; i++) {
507                 final View v = getChildAt(i);
508                 final LayoutParams lp = (LayoutParams) v.getLayoutParams();
509                 if (v.getVisibility() == GONE || lp.isOverflowButton) {
510                     continue;
511                 }
512 
513                 startRight -= lp.rightMargin;
514                 int width = v.getMeasuredWidth();
515                 int height = v.getMeasuredHeight();
516                 int t = midVertical - height / 2;
517                 v.layout(startRight - width, t, startRight, t + height);
518                 startRight -= width + lp.leftMargin + spacerSize;
519             }
520         } else {
521             int startLeft = getPaddingLeft();
522             for (int i = 0; i < childCount; i++) {
523                 final View v = getChildAt(i);
524                 final LayoutParams lp = (LayoutParams) v.getLayoutParams();
525                 if (v.getVisibility() == GONE || lp.isOverflowButton) {
526                     continue;
527                 }
528 
529                 startLeft += lp.leftMargin;
530                 int width = v.getMeasuredWidth();
531                 int height = v.getMeasuredHeight();
532                 int t = midVertical - height / 2;
533                 v.layout(startLeft, t, startLeft + width, t + height);
534                 startLeft += width + lp.rightMargin + spacerSize;
535             }
536         }
537     }
538 
539     @Override
onDetachedFromWindow()540     public void onDetachedFromWindow() {
541         super.onDetachedFromWindow();
542         dismissPopupMenus();
543     }
544 
545     /**
546      * Set the icon to use for the overflow button.
547      *
548      * @param icon Drawable to set, may be null to clear the icon
549      */
setOverflowIcon(@ullable Drawable icon)550     public void setOverflowIcon(@Nullable Drawable icon) {
551         getMenu();
552         mPresenter.setOverflowIcon(icon);
553     }
554 
555     /**
556      * Return the current drawable used as the overflow icon.
557      *
558      * @return The overflow icon drawable
559      */
560     @Nullable
getOverflowIcon()561     public Drawable getOverflowIcon() {
562         getMenu();
563         return mPresenter.getOverflowIcon();
564     }
565 
566     /** @hide */
isOverflowReserved()567     public boolean isOverflowReserved() {
568         return mReserveOverflow;
569     }
570 
571     /** @hide */
setOverflowReserved(boolean reserveOverflow)572     public void setOverflowReserved(boolean reserveOverflow) {
573         mReserveOverflow = reserveOverflow;
574     }
575 
576     @Override
generateDefaultLayoutParams()577     protected LayoutParams generateDefaultLayoutParams() {
578         LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
579                 LayoutParams.WRAP_CONTENT);
580         params.gravity = Gravity.CENTER_VERTICAL;
581         return params;
582     }
583 
584     @Override
generateLayoutParams(AttributeSet attrs)585     public LayoutParams generateLayoutParams(AttributeSet attrs) {
586         return new LayoutParams(getContext(), attrs);
587     }
588 
589     @Override
generateLayoutParams(ViewGroup.LayoutParams p)590     protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
591         if (p != null) {
592             final LayoutParams result = p instanceof LayoutParams
593                     ? new LayoutParams((LayoutParams) p)
594                     : new LayoutParams(p);
595             if (result.gravity <= Gravity.NO_GRAVITY) {
596                 result.gravity = Gravity.CENTER_VERTICAL;
597             }
598             return result;
599         }
600         return generateDefaultLayoutParams();
601     }
602 
603     @Override
checkLayoutParams(ViewGroup.LayoutParams p)604     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
605         return p != null && p instanceof LayoutParams;
606     }
607 
608     /** @hide */
generateOverflowButtonLayoutParams()609     public LayoutParams generateOverflowButtonLayoutParams() {
610         LayoutParams result = generateDefaultLayoutParams();
611         result.isOverflowButton = true;
612         return result;
613     }
614 
615     /** @hide */
invokeItem(MenuItemImpl item)616     public boolean invokeItem(MenuItemImpl item) {
617         return mMenu.performItemAction(item, 0);
618     }
619 
620     /** @hide */
getWindowAnimations()621     public int getWindowAnimations() {
622         return 0;
623     }
624 
625     /** @hide */
initialize(@ullable MenuBuilder menu)626     public void initialize(@Nullable MenuBuilder menu) {
627         mMenu = menu;
628     }
629 
630     /**
631      * Returns the Menu object that this ActionMenuView is currently presenting.
632      *
633      * <p>Applications should use this method to obtain the ActionMenuView's Menu object
634      * and inflate or add content to it as necessary.</p>
635      *
636      * @return the Menu presented by this view
637      */
getMenu()638     public Menu getMenu() {
639         if (mMenu == null) {
640             final Context context = getContext();
641             mMenu = new MenuBuilder(context);
642             mMenu.setCallback(new MenuBuilderCallback());
643             mPresenter = new ActionMenuPresenter(context);
644             mPresenter.setReserveOverflow(true);
645             mPresenter.setCallback(mActionMenuPresenterCallback != null
646                     ? mActionMenuPresenterCallback : new ActionMenuPresenterCallback());
647             mMenu.addMenuPresenter(mPresenter, mPopupContext);
648             mPresenter.setMenuView(this);
649         }
650 
651         return mMenu;
652     }
653 
654     /**
655      * Must be called before the first call to getMenu()
656      * @hide
657      */
setMenuCallbacks(MenuPresenter.Callback pcb, MenuBuilder.Callback mcb)658     public void setMenuCallbacks(MenuPresenter.Callback pcb, MenuBuilder.Callback mcb) {
659         mActionMenuPresenterCallback = pcb;
660         mMenuBuilderCallback = mcb;
661     }
662 
663     /**
664      * Returns the current menu or null if one has not yet been configured.
665      * @hide Internal use only for action bar integration
666      */
peekMenu()667     public MenuBuilder peekMenu() {
668         return mMenu;
669     }
670 
671     /**
672      * Show the overflow items from the associated menu.
673      *
674      * @return true if the menu was able to be shown, false otherwise
675      */
showOverflowMenu()676     public boolean showOverflowMenu() {
677         return mPresenter != null && mPresenter.showOverflowMenu();
678     }
679 
680     /**
681      * Hide the overflow items from the associated menu.
682      *
683      * @return true if the menu was able to be hidden, false otherwise
684      */
hideOverflowMenu()685     public boolean hideOverflowMenu() {
686         return mPresenter != null && mPresenter.hideOverflowMenu();
687     }
688 
689     /**
690      * Check whether the overflow menu is currently showing. This may not reflect
691      * a pending show operation in progress.
692      *
693      * @return true if the overflow menu is currently showing
694      */
isOverflowMenuShowing()695     public boolean isOverflowMenuShowing() {
696         return mPresenter != null && mPresenter.isOverflowMenuShowing();
697     }
698 
699     /** @hide */
isOverflowMenuShowPending()700     public boolean isOverflowMenuShowPending() {
701         return mPresenter != null && mPresenter.isOverflowMenuShowPending();
702     }
703 
704     /**
705      * Dismiss any popups associated with this menu view.
706      */
dismissPopupMenus()707     public void dismissPopupMenus() {
708         if (mPresenter != null) {
709             mPresenter.dismissPopupMenus();
710         }
711     }
712 
713     /**
714      * @hide Private LinearLayout (superclass) API. Un-hide if LinearLayout API is made public.
715      */
716     @Override
hasDividerBeforeChildAt(int childIndex)717     protected boolean hasDividerBeforeChildAt(int childIndex) {
718         if (childIndex == 0) {
719             return false;
720         }
721         final View childBefore = getChildAt(childIndex - 1);
722         final View child = getChildAt(childIndex);
723         boolean result = false;
724         if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
725             result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
726         }
727         if (childIndex > 0 && child instanceof ActionMenuChildView) {
728             result |= ((ActionMenuChildView) child).needsDividerBefore();
729         }
730         return result;
731     }
732 
733     /** @hide */
dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event)734     public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
735         return false;
736     }
737 
738     /** @hide */
setExpandedActionViewsExclusive(boolean exclusive)739     public void setExpandedActionViewsExclusive(boolean exclusive) {
740         mPresenter.setExpandedActionViewsExclusive(exclusive);
741     }
742 
743     /**
744      * Interface responsible for receiving menu item click events if the items themselves
745      * do not have individual item click listeners.
746      */
747     public interface OnMenuItemClickListener {
748         /**
749          * This method will be invoked when a menu item is clicked if the item itself did
750          * not already handle the event.
751          *
752          * @param item {@link MenuItem} that was clicked
753          * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
754          */
onMenuItemClick(MenuItem item)755         public boolean onMenuItemClick(MenuItem item);
756     }
757 
758     private class MenuBuilderCallback implements MenuBuilder.Callback {
759         @Override
onMenuItemSelected(MenuBuilder menu, MenuItem item)760         public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
761             return mOnMenuItemClickListener != null &&
762                     mOnMenuItemClickListener.onMenuItemClick(item);
763         }
764 
765         @Override
onMenuModeChange(MenuBuilder menu)766         public void onMenuModeChange(MenuBuilder menu) {
767             if (mMenuBuilderCallback != null) {
768                 mMenuBuilderCallback.onMenuModeChange(menu);
769             }
770         }
771     }
772 
773     private class ActionMenuPresenterCallback implements ActionMenuPresenter.Callback {
774         @Override
onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing)775         public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
776         }
777 
778         @Override
onOpenSubMenu(MenuBuilder subMenu)779         public boolean onOpenSubMenu(MenuBuilder subMenu) {
780             return false;
781         }
782     }
783 
784     /** @hide */
785     public interface ActionMenuChildView {
needsDividerBefore()786         public boolean needsDividerBefore();
needsDividerAfter()787         public boolean needsDividerAfter();
788     }
789 
790     public static class LayoutParams extends LinearLayout.LayoutParams {
791         /** @hide */
792         @ViewDebug.ExportedProperty(category = "layout")
793         public boolean isOverflowButton;
794 
795         /** @hide */
796         @ViewDebug.ExportedProperty(category = "layout")
797         public int cellsUsed;
798 
799         /** @hide */
800         @ViewDebug.ExportedProperty(category = "layout")
801         public int extraPixels;
802 
803         /** @hide */
804         @ViewDebug.ExportedProperty(category = "layout")
805         public boolean expandable;
806 
807         /** @hide */
808         @ViewDebug.ExportedProperty(category = "layout")
809         public boolean preventEdgeOffset;
810 
811         /** @hide */
812         public boolean expanded;
813 
LayoutParams(Context c, AttributeSet attrs)814         public LayoutParams(Context c, AttributeSet attrs) {
815             super(c, attrs);
816         }
817 
LayoutParams(ViewGroup.LayoutParams other)818         public LayoutParams(ViewGroup.LayoutParams other) {
819             super(other);
820         }
821 
LayoutParams(LayoutParams other)822         public LayoutParams(LayoutParams other) {
823             super((LinearLayout.LayoutParams) other);
824             isOverflowButton = other.isOverflowButton;
825         }
826 
LayoutParams(int width, int height)827         public LayoutParams(int width, int height) {
828             super(width, height);
829             isOverflowButton = false;
830         }
831 
832         /** @hide */
LayoutParams(int width, int height, boolean isOverflowButton)833         public LayoutParams(int width, int height, boolean isOverflowButton) {
834             super(width, height);
835             this.isOverflowButton = isOverflowButton;
836         }
837 
838         /** @hide */
839         @Override
encodeProperties(@onNull ViewHierarchyEncoder encoder)840         protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
841             super.encodeProperties(encoder);
842 
843             encoder.addProperty("layout:overFlowButton", isOverflowButton);
844             encoder.addProperty("layout:cellsUsed", cellsUsed);
845             encoder.addProperty("layout:extraPixels", extraPixels);
846             encoder.addProperty("layout:expandable", expandable);
847             encoder.addProperty("layout:preventEdgeOffset", preventEdgeOffset);
848         }
849     }
850 }
851