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