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