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