IconMenuView.java revision 189ee18d6c6483ad63cc864267328259e2e00b95
1/* 2 * Copyright (C) 2006 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 17package com.android.internal.view.menu; 18 19import com.android.internal.view.menu.MenuBuilder.ItemInvoker; 20 21import android.content.Context; 22import android.content.res.Resources; 23import android.content.res.TypedArray; 24import android.graphics.Canvas; 25import android.graphics.Rect; 26import android.graphics.drawable.Drawable; 27import android.os.Parcel; 28import android.os.Parcelable; 29import android.util.AttributeSet; 30import android.view.KeyEvent; 31import android.view.View; 32import android.view.ViewConfiguration; 33import android.view.ViewGroup; 34import android.view.LayoutInflater; 35 36import java.util.ArrayList; 37 38/** 39 * The icon menu view is an icon-based menu usually with a subset of all the menu items. 40 * It is opened as the default menu, and shows either the first five or all six of the menu items 41 * with text and icon. In the situation of there being more than six items, the first five items 42 * will be accompanied with a 'More' button that opens an {@link ExpandedMenuView} which lists 43 * all the menu items. 44 * 45 * @attr ref android.R.styleable#IconMenuView_rowHeight 46 * @attr ref android.R.styleable#IconMenuView_maxRows 47 * @attr ref android.R.styleable#IconMenuView_maxItemsPerRow 48 * 49 * @hide 50 */ 51public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuView, Runnable { 52 private static final int ITEM_CAPTION_CYCLE_DELAY = 1000; 53 54 private MenuBuilder mMenu; 55 56 /** Height of each row */ 57 private int mRowHeight; 58 /** Maximum number of rows to be shown */ 59 private int mMaxRows; 60 /** Maximum number of items to show in the icon menu. */ 61 private int mMaxItems; 62 /** Maximum number of items per row */ 63 private int mMaxItemsPerRow; 64 /** Actual number of items (the 'More' view does not count as an item) shown */ 65 private int mNumActualItemsShown; 66 67 /** Divider that is drawn between all rows */ 68 private Drawable mHorizontalDivider; 69 /** Height of the horizontal divider */ 70 private int mHorizontalDividerHeight; 71 /** Set of horizontal divider positions where the horizontal divider will be drawn */ 72 private ArrayList<Rect> mHorizontalDividerRects; 73 74 /** Divider that is drawn between all columns */ 75 private Drawable mVerticalDivider; 76 /** Width of the vertical divider */ 77 private int mVerticalDividerWidth; 78 /** Set of vertical divider positions where the vertical divider will be drawn */ 79 private ArrayList<Rect> mVerticalDividerRects; 80 81 /** Icon for the 'More' button */ 82 private Drawable mMoreIcon; 83 84 /** Item view for the 'More' button */ 85 private IconMenuItemView mMoreItemView; 86 87 /** Background of each item (should contain the selected and focused states) */ 88 private Drawable mItemBackground; 89 90 /** Default animations for this menu */ 91 private int mAnimations; 92 93 /** 94 * Whether this IconMenuView has stale children and needs to update them. 95 * Set true by {@link #markStaleChildren()} and reset to false by 96 * {@link #onMeasure(int, int)} 97 */ 98 private boolean mHasStaleChildren; 99 100 /** 101 * Longpress on MENU (while this is shown) switches to shortcut caption 102 * mode. When the user releases the longpress, we do not want to pass the 103 * key-up event up since that will dismiss the menu. 104 */ 105 private boolean mMenuBeingLongpressed = false; 106 107 /** 108 * While {@link #mMenuBeingLongpressed}, we toggle the children's caption 109 * mode between each's title and its shortcut. This is the last caption mode 110 * we broadcasted to children. 111 */ 112 private boolean mLastChildrenCaptionMode; 113 114 /** 115 * The layout to use for menu items. Each index is the row number (0 is the 116 * top-most). Each value contains the number of items in that row. 117 * <p> 118 * The length of this array should not be used to get the number of rows in 119 * the current layout, instead use {@link #mLayoutNumRows}. 120 */ 121 private int[] mLayout; 122 123 /** 124 * The number of rows in the current layout. 125 */ 126 private int mLayoutNumRows; 127 128 /** 129 * Instantiates the IconMenuView that is linked with the provided MenuBuilder. 130 */ 131 public IconMenuView(Context context, AttributeSet attrs) { 132 super(context, attrs); 133 134 TypedArray a = 135 context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.IconMenuView, 0, 0); 136 mRowHeight = a.getDimensionPixelSize(com.android.internal.R.styleable.IconMenuView_rowHeight, 64); 137 mMaxRows = a.getInt(com.android.internal.R.styleable.IconMenuView_maxRows, 2); 138 mMaxItems = a.getInt(com.android.internal.R.styleable.IconMenuView_maxItems, 6); 139 mMaxItemsPerRow = a.getInt(com.android.internal.R.styleable.IconMenuView_maxItemsPerRow, 3); 140 mMoreIcon = a.getDrawable(com.android.internal.R.styleable.IconMenuView_moreIcon); 141 a.recycle(); 142 143 a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.MenuView, 0, 0); 144 mItemBackground = a.getDrawable(com.android.internal.R.styleable.MenuView_itemBackground); 145 mHorizontalDivider = a.getDrawable(com.android.internal.R.styleable.MenuView_horizontalDivider); 146 mHorizontalDividerRects = new ArrayList<Rect>(); 147 mVerticalDivider = a.getDrawable(com.android.internal.R.styleable.MenuView_verticalDivider); 148 mVerticalDividerRects = new ArrayList<Rect>(); 149 mAnimations = a.getResourceId(com.android.internal.R.styleable.MenuView_windowAnimationStyle, 0); 150 a.recycle(); 151 152 if (mHorizontalDivider != null) { 153 mHorizontalDividerHeight = mHorizontalDivider.getIntrinsicHeight(); 154 // Make sure to have some height for the divider 155 if (mHorizontalDividerHeight == -1) mHorizontalDividerHeight = 1; 156 } 157 158 if (mVerticalDivider != null) { 159 mVerticalDividerWidth = mVerticalDivider.getIntrinsicWidth(); 160 // Make sure to have some width for the divider 161 if (mVerticalDividerWidth == -1) mVerticalDividerWidth = 1; 162 } 163 164 mLayout = new int[mMaxRows]; 165 166 // This view will be drawing the dividers 167 setWillNotDraw(false); 168 169 // This is so we'll receive the MENU key in touch mode 170 setFocusableInTouchMode(true); 171 // This is so our children can still be arrow-key focused 172 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 173 } 174 175 /** 176 * Figures out the layout for the menu items. 177 * 178 * @param width The available width for the icon menu. 179 */ 180 private void layoutItems(int width) { 181 int numItems = getChildCount(); 182 if (numItems == 0) { 183 mLayoutNumRows = 0; 184 return; 185 } 186 187 // Start with the least possible number of rows 188 int curNumRows = 189 Math.min((int) Math.ceil(numItems / (float) mMaxItemsPerRow), mMaxRows); 190 191 /* 192 * Increase the number of rows until we find a configuration that fits 193 * all of the items' titles. Worst case, we use mMaxRows. 194 */ 195 for (; curNumRows <= mMaxRows; curNumRows++) { 196 layoutItemsUsingGravity(curNumRows, numItems); 197 198 if (curNumRows >= numItems) { 199 // Can't have more rows than items 200 break; 201 } 202 203 if (doItemsFit()) { 204 // All the items fit, so this is a good configuration 205 break; 206 } 207 } 208 } 209 210 /** 211 * Figures out the layout for the menu items by equally distributing, and 212 * adding any excess items equally to lower rows. 213 * 214 * @param numRows The total number of rows for the menu view 215 * @param numItems The total number of items (across all rows) contained in 216 * the menu view 217 * @return int[] Where the value of index i contains the number of items for row i 218 */ 219 private void layoutItemsUsingGravity(int numRows, int numItems) { 220 int numBaseItemsPerRow = numItems / numRows; 221 int numLeftoverItems = numItems % numRows; 222 /** 223 * The bottom rows will each get a leftover item. Rows (indexed at 0) 224 * that are >= this get a leftover item. Note: if there are 0 leftover 225 * items, no rows will get them since this value will be greater than 226 * the last row. 227 */ 228 int rowsThatGetALeftoverItem = numRows - numLeftoverItems; 229 230 int[] layout = mLayout; 231 for (int i = 0; i < numRows; i++) { 232 layout[i] = numBaseItemsPerRow; 233 234 // Fill the bottom rows with a leftover item each 235 if (i >= rowsThatGetALeftoverItem) { 236 layout[i]++; 237 } 238 } 239 240 mLayoutNumRows = numRows; 241 } 242 243 /** 244 * Checks whether each item's title is fully visible using the current 245 * layout. 246 * 247 * @return True if the items fit (each item's text is fully visible), false 248 * otherwise. 249 */ 250 private boolean doItemsFit() { 251 int itemPos = 0; 252 253 int[] layout = mLayout; 254 int numRows = mLayoutNumRows; 255 for (int row = 0; row < numRows; row++) { 256 int numItemsOnRow = layout[row]; 257 258 /* 259 * If there is only one item on this row, increasing the 260 * number of rows won't help. 261 */ 262 if (numItemsOnRow == 1) { 263 itemPos++; 264 continue; 265 } 266 267 for (int itemsOnRowCounter = numItemsOnRow; itemsOnRowCounter > 0; 268 itemsOnRowCounter--) { 269 View child = getChildAt(itemPos++); 270 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 271 if (lp.maxNumItemsOnRow < numItemsOnRow) { 272 return false; 273 } 274 } 275 } 276 277 return true; 278 } 279 280 /** 281 * Adds an IconMenuItemView to this icon menu view. 282 * @param itemView The item's view to add 283 */ 284 private void addItemView(IconMenuItemView itemView) { 285 // Set ourselves on the item view 286 itemView.setIconMenuView(this); 287 288 // Apply the background to the item view 289 itemView.setBackgroundDrawable( 290 mItemBackground.getConstantState().newDrawable( 291 getContext().getResources())); 292 293 // This class is the invoker for all its item views 294 itemView.setItemInvoker(this); 295 296 addView(itemView, itemView.getTextAppropriateLayoutParams()); 297 } 298 299 /** 300 * Creates the item view for the 'More' button which is used to switch to 301 * the expanded menu view. This button is a special case since it does not 302 * have a MenuItemData backing it. 303 * @return The IconMenuItemView for the 'More' button 304 */ 305 private IconMenuItemView createMoreItemView() { 306 LayoutInflater inflater = mMenu.getMenuType(MenuBuilder.TYPE_ICON).getInflater(); 307 308 final IconMenuItemView itemView = (IconMenuItemView) inflater.inflate( 309 com.android.internal.R.layout.icon_menu_item_layout, null); 310 311 Resources r = getContext().getResources(); 312 itemView.initialize(r.getText(com.android.internal.R.string.more_item_label), mMoreIcon); 313 314 // Set up a click listener on the view since there will be no invocation sequence 315 // due to the lack of a MenuItemData this view 316 itemView.setOnClickListener(new OnClickListener() { 317 public void onClick(View v) { 318 // Switches the menu to expanded mode 319 MenuBuilder.Callback cb = mMenu.getCallback(); 320 if (cb != null) { 321 // Call callback 322 cb.onMenuModeChange(mMenu); 323 } 324 } 325 }); 326 327 return itemView; 328 } 329 330 331 public void initialize(MenuBuilder menu, int menuType) { 332 mMenu = menu; 333 updateChildren(true); 334 } 335 336 public void updateChildren(boolean cleared) { 337 // This method does a clear refresh of children 338 removeAllViews(); 339 340 // IconMenuView never wants content sorted for an overflow action button, since 341 // it is never used in the presence of an overflow button. 342 final ArrayList<MenuItemImpl> itemsToShow = mMenu.getNonActionItems(false); 343 final int numItems = itemsToShow.size(); 344 final int numItemsThatCanFit = mMaxItems; 345 // Minimum of the num that can fit and the num that we have 346 final int minFitMinus1AndNumItems = Math.min(numItemsThatCanFit - 1, numItems); 347 348 MenuItemImpl itemData; 349 // Traverse through all but the last item that can fit since that last item can either 350 // be a 'More' button or a sixth item 351 for (int i = 0; i < minFitMinus1AndNumItems; i++) { 352 itemData = itemsToShow.get(i); 353 addItemView((IconMenuItemView) itemData.getItemView(MenuBuilder.TYPE_ICON, this)); 354 } 355 356 if (numItems > numItemsThatCanFit) { 357 // If there are more items than we can fit, show the 'More' button to 358 // switch to expanded mode 359 if (mMoreItemView == null) { 360 mMoreItemView = createMoreItemView(); 361 } 362 363 addItemView(mMoreItemView); 364 365 // The last view is the more button, so the actual number of items is one less than 366 // the number that can fit 367 mNumActualItemsShown = numItemsThatCanFit - 1; 368 } else if (numItems == numItemsThatCanFit) { 369 // There are exactly the number we can show, so show the last item 370 final MenuItemImpl lastItemData = itemsToShow.get(numItemsThatCanFit - 1); 371 addItemView((IconMenuItemView) lastItemData.getItemView(MenuBuilder.TYPE_ICON, this)); 372 373 // The items shown fit exactly 374 mNumActualItemsShown = numItemsThatCanFit; 375 } 376 } 377 378 /** 379 * The positioning algorithm that gets called from onMeasure. It 380 * just computes positions for each child, and then stores them in the child's layout params. 381 * @param menuWidth The width of this menu to assume for positioning 382 * @param menuHeight The height of this menu to assume for positioning 383 */ 384 private void positionChildren(int menuWidth, int menuHeight) { 385 // Clear the containers for the positions where the dividers should be drawn 386 if (mHorizontalDivider != null) mHorizontalDividerRects.clear(); 387 if (mVerticalDivider != null) mVerticalDividerRects.clear(); 388 389 // Get the minimum number of rows needed 390 final int numRows = mLayoutNumRows; 391 final int numRowsMinus1 = numRows - 1; 392 final int numItemsForRow[] = mLayout; 393 394 // The item position across all rows 395 int itemPos = 0; 396 View child; 397 IconMenuView.LayoutParams childLayoutParams = null; 398 399 // Use float for this to get precise positions (uniform item widths 400 // instead of last one taking any slack), and then convert to ints at last opportunity 401 float itemLeft; 402 float itemTop = 0; 403 // Since each row can have a different number of items, this will be computed per row 404 float itemWidth; 405 // Subtract the space needed for the horizontal dividers 406 final float itemHeight = (menuHeight - mHorizontalDividerHeight * (numRows - 1)) 407 / (float)numRows; 408 409 for (int row = 0; row < numRows; row++) { 410 // Start at the left 411 itemLeft = 0; 412 413 // Subtract the space needed for the vertical dividers, and divide by the number of items 414 itemWidth = (menuWidth - mVerticalDividerWidth * (numItemsForRow[row] - 1)) 415 / (float)numItemsForRow[row]; 416 417 for (int itemPosOnRow = 0; itemPosOnRow < numItemsForRow[row]; itemPosOnRow++) { 418 // Tell the child to be exactly this size 419 child = getChildAt(itemPos); 420 child.measure(MeasureSpec.makeMeasureSpec((int) itemWidth, MeasureSpec.EXACTLY), 421 MeasureSpec.makeMeasureSpec((int) itemHeight, MeasureSpec.EXACTLY)); 422 423 // Remember the child's position for layout 424 childLayoutParams = (IconMenuView.LayoutParams) child.getLayoutParams(); 425 childLayoutParams.left = (int) itemLeft; 426 childLayoutParams.right = (int) (itemLeft + itemWidth); 427 childLayoutParams.top = (int) itemTop; 428 childLayoutParams.bottom = (int) (itemTop + itemHeight); 429 430 // Increment by item width 431 itemLeft += itemWidth; 432 itemPos++; 433 434 // Add a vertical divider to draw 435 if (mVerticalDivider != null) { 436 mVerticalDividerRects.add(new Rect((int) itemLeft, 437 (int) itemTop, (int) (itemLeft + mVerticalDividerWidth), 438 (int) (itemTop + itemHeight))); 439 } 440 441 // Increment by divider width (even if we're not computing 442 // dividers, since we need to leave room for them when 443 // calculating item positions) 444 itemLeft += mVerticalDividerWidth; 445 } 446 447 // Last child on each row should extend to very right edge 448 if (childLayoutParams != null) { 449 childLayoutParams.right = menuWidth; 450 } 451 452 itemTop += itemHeight; 453 454 // Add a horizontal divider to draw 455 if ((mHorizontalDivider != null) && (row < numRowsMinus1)) { 456 mHorizontalDividerRects.add(new Rect(0, (int) itemTop, menuWidth, 457 (int) (itemTop + mHorizontalDividerHeight))); 458 459 itemTop += mHorizontalDividerHeight; 460 } 461 } 462 } 463 464 @Override 465 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 466 if (mHasStaleChildren) { 467 mHasStaleChildren = false; 468 469 // If we have stale data, resync with the menu 470 updateChildren(false); 471 } 472 473 int measuredWidth = resolveSize(Integer.MAX_VALUE, widthMeasureSpec); 474 calculateItemFittingMetadata(measuredWidth); 475 layoutItems(measuredWidth); 476 477 // Get the desired height of the icon menu view (last row of items does 478 // not have a divider below) 479 final int layoutNumRows = mLayoutNumRows; 480 final int desiredHeight = (mRowHeight + mHorizontalDividerHeight) * 481 layoutNumRows - mHorizontalDividerHeight; 482 483 // Maximum possible width and desired height 484 setMeasuredDimension(measuredWidth, 485 resolveSize(desiredHeight, heightMeasureSpec)); 486 487 // Position the children 488 if (layoutNumRows > 0) { 489 positionChildren(getMeasuredWidth(), getMeasuredHeight()); 490 } 491 } 492 493 494 @Override 495 protected void onLayout(boolean changed, int l, int t, int r, int b) { 496 View child; 497 IconMenuView.LayoutParams childLayoutParams; 498 499 for (int i = getChildCount() - 1; i >= 0; i--) { 500 child = getChildAt(i); 501 childLayoutParams = (IconMenuView.LayoutParams)child 502 .getLayoutParams(); 503 504 // Layout children according to positions set during the measure 505 child.layout(childLayoutParams.left, childLayoutParams.top, childLayoutParams.right, 506 childLayoutParams.bottom); 507 } 508 } 509 510 @Override 511 protected void onDraw(Canvas canvas) { 512 Drawable drawable = mHorizontalDivider; 513 if (drawable != null) { 514 // If we have a horizontal divider to draw, draw it at the remembered positions 515 final ArrayList<Rect> rects = mHorizontalDividerRects; 516 for (int i = rects.size() - 1; i >= 0; i--) { 517 drawable.setBounds(rects.get(i)); 518 drawable.draw(canvas); 519 } 520 } 521 522 drawable = mVerticalDivider; 523 if (drawable != null) { 524 // If we have a vertical divider to draw, draw it at the remembered positions 525 final ArrayList<Rect> rects = mVerticalDividerRects; 526 for (int i = rects.size() - 1; i >= 0; i--) { 527 drawable.setBounds(rects.get(i)); 528 drawable.draw(canvas); 529 } 530 } 531 } 532 533 public boolean invokeItem(MenuItemImpl item) { 534 return mMenu.performItemAction(item, 0); 535 } 536 537 @Override 538 public LayoutParams generateLayoutParams(AttributeSet attrs) { 539 return new IconMenuView.LayoutParams(getContext(), attrs); 540 } 541 542 @Override 543 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 544 // Override to allow type-checking of LayoutParams. 545 return p instanceof IconMenuView.LayoutParams; 546 } 547 548 /** 549 * Marks as having stale children. 550 */ 551 void markStaleChildren() { 552 if (!mHasStaleChildren) { 553 mHasStaleChildren = true; 554 requestLayout(); 555 } 556 } 557 558 /** 559 * @return The number of actual items shown (those that are backed by an 560 * {@link MenuView.ItemView} implementation--eg: excludes More 561 * item). 562 */ 563 int getNumActualItemsShown() { 564 return mNumActualItemsShown; 565 } 566 567 568 public int getWindowAnimations() { 569 return mAnimations; 570 } 571 572 /** 573 * Returns the number of items per row. 574 * <p> 575 * This should only be used for testing. 576 * 577 * @return The length of the array is the number of rows. A value at a 578 * position is the number of items in that row. 579 * @hide 580 */ 581 public int[] getLayout() { 582 return mLayout; 583 } 584 585 /** 586 * Returns the number of rows in the layout. 587 * <p> 588 * This should only be used for testing. 589 * 590 * @return The length of the array is the number of rows. A value at a 591 * position is the number of items in that row. 592 * @hide 593 */ 594 public int getLayoutNumRows() { 595 return mLayoutNumRows; 596 } 597 598 @Override 599 public boolean dispatchKeyEvent(KeyEvent event) { 600 601 if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) { 602 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 603 removeCallbacks(this); 604 postDelayed(this, ViewConfiguration.getLongPressTimeout()); 605 } else if (event.getAction() == KeyEvent.ACTION_UP) { 606 607 if (mMenuBeingLongpressed) { 608 // It was in cycle mode, so reset it (will also remove us 609 // from being called back) 610 setCycleShortcutCaptionMode(false); 611 return true; 612 613 } else { 614 // Just remove us from being called back 615 removeCallbacks(this); 616 // Fall through to normal processing too 617 } 618 } 619 } 620 621 return super.dispatchKeyEvent(event); 622 } 623 624 @Override 625 protected void onAttachedToWindow() { 626 super.onAttachedToWindow(); 627 628 requestFocus(); 629 } 630 631 @Override 632 protected void onDetachedFromWindow() { 633 setCycleShortcutCaptionMode(false); 634 super.onDetachedFromWindow(); 635 } 636 637 @Override 638 public void onWindowFocusChanged(boolean hasWindowFocus) { 639 640 if (!hasWindowFocus) { 641 setCycleShortcutCaptionMode(false); 642 } 643 644 super.onWindowFocusChanged(hasWindowFocus); 645 } 646 647 /** 648 * Sets the shortcut caption mode for IconMenuView. This mode will 649 * continuously cycle between a child's shortcut and its title. 650 * 651 * @param cycleShortcutAndNormal Whether to go into cycling shortcut mode, 652 * or to go back to normal. 653 */ 654 private void setCycleShortcutCaptionMode(boolean cycleShortcutAndNormal) { 655 656 if (!cycleShortcutAndNormal) { 657 /* 658 * We're setting back to title, so remove any callbacks for setting 659 * to shortcut 660 */ 661 removeCallbacks(this); 662 setChildrenCaptionMode(false); 663 mMenuBeingLongpressed = false; 664 665 } else { 666 667 // Set it the first time (the cycle will be started in run()). 668 setChildrenCaptionMode(true); 669 } 670 671 } 672 673 /** 674 * When this method is invoked if the menu is currently not being 675 * longpressed, it means that the longpress has just been reached (so we set 676 * longpress flag, and start cycling). If it is being longpressed, we cycle 677 * to the next mode. 678 */ 679 public void run() { 680 681 if (mMenuBeingLongpressed) { 682 683 // Cycle to other caption mode on the children 684 setChildrenCaptionMode(!mLastChildrenCaptionMode); 685 686 } else { 687 688 // Switch ourselves to continuously cycle the items captions 689 mMenuBeingLongpressed = true; 690 setCycleShortcutCaptionMode(true); 691 } 692 693 // We should run again soon to cycle to the other caption mode 694 postDelayed(this, ITEM_CAPTION_CYCLE_DELAY); 695 } 696 697 /** 698 * Iterates children and sets the desired shortcut mode. Only 699 * {@link #setCycleShortcutCaptionMode(boolean)} and {@link #run()} should call 700 * this. 701 * 702 * @param shortcut Whether to show shortcut or the title. 703 */ 704 private void setChildrenCaptionMode(boolean shortcut) { 705 706 // Set the last caption mode pushed to children 707 mLastChildrenCaptionMode = shortcut; 708 709 for (int i = getChildCount() - 1; i >= 0; i--) { 710 ((IconMenuItemView) getChildAt(i)).setCaptionMode(shortcut); 711 } 712 } 713 714 /** 715 * For each item, calculates the most dense row that fully shows the item's 716 * title. 717 * 718 * @param width The available width of the icon menu. 719 */ 720 private void calculateItemFittingMetadata(int width) { 721 int maxNumItemsPerRow = mMaxItemsPerRow; 722 int numItems = getChildCount(); 723 for (int i = 0; i < numItems; i++) { 724 LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); 725 // Start with 1, since that case does not get covered in the loop below 726 lp.maxNumItemsOnRow = 1; 727 for (int curNumItemsPerRow = maxNumItemsPerRow; curNumItemsPerRow > 0; 728 curNumItemsPerRow--) { 729 // Check whether this item can fit into a row containing curNumItemsPerRow 730 if (lp.desiredWidth < width / curNumItemsPerRow) { 731 // It can, mark this value as the most dense row it can fit into 732 lp.maxNumItemsOnRow = curNumItemsPerRow; 733 break; 734 } 735 } 736 } 737 } 738 739 @Override 740 protected Parcelable onSaveInstanceState() { 741 Parcelable superState = super.onSaveInstanceState(); 742 743 View focusedView = getFocusedChild(); 744 745 for (int i = getChildCount() - 1; i >= 0; i--) { 746 if (getChildAt(i) == focusedView) { 747 return new SavedState(superState, i); 748 } 749 } 750 751 return new SavedState(superState, -1); 752 } 753 754 @Override 755 protected void onRestoreInstanceState(Parcelable state) { 756 SavedState ss = (SavedState) state; 757 super.onRestoreInstanceState(ss.getSuperState()); 758 759 if (ss.focusedPosition >= getChildCount()) { 760 return; 761 } 762 763 View v = getChildAt(ss.focusedPosition); 764 if (v != null) { 765 v.requestFocus(); 766 } 767 } 768 769 private static class SavedState extends BaseSavedState { 770 int focusedPosition; 771 772 /** 773 * Constructor called from {@link IconMenuView#onSaveInstanceState()} 774 */ 775 public SavedState(Parcelable superState, int focusedPosition) { 776 super(superState); 777 this.focusedPosition = focusedPosition; 778 } 779 780 /** 781 * Constructor called from {@link #CREATOR} 782 */ 783 private SavedState(Parcel in) { 784 super(in); 785 focusedPosition = in.readInt(); 786 } 787 788 @Override 789 public void writeToParcel(Parcel dest, int flags) { 790 super.writeToParcel(dest, flags); 791 dest.writeInt(focusedPosition); 792 } 793 794 public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { 795 public SavedState createFromParcel(Parcel in) { 796 return new SavedState(in); 797 } 798 799 public SavedState[] newArray(int size) { 800 return new SavedState[size]; 801 } 802 }; 803 804 } 805 806 /** 807 * Layout parameters specific to IconMenuView (stores the left, top, right, bottom from the 808 * measure pass). 809 */ 810 public static class LayoutParams extends ViewGroup.MarginLayoutParams 811 { 812 int left, top, right, bottom; 813 int desiredWidth; 814 int maxNumItemsOnRow; 815 816 public LayoutParams(Context c, AttributeSet attrs) { 817 super(c, attrs); 818 } 819 820 public LayoutParams(int width, int height) { 821 super(width, height); 822 } 823 } 824} 825