private void onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec) { // We already know the width mode is EXACTLY if we're here. int heightMode = android.view.View.MeasureSpec.getMode(heightMeasureSpec); int widthSize = android.view.View.MeasureSpec.getSize(widthMeasureSpec); int heightSize = android.view.View.MeasureSpec.getSize(heightMeasureSpec); int widthPadding = getPaddingLeft() + getPaddingRight(); int heightPadding = getPaddingTop() + getPaddingBottom(); widthSize -= widthPadding; // Divide the view into cells. int cellCount = widthSize / mMinCellSize; int cellSizeRemaining = widthSize % mMinCellSize; if (cellCount == 0) { // Give up, nothing fits. setMeasuredDimension(widthSize, 0); return; } int cellSize = mMinCellSize + cellSizeRemaining / cellCount; int cellsRemaining = cellCount; int maxChildHeight = 0; int maxCellsUsed = 0; int expandableItemCount = 0; int visibleItemCount = 0; bool hasOverflow = false; // This is used as a bitfield to locate the smallest items present. Assumes childCount < 64. long smallestItemsAt = 0; int childCount = getChildCount(); { for (int i = 0; i < childCount; i++) { android.view.View child = getChildAt(i); if (child.getVisibility() == GONE) { continue; } bool isGeneratedItem = child is [email protected]; visibleItemCount++; if (isGeneratedItem) { // Reset padding for generated menu item views; it may change below // and views are recycled. child.setPadding(mGeneratedItemPadding, 0, mGeneratedItemPadding, 0); } [email protected] lp = ([email protected] .LayoutParams)child.getLayoutParams(); lp.expanded = false; lp.extraPixels = 0; lp.cellsUsed = 0; lp.expandable = false; lp.leftMargin = 0; lp.rightMargin = 0; lp.preventEdgeOffset = isGeneratedItem && (([email protected] )child).hasText(); // Overflow always gets 1 cell. No more, no less. int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining; int cellsUsed = measureChildForCells(child, cellSize, cellsAvailable, heightMeasureSpec , heightPadding); maxCellsUsed = System.Math.Max(maxCellsUsed, cellsUsed); if (lp.expandable) { expandableItemCount++; } if (lp.isOverflowButton) { hasOverflow = true; } cellsRemaining -= cellsUsed; maxChildHeight = System.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. bool 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. bool needsExpansion = false; while (expandableItemCount > 0 && cellsRemaining > 0) { int minCells = int.MaxValue; long minCellsAt = 0; // Bit locations are indices of relevant child views int minCellsItemCount = 0; { for (int i_1 = 0; i_1 < childCount; i_1++) { android.view.View child = getChildAt(i_1); [email protected] lp = ([email protected] .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_1; minCellsItemCount = 1; } else { if (lp.cellsUsed == minCells) { minCellsAt |= 1 << i_1; 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_2 = 0; i_2 < childCount; i_2++) { android.view.View child = getChildAt(i_2); [email protected] lp = ([email protected] .LayoutParams)child.getLayoutParams(); if ((minCellsAt & (1 << i_2)) == 0) { // If this item is already at our small item count, mark it for later. if (lp.cellsUsed == minCells) { smallestItemsAt |= 1 << i_2; } 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 bool singleItem = !hasOverflow && visibleItemCount == 1; if (cellsRemaining > 0 && smallestItemsAt != 0 && (cellsRemaining < visibleItemCount - 1 || singleItem || maxCellsUsed > 1)) { float expandCount = Sharpen.Util.Long_GetBitCount(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) { [email protected] lp = ([email protected] .LayoutParams)getChildAt(0).getLayoutParams(); if (!lp.preventEdgeOffset) { expandCount -= 0.5f; } } if ((smallestItemsAt & (1 << (childCount - 1))) != 0) { [email protected] lp = (([email protected] .LayoutParams)getChildAt(childCount - 1).getLayoutParams()); if (!lp.preventEdgeOffset) { expandCount -= 0.5f; } } } int extraPixels = expandCount > 0 ? (int)(cellsRemaining * cellSize / expandCount ) : 0; { for (int i_1 = 0; i_1 < childCount; i_1++) { if ((smallestItemsAt & (1 << i_1)) == 0) { continue; } android.view.View child = getChildAt(i_1); [email protected] lp = ([email protected] .LayoutParams)child.getLayoutParams(); if (child is [email protected]) { // If this is one of our views, expand and measure at the larger size. lp.extraPixels = extraPixels; lp.expanded = true; if (i_1 == 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_1 != 0) { lp.leftMargin = extraPixels / 2; } if (i_1 != childCount - 1) { lp.rightMargin = extraPixels / 2; } } } } } cellsRemaining = 0; } // Remeasure any items that have had extra space allocated to them. if (needsExpansion) { int heightSpec = android.view.View.MeasureSpec.makeMeasureSpec(heightSize - heightPadding , heightMode); { for (int i_1 = 0; i_1 < childCount; i_1++) { android.view.View child = getChildAt(i_1); [email protected] lp = ([email protected] .LayoutParams)child.getLayoutParams(); if (!lp.expanded) { continue; } int width = lp.cellsUsed * cellSize + lp.extraPixels; child.measure(android.view.View.MeasureSpec.makeMeasureSpec(width, android.view.View .MeasureSpec.EXACTLY), heightSpec); } } } if (heightMode != android.view.View.MeasureSpec.EXACTLY) { heightSize = maxChildHeight; } setMeasuredDimension(widthSize, heightSize); mMeasuredExtraWidth = cellsRemaining * cellSize; }