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