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