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