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