1 package com.android.launcher3.folder; 2 3 import com.android.launcher3.Flags; 4 5 public class ClippedFolderIconLayoutRule { 6 7 public static final int MAX_NUM_ITEMS_IN_PREVIEW = 4; 8 private static final int MIN_NUM_ITEMS_IN_PREVIEW = 2; 9 10 private static final float MIN_SCALE = 0.44f; 11 private static final float MAX_SCALE = 0.51f; 12 // TODO: figure out exact radius for different icons 13 private static final float MAX_RADIUS_DILATION_SHAPES = 0.15f; 14 private static final float MAX_RADIUS_DILATION = 0.25f; 15 // The max amount of overlap the preview items can go outside of the background bounds. 16 public static final float ICON_OVERLAP_FACTOR = 1 + (MAX_RADIUS_DILATION / 2f); 17 public static final float ICON_OVERLAP_FACTOR_SHAPES = 1f; 18 private static final float ITEM_RADIUS_SCALE_FACTOR = 1.15f; 19 20 public static final int EXIT_INDEX = -2; 21 public static final int ENTER_INDEX = -3; 22 23 private float[] mTmpPoint = new float[2]; 24 25 private float mAvailableSpace; 26 private float mRadius; 27 private float mIconSize; 28 private boolean mIsRtl; 29 private float mBaselineIconScale; 30 init(int availableSpace, float intrinsicIconSize, boolean rtl)31 public void init(int availableSpace, float intrinsicIconSize, boolean rtl) { 32 mAvailableSpace = availableSpace; 33 mRadius = ITEM_RADIUS_SCALE_FACTOR * availableSpace / 2f; 34 mIconSize = intrinsicIconSize; 35 mIsRtl = rtl; 36 mBaselineIconScale = availableSpace / intrinsicIconSize; 37 } 38 computePreviewItemDrawingParams(int index, int curNumItems, PreviewItemDrawingParams params)39 public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems, 40 PreviewItemDrawingParams params) { 41 float totalScale = scaleForItem(curNumItems); 42 float transX; 43 float transY; 44 45 if (index == EXIT_INDEX) { 46 // 0 1 * <-- Exit position (row 0, col 2) 47 // 2 3 48 getGridPosition(0, 2, mTmpPoint); 49 } else if (index == ENTER_INDEX) { 50 // 0 1 51 // 2 3 * <-- Enter position (row 1, col 2) 52 getGridPosition(1, 2, mTmpPoint); 53 } else if (index >= MAX_NUM_ITEMS_IN_PREVIEW) { 54 // Items beyond those displayed in the preview are animated to the center 55 mTmpPoint[0] = mTmpPoint[1] = mAvailableSpace / 2 - (mIconSize * totalScale) / 2; 56 } else if (Flags.enableLauncherIconShapes()) { 57 if (index == 0) { 58 // top left 59 getGridPosition(0, 0, mTmpPoint); 60 } else if (index == 1) { 61 // top right 62 getGridPosition(0, 1, mTmpPoint); 63 } else if (index == 2) { 64 // bottom left 65 getGridPosition(1, 0, mTmpPoint); 66 } else if (index == 3) { 67 // bottom right 68 getGridPosition(1, 1, mTmpPoint); 69 } 70 } else { 71 getPosition(index, curNumItems, mTmpPoint); 72 } 73 74 transX = mTmpPoint[0]; 75 transY = mTmpPoint[1]; 76 77 if (params == null) { 78 params = new PreviewItemDrawingParams(transX, transY, totalScale); 79 } else { 80 params.update(transX, transY, totalScale); 81 } 82 return params; 83 } 84 85 /** 86 * Builds a grid based on the positioning of the items when there are 87 * {@link #MAX_NUM_ITEMS_IN_PREVIEW} in the preview. 88 * 89 * Positions in the grid: 0 1 // 0 is row 0, col 1 90 * 2 3 // 3 is row 1, col 1 91 */ getGridPosition(int row, int col, float[] result)92 private void getGridPosition(int row, int col, float[] result) { 93 // We use position 0 and 3 to calculate the x and y distances between items. 94 getPosition(0, 4, result); 95 float left = result[0]; 96 float top = result[1]; 97 98 getPosition(3, 4, result); 99 float dx = result[0] - left; 100 float dy = result[1] - top; 101 102 result[0] = left + (col * dx); 103 result[1] = top + (row * dy); 104 } 105 106 // b/392610664 TODO: Change positioning from circular geometry to square / grid-based. getPosition(int index, int curNumItems, float[] result)107 private void getPosition(int index, int curNumItems, float[] result) { 108 // The case of two items is homomorphic to the case of one. 109 curNumItems = Math.max(curNumItems, 2); 110 111 // We model the preview as a circle of items starting in the appropriate piece of the 112 // upper left quadrant (to achieve horizontal and vertical symmetry). 113 double theta0 = mIsRtl ? 0 : Math.PI; 114 115 // In RTL we go counterclockwise 116 int direction = mIsRtl ? 1 : -1; 117 118 double thetaShift = 0; 119 if (curNumItems == 3) { 120 thetaShift = Math.PI / 2; 121 } else if (curNumItems == 4) { 122 thetaShift = Math.PI / 4; 123 } 124 theta0 += direction * thetaShift; 125 126 // We want the items to appear in reading order. For the case of 1, 2 and 3 items, this 127 // is natural for the circular model. With 4 items, however, we need to swap the 3rd and 128 // 4th indices to achieve reading order. 129 if (curNumItems == 4 && index == 3) { 130 index = 2; 131 } else if (curNumItems == 4 && index == 2) { 132 index = 3; 133 } 134 135 // We bump the radius up between 0 and MAX_RADIUS_DILATION % as the number of items increase 136 float radiusDilation = Flags.enableLauncherIconShapes() ? MAX_RADIUS_DILATION_SHAPES 137 : MAX_RADIUS_DILATION; 138 float radius = mRadius * (1 + radiusDilation * (curNumItems - MIN_NUM_ITEMS_IN_PREVIEW) 139 / (MAX_NUM_ITEMS_IN_PREVIEW - MIN_NUM_ITEMS_IN_PREVIEW)); 140 double theta = theta0 + index * (2 * Math.PI / curNumItems) * direction; 141 142 float halfIconSize = (mIconSize * scaleForItem(curNumItems)) / 2; 143 144 // Map the location along the circle, and offset the coordinates to represent the center 145 // of the icon, and to be based from the top / left of the preview area. The y component 146 // is inverted to match the coordinate system. 147 result[0] = mAvailableSpace / 2 + (float) (radius * Math.cos(theta) / 2) - halfIconSize; 148 result[1] = mAvailableSpace / 2 + (float) (- radius * Math.sin(theta) / 2) - halfIconSize; 149 150 } 151 scaleForItem(int numItems)152 public float scaleForItem(int numItems) { 153 // Scale is determined by the number of items in the preview. 154 final float scale; 155 if (numItems <= 3 && !Flags.enableLauncherIconShapes()) { 156 scale = MAX_SCALE; 157 } else { 158 scale = MIN_SCALE; 159 } 160 return scale * mBaselineIconScale; 161 } 162 getIconSize()163 public float getIconSize() { 164 return mIconSize; 165 } 166 167 /** 168 * Gets correct constant for icon overlap. 169 */ getIconOverlapFactor()170 public static float getIconOverlapFactor() { 171 if (Flags.enableLauncherIconShapes()) { 172 return ICON_OVERLAP_FACTOR_SHAPES; 173 } else { 174 return ICON_OVERLAP_FACTOR; 175 } 176 } 177 } 178