Fields Summary |
---|
private static final int | ITEM_CAPTION_CYCLE_DELAY |
private MenuBuilder | mMenu |
private int | mRowHeightHeight of each row |
private int | mMaxRowsMaximum number of rows to be shown |
private int | mMaxItemsMaximum number of items to show in the icon menu. |
private int | mMaxItemsPerRowMaximum number of items per row |
private int | mNumActualItemsShownActual number of items (the 'More' view does not count as an item) shown |
private android.graphics.drawable.Drawable | mHorizontalDividerDivider that is drawn between all rows |
private int | mHorizontalDividerHeightHeight of the horizontal divider |
private ArrayList | mHorizontalDividerRectsSet of horizontal divider positions where the horizontal divider will be drawn |
private android.graphics.drawable.Drawable | mVerticalDividerDivider that is drawn between all columns |
private int | mVerticalDividerWidthWidth of the vertical divider |
private ArrayList | mVerticalDividerRectsSet of vertical divider positions where the vertical divider will be drawn |
private android.graphics.drawable.Drawable | mMoreIconIcon for the 'More' button |
private IconMenuItemView | mMoreItemViewItem view for the 'More' button |
private android.graphics.drawable.Drawable | mItemBackgroundBackground of each item (should contain the selected and focused states) |
private int | mAnimationsDefault animations for this menu |
private boolean | mHasStaleChildrenWhether this IconMenuView has stale children and needs to update them.
Set true by {@link #markStaleChildren()} and reset to false by
{@link #onMeasure(int, int)} |
private boolean | mMenuBeingLongpressedLongpress on MENU (while this is shown) switches to shortcut caption
mode. When the user releases the longpress, we do not want to pass the
key-up event up since that will dismiss the menu. |
private boolean | mLastChildrenCaptionModeWhile {@link #mMenuBeingLongpressed}, we toggle the children's caption
mode between each's title and its shortcut. This is the last caption mode
we broadcasted to children. |
private int[] | mLayoutThe layout to use for menu items. Each index is the row number (0 is the
top-most). Each value contains the number of items in that row.
The length of this array should not be used to get the number of rows in
the current layout, instead use {@link #mLayoutNumRows}. |
private int | mLayoutNumRowsThe number of rows in the current layout. |
Methods Summary |
---|
private void | addItemView(IconMenuItemView itemView)Adds an IconMenuItemView to this icon menu view.
// Set ourselves on the item view
itemView.setIconMenuView(this);
// Apply the background to the item view
itemView.setBackgroundDrawable(mItemBackground.getConstantState().newDrawable());
// This class is the invoker for all its item views
itemView.setItemInvoker(this);
addView(itemView, itemView.getTextAppropriateLayoutParams());
|
private void | calculateItemFittingMetadata(int width)For each item, calculates the most dense row that fully shows the item's
title.
int maxNumItemsPerRow = mMaxItemsPerRow;
int numItems = getChildCount();
for (int i = 0; i < numItems; i++) {
LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
// Start with 1, since that case does not get covered in the loop below
lp.maxNumItemsOnRow = 1;
for (int curNumItemsPerRow = maxNumItemsPerRow; curNumItemsPerRow > 0;
curNumItemsPerRow--) {
// Check whether this item can fit into a row containing curNumItemsPerRow
if (lp.desiredWidth < width / curNumItemsPerRow) {
// It can, mark this value as the most dense row it can fit into
lp.maxNumItemsOnRow = curNumItemsPerRow;
break;
}
}
}
|
protected boolean | checkLayoutParams(ViewGroup.LayoutParams p)
// Override to allow type-checking of LayoutParams.
return p instanceof IconMenuView.LayoutParams;
|
private IconMenuItemView | createMoreItemView()Creates the item view for the 'More' button which is used to switch to
the expanded menu view. This button is a special case since it does not
have a MenuItemData backing it.
LayoutInflater inflater = mMenu.getMenuType(MenuBuilder.TYPE_ICON).getInflater();
final IconMenuItemView itemView = (IconMenuItemView) inflater.inflate(
com.android.internal.R.layout.icon_menu_item_layout, null);
Resources r = getContext().getResources();
itemView.initialize(r.getText(com.android.internal.R.string.more_item_label), mMoreIcon);
// Set up a click listener on the view since there will be no invocation sequence
// due to the lack of a MenuItemData this view
itemView.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// Switches the menu to expanded mode
MenuBuilder.Callback cb = mMenu.getCallback();
if (cb != null) {
// Call callback
cb.onMenuModeChange(mMenu);
}
}
});
return itemView;
|
public boolean | dispatchKeyEvent(android.view.KeyEvent event)
if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) {
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
removeCallbacks(this);
postDelayed(this, ViewConfiguration.getLongPressTimeout());
} else if (event.getAction() == KeyEvent.ACTION_UP) {
if (mMenuBeingLongpressed) {
// It was in cycle mode, so reset it (will also remove us
// from being called back)
setCycleShortcutCaptionMode(false);
return true;
} else {
// Just remove us from being called back
removeCallbacks(this);
// Fall through to normal processing too
}
}
}
return super.dispatchKeyEvent(event);
|
private boolean | doItemsFit()Checks whether each item's title is fully visible using the current
layout.
int itemPos = 0;
int[] layout = mLayout;
int numRows = mLayoutNumRows;
for (int row = 0; row < numRows; row++) {
int numItemsOnRow = layout[row];
/*
* If there is only one item on this row, increasing the
* number of rows won't help.
*/
if (numItemsOnRow == 1) {
itemPos++;
continue;
}
for (int itemsOnRowCounter = numItemsOnRow; itemsOnRowCounter > 0;
itemsOnRowCounter--) {
View child = getChildAt(itemPos++);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.maxNumItemsOnRow < numItemsOnRow) {
return false;
}
}
}
return true;
|
public com.android.internal.view.menu.IconMenuView$LayoutParams | generateLayoutParams(android.util.AttributeSet attrs)
return new IconMenuView.LayoutParams(getContext(), attrs);
|
public int[] | getLayout()Returns the number of items per row.
This should only be used for testing.
return mLayout;
|
public int | getLayoutNumRows()Returns the number of rows in the layout.
This should only be used for testing.
return mLayoutNumRows;
|
int | getNumActualItemsShown()
return mNumActualItemsShown;
|
public int | getWindowAnimations()
return mAnimations;
|
public void | initialize(MenuBuilder menu, int menuType)
mMenu = menu;
updateChildren(true);
|
public boolean | invokeItem(MenuItemImpl item)
return mMenu.performItemAction(item, 0);
|
private void | layoutItems(int width)Figures out the layout for the menu items.
int numItems = getChildCount();
// Start with the least possible number of rows
int curNumRows =
Math.min((int) Math.ceil(numItems / (float) mMaxItemsPerRow), mMaxRows);
/*
* Increase the number of rows until we find a configuration that fits
* all of the items' titles. Worst case, we use mMaxRows.
*/
for (; curNumRows <= mMaxRows; curNumRows++) {
layoutItemsUsingGravity(curNumRows, numItems);
if (curNumRows >= numItems) {
// Can't have more rows than items
break;
}
if (doItemsFit()) {
// All the items fit, so this is a good configuration
break;
}
}
|
private void | layoutItemsUsingGravity(int numRows, int numItems)Figures out the layout for the menu items by equally distributing, and
adding any excess items equally to lower rows.
int numBaseItemsPerRow = numItems / numRows;
int numLeftoverItems = numItems % numRows;
/**
* The bottom rows will each get a leftover item. Rows (indexed at 0)
* that are >= this get a leftover item. Note: if there are 0 leftover
* items, no rows will get them since this value will be greater than
* the last row.
*/
int rowsThatGetALeftoverItem = numRows - numLeftoverItems;
int[] layout = mLayout;
for (int i = 0; i < numRows; i++) {
layout[i] = numBaseItemsPerRow;
// Fill the bottom rows with a leftover item each
if (i >= rowsThatGetALeftoverItem) {
layout[i]++;
}
}
mLayoutNumRows = numRows;
|
void | markStaleChildren()Marks as having stale children.
if (!mHasStaleChildren) {
mHasStaleChildren = true;
requestLayout();
}
|
protected void | onAttachedToWindow()
super.onAttachedToWindow();
requestFocus();
|
protected void | onDetachedFromWindow()
setCycleShortcutCaptionMode(false);
super.onDetachedFromWindow();
|
protected void | onDraw(android.graphics.Canvas canvas)
if (mHorizontalDivider != null) {
// If we have a horizontal divider to draw, draw it at the remembered positions
for (int i = mHorizontalDividerRects.size() - 1; i >= 0; i--) {
mHorizontalDivider.setBounds(mHorizontalDividerRects.get(i));
mHorizontalDivider.draw(canvas);
}
}
if (mVerticalDivider != null) {
// If we have a vertical divider to draw, draw it at the remembered positions
for (int i = mVerticalDividerRects.size() - 1; i >= 0; i--) {
mVerticalDivider.setBounds(mVerticalDividerRects.get(i));
mVerticalDivider.draw(canvas);
}
}
|
protected void | onLayout(boolean changed, int l, int t, int r, int b)
View child;
IconMenuView.LayoutParams childLayoutParams;
for (int i = getChildCount() - 1; i >= 0; i--) {
child = getChildAt(i);
childLayoutParams = (IconMenuView.LayoutParams)child
.getLayoutParams();
// Layout children according to positions set during the measure
child.layout(childLayoutParams.left, childLayoutParams.top, childLayoutParams.right,
childLayoutParams.bottom);
}
|
protected void | onMeasure(int widthMeasureSpec, int heightMeasureSpec)
if (mHasStaleChildren) {
mHasStaleChildren = false;
// If we have stale data, resync with the menu
updateChildren(false);
}
int measuredWidth = resolveSize(Integer.MAX_VALUE, widthMeasureSpec);
calculateItemFittingMetadata(measuredWidth);
layoutItems(measuredWidth);
// Get the desired height of the icon menu view (last row of items does
// not have a divider below)
final int desiredHeight = (mRowHeight + mHorizontalDividerHeight) *
mLayoutNumRows - mHorizontalDividerHeight;
// Maximum possible width and desired height
setMeasuredDimension(measuredWidth,
resolveSize(desiredHeight, heightMeasureSpec));
// Position the children
positionChildren(mMeasuredWidth, mMeasuredHeight);
|
protected void | onRestoreInstanceState(android.os.Parcelable state)
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (ss.focusedPosition >= getChildCount()) {
return;
}
View v = getChildAt(ss.focusedPosition);
if (v != null) {
v.requestFocus();
}
|
protected android.os.Parcelable | onSaveInstanceState()
Parcelable superState = super.onSaveInstanceState();
View focusedView = getFocusedChild();
for (int i = getChildCount() - 1; i >= 0; i--) {
if (getChildAt(i) == focusedView) {
return new SavedState(superState, i);
}
}
return new SavedState(superState, -1);
|
public void | onWindowFocusChanged(boolean hasWindowFocus)
if (!hasWindowFocus) {
setCycleShortcutCaptionMode(false);
}
super.onWindowFocusChanged(hasWindowFocus);
|
private void | positionChildren(int menuWidth, int menuHeight)The positioning algorithm that gets called from onMeasure. It
just computes positions for each child, and then stores them in the child's layout params.
// Clear the containers for the positions where the dividers should be drawn
if (mHorizontalDivider != null) mHorizontalDividerRects.clear();
if (mVerticalDivider != null) mVerticalDividerRects.clear();
// Get the minimum number of rows needed
final int numRows = mLayoutNumRows;
final int numRowsMinus1 = numRows - 1;
final int numItemsForRow[] = mLayout;
// The item position across all rows
int itemPos = 0;
View child;
IconMenuView.LayoutParams childLayoutParams = null;
// Use float for this to get precise positions (uniform item widths
// instead of last one taking any slack), and then convert to ints at last opportunity
float itemLeft;
float itemTop = 0;
// Since each row can have a different number of items, this will be computed per row
float itemWidth;
// Subtract the space needed for the horizontal dividers
final float itemHeight = (menuHeight - mHorizontalDividerHeight * (numRows - 1))
/ (float)numRows;
for (int row = 0; row < numRows; row++) {
// Start at the left
itemLeft = 0;
// Subtract the space needed for the vertical dividers, and divide by the number of items
itemWidth = (menuWidth - mVerticalDividerWidth * (numItemsForRow[row] - 1))
/ (float)numItemsForRow[row];
for (int itemPosOnRow = 0; itemPosOnRow < numItemsForRow[row]; itemPosOnRow++) {
// Tell the child to be exactly this size
child = getChildAt(itemPos);
child.measure(MeasureSpec.makeMeasureSpec((int) itemWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec((int) itemHeight, MeasureSpec.EXACTLY));
// Remember the child's position for layout
childLayoutParams = (IconMenuView.LayoutParams) child.getLayoutParams();
childLayoutParams.left = (int) itemLeft;
childLayoutParams.right = (int) (itemLeft + itemWidth);
childLayoutParams.top = (int) itemTop;
childLayoutParams.bottom = (int) (itemTop + itemHeight);
// Increment by item width
itemLeft += itemWidth;
itemPos++;
// Add a vertical divider to draw
if (mVerticalDivider != null) {
mVerticalDividerRects.add(new Rect((int) itemLeft,
(int) itemTop, (int) (itemLeft + mVerticalDividerWidth),
(int) (itemTop + itemHeight)));
}
// Increment by divider width (even if we're not computing
// dividers, since we need to leave room for them when
// calculating item positions)
itemLeft += mVerticalDividerWidth;
}
// Last child on each row should extend to very right edge
if (childLayoutParams != null) {
childLayoutParams.right = menuWidth;
}
itemTop += itemHeight;
// Add a horizontal divider to draw
if ((mHorizontalDivider != null) && (row < numRowsMinus1)) {
mHorizontalDividerRects.add(new Rect(0, (int) itemTop, menuWidth,
(int) (itemTop + mHorizontalDividerHeight)));
itemTop += mHorizontalDividerHeight;
}
}
|
public void | run()When this method is invoked if the menu is currently not being
longpressed, it means that the longpress has just been reached (so we set
longpress flag, and start cycling). If it is being longpressed, we cycle
to the next mode.
if (mMenuBeingLongpressed) {
// Cycle to other caption mode on the children
setChildrenCaptionMode(!mLastChildrenCaptionMode);
} else {
// Switch ourselves to continuously cycle the items captions
mMenuBeingLongpressed = true;
setCycleShortcutCaptionMode(true);
}
// We should run again soon to cycle to the other caption mode
postDelayed(this, ITEM_CAPTION_CYCLE_DELAY);
|
private void | setChildrenCaptionMode(boolean shortcut)Iterates children and sets the desired shortcut mode. Only
{@link #setCycleShortcutCaptionMode(boolean)} and {@link #run()} should call
this.
// Set the last caption mode pushed to children
mLastChildrenCaptionMode = shortcut;
for (int i = getChildCount() - 1; i >= 0; i--) {
((IconMenuItemView) getChildAt(i)).setCaptionMode(shortcut);
}
|
private void | setCycleShortcutCaptionMode(boolean cycleShortcutAndNormal)Sets the shortcut caption mode for IconMenuView. This mode will
continuously cycle between a child's shortcut and its title.
if (!cycleShortcutAndNormal) {
/*
* We're setting back to title, so remove any callbacks for setting
* to shortcut
*/
removeCallbacks(this);
setChildrenCaptionMode(false);
mMenuBeingLongpressed = false;
} else {
// Set it the first time (the cycle will be started in run()).
setChildrenCaptionMode(true);
}
|
public void | updateChildren(boolean cleared)
// This method does a clear refresh of children
removeAllViews();
final ArrayList<MenuItemImpl> itemsToShow = mMenu.getVisibleItems();
final int numItems = itemsToShow.size();
final int numItemsThatCanFit = mMaxItems;
// Minimum of the num that can fit and the num that we have
final int minFitMinus1AndNumItems = Math.min(numItemsThatCanFit - 1, numItems);
MenuItemImpl itemData;
// Traverse through all but the last item that can fit since that last item can either
// be a 'More' button or a sixth item
for (int i = 0; i < minFitMinus1AndNumItems; i++) {
itemData = itemsToShow.get(i);
addItemView((IconMenuItemView) itemData.getItemView(MenuBuilder.TYPE_ICON, this));
}
if (numItems > numItemsThatCanFit) {
// If there are more items than we can fit, show the 'More' button to
// switch to expanded mode
if (mMoreItemView == null) {
mMoreItemView = createMoreItemView();
}
addItemView(mMoreItemView);
// The last view is the more button, so the actual number of items is one less than
// the number that can fit
mNumActualItemsShown = numItemsThatCanFit - 1;
} else if (numItems == numItemsThatCanFit) {
// There are exactly the number we can show, so show the last item
final MenuItemImpl lastItemData = itemsToShow.get(numItemsThatCanFit - 1);
addItemView((IconMenuItemView) lastItemData.getItemView(MenuBuilder.TYPE_ICON, this));
// The items shown fit exactly
mNumActualItemsShown = numItemsThatCanFit;
}
|