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