blob: 8585addd52521a4fe8829eecfde9b74064bc835b [file] [log] [blame]
Sunny Goyal290800b2015-03-05 11:33:33 -08001/**
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Sunny Goyalc3a609f2015-02-26 17:43:50 -080017package com.android.launcher3;
18
19import android.content.Context;
20import android.util.AttributeSet;
21import android.view.LayoutInflater;
22import android.view.View;
23
Sunny Goyalbc753352015-03-05 09:40:44 -080024import com.android.launcher3.Workspace.ItemOperator;
25
Sunny Goyalc3a609f2015-02-26 17:43:50 -080026import java.util.ArrayList;
27
Sunny Goyalbc753352015-03-05 09:40:44 -080028public class FolderCellLayout extends CellLayout implements Folder.FolderContent {
Sunny Goyalc3a609f2015-02-26 17:43:50 -080029
30 private static final int REORDER_ANIMATION_DURATION = 230;
31 private static final int START_VIEW_REORDER_DELAY = 30;
32 private static final float VIEW_REORDER_DELAY_FACTOR = 0.9f;
33
34 private static final int[] sTempPosArray = new int[2];
35
36 private final FolderKeyEventListener mKeyListener = new FolderKeyEventListener();
37 private final LayoutInflater mInflater;
38 private final IconCache mIconCache;
39
40 private final int mMaxCountX;
41 private final int mMaxCountY;
42 private final int mMaxNumItems;
43
44 // Indicates the last number of items used to set up the grid size
45 private int mAllocatedContentSize;
46
47 private Folder mFolder;
48 private FocusIndicatorView mFocusIndicatorView;
49
50 public FolderCellLayout(Context context) {
51 this(context, null);
52 }
53
54 public FolderCellLayout(Context context, AttributeSet attrs) {
55 this(context, attrs, 0);
56 }
57
58 public FolderCellLayout(Context context, AttributeSet attrs, int defStyle) {
59 super(context, attrs, defStyle);
60
61 LauncherAppState app = LauncherAppState.getInstance();
62 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
63 mMaxCountX = (int) grid.numColumns;
64 mMaxCountY = (int) grid.numRows;
65 mMaxNumItems = mMaxCountX * mMaxCountY;
66
67 mInflater = LayoutInflater.from(context);
68 mIconCache = app.getIconCache();
Sunny Goyalbc753352015-03-05 09:40:44 -080069
70 setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
71 getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
72 setInvertIfRtl(true);
Sunny Goyalc3a609f2015-02-26 17:43:50 -080073 }
74
Sunny Goyalbc753352015-03-05 09:40:44 -080075 @Override
Sunny Goyalc3a609f2015-02-26 17:43:50 -080076 public void setFolder(Folder folder) {
77 mFolder = folder;
78 mFocusIndicatorView = (FocusIndicatorView) folder.findViewById(R.id.focus_indicator);
79 }
80
81 /**
82 * Sets up the grid size such that {@param count} items can fit in the grid.
83 * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while
84 * maintaining the restrictions of {@link #mMaxCountX} &amp; {@link #mMaxCountY}.
85 */
86 private void setupContentDimensions(int count) {
87 mAllocatedContentSize = count;
88 int countX = getCountX();
89 int countY = getCountY();
90 boolean done = false;
91
92 while (!done) {
93 int oldCountX = countX;
94 int oldCountY = countY;
95 if (countX * countY < count) {
96 // Current grid is too small, expand it
97 if ((countX <= countY || countY == mMaxCountY) && countX < mMaxCountX) {
98 countX++;
99 } else if (countY < mMaxCountY) {
100 countY++;
101 }
102 if (countY == 0) countY++;
103 } else if ((countY - 1) * countX >= count && countY >= countX) {
104 countY = Math.max(0, countY - 1);
105 } else if ((countX - 1) * countY >= count) {
106 countX = Math.max(0, countX - 1);
107 }
108 done = countX == oldCountX && countY == oldCountY;
109 }
110 setGridSize(countX, countY);
111 }
112
Sunny Goyalbc753352015-03-05 09:40:44 -0800113 @Override
Sunny Goyalc3a609f2015-02-26 17:43:50 -0800114 public ArrayList<ShortcutInfo> bindItems(ArrayList<ShortcutInfo> items) {
115 ArrayList<ShortcutInfo> extra = new ArrayList<ShortcutInfo>();
116 setupContentDimensions(Math.min(items.size(), mMaxNumItems));
117
118 int countX = getCountX();
119 int rank = 0;
120 for (ShortcutInfo item : items) {
121 if (rank >= mMaxNumItems) {
122 extra.add(item);
123 continue;
124 }
125
126 item.rank = rank;
127 item.cellX = rank % countX;
128 item.cellY = rank / countX;
129 addNewView(item);
130 rank++;
131 }
132 return extra;
133 }
134
Sunny Goyalbc753352015-03-05 09:40:44 -0800135 @Override
Sunny Goyal5d85c442015-03-10 13:14:47 -0700136 public int allocateRankForNewItem(ShortcutInfo info) {
Sunny Goyalc3a609f2015-02-26 17:43:50 -0800137 int rank = getItemCount();
138 mFolder.rearrangeChildren(rank + 1);
139 return rank;
140 }
141
Sunny Goyalbc753352015-03-05 09:40:44 -0800142 @Override
Sunny Goyalc3a609f2015-02-26 17:43:50 -0800143 public View createAndAddViewForRank(ShortcutInfo item, int rank) {
144 updateItemXY(item, rank);
145 return addNewView(item);
146 }
147
Sunny Goyalbc753352015-03-05 09:40:44 -0800148 @Override
Sunny Goyalc3a609f2015-02-26 17:43:50 -0800149 public void addViewForRank(View view, ShortcutInfo item, int rank) {
150 updateItemXY(item, rank);
151 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
152 lp.cellX = item.cellX;
153 lp.cellY = item.cellY;
Sunny Goyal82e861d2015-03-05 14:56:29 -0800154 addViewToCellLayout(view, -1, mFolder.mLauncher.getViewIdForItem(item), lp, true);
Sunny Goyalc3a609f2015-02-26 17:43:50 -0800155 }
156
Sunny Goyal290800b2015-03-05 11:33:33 -0800157 @Override
158 public void removeItem(View v) {
159 removeView(v);
160 }
161
Sunny Goyalc3a609f2015-02-26 17:43:50 -0800162 /**
163 * Updates the item cellX and cellY position
164 */
165 private void updateItemXY(ShortcutInfo item, int rank) {
166 item.rank = rank;
167 int countX = getCountX();
168 item.cellX = rank % countX;
169 item.cellY = rank / countX;
170 }
171
172 private View addNewView(ShortcutInfo item) {
173 final BubbleTextView textView = (BubbleTextView) mInflater.inflate(
174 R.layout.folder_application, getShortcutsAndWidgets(), false);
175 textView.applyFromShortcutInfo(item, mIconCache, false);
176 textView.setOnClickListener(mFolder);
177 textView.setOnLongClickListener(mFolder);
178 textView.setOnFocusChangeListener(mFocusIndicatorView);
179 textView.setOnKeyListener(mKeyListener);
180
181 CellLayout.LayoutParams lp = new CellLayout.LayoutParams(
182 item.cellX, item.cellY, item.spanX, item.spanY);
Sunny Goyal82e861d2015-03-05 14:56:29 -0800183 addViewToCellLayout(textView, -1, mFolder.mLauncher.getViewIdForItem(item), lp, true);
Sunny Goyalc3a609f2015-02-26 17:43:50 -0800184 return textView;
185 }
186
187 /**
188 * Refer {@link #findNearestArea(int, int, int, int, View, boolean, int[])}
Sunny Goyalc3a609f2015-02-26 17:43:50 -0800189 */
Sunny Goyalbc753352015-03-05 09:40:44 -0800190 @Override
191 public int findNearestArea(int pixelX, int pixelY) {
192 findNearestArea(pixelX, pixelY, 1, 1, null, false, sTempPosArray);
Sunny Goyalc3a609f2015-02-26 17:43:50 -0800193 if (mFolder.isLayoutRtl()) {
194 sTempPosArray[0] = getCountX() - sTempPosArray[0] - 1;
195 }
196
197 // Convert this position to rank.
198 return Math.min(mAllocatedContentSize - 1,
199 sTempPosArray[1] * getCountX() + sTempPosArray[0]);
200 }
201
Sunny Goyalbc753352015-03-05 09:40:44 -0800202 @Override
Sunny Goyalc3a609f2015-02-26 17:43:50 -0800203 public boolean isFull() {
204 return getItemCount() >= mMaxNumItems;
205 }
206
Sunny Goyalbc753352015-03-05 09:40:44 -0800207 @Override
Sunny Goyalc3a609f2015-02-26 17:43:50 -0800208 public int getItemCount() {
209 return getShortcutsAndWidgets().getChildCount();
210 }
211
Sunny Goyalbc753352015-03-05 09:40:44 -0800212 @Override
Sunny Goyalc3a609f2015-02-26 17:43:50 -0800213 public void arrangeChildren(ArrayList<View> list, int itemCount) {
214 setupContentDimensions(itemCount);
215 removeAllViews();
216
217 int newX, newY;
218 int rank = 0;
219 int countX = getCountX();
220 for (View v : list) {
221 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
222 newX = rank % countX;
223 newY = rank / countX;
224 ItemInfo info = (ItemInfo) v.getTag();
225 if (info.cellX != newX || info.cellY != newY || info.rank != rank) {
226 info.cellX = newX;
227 info.cellY = newY;
228 info.rank = rank;
229 LauncherModel.addOrMoveItemInDatabase(getContext(), info,
230 mFolder.mInfo.id, 0, info.cellX, info.cellY);
231 }
232 lp.cellX = info.cellX;
233 lp.cellY = info.cellY;
234 rank ++;
Sunny Goyal82e861d2015-03-05 14:56:29 -0800235 addViewToCellLayout(v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true);
Sunny Goyalc3a609f2015-02-26 17:43:50 -0800236 }
237 }
238
Sunny Goyalbc753352015-03-05 09:40:44 -0800239 @Override
240 public View iterateOverItems(ItemOperator op) {
241 for (int j = 0; j < getCountY(); j++) {
242 for (int i = 0; i < getCountX(); i++) {
243 View v = getChildAt(i, j);
244 if ((v != null) && op.evaluate((ItemInfo) v.getTag(), v, this)) {
245 return v;
246 }
247 }
248 }
249 return null;
250 }
251
252 @Override
253 public String getAccessibilityDescription() {
254 return String.format(getContext().getString(R.string.folder_opened),
255 getCountX(), getCountY());
256 }
257
258 @Override
259 public void setFocusOnFirstChild() {
260 View firstChild = getChildAt(0, 0);
261 if (firstChild != null) {
262 firstChild.requestFocus();
263 }
264 }
265
266 @Override
267 public View getLastItem() {
Sunny Goyal82e861d2015-03-05 14:56:29 -0800268 int lastRank = getShortcutsAndWidgets().getChildCount() - 1;
Sunny Goyal1c0e6332015-03-05 16:56:12 -0800269 // count can be zero if the folder is not yet laid out.
270 int count = getCountX();
271 if (count > 0) {
272 return getShortcutsAndWidgets().getChildAt(lastRank % count, lastRank / count);
273 } else {
274 return getShortcutsAndWidgets().getChildAt(lastRank);
275 }
Sunny Goyalbc753352015-03-05 09:40:44 -0800276 }
277
278 @Override
Sunny Goyalc3a609f2015-02-26 17:43:50 -0800279 public void realTimeReorder(int empty, int target) {
280 boolean wrap;
281 int startX;
282 int endX;
283 int startY;
284 int delay = 0;
285 float delayAmount = START_VIEW_REORDER_DELAY;
286
287 int countX = getCountX();
288 int emptyX = empty % getCountX();
289 int emptyY = empty / countX;
290
291 int targetX = target % countX;
292 int targetY = target / countX;
293
294 if (target > empty) {
295 wrap = emptyX == countX - 1;
296 startY = wrap ? emptyY + 1 : emptyY;
297 for (int y = startY; y <= targetY; y++) {
298 startX = y == emptyY ? emptyX + 1 : 0;
299 endX = y < targetY ? countX - 1 : targetX;
300 for (int x = startX; x <= endX; x++) {
301 View v = getChildAt(x,y);
302 if (animateChildToPosition(v, emptyX, emptyY,
303 REORDER_ANIMATION_DURATION, delay, true, true)) {
304 emptyX = x;
305 emptyY = y;
306 delay += delayAmount;
307 delayAmount *= VIEW_REORDER_DELAY_FACTOR;
308 }
309 }
310 }
311 } else {
312 wrap = emptyX == 0;
313 startY = wrap ? emptyY - 1 : emptyY;
314 for (int y = startY; y >= targetY; y--) {
315 startX = y == emptyY ? emptyX - 1 : countX - 1;
316 endX = y > targetY ? 0 : targetX;
317 for (int x = startX; x >= endX; x--) {
318 View v = getChildAt(x,y);
319 if (animateChildToPosition(v, emptyX, emptyY,
320 REORDER_ANIMATION_DURATION, delay, true, true)) {
321 emptyX = x;
322 emptyY = y;
323 delay += delayAmount;
324 delayAmount *= VIEW_REORDER_DELAY_FACTOR;
325 }
326 }
327 }
328 }
329 }
330}