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 
     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 
     72     public ActionMenuView(Context context) {
     73         this(context, null);
     74     }
     75 
     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      */
     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      */
    109     public int getPopupTheme() {
    110         return mPopupTheme;
    111     }
    112 
    113     /**
    114      * @param presenter Menu presenter used to display popup menu
    115      * @hide
    116      */
    117     public void setPresenter(ActionMenuPresenter presenter) {
    118         mPresenter = presenter;
    119         mPresenter.setMenuView(this);
    120     }
    121 
    122     @Override
    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 
    136     public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
    137         mOnMenuItemClickListener = listener;
    138     }
    139 
    140     @Override
    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 
    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      */
    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
    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
    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      */
    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
    561     public Drawable getOverflowIcon() {
    562         getMenu();
    563         return mPresenter.getOverflowIcon();
    564     }
    565 
    566     /** @hide */
    567     public boolean isOverflowReserved() {
    568         return mReserveOverflow;
    569     }
    570 
    571     /** @hide */
    572     public void setOverflowReserved(boolean reserveOverflow) {
    573         mReserveOverflow = reserveOverflow;
    574     }
    575 
    576     @Override
    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
    585     public LayoutParams generateLayoutParams(AttributeSet attrs) {
    586         return new LayoutParams(getContext(), attrs);
    587     }
    588 
    589     @Override
    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
    604     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    605         return p != null && p instanceof LayoutParams;
    606     }
    607 
    608     /** @hide */
    609     public LayoutParams generateOverflowButtonLayoutParams() {
    610         LayoutParams result = generateDefaultLayoutParams();
    611         result.isOverflowButton = true;
    612         return result;
    613     }
    614 
    615     /** @hide */
    616     public boolean invokeItem(MenuItemImpl item) {
    617         return mMenu.performItemAction(item, 0);
    618     }
    619 
    620     /** @hide */
    621     public int getWindowAnimations() {
    622         return 0;
    623     }
    624 
    625     /** @hide */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    695     public boolean isOverflowMenuShowing() {
    696         return mPresenter != null && mPresenter.isOverflowMenuShowing();
    697     }
    698 
    699     /** @hide */
    700     public boolean isOverflowMenuShowPending() {
    701         return mPresenter != null && mPresenter.isOverflowMenuShowPending();
    702     }
    703 
    704     /**
    705      * Dismiss any popups associated with this menu view.
    706      */
    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
    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 */
    734     public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
    735         return false;
    736     }
    737 
    738     /** @hide */
    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          */
    755         public boolean onMenuItemClick(MenuItem item);
    756     }
    757 
    758     private class MenuBuilderCallback implements MenuBuilder.Callback {
    759         @Override
    760         public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
    761             return mOnMenuItemClickListener != null &&
    762                     mOnMenuItemClickListener.onMenuItemClick(item);
    763         }
    764 
    765         @Override
    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
    775         public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
    776         }
    777 
    778         @Override
    779         public boolean onOpenSubMenu(MenuBuilder subMenu) {
    780             return false;
    781         }
    782     }
    783 
    784     /** @hide */
    785     public interface ActionMenuChildView {
    786         public boolean needsDividerBefore();
    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 
    814         public LayoutParams(Context c, AttributeSet attrs) {
    815             super(c, attrs);
    816         }
    817 
    818         public LayoutParams(ViewGroup.LayoutParams other) {
    819             super(other);
    820         }
    821 
    822         public LayoutParams(LayoutParams other) {
    823             super((LinearLayout.LayoutParams) other);
    824             isOverflowButton = other.isOverflowButton;
    825         }
    826 
    827         public LayoutParams(int width, int height) {
    828             super(width, height);
    829             isOverflowButton = false;
    830         }
    831 
    832         /** @hide */
    833         public LayoutParams(int width, int height, boolean isOverflowButton) {
    834             super(width, height);
    835             this.isOverflowButton = isOverflowButton;
    836         }
    837 
    838         /** @hide */
    839         @Override
    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