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