Project: ActionBarSherlock
Code Examples
/*
 * 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)) == 0continue
 
                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) { 
        if (childIndex == 0) { 
            return false
        } 
        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; 
        } 
    } 
}