blob: 66f5224bb33581dfb1cdd4dec43543a7046a7a17 [file] [log] [blame]
Sunny Goyalc3a609f2015-02-26 17:43:50 -08001package com.android.launcher3;
2
3import android.content.Context;
4import android.util.AttributeSet;
5import android.view.LayoutInflater;
6import android.view.View;
7
8import java.util.ArrayList;
9
10public class FolderCellLayout extends CellLayout {
11
12 private static final int REORDER_ANIMATION_DURATION = 230;
13 private static final int START_VIEW_REORDER_DELAY = 30;
14 private static final float VIEW_REORDER_DELAY_FACTOR = 0.9f;
15
16 private static final int[] sTempPosArray = new int[2];
17
18 private final FolderKeyEventListener mKeyListener = new FolderKeyEventListener();
19 private final LayoutInflater mInflater;
20 private final IconCache mIconCache;
21
22 private final int mMaxCountX;
23 private final int mMaxCountY;
24 private final int mMaxNumItems;
25
26 // Indicates the last number of items used to set up the grid size
27 private int mAllocatedContentSize;
28
29 private Folder mFolder;
30 private FocusIndicatorView mFocusIndicatorView;
31
32 public FolderCellLayout(Context context) {
33 this(context, null);
34 }
35
36 public FolderCellLayout(Context context, AttributeSet attrs) {
37 this(context, attrs, 0);
38 }
39
40 public FolderCellLayout(Context context, AttributeSet attrs, int defStyle) {
41 super(context, attrs, defStyle);
42
43 LauncherAppState app = LauncherAppState.getInstance();
44 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
45 mMaxCountX = (int) grid.numColumns;
46 mMaxCountY = (int) grid.numRows;
47 mMaxNumItems = mMaxCountX * mMaxCountY;
48
49 mInflater = LayoutInflater.from(context);
50 mIconCache = app.getIconCache();
51 }
52
53 public void setFolder(Folder folder) {
54 mFolder = folder;
55 mFocusIndicatorView = (FocusIndicatorView) folder.findViewById(R.id.focus_indicator);
56 }
57
58 /**
59 * Sets up the grid size such that {@param count} items can fit in the grid.
60 * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while
61 * maintaining the restrictions of {@link #mMaxCountX} &amp; {@link #mMaxCountY}.
62 */
63 private void setupContentDimensions(int count) {
64 mAllocatedContentSize = count;
65 int countX = getCountX();
66 int countY = getCountY();
67 boolean done = false;
68
69 while (!done) {
70 int oldCountX = countX;
71 int oldCountY = countY;
72 if (countX * countY < count) {
73 // Current grid is too small, expand it
74 if ((countX <= countY || countY == mMaxCountY) && countX < mMaxCountX) {
75 countX++;
76 } else if (countY < mMaxCountY) {
77 countY++;
78 }
79 if (countY == 0) countY++;
80 } else if ((countY - 1) * countX >= count && countY >= countX) {
81 countY = Math.max(0, countY - 1);
82 } else if ((countX - 1) * countY >= count) {
83 countX = Math.max(0, countX - 1);
84 }
85 done = countX == oldCountX && countY == oldCountY;
86 }
87 setGridSize(countX, countY);
88 }
89
90 /**
91 * Binds items to the layout.
92 * @return list of items that could not be bound, probably because we hit the max size limit.
93 */
94 public ArrayList<ShortcutInfo> bindItems(ArrayList<ShortcutInfo> items) {
95 ArrayList<ShortcutInfo> extra = new ArrayList<ShortcutInfo>();
96 setupContentDimensions(Math.min(items.size(), mMaxNumItems));
97
98 int countX = getCountX();
99 int rank = 0;
100 for (ShortcutInfo item : items) {
101 if (rank >= mMaxNumItems) {
102 extra.add(item);
103 continue;
104 }
105
106 item.rank = rank;
107 item.cellX = rank % countX;
108 item.cellY = rank / countX;
109 addNewView(item);
110 rank++;
111 }
112 return extra;
113 }
114
115 /**
116 * Create space for a new item at the end, and returns the rank for that item.
117 * Resizes the content if necessary.
118 */
119 public int allocateNewLastItemRank() {
120 int rank = getItemCount();
121 mFolder.rearrangeChildren(rank + 1);
122 return rank;
123 }
124
125 /**
126 * Adds the new item to the end of the grid. Resizes the content if necessary.
127 */
128 public View createAndAddShortcutToEnd(ShortcutInfo item) {
129 int rank = allocateNewLastItemRank();
130 return createAndAddViewForRank(item, rank);
131 }
132
133 public View createAndAddViewForRank(ShortcutInfo item, int rank) {
134 updateItemXY(item, rank);
135 return addNewView(item);
136 }
137
138 /**
139 * Adds the {@param view} to the layout based on {@param rank} and updated the position
140 * related attributes. It assumes that {@param item} is already attached to the view.
141 */
142 public void addViewForRank(View view, ShortcutInfo item, int rank) {
143 updateItemXY(item, rank);
144 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
145 lp.cellX = item.cellX;
146 lp.cellY = item.cellY;
147 addViewToCellLayout(view, -1, (int) item.id, lp, true);
148 }
149
150 /**
151 * Updates the item cellX and cellY position
152 */
153 private void updateItemXY(ShortcutInfo item, int rank) {
154 item.rank = rank;
155 int countX = getCountX();
156 item.cellX = rank % countX;
157 item.cellY = rank / countX;
158 }
159
160 private View addNewView(ShortcutInfo item) {
161 final BubbleTextView textView = (BubbleTextView) mInflater.inflate(
162 R.layout.folder_application, getShortcutsAndWidgets(), false);
163 textView.applyFromShortcutInfo(item, mIconCache, false);
164 textView.setOnClickListener(mFolder);
165 textView.setOnLongClickListener(mFolder);
166 textView.setOnFocusChangeListener(mFocusIndicatorView);
167 textView.setOnKeyListener(mKeyListener);
168
169 CellLayout.LayoutParams lp = new CellLayout.LayoutParams(
170 item.cellX, item.cellY, item.spanX, item.spanY);
171 addViewToCellLayout(textView, -1, (int)item.id, lp, true);
172 return textView;
173 }
174
175 /**
176 * Refer {@link #findNearestArea(int, int, int, int, View, boolean, int[])}
177 *
178 * @return The rank of a vacant area that can contain this object,
179 * nearest the requested location.
180 */
181 public int findNearestArea(int pixelX, int pixelY, int spanX, int spanY) {
182 findNearestArea(pixelX, pixelY, spanX, spanY, null, false, sTempPosArray);
183 if (mFolder.isLayoutRtl()) {
184 sTempPosArray[0] = getCountX() - sTempPosArray[0] - 1;
185 }
186
187 // Convert this position to rank.
188 return Math.min(mAllocatedContentSize - 1,
189 sTempPosArray[1] * getCountX() + sTempPosArray[0]);
190 }
191
192 public boolean isFull() {
193 return getItemCount() >= mMaxNumItems;
194 }
195
196 public int getItemCount() {
197 return getShortcutsAndWidgets().getChildCount();
198 }
199
200 /**
201 * Updates position and rank of all the children in the view based.
202 * @param list the ordered list of children.
203 * @param itemCount if greater than the total children count, empty spaces are left at the end.
204 */
205 public void arrangeChildren(ArrayList<View> list, int itemCount) {
206 setupContentDimensions(itemCount);
207 removeAllViews();
208
209 int newX, newY;
210 int rank = 0;
211 int countX = getCountX();
212 for (View v : list) {
213 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
214 newX = rank % countX;
215 newY = rank / countX;
216 ItemInfo info = (ItemInfo) v.getTag();
217 if (info.cellX != newX || info.cellY != newY || info.rank != rank) {
218 info.cellX = newX;
219 info.cellY = newY;
220 info.rank = rank;
221 LauncherModel.addOrMoveItemInDatabase(getContext(), info,
222 mFolder.mInfo.id, 0, info.cellX, info.cellY);
223 }
224 lp.cellX = info.cellX;
225 lp.cellY = info.cellY;
226 rank ++;
227 addViewToCellLayout(v, -1, (int)info.id, lp, true);
228 }
229 }
230
231 /**
232 * Reorders the items such that the {@param empty} spot moves to {@param target}
233 */
234 public void realTimeReorder(int empty, int target) {
235 boolean wrap;
236 int startX;
237 int endX;
238 int startY;
239 int delay = 0;
240 float delayAmount = START_VIEW_REORDER_DELAY;
241
242 int countX = getCountX();
243 int emptyX = empty % getCountX();
244 int emptyY = empty / countX;
245
246 int targetX = target % countX;
247 int targetY = target / countX;
248
249 if (target > empty) {
250 wrap = emptyX == countX - 1;
251 startY = wrap ? emptyY + 1 : emptyY;
252 for (int y = startY; y <= targetY; y++) {
253 startX = y == emptyY ? emptyX + 1 : 0;
254 endX = y < targetY ? countX - 1 : targetX;
255 for (int x = startX; x <= endX; x++) {
256 View v = getChildAt(x,y);
257 if (animateChildToPosition(v, emptyX, emptyY,
258 REORDER_ANIMATION_DURATION, delay, true, true)) {
259 emptyX = x;
260 emptyY = y;
261 delay += delayAmount;
262 delayAmount *= VIEW_REORDER_DELAY_FACTOR;
263 }
264 }
265 }
266 } else {
267 wrap = emptyX == 0;
268 startY = wrap ? emptyY - 1 : emptyY;
269 for (int y = startY; y >= targetY; y--) {
270 startX = y == emptyY ? emptyX - 1 : countX - 1;
271 endX = y > targetY ? 0 : targetX;
272 for (int x = startX; x >= endX; x--) {
273 View v = getChildAt(x,y);
274 if (animateChildToPosition(v, emptyX, emptyY,
275 REORDER_ANIMATION_DURATION, delay, true, true)) {
276 emptyX = x;
277 emptyY = y;
278 delay += delayAmount;
279 delayAmount *= VIEW_REORDER_DELAY_FACTOR;
280 }
281 }
282 }
283 }
284 }
285}