aboutsummaryrefslogtreecommitdiffstats
path: root/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java
diff options
context:
space:
mode:
Diffstat (limited to 'com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java')
-rw-r--r--com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java572
1 files changed, 572 insertions, 0 deletions
diff --git a/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java
new file mode 100644
index 000000000..40ab73c50
--- /dev/null
+++ b/com_actionbarsherlock/src/com/actionbarsherlock/internal/view/menu/ActionMenuView.java
@@ -0,0 +1,572 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.actionbarsherlock.internal.view.menu;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.LinearLayout;
+import com.actionbarsherlock.internal.widget.IcsLinearLayout;
+
+/**
+ * @hide
+ */
+public class ActionMenuView extends IcsLinearLayout implements MenuBuilder.ItemInvoker, MenuView {
+ //UNUSED private static final String TAG = "ActionMenuView";
+ private static final boolean IS_FROYO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
+
+ static final int MIN_CELL_SIZE = 56; // dips
+ static final int GENERATED_ITEM_PADDING = 4; // dips
+
+ private MenuBuilder mMenu;
+
+ private boolean mReserveOverflow;
+ private ActionMenuPresenter mPresenter;
+ private boolean mFormatItems;
+ private int mFormatItemsWidth;
+ private int mMinCellSize;
+ private int mGeneratedItemPadding;
+ //UNUSED private int mMeasuredExtraWidth;
+
+ private boolean mFirst = true;
+
+ public ActionMenuView(Context context) {
+ this(context, null);
+ }
+
+ public ActionMenuView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setBaselineAligned(false);
+ final float density = context.getResources().getDisplayMetrics().density;
+ mMinCellSize = (int) (MIN_CELL_SIZE * density);
+ mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
+ }
+
+ public void setPresenter(ActionMenuPresenter presenter) {
+ mPresenter = presenter;
+ }
+
+ public boolean isExpandedFormat() {
+ return mFormatItems;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (IS_FROYO) {
+ super.onConfigurationChanged(newConfig);
+ }
+ mPresenter.updateMenuView(false);
+
+ if (mPresenter != null && mPresenter.isOverflowMenuShowing()) {
+ mPresenter.hideOverflowMenu();
+ mPresenter.showOverflowMenu();
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ //Need to trigger a relayout since we may have been added extremely
+ //late in the initial rendering (e.g., when contained in a ViewPager).
+ //See: https://github.com/JakeWharton/ActionBarSherlock/issues/272
+ if (!IS_FROYO && mFirst) {
+ mFirst = false;
+ requestLayout();
+ return;
+ }
+ super.onDraw(canvas);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // If we've been given an exact size to match, apply special formatting during layout.
+ final boolean wasFormatted = mFormatItems;
+ mFormatItems = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
+
+ if (wasFormatted != mFormatItems) {
+ mFormatItemsWidth = 0; // Reset this when switching modes
+ }
+
+ // Special formatting can change whether items can fit as action buttons.
+ // Kick the menu and update presenters when this changes.
+ final int widthSize = MeasureSpec.getMode(widthMeasureSpec);
+ if (mFormatItems && mMenu != null && widthSize != mFormatItemsWidth) {
+ mFormatItemsWidth = widthSize;
+ mMenu.onItemsChanged(true);
+ }
+
+ if (mFormatItems) {
+ onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec);
+ } else {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ private void onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec) {
+ // We already know the width mode is EXACTLY if we're here.
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ final int widthPadding = getPaddingLeft() + getPaddingRight();
+ final int heightPadding = getPaddingTop() + getPaddingBottom();
+
+ widthSize -= widthPadding;
+
+ // Divide the view into cells.
+ final int cellCount = widthSize / mMinCellSize;
+ final int cellSizeRemaining = widthSize % mMinCellSize;
+
+ if (cellCount == 0) {
+ // Give up, nothing fits.
+ setMeasuredDimension(widthSize, 0);
+ return;
+ }
+
+ final int cellSize = mMinCellSize + cellSizeRemaining / cellCount;
+
+ int cellsRemaining = cellCount;
+ int maxChildHeight = 0;
+ int maxCellsUsed = 0;
+ int expandableItemCount = 0;
+ int visibleItemCount = 0;
+ boolean hasOverflow = false;
+
+ // This is used as a bitfield to locate the smallest items present. Assumes childCount < 64.
+ long smallestItemsAt = 0;
+
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == GONE) continue;
+
+ final boolean isGeneratedItem = child instanceof ActionMenuItemView;
+ visibleItemCount++;
+
+ if (isGeneratedItem) {
+ // Reset padding for generated menu item views; it may change below
+ // and views are recycled.
+ child.setPadding(mGeneratedItemPadding, 0, mGeneratedItemPadding, 0);
+ }
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ lp.expanded = false;
+ lp.extraPixels = 0;
+ lp.cellsUsed = 0;
+ lp.expandable = false;
+ lp.leftMargin = 0;
+ lp.rightMargin = 0;
+ lp.preventEdgeOffset = isGeneratedItem && ((ActionMenuItemView) child).hasText();
+
+ // Overflow always gets 1 cell. No more, no less.
+ final int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining;
+
+ final int cellsUsed = measureChildForCells(child, cellSize, cellsAvailable,
+ heightMeasureSpec, heightPadding);
+
+ maxCellsUsed = Math.max(maxCellsUsed, cellsUsed);
+ if (lp.expandable) expandableItemCount++;
+ if (lp.isOverflowButton) hasOverflow = true;
+
+ cellsRemaining -= cellsUsed;
+ maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
+ if (cellsUsed == 1) smallestItemsAt |= (1 << i);
+ }
+
+ // When we have overflow and a single expanded (text) item, we want to try centering it
+ // visually in the available space even though overflow consumes some of it.
+ final boolean centerSingleExpandedItem = hasOverflow && visibleItemCount == 2;
+
+ // Divide space for remaining cells if we have items that can expand.
+ // Try distributing whole leftover cells to smaller items first.
+
+ boolean needsExpansion = false;
+ while (expandableItemCount > 0 && cellsRemaining > 0) {
+ int minCells = Integer.MAX_VALUE;
+ long minCellsAt = 0; // Bit locations are indices of relevant child views
+ int minCellsItemCount = 0;
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ // Don't try to expand items that shouldn't.
+ if (!lp.expandable) continue;
+
+ // Mark indices of children that can receive an extra cell.
+ if (lp.cellsUsed < minCells) {
+ minCells = lp.cellsUsed;
+ minCellsAt = 1 << i;
+ minCellsItemCount = 1;
+ } else if (lp.cellsUsed == minCells) {
+ minCellsAt |= 1 << i;
+ minCellsItemCount++;
+ }
+ }
+
+ // Items that get expanded will always be in the set of smallest items when we're done.
+ smallestItemsAt |= minCellsAt;
+
+ if (minCellsItemCount > cellsRemaining) break; // Couldn't expand anything evenly. Stop.
+
+ // We have enough cells, all minimum size items will be incremented.
+ minCells++;
+
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if ((minCellsAt & (1 << i)) == 0) {
+ // If this item is already at our small item count, mark it for later.
+ if (lp.cellsUsed == minCells) smallestItemsAt |= 1 << i;
+ continue;
+ }
+
+ if (centerSingleExpandedItem && lp.preventEdgeOffset && cellsRemaining == 1) {
+ // Add padding to this item such that it centers.
+ child.setPadding(mGeneratedItemPadding + cellSize, 0, mGeneratedItemPadding, 0);
+ }
+ lp.cellsUsed++;
+ lp.expanded = true;
+ cellsRemaining--;
+ }
+
+ needsExpansion = true;
+ }
+
+ // Divide any space left that wouldn't divide along cell boundaries
+ // evenly among the smallest items
+
+ final boolean singleItem = !hasOverflow && visibleItemCount == 1;
+ if (cellsRemaining > 0 && smallestItemsAt != 0 &&
+ (cellsRemaining < visibleItemCount - 1 || singleItem || maxCellsUsed > 1)) {
+ float expandCount = Long.bitCount(smallestItemsAt);
+
+ if (!singleItem) {
+ // The items at the far edges may only expand by half in order to pin to either side.
+ if ((smallestItemsAt & 1) != 0) {
+ LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams();
+ if (!lp.preventEdgeOffset) expandCount -= 0.5f;
+ }
+ if ((smallestItemsAt & (1 << (childCount - 1))) != 0) {
+ LayoutParams lp = ((LayoutParams) getChildAt(childCount - 1).getLayoutParams());
+ if (!lp.preventEdgeOffset) expandCount -= 0.5f;
+ }
+ }
+
+ final int extraPixels = expandCount > 0 ?
+ (int) (cellsRemaining * cellSize / expandCount) : 0;
+
+ for (int i = 0; i < childCount; i++) {
+ if ((smallestItemsAt & (1 << i)) == 0) continue;
+
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (child instanceof ActionMenuItemView) {
+ // If this is one of our views, expand and measure at the larger size.
+ lp.extraPixels = extraPixels;
+ lp.expanded = true;
+ if (i == 0 && !lp.preventEdgeOffset) {
+ // First item gets part of its new padding pushed out of sight.
+ // The last item will get this implicitly from layout.
+ lp.leftMargin = -extraPixels / 2;
+ }
+ needsExpansion = true;
+ } else if (lp.isOverflowButton) {
+ lp.extraPixels = extraPixels;
+ lp.expanded = true;
+ lp.rightMargin = -extraPixels / 2;
+ needsExpansion = true;
+ } else {
+ // If we don't know what it is, give it some margins instead
+ // and let it center within its space. We still want to pin
+ // against the edges.
+ if (i != 0) {
+ lp.leftMargin = extraPixels / 2;
+ }
+ if (i != childCount - 1) {
+ lp.rightMargin = extraPixels / 2;
+ }
+ }
+ }
+
+ cellsRemaining = 0;
+ }
+
+ // Remeasure any items that have had extra space allocated to them.
+ if (needsExpansion) {
+ int heightSpec = MeasureSpec.makeMeasureSpec(heightSize - heightPadding, heightMode);
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ if (!lp.expanded) continue;
+
+ final int width = lp.cellsUsed * cellSize + lp.extraPixels;
+ child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), heightSpec);
+ }
+ }
+
+ if (heightMode != MeasureSpec.EXACTLY) {
+ heightSize = maxChildHeight;
+ }
+
+ setMeasuredDimension(widthSize, heightSize);
+ //UNUSED mMeasuredExtraWidth = cellsRemaining * cellSize;
+ }
+
+ /**
+ * Measure a child view to fit within cell-based formatting. The child's width
+ * will be measured to a whole multiple of cellSize.
+ *
+ * <p>Sets the expandable and cellsUsed fields of LayoutParams.
+ *
+ * @param child Child to measure
+ * @param cellSize Size of one cell
+ * @param cellsRemaining Number of cells remaining that this view can expand to fill
+ * @param parentHeightMeasureSpec MeasureSpec used by the parent view
+ * @param parentHeightPadding Padding present in the parent view
+ * @return Number of cells this child was measured to occupy
+ */
+ static int measureChildForCells(View child, int cellSize, int cellsRemaining,
+ int parentHeightMeasureSpec, int parentHeightPadding) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ final int childHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec) -
+ parentHeightPadding;
+ final int childHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec);
+ final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode);
+
+ int cellsUsed = 0;
+ if (cellsRemaining > 0) {
+ final int childWidthSpec = MeasureSpec.makeMeasureSpec(
+ cellSize * cellsRemaining, MeasureSpec.AT_MOST);
+ child.measure(childWidthSpec, childHeightSpec);
+
+ final int measuredWidth = child.getMeasuredWidth();
+ cellsUsed = measuredWidth / cellSize;
+ if (measuredWidth % cellSize != 0) cellsUsed++;
+ }
+
+ final ActionMenuItemView itemView = child instanceof ActionMenuItemView ?
+ (ActionMenuItemView) child : null;
+ final boolean expandable = !lp.isOverflowButton && itemView != null && itemView.hasText();
+ lp.expandable = expandable;
+
+ lp.cellsUsed = cellsUsed;
+ final int targetWidth = cellsUsed * cellSize;
+ child.measure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
+ childHeightSpec);
+ return cellsUsed;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (!mFormatItems) {
+ super.onLayout(changed, left, top, right, bottom);
+ return;
+ }
+
+ final int childCount = getChildCount();
+ final int midVertical = (top + bottom) / 2;
+ final int dividerWidth = 0;//getDividerWidth();
+ int overflowWidth = 0;
+ //UNUSED int nonOverflowWidth = 0;
+ int nonOverflowCount = 0;
+ int widthRemaining = right - left - getPaddingRight() - getPaddingLeft();
+ boolean hasOverflow = false;
+ for (int i = 0; i < childCount; i++) {
+ final View v = getChildAt(i);
+ if (v.getVisibility() == GONE) {
+ continue;
+ }
+
+ LayoutParams p = (LayoutParams) v.getLayoutParams();
+ if (p.isOverflowButton) {
+ overflowWidth = v.getMeasuredWidth();
+ if (hasDividerBeforeChildAt(i)) {
+ overflowWidth += dividerWidth;
+ }
+
+ int height = v.getMeasuredHeight();
+ int r = getWidth() - getPaddingRight() - p.rightMargin;
+ int l = r - overflowWidth;
+ int t = midVertical - (height / 2);
+ int b = t + height;
+ v.layout(l, t, r, b);
+
+ widthRemaining -= overflowWidth;
+ hasOverflow = true;
+ } else {
+ final int size = v.getMeasuredWidth() + p.leftMargin + p.rightMargin;
+ //UNUSED nonOverflowWidth += size;
+ widthRemaining -= size;
+ if (hasDividerBeforeChildAt(i)) {
+ //UNUSED nonOverflowWidth += dividerWidth;
+ }
+ nonOverflowCount++;
+ }
+ }
+
+ if (childCount == 1 && !hasOverflow) {
+ // Center a single child
+ final View v = getChildAt(0);
+ final int width = v.getMeasuredWidth();
+ final int height = v.getMeasuredHeight();
+ final int midHorizontal = (right - left) / 2;
+ final int l = midHorizontal - width / 2;
+ final int t = midVertical - height / 2;
+ v.layout(l, t, l + width, t + height);
+ return;
+ }
+
+ final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1);
+ final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0);
+
+ int startLeft = getPaddingLeft();
+ for (int i = 0; i < childCount; i++) {
+ final View v = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) v.getLayoutParams();
+ if (v.getVisibility() == GONE || lp.isOverflowButton) {
+ continue;
+ }
+
+ startLeft += lp.leftMargin;
+ int width = v.getMeasuredWidth();
+ int height = v.getMeasuredHeight();
+ int t = midVertical - height / 2;
+ v.layout(startLeft, t, startLeft + width, t + height);
+ startLeft += width + lp.rightMargin + spacerSize;
+ }
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mPresenter.dismissPopupMenus();
+ }
+
+ public boolean isOverflowReserved() {
+ return mReserveOverflow;
+ }
+
+ public void setOverflowReserved(boolean reserveOverflow) {
+ mReserveOverflow = reserveOverflow;
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT);
+ params.gravity = Gravity.CENTER_VERTICAL;
+ return params;
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ if (p instanceof LayoutParams) {
+ LayoutParams result = new LayoutParams((LayoutParams) p);
+ if (result.gravity <= Gravity.NO_GRAVITY) {
+ result.gravity = Gravity.CENTER_VERTICAL;
+ }
+ return result;
+ }
+ return generateDefaultLayoutParams();
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p != null && p instanceof LayoutParams;
+ }
+
+ public LayoutParams generateOverflowButtonLayoutParams() {
+ LayoutParams result = generateDefaultLayoutParams();
+ result.isOverflowButton = true;
+ return result;
+ }
+
+ public boolean invokeItem(MenuItemImpl item) {
+ return mMenu.performItemAction(item, 0);
+ }
+
+ public int getWindowAnimations() {
+ return 0;
+ }
+
+ public void initialize(MenuBuilder menu) {
+ mMenu = menu;
+ }
+
+ //@Override
+ protected boolean hasDividerBeforeChildAt(int childIndex) {
+ final View childBefore = getChildAt(childIndex - 1);
+ final View child = getChildAt(childIndex);
+ boolean result = false;
+ if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
+ result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
+ }
+ if (childIndex > 0 && child instanceof ActionMenuChildView) {
+ result |= ((ActionMenuChildView) child).needsDividerBefore();
+ }
+ return result;
+ }
+
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ return false;
+ }
+
+ public interface ActionMenuChildView {
+ public boolean needsDividerBefore();
+ public boolean needsDividerAfter();
+ }
+
+ public static class LayoutParams extends LinearLayout.LayoutParams {
+ public boolean isOverflowButton;
+ public int cellsUsed;
+ public int extraPixels;
+ public boolean expandable;
+ public boolean preventEdgeOffset;
+
+ public boolean expanded;
+
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ }
+
+ public LayoutParams(LayoutParams other) {
+ super((LinearLayout.LayoutParams) other);
+ isOverflowButton = other.isOverflowButton;
+ }
+
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ isOverflowButton = false;
+ }
+
+ public LayoutParams(int width, int height, boolean isOverflowButton) {
+ super(width, height);
+ this.isOverflowButton = isOverflowButton;
+ }
+ }
+}