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