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