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