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.44f; 9 private static final float MAX_SCALE = 0.51f; 10 private static final float MAX_RADIUS_DILATION = 0.25f; 11 // The max amount of overlap the preview items can go outside of the background bounds. 12 public static final float ICON_OVERLAP_FACTOR = 1 + (MAX_RADIUS_DILATION / 2f); 13 private static final float ITEM_RADIUS_SCALE_FACTOR = 1.15f; 14 15 public static final int EXIT_INDEX = -2; 16 public static final int ENTER_INDEX = -3; 17 18 private float[] mTmpPoint = new float[2]; 19 20 private float mAvailableSpace; 21 private float mRadius; 22 private float mIconSize; 23 private boolean mIsRtl; 24 private float mBaselineIconScale; 25 init(int availableSpace, float intrinsicIconSize, boolean rtl)26 public void init(int availableSpace, float intrinsicIconSize, boolean rtl) { 27 mAvailableSpace = availableSpace; 28 mRadius = ITEM_RADIUS_SCALE_FACTOR * availableSpace / 2f; 29 mIconSize = intrinsicIconSize; 30 mIsRtl = rtl; 31 mBaselineIconScale = availableSpace / (intrinsicIconSize * 1f); 32 } 33 computePreviewItemDrawingParams(int index, int curNumItems, PreviewItemDrawingParams params)34 public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems, 35 PreviewItemDrawingParams params) { 36 float totalScale = scaleForItem(curNumItems); 37 float transX; 38 float transY; 39 40 if (index == EXIT_INDEX) { 41 // 0 1 * <-- Exit position (row 0, col 2) 42 // 2 3 43 getGridPosition(0, 2, mTmpPoint); 44 } else if (index == ENTER_INDEX) { 45 // 0 1 46 // 2 3 * <-- Enter position (row 1, col 2) 47 getGridPosition(1, 2, mTmpPoint); 48 } else if (index >= MAX_NUM_ITEMS_IN_PREVIEW) { 49 // Items beyond those displayed in the preview are animated to the center 50 mTmpPoint[0] = mTmpPoint[1] = mAvailableSpace / 2 - (mIconSize * totalScale) / 2; 51 } else { 52 getPosition(index, curNumItems, mTmpPoint); 53 } 54 55 transX = mTmpPoint[0]; 56 transY = mTmpPoint[1]; 57 58 if (params == null) { 59 params = new PreviewItemDrawingParams(transX, transY, totalScale); 60 } else { 61 params.update(transX, transY, totalScale); 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 / 2; 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 <= 3) { 134 scale = MAX_SCALE; 135 } else { 136 scale = MIN_SCALE; 137 } 138 return scale * mBaselineIconScale; 139 } 140 getIconSize()141 public float getIconSize() { 142 return mIconSize; 143 } 144 } 145