diff --git a/res/values/attrs.xml b/res/values/attrs.xml index e593fb497d..0adde81be0 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -135,6 +135,16 @@ + + + + + + + + + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index eaf7a5ff8c..1ce7840ae5 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -20,12 +20,14 @@ 8dp - 8dp + 7dp 8dp + 16dp 5.5dp 8dp + 7dp 8dp diff --git a/res/xml/size_limits.xml b/res/xml/size_limits.xml new file mode 100644 index 0000000000..ba570146de --- /dev/null +++ b/res/xml/size_limits.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java index bc3e341806..1330ed4edd 100644 --- a/src/com/android/launcher3/AppWidgetResizeFrame.java +++ b/src/com/android/launcher3/AppWidgetResizeFrame.java @@ -279,8 +279,9 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O * Based on the current deltas, we determine if and how to resize the widget. */ private void resizeWidgetIfNeeded(boolean onDismiss) { - float xThreshold = mCellLayout.getCellWidth(); - float yThreshold = mCellLayout.getCellHeight(); + DeviceProfile dp = mLauncher.getDeviceProfile(); + float xThreshold = mCellLayout.getCellWidth() + dp.cellLayoutBorderSpacingPx; + float yThreshold = mCellLayout.getCellHeight() + dp.cellLayoutBorderSpacingPx; int hSpanInc = getSpanIncrement((mDeltaX + mDeltaXAddOn) / xThreshold - mRunningHInc); int vSpanInc = getSpanIncrement((mDeltaY + mDeltaYAddOn) / yThreshold - mRunningVInc); @@ -364,13 +365,18 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O final float density = context.getResources().getDisplayMetrics().density; final Point[] cellSize = CELL_SIZE.get(context); + final int borderSpacing = context.getResources() + .getDimensionPixelSize(R.dimen.dynamic_grid_cell_border_spacing); + final float hBorderSpacing = (spanX - 1) * borderSpacing; + final float vBorderSpacing = (spanY - 1) * borderSpacing; + // Compute landscape size - int landWidth = (int) ((spanX * cellSize[0].x) / density); - int landHeight = (int) ((spanY * cellSize[0].y) / density); + int landWidth = (int) (((spanX * cellSize[0].x) + hBorderSpacing) / density); + int landHeight = (int) (((spanY * cellSize[0].y) + vBorderSpacing) / density); // Compute portrait size - int portWidth = (int) ((spanX * cellSize[1].x) / density); - int portHeight = (int) ((spanY * cellSize[1].y) / density); + int portWidth = (int) (((spanX * cellSize[1].x) + hBorderSpacing) / density); + int portHeight = (int) (((spanY * cellSize[1].y) + vBorderSpacing) / density); rect.set(portWidth, landHeight, landWidth, portHeight); return rect; } @@ -384,8 +390,9 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O } private void onTouchUp() { - int xThreshold = mCellLayout.getCellWidth(); - int yThreshold = mCellLayout.getCellHeight(); + DeviceProfile dp = mLauncher.getDeviceProfile(); + int xThreshold = mCellLayout.getCellWidth() + dp.cellLayoutBorderSpacingPx; + int yThreshold = mCellLayout.getCellHeight() + dp.cellLayoutBorderSpacingPx; mDeltaXAddOn = mRunningHInc * xThreshold; mDeltaYAddOn = mRunningVInc * yThreshold; diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 21297c9f05..b05a13a5b0 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -17,6 +17,7 @@ package com.android.launcher3; import static com.android.launcher3.FastBitmapDrawable.newIcon; +import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS; import static com.android.launcher3.graphics.IconShape.getShape; import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon; import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; @@ -198,6 +199,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx); setCompoundDrawablePadding(grid.iconDrawablePaddingPx); defaultIconSize = grid.iconSizePx; + setCenterVertically(ENABLE_FOUR_COLUMNS.get()); } else if (mDisplay == DISPLAY_ALL_APPS) { setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx); setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx); @@ -232,7 +234,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, int shadowSize = context.getResources().getDimensionPixelSize( R.dimen.blur_size_click_shadow); mHighlightShadowFilter = new BlurMaskFilter(shadowSize, BlurMaskFilter.Blur.INNER); - } @Override @@ -554,6 +555,14 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, outBounds.set(left, top, right, bottom); } + + /** + * Sets whether to vertically center the content. + */ + public void setCenterVertically(boolean centerVertically) { + mCenterVertically = centerVertically; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mCenterVertically) { diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 452207db3b..29812fda9f 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -88,6 +88,8 @@ public class CellLayout extends ViewGroup { @Thunk int mCellHeight; private int mFixedCellWidth; private int mFixedCellHeight; + @ViewDebug.ExportedProperty(category = "launcher") + private final int mBorderSpacing; @ViewDebug.ExportedProperty(category = "launcher") private int mCountX; @@ -208,6 +210,7 @@ public class CellLayout extends ViewGroup { DeviceProfile grid = mActivity.getDeviceProfile(); + mBorderSpacing = grid.cellLayoutBorderSpacingPx; mCellWidth = mCellHeight = -1; mFixedCellWidth = mFixedCellHeight = -1; @@ -288,7 +291,8 @@ public class CellLayout extends ViewGroup { } mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context, mContainerType); - mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY); + mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY, + mBorderSpacing); addView(mShortcutsAndWidgets); } @@ -345,7 +349,8 @@ public class CellLayout extends ViewGroup { public void setCellDimensions(int width, int height) { mFixedCellWidth = mCellWidth = width; mFixedCellHeight = mCellHeight = height; - mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY); + mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY, + mBorderSpacing); } public void setGridSize(int x, int y) { @@ -354,7 +359,8 @@ public class CellLayout extends ViewGroup { mOccupied = new GridOccupancy(mCountX, mCountY); mTmpOccupied = new GridOccupancy(mCountX, mCountY); mTempRectStack.clear(); - mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY); + mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY, + mBorderSpacing); requestLayout(); } @@ -475,8 +481,8 @@ public class CellLayout extends ViewGroup { for (int j = 0; j < mCountY; j++) { canvas.save(); - int transX = i * mCellWidth; - int transY = j * mCellHeight; + int transX = i * mCellWidth + (i * mBorderSpacing); + int transY = j * mCellHeight + (j * mBorderSpacing); canvas.translate(getPaddingLeft() + transX, getPaddingTop() + transY); @@ -591,6 +597,7 @@ public class CellLayout extends ViewGroup { if (child instanceof BubbleTextView) { BubbleTextView bubbleChild = (BubbleTextView) child; bubbleChild.setTextVisibility(mContainerType != HOTSEAT); + bubbleChild.setCenterVertically(mContainerType != HOTSEAT); } child.setScaleX(mChildScale); @@ -706,11 +713,9 @@ public class CellLayout extends ViewGroup { * @param result Array of 2 ints to hold the x and y coordinate of the point */ void cellToPoint(int cellX, int cellY, int[] result) { - final int hStartPadding = getPaddingLeft(); - final int vStartPadding = getPaddingTop(); - - result[0] = hStartPadding + cellX * mCellWidth; - result[1] = vStartPadding + cellY * mCellHeight; + cellToRect(cellX, cellY, 1, 1, mTempRect); + result[0] = mTempRect.left; + result[1] = mTempRect.top; } /** @@ -734,25 +739,9 @@ public class CellLayout extends ViewGroup { * @param result Array of 2 ints to hold the x and y coordinate of the point */ void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) { - final int hStartPadding = getPaddingLeft(); - final int vStartPadding = getPaddingTop(); - result[0] = hStartPadding + cellX * mCellWidth + (spanX * mCellWidth) / 2; - result[1] = vStartPadding + cellY * mCellHeight + (spanY * mCellHeight) / 2; - } - - /** - * Given a cell coordinate and span fills out a corresponding pixel rect - * - * @param cellX X coordinate of the cell - * @param cellY Y coordinate of the cell - * @param result Rect in which to write the result - */ - void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) { - final int hStartPadding = getPaddingLeft(); - final int vStartPadding = getPaddingTop(); - final int left = hStartPadding + cellX * mCellWidth; - final int top = vStartPadding + cellY * mCellHeight; - result.set(left, top, left + (spanX * mCellWidth), top + (spanY * mCellHeight)); + cellToRect(cellX, cellY, spanX, spanY, mTempRect); + result[0] = mTempRect.centerX(); + result[1] = mTempRect.centerY(); } public float getDistanceFromCell(float x, float y, int[] cell) { @@ -783,12 +772,15 @@ public class CellLayout extends ViewGroup { int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom()); if (mFixedCellWidth < 0 || mFixedCellHeight < 0) { - int cw = DeviceProfile.calculateCellWidth(childWidthSize, mCountX); - int ch = DeviceProfile.calculateCellHeight(childHeightSize, mCountY); + int cw = DeviceProfile.calculateCellWidth(childWidthSize, mBorderSpacing, + mCountX); + int ch = DeviceProfile.calculateCellHeight(childHeightSize, mBorderSpacing, + mCountY); if (cw != mCellWidth || ch != mCellHeight) { mCellWidth = cw; mCellHeight = ch; - mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY); + mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY, + mBorderSpacing); } } @@ -838,10 +830,11 @@ public class CellLayout extends ViewGroup { /** * Returns the amount of space left over after subtracting padding and cells. This space will be * very small, a few pixels at most, and is a result of rounding down when calculating the cell - * width in {@link DeviceProfile#calculateCellWidth(int, int)}. + * width in {@link DeviceProfile#calculateCellWidth(int, int, int)}. */ public int getUnusedHorizontalSpace() { - return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth); + return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth) + - ((mCountX - 1) * mBorderSpacing); } public Drawable getScrimBackground() { @@ -857,8 +850,8 @@ public class CellLayout extends ViewGroup { return mShortcutsAndWidgets; } - public View getChildAt(int x, int y) { - return mShortcutsAndWidgets.getChildAt(x, y); + public View getChildAt(int cellX, int cellY) { + return mShortcutsAndWidgets.getChildAt(cellX, cellY); } public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration, @@ -989,11 +982,11 @@ public class CellLayout extends ViewGroup { } // Center horizontaly - left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2; + left += (r.width() - dragOutline.getWidth()) / 2; if (v != null && v.getViewType() == DraggableView.DRAGGABLE_WIDGET) { // Center vertically - top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2; + top += (r.height() - dragOutline.getHeight()) / 2; } else if (v != null && v.getViewType() == DraggableView.DRAGGABLE_ICON) { int cHeight = getShortcutsAndWidgets().getCellContentHeight(); int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f)); @@ -2153,7 +2146,7 @@ public class CellLayout extends ViewGroup { findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination); Rect dragRect = new Rect(); - regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect); + cellToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect); dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY()); Rect dropRegionRect = new Rect(); @@ -2163,7 +2156,7 @@ public class CellLayout extends ViewGroup { int dropRegionSpanX = dropRegionRect.width(); int dropRegionSpanY = dropRegionRect.height(); - regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(), + cellToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(), dropRegionRect.height(), dropRegionRect); int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX; @@ -2521,10 +2514,11 @@ public class CellLayout extends ViewGroup { final int hStartPadding = getPaddingLeft(); final int vStartPadding = getPaddingTop(); - int width = cellHSpan * cellWidth; - int height = cellVSpan * cellHeight; - int x = hStartPadding + cellX * cellWidth; - int y = vStartPadding + cellY * cellHeight; + int x = hStartPadding + (cellX * mBorderSpacing) + (cellX * cellWidth); + int y = vStartPadding + (cellY * mBorderSpacing) + (cellY * cellHeight); + + int width = cellHSpan * cellWidth + ((cellHSpan - 1) * mBorderSpacing); + int height = cellVSpan * cellHeight + ((cellVSpan - 1) * mBorderSpacing); resultRect.set(x, y, x + width, y + height); } @@ -2542,11 +2536,13 @@ public class CellLayout extends ViewGroup { } public int getDesiredWidth() { - return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth); + return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) + + ((mCountX - 1) * mBorderSpacing); } public int getDesiredHeight() { - return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight); + return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) + + ((mCountY - 1) * mBorderSpacing); } public boolean isOccupied(int x, int y) { @@ -2661,19 +2657,21 @@ public class CellLayout extends ViewGroup { this.cellVSpan = cellVSpan; } - public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount) { - setup(cellWidth, cellHeight, invertHorizontally, colCount, 1.0f, 1.0f); + public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount, + int rowCount, int borderSpacing) { + setup(cellWidth, cellHeight, invertHorizontally, colCount, rowCount, 1.0f, 1.0f, + borderSpacing); } /** - * Use this method, as opposed to {@link #setup(int, int, boolean, int)}, if the view needs - * to be scaled. + * Use this method, as opposed to {@link #setup(int, int, boolean, int, int, int)}, + * if the view needs to be scaled. * * ie. In multi-window mode, we setup widgets so that they are measured and laid out * using their full/invariant device profile sizes. */ public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount, - float cellScaleX, float cellScaleY) { + int rowCount, float cellScaleX, float cellScaleY, int borderSpacing) { if (isLockedToGrid) { final int myCellHSpan = cellHSpan; final int myCellVSpan = cellVSpan; @@ -2684,17 +2682,23 @@ public class CellLayout extends ViewGroup { myCellX = colCount - myCellX - cellHSpan; } - width = (int) (myCellHSpan * cellWidth / cellScaleX - leftMargin - rightMargin); - height = (int) (myCellVSpan * cellHeight / cellScaleY - topMargin - bottomMargin); - x = (myCellX * cellWidth + leftMargin); - y = (myCellY * cellHeight + topMargin); + int hBorderSpacing = (myCellHSpan - 1) * borderSpacing; + int vBorderSpacing = (myCellVSpan - 1) * borderSpacing; + + float myCellWidth = ((myCellHSpan * cellWidth) + hBorderSpacing) / cellScaleX; + float myCellHeight = ((myCellVSpan * cellHeight) + vBorderSpacing) / cellScaleY; + + width = Math.round(myCellWidth) - leftMargin - rightMargin; + height = Math.round(myCellHeight) - topMargin - bottomMargin; + x = leftMargin + (myCellX * cellWidth) + (myCellX * borderSpacing); + y = topMargin + (myCellY * cellHeight) + (myCellY * borderSpacing); } } /** * Sets the position to the provided point */ - public void setXY(Point point) { + public void setCellXY(Point point) { cellX = point.x; cellY = point.y; } diff --git a/src/com/android/launcher3/DevicePaddings.java b/src/com/android/launcher3/DevicePaddings.java new file mode 100644 index 0000000000..4827f362c8 --- /dev/null +++ b/src/com/android/launcher3/DevicePaddings.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2021 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.android.launcher3; + +import android.content.Context; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.util.AttributeSet; +import android.util.Log; +import android.util.TypedValue; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * Workspace items have a fixed height, so we need a way to distribute any unused workspace height. + * + * The unused or "extra" height is allocated to three different variable heights: + * - The space above the workspace + * - The space between the workspace and hotseat + * - The espace below the hotseat + */ +public class DevicePaddings { + + private static final String DEVICE_PADDING = "device-paddings"; + private static final String DEVICE_PADDINGS = "device-padding"; + + private static final String WORKSPACE_TOP_PADDING = "workspaceTopPadding"; + private static final String WORKSPACE_BOTTOM_PADDING = "workspaceBottomPadding"; + private static final String HOTSEAT_BOTTOM_PADDING = "hotseatBottomPadding"; + + private static final String TAG = DevicePaddings.class.getSimpleName(); + private static final boolean DEBUG = false; + + ArrayList mDevicePaddings = new ArrayList<>(); + + public DevicePaddings(Context context) { + try (XmlResourceParser parser = context.getResources().getXml(R.xml.size_limits)) { + final int depth = parser.getDepth(); + int type; + while (((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { + if ((type == XmlPullParser.START_TAG) && DEVICE_PADDING.equals(parser.getName())) { + final int displayDepth = parser.getDepth(); + while (((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > displayDepth) + && type != XmlPullParser.END_DOCUMENT) { + if ((type == XmlPullParser.START_TAG) + && DEVICE_PADDINGS.equals(parser.getName())) { + TypedArray a = context.obtainStyledAttributes( + Xml.asAttributeSet(parser), R.styleable.DevicePadding); + int maxWidthPx = a.getDimensionPixelSize( + R.styleable.DevicePadding_maxEmptySpace, 0); + a.recycle(); + + PaddingFormula workspaceTopPadding = null; + PaddingFormula workspaceBottomPadding = null; + PaddingFormula hotseatBottomPadding = null; + + final int limitDepth = parser.getDepth(); + while (((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > limitDepth) + && type != XmlPullParser.END_DOCUMENT) { + AttributeSet attr = Xml.asAttributeSet(parser); + if ((type == XmlPullParser.START_TAG)) { + if (WORKSPACE_TOP_PADDING.equals(parser.getName())) { + workspaceTopPadding = new PaddingFormula(context, attr); + } else if (WORKSPACE_BOTTOM_PADDING.equals(parser.getName())) { + workspaceBottomPadding = new PaddingFormula(context, attr); + } else if (HOTSEAT_BOTTOM_PADDING.equals(parser.getName())) { + hotseatBottomPadding = new PaddingFormula(context, attr); + } + } + } + + if (workspaceTopPadding == null + || workspaceBottomPadding == null + || hotseatBottomPadding == null) { + throw new RuntimeException("DevicePadding missing padding."); + } + + mDevicePaddings.add(new DevicePadding(maxWidthPx, workspaceTopPadding, + workspaceBottomPadding, hotseatBottomPadding)); + } + } + } + } + } catch (IOException | XmlPullParserException e) { + throw new RuntimeException(e); + } + + // Sort ascending by maxEmptySpacePx + mDevicePaddings.sort((sl1, sl2) -> Integer.compare(sl1.maxEmptySpacePx, + sl2.maxEmptySpacePx)); + } + + public DevicePadding getDevicePadding(int extraSpacePx) { + for (DevicePadding limit : mDevicePaddings) { + if (extraSpacePx <= limit.maxEmptySpacePx) { + return limit; + } + } + + return mDevicePaddings.get(mDevicePaddings.size() - 1); + } + + /** + * Holds all the formulas to calculate the padding for a particular device based on the + * amount of extra space. + */ + public static final class DevicePadding { + + private final int maxEmptySpacePx; + private final PaddingFormula workspaceTopPadding; + private final PaddingFormula workspaceBottomPadding; + private final PaddingFormula hotseatBottomPadding; + + public DevicePadding(int maxEmptySpacePx, + PaddingFormula workspaceTopPadding, + PaddingFormula workspaceBottomPadding, + PaddingFormula hotseatBottomPadding) { + this.maxEmptySpacePx = maxEmptySpacePx; + this.workspaceTopPadding = workspaceTopPadding; + this.workspaceBottomPadding = workspaceBottomPadding; + this.hotseatBottomPadding = hotseatBottomPadding; + } + + public int getWorkspaceTopPadding(int extraSpacePx) { + return workspaceTopPadding.calculate(extraSpacePx); + } + + public int getWorkspaceBottomPadding(int extraSpacePx) { + return workspaceBottomPadding.calculate(extraSpacePx); + } + + public int getHotseatBottomPadding(int extraSpacePx) { + return hotseatBottomPadding.calculate(extraSpacePx); + } + } + + /** + * Used to calculate a padding based on three variables: a, b, and c. + * + * Calculation: a * (extraSpace - c) + b + */ + private static final class PaddingFormula { + + private final float a; + private final float b; + private final float c; + + public PaddingFormula(Context context, AttributeSet attrs) { + TypedArray t = context.obtainStyledAttributes(attrs, + R.styleable.DevicePaddingFormula); + + a = getValue(t, R.styleable.DevicePaddingFormula_a); + b = getValue(t, R.styleable.DevicePaddingFormula_b); + c = getValue(t, R.styleable.DevicePaddingFormula_c); + + t.recycle(); + } + + public int calculate(int extraSpacePx) { + if (DEBUG) { + Log.d(TAG, "a=" + a + " * (" + extraSpacePx + " - " + c + ") + b=" + b); + } + return Math.round(a * (extraSpacePx - c) + b); + } + + private static float getValue(TypedArray a, int index) { + if (a.getType(index) == TypedValue.TYPE_DIMENSION) { + return a.getDimensionPixelSize(index, 0); + } else if (a.getType(index) == TypedValue.TYPE_FLOAT) { + return a.getFloat(index, 0); + } + return 0; + } + + @Override + public String toString() { + return "a=" + a + ", b=" + b + ", c=" + c; + } + } +} diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 4d5bd5d95f..19915b7026 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -16,6 +16,8 @@ package com.android.launcher3; +import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS; + import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -25,6 +27,7 @@ import android.graphics.Rect; import android.view.Surface; import com.android.launcher3.CellLayout.ContainerType; +import com.android.launcher3.DevicePaddings.DevicePadding; import com.android.launcher3.graphics.IconShape; import com.android.launcher3.icons.DotRenderer; import com.android.launcher3.icons.IconNormalizer; @@ -74,12 +77,16 @@ public class DeviceProfile { // Workspace public final int desiredWorkspaceLeftRightMarginPx; + public final int cellLayoutBorderSpacingPx; public final int cellLayoutPaddingLeftRightPx; public final int cellLayoutBottomPaddingPx; public final int edgeMarginPx; public float workspaceSpringLoadShrinkFactor; public final int workspaceSpringLoadedBottomSpace; + public int workspaceTopPadding; + public int workspaceBottomPadding; + // Workspace page indicator public final int workspacePageIndicatorHeight; private final int mWorkspacePageIndicatorOverlapWorkspace; @@ -92,6 +99,7 @@ public class DeviceProfile { public int cellWidthPx; public int cellHeightPx; + public int cellYPaddingPx; public int workspaceCellPaddingXPx; // Folder @@ -187,6 +195,11 @@ public class DeviceProfile { edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin); desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : edgeMarginPx; + cellYPaddingPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_y); + cellLayoutBorderSpacingPx = isVerticalBarLayout() + || isMultiWindowMode + || !ENABLE_FOUR_COLUMNS.get() + ? 0 : res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_border_spacing); int cellLayoutPaddingLeftRightMultiplier = !isVerticalBarLayout() && isTablet ? PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER : 1; int cellLayoutPadding = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding); @@ -220,22 +233,31 @@ public class DeviceProfile { res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding); // Add a bit of space between nav bar and hotseat in vertical bar layout. hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0; + int hotseatExtraVerticalSize = + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size); hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, mInfo.metrics) + (isVerticalBarLayout() ? (hotseatBarSidePaddingStartPx + hotseatBarSidePaddingEndPx) - : (res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size) - + hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx)); + : (hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx + + (ENABLE_FOUR_COLUMNS.get() ? 0 : hotseatExtraVerticalSize))); // Calculate all of the remaining variables. - updateAvailableDimensions(res); - + int extraSpace = updateAvailableDimensions(res); // Now that we have all of the variables calculated, we can tune certain sizes. - if (!isVerticalBarLayout() && isPhone && isTallDevice) { + if (ENABLE_FOUR_COLUMNS.get()) { + DevicePadding padding = inv.devicePaddings.getDevicePadding(extraSpace); + workspaceTopPadding = padding.getWorkspaceTopPadding(extraSpace); + workspaceBottomPadding = padding.getWorkspaceBottomPadding(extraSpace); + + float hotseatBarBottomPadding = padding.getHotseatBottomPadding(extraSpace); + hotseatBarSizePx += hotseatBarBottomPadding; + hotseatBarBottomPaddingPx += hotseatBarBottomPadding; + } else if (!isVerticalBarLayout() && isPhone && isTallDevice) { // We increase the hotseat size when there is extra space. // ie. For a display with a large aspect ratio, we can keep the icons on the workspace // in portrait mode closer together by adding more height to the hotseat. // Note: This calculation was created after noticing a pattern in the design spec. - int extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2 + extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2 - workspacePageIndicatorHeight; hotseatBarSizePx += extraSpace; hotseatBarBottomPaddingPx += extraSpace; @@ -328,17 +350,24 @@ public class DeviceProfile { + topBottomPadding * 2; } - private void updateAvailableDimensions(Resources res) { + /** + * Returns the amount of extra (or unused) vertical space. + */ + private int updateAvailableDimensions(Resources res) { updateIconSize(1f, res); // Check to see if the icons fit within the available height. If not, then scale down. - float usedHeight = (cellHeightPx * inv.numRows); + float usedHeight = (cellHeightPx * inv.numRows) + + (cellLayoutBorderSpacingPx * (inv.numRows - 1)); int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y); + float extraHeight = Math.max(0, maxHeight - usedHeight); if (usedHeight > maxHeight) { float scale = maxHeight / usedHeight; updateIconSize(scale, res); + extraHeight = 0; } updateAvailableFolderCellDimensions(res); + return Math.round(extraHeight); } /** @@ -355,16 +384,23 @@ public class DeviceProfile { iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, mInfo.metrics) * scale); iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale); - cellHeightPx = iconSizePx + iconDrawablePaddingPx - + Utilities.calculateTextHeight(iconTextSizePx); - int cellYPadding = (getCellSize().y - cellHeightPx) / 2; - if (iconDrawablePaddingPx > cellYPadding && !isVerticalLayout - && !isMultiWindowMode) { - // Ensures that the label is closer to its corresponding icon. This is not an issue - // with vertical bar layout or multi-window mode since the issue is handled separately - // with their calls to {@link #adjustToHideWorkspaceLabels}. - cellHeightPx -= (iconDrawablePaddingPx - cellYPadding); - iconDrawablePaddingPx = cellYPadding; + if (ENABLE_FOUR_COLUMNS.get()) { + cellHeightPx = iconSizePx + iconDrawablePaddingPx + + Utilities.calculateTextHeight(iconTextSizePx) + + (cellYPaddingPx * 2); + } else { + cellYPaddingPx = 0; + cellHeightPx = iconSizePx + iconDrawablePaddingPx + + Utilities.calculateTextHeight(iconTextSizePx); + int cellPaddingY = (getCellSize().y - cellHeightPx) / 2; + if (iconDrawablePaddingPx > cellPaddingY && !isVerticalLayout + && !isMultiWindowMode) { + // Ensures that the label is closer to its corresponding icon. This is not an issue + // with vertical bar layout or multi-window mode since the issue is handled + // separately with their calls to {@link #adjustToHideWorkspaceLabels}. + cellHeightPx -= (iconDrawablePaddingPx - cellPaddingY); + iconDrawablePaddingPx = cellPaddingY; + } } cellWidthPx = iconSizePx + iconDrawablePaddingPx; @@ -425,13 +461,15 @@ public class DeviceProfile { Point totalWorkspacePadding = getTotalWorkspacePadding(); // Check if the icons fit within the available height. - float contentUsedHeight = folderCellHeightPx * inv.numFolderRows; + float contentUsedHeight = folderCellHeightPx * inv.numFolderRows + + ((inv.numFolderRows - 1) * cellLayoutBorderSpacingPx); int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y - folderBottomPanelSize - folderMargin; float scaleY = contentMaxHeight / contentUsedHeight; // Check if the icons fit within the available width. - float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns; + float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns + + ((inv.numFolderColumns - 1) * cellLayoutBorderSpacingPx); int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin; float scaleX = contentMaxWidth / contentUsedWidth; @@ -479,9 +517,9 @@ public class DeviceProfile { // not matter. Point padding = getTotalWorkspacePadding(); result.x = calculateCellWidth(availableWidthPx - padding.x - - cellLayoutPaddingLeftRightPx * 2, numColumns); + - cellLayoutPaddingLeftRightPx * 2, cellLayoutBorderSpacingPx, numColumns); result.y = calculateCellHeight(availableHeightPx - padding.y - - cellLayoutBottomPaddingPx, numRows); + - cellLayoutBottomPaddingPx, cellLayoutBorderSpacingPx, numRows); return result; } @@ -509,7 +547,7 @@ public class DeviceProfile { } } else { int paddingBottom = hotseatBarSizePx + workspacePageIndicatorHeight - - mWorkspacePageIndicatorOverlapWorkspace; + + workspaceBottomPadding - mWorkspacePageIndicatorOverlapWorkspace; if (isTablet) { // Pad the left and right of the workspace to ensure consistent spacing // between all icons @@ -526,7 +564,7 @@ public class DeviceProfile { } else { // Pad the top and bottom of the workspace with search/hotseat bar sizes padding.set(desiredWorkspaceLeftRightMarginPx, - edgeMarginPx, + workspaceTopPadding + edgeMarginPx, desiredWorkspaceLeftRightMarginPx, paddingBottom); } @@ -581,11 +619,11 @@ public class DeviceProfile { } } - public static int calculateCellWidth(int width, int countX) { - return width / countX; + public static int calculateCellWidth(int width, int borderSpacing, int countX) { + return (width - ((countX - 1) * borderSpacing)) / countX; } - public static int calculateCellHeight(int height, int countY) { - return height / countY; + public static int calculateCellHeight(int height, int borderSpacing, int countY) { + return (height - ((countY - 1) * borderSpacing)) / countY; } /** diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index aa3ef9b139..2a08c50008 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -130,6 +130,8 @@ public class InvariantDeviceProfile { public DeviceProfile landscapeProfile; public DeviceProfile portraitProfile; + public DevicePaddings devicePaddings; + public Point defaultWallpaperSize; public Rect defaultWidgetPadding; @@ -159,6 +161,7 @@ public class InvariantDeviceProfile { demoModeLayoutId = p.demoModeLayoutId; mExtraAttrs = p.mExtraAttrs; mOverlayMonitor = p.mOverlayMonitor; + devicePaddings = p.devicePaddings; } @TargetApi(23) @@ -210,6 +213,8 @@ public class InvariantDeviceProfile { result.landscapeIconSize = defaultDisplayOption.landscapeIconSize; result.allAppsIconSize = Math.min( defaultDisplayOption.allAppsIconSize, myDisplayOption.allAppsIconSize); + + devicePaddings = new DevicePaddings(context); initGrid(context, myInfo, result); } @@ -237,6 +242,7 @@ public class InvariantDeviceProfile { ArrayList allOptions = getPredefinedDeviceProfiles(context, gridName); DisplayOption displayOption = invDistWeightedInterpolate(displayInfo, allOptions); + devicePaddings = new DevicePaddings(context); initGrid(context, displayInfo, displayOption); return displayOption.grid.name; } diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java index ee0c7bb2f9..1c5081cfca 100644 --- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java +++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java @@ -42,8 +42,10 @@ public class ShortcutAndWidgetContainer extends ViewGroup { private int mCellWidth; private int mCellHeight; + private int mBorderSpacing; private int mCountX; + private int mCountY; private final ActivityContext mActivity; private boolean mInvertIfRtl = false; @@ -55,20 +57,23 @@ public class ShortcutAndWidgetContainer extends ViewGroup { mContainerType = containerType; } - public void setCellDimensions(int cellWidth, int cellHeight, int countX, int countY) { + public void setCellDimensions(int cellWidth, int cellHeight, int countX, int countY, + int borderSpacing) { mCellWidth = cellWidth; mCellHeight = cellHeight; mCountX = countX; + mCountY = countY; + mBorderSpacing = borderSpacing; } - public View getChildAt(int x, int y) { + public View getChildAt(int cellX, int cellY) { final int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); - if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) && - (lp.cellY <= y) && (y < lp.cellY + lp.cellVSpan)) { + if ((lp.cellX <= cellX) && (cellX < lp.cellX + lp.cellHSpan) + && (lp.cellY <= cellY) && (cellY < lp.cellY + lp.cellVSpan)) { return child; } } @@ -95,10 +100,11 @@ public class ShortcutAndWidgetContainer extends ViewGroup { CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); if (child instanceof LauncherAppWidgetHostView) { DeviceProfile profile = mActivity.getDeviceProfile(); - lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, - profile.appWidgetScale.x, profile.appWidgetScale.y); + lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY, + profile.appWidgetScale.x, profile.appWidgetScale.y, mBorderSpacing); } else { - lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX); + lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY, + mBorderSpacing); } } @@ -117,11 +123,12 @@ public class ShortcutAndWidgetContainer extends ViewGroup { final DeviceProfile profile = mActivity.getDeviceProfile(); if (child instanceof LauncherAppWidgetHostView) { - lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, - profile.appWidgetScale.x, profile.appWidgetScale.y); + lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY, + profile.appWidgetScale.x, profile.appWidgetScale.y, mBorderSpacing); // Widgets have their own padding } else { - lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX); + lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY, + mBorderSpacing); // Center the icon/folder int cHeight = getCellContentHeight(); int cellPaddingY = (int) Math.max(0, ((lp.height - cHeight) / 2f)); diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java index 4f79fb8b6b..aef32d7a28 100644 --- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java +++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java @@ -109,7 +109,8 @@ public class AppsSearchContainerLayout extends ExtendedEditText int rowWidth = myRequestedWidth - mAppsView.getActiveRecyclerView().getPaddingLeft() - mAppsView.getActiveRecyclerView().getPaddingRight(); - int cellWidth = DeviceProfile.calculateCellWidth(rowWidth, dp.inv.numHotseatIcons); + int cellWidth = DeviceProfile.calculateCellWidth(rowWidth, dp.cellLayoutBorderSpacingPx, + dp.inv.numHotseatIcons); int iconVisibleSize = Math.round(ICON_VISIBLE_AREA_FACTOR * dp.iconSizePx); int iconPadding = cellWidth - iconVisibleSize; diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index fe310f626c..6477de3757 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -177,6 +177,8 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel icon.setFolder(folder); icon.setOnFocusChangeListener(launcher.getFocusHandler()); + icon.mBackground.paddingY = icon.isInHotseat() + ? 0 : launcher.getDeviceProfile().cellYPaddingPx; return icon; } @@ -199,7 +201,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel icon.mFolderName.setText(folderInfo.title); icon.mFolderName.setCompoundDrawablePadding(0); FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams(); - lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx; + lp.topMargin = grid.cellYPaddingPx + grid.iconSizePx + grid.iconDrawablePaddingPx; icon.setTag(folderInfo); icon.setOnClickListener(ItemClickHandler.INSTANCE); @@ -218,6 +220,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel icon.setAccessibilityDelegate(activity.getAccessibilityDelegate()); + icon.mBackground.paddingY = icon.isInHotseat() ? 0 : grid.cellYPaddingPx; icon.mPreviewVerifier = new FolderGridOrganizer(activity.getDeviceProfile().inv); icon.mPreviewVerifier.setFolderInfo(folderInfo); icon.updatePreviewItems(false); @@ -579,6 +582,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel public void setFolderBackground(PreviewBackground bg) { mBackground = bg; mBackground.setInvalidateDelegate(this); + mBackground.paddingY = isInHotseat() ? 0 : mActivity.getDeviceProfile().cellYPaddingPx; } @Override @@ -745,9 +749,13 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel mInfo.removeListener(mFolder); } + private boolean isInHotseat() { + return mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT; + } + public void clearLeaveBehindIfExists() { ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true; - if (mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + if (isInHotseat()) { CellLayout cl = (CellLayout) getParent().getParent(); cl.clearFolderLeaveBehind(); } @@ -757,7 +765,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); // While the folder is open, the position of the icon cannot change. lp.canReorder = false; - if (mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + if (isInHotseat()) { CellLayout cl = (CellLayout) getParent().getParent(); cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY); } diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index a08dd3064a..df3e92cf1e 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -193,7 +193,7 @@ public class FolderPagedView extends PagedView { int pageNo = rank / mOrganizer.getMaxItemsPerPage(); CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); - lp.setXY(mOrganizer.getPosForRank(rank)); + lp.setCellXY(mOrganizer.getPosForRank(rank)); getPageAt(pageNo).addViewToCellLayout(view, -1, item.getViewId(), lp, true); } @@ -306,7 +306,7 @@ public class FolderPagedView extends PagedView { if (v != null) { CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); ItemInfo info = (ItemInfo) v.getTag(); - lp.setXY(mOrganizer.getPosForRank(rank)); + lp.setCellXY(mOrganizer.getPosForRank(rank)); currentPage.addViewToCellLayout(v, -1, info.getViewId(), lp, true); if (mOrganizer.isItemInPreview(rank) && v instanceof BubbleTextView) { diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java index 27b906bcbb..767fffe35b 100644 --- a/src/com/android/launcher3/folder/PreviewBackground.java +++ b/src/com/android/launcher3/folder/PreviewBackground.java @@ -74,6 +74,7 @@ public class PreviewBackground extends CellLayout.DelegatedCellDrawing { int previewSize; int basePreviewOffsetX; int basePreviewOffsetY; + int paddingY; private CellLayout mDrawingDelegate; @@ -157,7 +158,7 @@ public class PreviewBackground extends CellLayout.DelegatedCellDrawing { previewSize = grid.folderIconSizePx; basePreviewOffsetX = (availableSpaceX - previewSize) / 2; - basePreviewOffsetY = topPadding + grid.folderIconOffsetYPx; + basePreviewOffsetY = paddingY + topPadding + grid.folderIconOffsetYPx; // Stroke width is 1dp mStrokeWidth = context.getResources().getDisplayMetrics().density;