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 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 | 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;
|
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.
Context context = getContext();
LayoutInflater inflater = LayoutInflater.from(context);
final IconMenuItemView itemView = (IconMenuItemView) inflater.inflate(
com.android.internal.R.layout.icon_menu_item_layout, null);
Resources r = context.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. Requires support from
// the menu's active callback.
mMenu.changeMenuMode();
}
});
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);
|
android.graphics.drawable.Drawable | getItemBackgroundDrawable()
return mItemBackground.getConstantState().newDrawable(getContext().getResources());
|
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 | getMaxItems()
return mMaxItems;
|
int | getNumActualItemsShown()
return mNumActualItemsShown;
|
public int | getWindowAnimations()
return mAnimations;
|
public void | initialize(MenuBuilder menu)
mMenu = menu;
|
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();
if (numItems == 0) {
mLayoutNumRows = 0;
return;
}
// 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)
Drawable drawable = mHorizontalDivider;
if (drawable != null) {
// If we have a horizontal divider to draw, draw it at the remembered positions
final ArrayList<Rect> rects = mHorizontalDividerRects;
for (int i = rects.size() - 1; i >= 0; i--) {
drawable.setBounds(rects.get(i));
drawable.draw(canvas);
}
}
drawable = mVerticalDivider;
if (drawable != null) {
// If we have a vertical divider to draw, draw it at the remembered positions
final ArrayList<Rect> rects = mVerticalDividerRects;
for (int i = rects.size() - 1; i >= 0; i--) {
drawable.setBounds(rects.get(i));
drawable.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)
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 layoutNumRows = mLayoutNumRows;
final int desiredHeight = (mRowHeight + mHorizontalDividerHeight) *
layoutNumRows - mHorizontalDividerHeight;
// Maximum possible width and desired height
setMeasuredDimension(measuredWidth,
resolveSize(desiredHeight, heightMeasureSpec));
// Position the children
if (layoutNumRows > 0) {
positionChildren(getMeasuredWidth(), getMeasuredHeight());
}
|
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);
}
|
void | setNumActualItemsShown(int count)
mNumActualItemsShown = count;
|