InCallMenuViewpublic class InCallMenuView extends android.view.ViewGroup Custom View used as the "options panel" for the InCallScreen
(i.e. the standard menu triggered by the MENU button.)
This class purely handles the layout and display of the in-call menu
items, *not* the actual contents of the menu or the states of the
items. (See InCallMenu for the corresponding "model" class.) |
Fields Summary |
---|
private static final String | LOG_TAG | private static final boolean | DBG | private int | mRowHeight | 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 | mItemBackgroundBackground of each item (should contain the selected and focused states) | private static final int | NUM_ROWSThe actual layout of items in the menu, organized into 3 rows.
Row 0 is the topmost row onscreen, item 0 is the leftmost item in a row.
Individual items may be disabled or hidden, but never move between
rows or change their order within a row. | private static final int | MAX_ITEMS_PER_ROW | private InCallMenuItemView[] | mItems | private int[] | mNumItemsForRow | private int[] | mNumVisibleItemsForRowNumber of visible items per row, given the current state of all the
menu items.
A row with zero visible items isn't drawn at all. | private int | mNumVisibleRows | private InCallScreen | mInCallScreenReference to the InCallScreen activity that owns us. This will be
null if we haven't been initialized yet *or* after the InCallScreen
activity has been destroyed. |
Constructors Summary |
---|
InCallMenuView(android.content.Context context, InCallScreen inCallScreen)
super(context);
if (DBG) log("InCallMenuView constructor...");
mInCallScreen = inCallScreen;
// Look up a few styled attrs from IconMenuView and/or MenuView
// (to keep our look and feel at least *somewhat* consistent with
// menus in other apps.)
TypedArray a =
mContext.obtainStyledAttributes(com.android.internal.R.styleable.IconMenuView);
if (DBG) log("- IconMenuView styled attrs: " + a);
mRowHeight = a.getDimensionPixelSize(
com.android.internal.R.styleable.IconMenuView_rowHeight, 64);
if (DBG) log(" - mRowHeight: " + mRowHeight);
a.recycle();
a = mContext.obtainStyledAttributes(com.android.internal.R.styleable.MenuView);
if (DBG) log("- MenuView styled attrs: " + a);
mItemBackground = a.getDrawable(com.android.internal.R.styleable.MenuView_itemBackground);
if (DBG) log(" - mItemBackground: " + mItemBackground);
mHorizontalDivider = a.getDrawable(com.android.internal.R.styleable.MenuView_horizontalDivider);
if (DBG) log(" - mHorizontalDivider: " + mHorizontalDivider);
mHorizontalDividerRects = new ArrayList<Rect>();
mVerticalDivider = a.getDrawable(com.android.internal.R.styleable.MenuView_verticalDivider);
if (DBG) log(" - mVerticalDivider: " + mVerticalDivider);
mVerticalDividerRects = new ArrayList<Rect>();
a.recycle();
if (mHorizontalDivider != null) {
mHorizontalDividerHeight = mHorizontalDivider.getIntrinsicHeight();
// Make sure to have some height for the divider
if (mHorizontalDividerHeight == -1) mHorizontalDividerHeight = 1;
}
if (mVerticalDivider != null) {
mVerticalDividerWidth = mVerticalDivider.getIntrinsicWidth();
// Make sure to have some width for the divider
if (mVerticalDividerWidth == -1) mVerticalDividerWidth = 1;
}
// This view will be drawing the dividers.
setWillNotDraw(false);
// Arrange to get key events even when there's no focused item in
// the in-call menu (i.e. when in touch mode).
// (We *always* want key events whenever we're visible, so that we
// can forward them to the InCallScreen activity; see dispatchKeyEvent().)
setFocusableInTouchMode(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
// The default ViewGroup.LayoutParams width and height are
// WRAP_CONTENT. (This applies to us right now since we
// initially have no LayoutParams at all.)
// But in the Menu framework, when returning a view from
// onCreatePanelView(), a layout width of WRAP_CONTENT indicates
// that you want the smaller-sized "More" menu frame. We want the
// full-screen-width menu frame instead, though, so we need to
// give ourselves a LayoutParams with width==FILL_PARENT.
ViewGroup.LayoutParams lp =
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
setLayoutParams(lp);
|
Methods Summary |
---|
void | addItemView(InCallMenuItemView itemView, int row)Adds an InCallMenuItemView to the specified row.
if (DBG) log("addItemView(" + itemView + ", row " + row + ")...");
if (row >= NUM_ROWS) {
throw new IllegalStateException("Row index " + row + " > NUM_ROWS");
}
int indexInRow = mNumItemsForRow[row];
if (indexInRow >= MAX_ITEMS_PER_ROW) {
throw new IllegalStateException("Too many items (" + indexInRow + ") in row " + row);
}
mNumItemsForRow[row]++;
mItems[row][indexInRow] = itemView;
//
// Finally, add this item as a child.
//
ViewGroup.LayoutParams lp = itemView.getLayoutParams();
if (lp == null) {
// Default layout parameters
lp = new LayoutParams(android.view.ViewGroup.LayoutParams.FILL_PARENT, android.view.ViewGroup.LayoutParams.FILL_PARENT);
}
// Apply the background to the item view
itemView.setBackgroundDrawable(mItemBackground.getConstantState().newDrawable());
addView(itemView, lp);
| protected boolean | checkLayoutParams(ViewGroup.LayoutParams p)
// Override to allow type-checking of LayoutParams.
return p instanceof InCallMenuView.LayoutParams;
| void | clearInCallScreenReference()Null out our reference to the InCallScreen activity.
This indicates that the InCallScreen activity has been destroyed.
mInCallScreen = null;
| public boolean | dispatchKeyEvent(android.view.KeyEvent event)
if (DBG) log("dispatchKeyEvent(" + event + ")...");
// In most other apps, when a menu is up, the menu itself handles
// keypresses. And keys that aren't handled by the menu do NOT
// get dispatched to the current Activity.
//
// But in the in-call UI, we don't have any menu shortcuts, *and*
// it's important for buttons like CALL to work normally even
// while the menu is up. So we handle ALL key events (with some
// exceptions -- see below) by simply forwarding them to the
// InCallScreen.
int keyCode = event.getKeyCode();
if (event.isDown()) {
switch (keyCode) {
// The BACK key dismisses the menu.
case KeyEvent.KEYCODE_BACK:
if (DBG) log("==> BACK key! handling it ourselves...");
// We don't need to do anything here (since BACK
// is magically handled by the framework); we just
// need to *not* forward it to the InCallScreen.
break;
// Don't send KEYCODE_DPAD_CENTER/KEYCODE_ENTER to the
// InCallScreen either, since the framework needs those to
// activate the focused item when using the trackball.
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
break;
// Anything else gets forwarded to the InCallScreen.
default:
if (DBG) log("==> dispatchKeyEvent: forwarding event to the InCallScreen");
if (mInCallScreen != null) {
return mInCallScreen.onKeyDown(keyCode, event);
}
break;
}
} else if (mInCallScreen != null &&
(keyCode == KeyEvent.KEYCODE_CALL ||
mInCallScreen.isKeyEventAcceptableDTMF(event))) {
// Forward the key-up for the call and dialer buttons to the
// InCallScreen. All other key-up events are NOT handled here,
// but instead fall through to dispatchKeyEvent from the superclass.
if (DBG) log("==> dispatchKeyEvent: forwarding key up event to the InCallScreen");
return mInCallScreen.onKeyUp(keyCode, event);
}
return super.dispatchKeyEvent(event);
| void | dumpState()
if (DBG) log("============ dumpState() ============");
if (DBG) log("- mItems LENGTH: " + mItems.length);
for (int row = 0; row < NUM_ROWS; row++) {
if (DBG) log("- Row " + row + ": length " + mItems[row].length
+ ", num items " + mNumItemsForRow[row]
+ ", num visible " + mNumVisibleItemsForRow[row]);
}
| public com.android.phone.InCallMenuView$LayoutParams | generateLayoutParams(android.util.AttributeSet attrs)
return new InCallMenuView.LayoutParams(getContext(), attrs);
| private void | log(java.lang.String msg)
Log.d(LOG_TAG, msg);
| protected void | onDraw(android.graphics.Canvas canvas)
if (DBG) log("onDraw()...");
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)
if (DBG) log("onLayout(changed " + changed
+ ", l " + l + " t " + t + " r " + r + " b " + b + ")...");
View child;
InCallMenuView.LayoutParams childLayoutParams;
for (int i = getChildCount() - 1; i >= 0; i--) {
child = getChildAt(i);
childLayoutParams = (InCallMenuView.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 (DBG) log("onMeasure(" + widthMeasureSpec + " x " + heightMeasureSpec + ")...");
// Get the desired height of the icon menu view (last row of items does
// not have a divider below)
final int desiredHeight = (mRowHeight + mHorizontalDividerHeight) * mNumVisibleRows
- mHorizontalDividerHeight;
// Maximum possible width and desired height
setMeasuredDimension(resolveSize(Integer.MAX_VALUE, widthMeasureSpec),
resolveSize(desiredHeight, heightMeasureSpec));
// Position the children
positionChildren(mMeasuredWidth, mMeasuredHeight);
| 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.
At this point the visibility of each item in mItems[][] is correct,
and mNumVisibleRows and mNumVisibleItemsForRow[] have already been
precomputed.
if (DBG) log("positionChildren(" + menuWidth + " x " + menuHeight + ")...");
// Clear the containers for the positions where the dividers should be drawn
if (mHorizontalDivider != null) mHorizontalDividerRects.clear();
if (mVerticalDivider != null) mVerticalDividerRects.clear();
InCallMenuItemView child;
InCallMenuView.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 * (mNumVisibleRows - 1))
/ (float) mNumVisibleRows;
// We add horizontal dividers between each visible row, so there should
// be a total of mNumVisibleRows-1 of them.
int numHorizDividersRemainingToDraw = mNumVisibleRows - 1;
for (int row = 0; row < NUM_ROWS; row++) {
int numItemsThisRow = mNumItemsForRow[row];
int numVisibleThisRow = mNumVisibleItemsForRow[row];
if (DBG) log(" - num visible for row " + row + ": " + numVisibleThisRow);
if (numVisibleThisRow == 0) {
continue;
}
InCallMenuItemView[] thisRow = mItems[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 * (numVisibleThisRow - 1))
/ (float) numVisibleThisRow;
for (int itemIndex = 0; itemIndex < numItemsThisRow; itemIndex++) {
child = mItems[row][itemIndex];
if (!child.isVisible()) continue;
if (DBG) log("==> child [" + row + "][" + itemIndex + "]: " + child);
// Tell the child to be exactly this size
child.measure(MeasureSpec.makeMeasureSpec((int) itemWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec((int) itemHeight, MeasureSpec.EXACTLY));
// Remember the child's position for layout
childLayoutParams = (InCallMenuView.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;
// 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 (if we need one under this row)
if ((mHorizontalDivider != null) && (numHorizDividersRemainingToDraw-- > 0)) {
mHorizontalDividerRects.add(new Rect(0, (int) itemTop, menuWidth,
(int) (itemTop + mHorizontalDividerHeight)));
itemTop += mHorizontalDividerHeight;
}
}
| void | updateVisibility()Precomputes the number of visible items per row, and the total
number of visible rows. (A row with zero visible items isn't
drawn at all.)
if (DBG) log("updateVisibility()...");
mNumVisibleRows = 0;
for (int row = 0; row < NUM_ROWS; row++) {
InCallMenuItemView[] thisRow = mItems[row];
int numItemsThisRow = mNumItemsForRow[row];
int numVisibleThisRow = 0;
for (int itemIndex = 0; itemIndex < numItemsThisRow; itemIndex++) {
// if (DBG) log(" - Checking item: " + mItems[row][itemIndex]);
if (mItems[row][itemIndex].isVisible()) numVisibleThisRow++;
}
if (DBG) log("==> Num visible for row " + row + ": " + numVisibleThisRow);
mNumVisibleItemsForRow[row] = numVisibleThisRow;
if (numVisibleThisRow > 0) mNumVisibleRows++;
}
if (DBG) log("==> Num visible rows: " + mNumVisibleRows);
|
|