blob: fe6b193e1c59fc26681933aa0c8614edc65dcabe [file] [log] [blame]
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001/*
2 * Copyright (C) 2008 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
17package com.android.launcher;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.graphics.Rect;
22import android.graphics.RectF;
23import android.util.AttributeSet;
24import android.view.ContextMenu;
25import android.view.MotionEvent;
26import android.view.View;
27import android.view.ViewDebug;
28import android.view.ViewGroup;
29
30import java.util.ArrayList;
31
32public class CellLayout extends ViewGroup {
33 private boolean mPortrait;
34
35 private int mCellWidth;
36 private int mCellHeight;
37
38 private int mLongAxisStartPadding;
39 private int mLongAxisEndPadding;
40
41 private int mShortAxisStartPadding;
42 private int mShortAxisEndPadding;
43
44 private int mShortAxisCells;
45 private int mLongAxisCells;
46
47 private int mWidthGap;
48 private int mHeightGap;
49
50 private final Rect mRect = new Rect();
51 private final CellInfo mCellInfo = new CellInfo();
52
53 int[] mCellXY = new int[2];
54
55 boolean[][] mOccupied;
56
57 private RectF mDragRect = new RectF();
58
59 private boolean mDirtyTag;
60
61 public CellLayout(Context context) {
62 this(context, null);
63 }
64
65 public CellLayout(Context context, AttributeSet attrs) {
66 this(context, attrs, 0);
67 }
68
69 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
70 super(context, attrs, defStyle);
71 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
72
73 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
74 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
75
76 mLongAxisStartPadding =
77 a.getDimensionPixelSize(R.styleable.CellLayout_longAxisStartPadding, 10);
78 mLongAxisEndPadding =
79 a.getDimensionPixelSize(R.styleable.CellLayout_longAxisEndPadding, 10);
80 mShortAxisStartPadding =
81 a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisStartPadding, 10);
82 mShortAxisEndPadding =
83 a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisEndPadding, 10);
84
85 mShortAxisCells = a.getInt(R.styleable.CellLayout_shortAxisCells, 4);
86 mLongAxisCells = a.getInt(R.styleable.CellLayout_longAxisCells, 4);
87
88 a.recycle();
89
90 setAlwaysDrawnWithCacheEnabled(false);
91
92 if (mOccupied == null) {
93 if (mPortrait) {
94 mOccupied = new boolean[mShortAxisCells][mLongAxisCells];
95 } else {
96 mOccupied = new boolean[mLongAxisCells][mShortAxisCells];
97 }
98 }
99 }
100
101 int getCountX() {
102 return mPortrait ? mShortAxisCells : mLongAxisCells;
103 }
104
105 int getCountY() {
106 return mPortrait ? mLongAxisCells : mShortAxisCells;
107 }
108
109 @Override
110 public void addView(View child, int index, ViewGroup.LayoutParams params) {
111 // Generate an id for each view, this assumes we have at most 256x256 cells
112 // per workspace screen
113 final LayoutParams cellParams = (LayoutParams) params;
114 child.setId(((getId() & 0xFF) << 16) |
115 (cellParams.cellX & 0xFF) << 8 | (cellParams.cellY & 0xFF));
116
117 super.addView(child, index, params);
118 }
119
120 @Override
121 public void requestChildFocus(View child, View focused) {
122 super.requestChildFocus(child, focused);
123 if (child != null) {
124 Rect r = new Rect();
125 child.getDrawingRect(r);
126 requestRectangleOnScreen(r);
127 }
128 }
129
130 @Override
131 protected void onAttachedToWindow() {
132 super.onAttachedToWindow();
133 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
134 }
135
136 @Override
137 public boolean onInterceptTouchEvent(MotionEvent ev) {
138 final int action = ev.getAction();
139 final CellInfo cellInfo = mCellInfo;
140
141 if (action == MotionEvent.ACTION_DOWN) {
142 final Rect frame = mRect;
143 final int x = (int) ev.getX() + mScrollX;
144 final int y = (int) ev.getY() + mScrollY;
145 final int count = getChildCount();
146
147 boolean found = false;
148 for (int i = count - 1; i >= 0; i--) {
149 final View child = getChildAt(i);
150
151 if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
152 child.getHitRect(frame);
153 if (frame.contains(x, y)) {
154 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
155 cellInfo.cell = child;
156 cellInfo.cellX = lp.cellX;
157 cellInfo.cellY = lp.cellY;
158 cellInfo.spanX = lp.cellHSpan;
159 cellInfo.spanY = lp.cellVSpan;
160 cellInfo.valid = true;
161 found = true;
162 mDirtyTag = false;
163 break;
164 }
165 }
166 }
167
168 if (!found) {
169 int cellXY[] = mCellXY;
170 pointToCellExact(x, y, cellXY);
171
172 final boolean portrait = mPortrait;
173 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
174 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
175
176 final boolean[][] occupied = mOccupied;
Jeff Sharkey70864282009-04-07 21:08:40 -0700177 findOccupiedCells(xCount, yCount, occupied, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800178
179 cellInfo.cell = null;
180 cellInfo.cellX = cellXY[0];
181 cellInfo.cellY = cellXY[1];
182 cellInfo.spanX = 1;
183 cellInfo.spanY = 1;
184 cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount &&
185 cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]];
186
187 // Instead of finding the interesting vacant cells here, wait until a
188 // caller invokes getTag() to retrieve the result. Finding the vacant
189 // cells is a bit expensive and can generate many new objects, it's
190 // therefore better to defer it until we know we actually need it.
191
192 mDirtyTag = true;
193 }
194 setTag(cellInfo);
195 } else if (action == MotionEvent.ACTION_UP) {
196 cellInfo.cell = null;
197 cellInfo.cellX = -1;
198 cellInfo.cellY = -1;
199 cellInfo.spanX = 0;
200 cellInfo.spanY = 0;
201 cellInfo.valid = false;
202 mDirtyTag = false;
203 setTag(cellInfo);
204 }
205
206 return false;
207 }
208
209 @Override
210 public CellInfo getTag() {
211 final CellInfo info = (CellInfo) super.getTag();
212 if (mDirtyTag && info.valid) {
213 final boolean portrait = mPortrait;
214 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
215 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
216
217 final boolean[][] occupied = mOccupied;
Jeff Sharkey70864282009-04-07 21:08:40 -0700218 findOccupiedCells(xCount, yCount, occupied, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800219
220 findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied);
221
222 mDirtyTag = false;
223 }
224 return info;
225 }
226
227 private static void findIntersectingVacantCells(CellInfo cellInfo, int x, int y,
228 int xCount, int yCount, boolean[][] occupied) {
229
230 cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
231 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
232 cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
233 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
234 cellInfo.clearVacantCells();
235
236 if (occupied[x][y]) {
237 return;
238 }
239
240 cellInfo.current.set(x, y, x, y);
241
242 findVacantCell(cellInfo.current, xCount, yCount, occupied, cellInfo);
243 }
244
245 private static void findVacantCell(Rect current, int xCount, int yCount, boolean[][] occupied,
246 CellInfo cellInfo) {
247
248 addVacantCell(current, cellInfo);
249
250 if (current.left > 0) {
251 if (isColumnEmpty(current.left - 1, current.top, current.bottom, occupied)) {
252 current.left--;
253 findVacantCell(current, xCount, yCount, occupied, cellInfo);
254 current.left++;
255 }
256 }
257
258 if (current.right < xCount - 1) {
259 if (isColumnEmpty(current.right + 1, current.top, current.bottom, occupied)) {
260 current.right++;
261 findVacantCell(current, xCount, yCount, occupied, cellInfo);
262 current.right--;
263 }
264 }
265
266 if (current.top > 0) {
267 if (isRowEmpty(current.top - 1, current.left, current.right, occupied)) {
268 current.top--;
269 findVacantCell(current, xCount, yCount, occupied, cellInfo);
270 current.top++;
271 }
272 }
273
274 if (current.bottom < yCount - 1) {
275 if (isRowEmpty(current.bottom + 1, current.left, current.right, occupied)) {
276 current.bottom++;
277 findVacantCell(current, xCount, yCount, occupied, cellInfo);
278 current.bottom--;
279 }
280 }
281 }
282
283 private static void addVacantCell(Rect current, CellInfo cellInfo) {
284 CellInfo.VacantCell cell = CellInfo.VacantCell.acquire();
285 cell.cellX = current.left;
286 cell.cellY = current.top;
287 cell.spanX = current.right - current.left + 1;
288 cell.spanY = current.bottom - current.top + 1;
289 if (cell.spanX > cellInfo.maxVacantSpanX) {
290 cellInfo.maxVacantSpanX = cell.spanX;
291 cellInfo.maxVacantSpanXSpanY = cell.spanY;
292 }
293 if (cell.spanY > cellInfo.maxVacantSpanY) {
294 cellInfo.maxVacantSpanY = cell.spanY;
295 cellInfo.maxVacantSpanYSpanX = cell.spanX;
296 }
297 cellInfo.vacantCells.add(cell);
298 }
299
300 private static boolean isColumnEmpty(int x, int top, int bottom, boolean[][] occupied) {
301 for (int y = top; y <= bottom; y++) {
302 if (occupied[x][y]) {
303 return false;
304 }
305 }
306 return true;
307 }
308
309 private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) {
310 for (int x = left; x <= right; x++) {
311 if (occupied[x][y]) {
312 return false;
313 }
314 }
315 return true;
316 }
317
Jeff Sharkey70864282009-04-07 21:08:40 -0700318 CellInfo findAllVacantCells(boolean[] occupiedCells, View ignoreView) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800319 final boolean portrait = mPortrait;
320 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
321 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
322
323 boolean[][] occupied = mOccupied;
324
325 if (occupiedCells != null) {
326 for (int y = 0; y < yCount; y++) {
327 for (int x = 0; x < xCount; x++) {
328 occupied[x][y] = occupiedCells[y * xCount + x];
329 }
330 }
331 } else {
Jeff Sharkey70864282009-04-07 21:08:40 -0700332 findOccupiedCells(xCount, yCount, occupied, ignoreView);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800333 }
334
335 CellInfo cellInfo = new CellInfo();
336
337 cellInfo.cellX = -1;
338 cellInfo.cellY = -1;
339 cellInfo.spanY = 0;
340 cellInfo.spanX = 0;
341 cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
342 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
343 cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
344 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
345 cellInfo.screen = mCellInfo.screen;
346
347 Rect current = cellInfo.current;
348
349 for (int x = 0; x < xCount; x++) {
350 for (int y = 0; y < yCount; y++) {
351 if (!occupied[x][y]) {
352 current.set(x, y, x, y);
353 findVacantCell(current, xCount, yCount, occupied, cellInfo);
354 occupied[x][y] = true;
355 }
356 }
357 }
358
359 cellInfo.valid = cellInfo.vacantCells.size() > 0;
360
361 // Assume the caller will perform their own cell searching, otherwise we
362 // risk causing an unnecessary rebuild after findCellForSpan()
363
364 return cellInfo;
365 }
366
367 /**
368 * Given a point, return the cell that strictly encloses that point
369 * @param x X coordinate of the point
370 * @param y Y coordinate of the point
371 * @param result Array of 2 ints to hold the x and y coordinate of the cell
372 */
373 void pointToCellExact(int x, int y, int[] result) {
374 final boolean portrait = mPortrait;
375
376 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
377 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
378
379 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
380 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
381
382 final int xAxis = portrait ? mShortAxisCells : mLongAxisCells;
383 final int yAxis = portrait ? mLongAxisCells : mShortAxisCells;
384
385 if (result[0] < 0) result[0] = 0;
386 if (result[0] >= xAxis) result[0] = xAxis - 1;
387 if (result[1] < 0) result[1] = 0;
388 if (result[1] >= yAxis) result[1] = yAxis - 1;
389 }
390
391 /**
392 * Given a point, return the cell that most closely encloses that point
393 * @param x X coordinate of the point
394 * @param y Y coordinate of the point
395 * @param result Array of 2 ints to hold the x and y coordinate of the cell
396 */
397 void pointToCellRounded(int x, int y, int[] result) {
398 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
399 }
400
401 /**
402 * Given a cell coordinate, return the point that represents the upper left corner of that cell
403 *
404 * @param cellX X coordinate of the cell
405 * @param cellY Y coordinate of the cell
406 *
407 * @param result Array of 2 ints to hold the x and y coordinate of the point
408 */
409 void cellToPoint(int cellX, int cellY, int[] result) {
410 final boolean portrait = mPortrait;
411
412 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
413 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
414
415
416 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
417 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
418 }
419
420 @Override
421 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
422 // TODO: currently ignoring padding
423
424 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
425 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
426
427 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
428 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
429
430 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
431 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
432 }
433
434 final int shortAxisCells = mShortAxisCells;
435 final int longAxisCells = mLongAxisCells;
436 final int longAxisStartPadding = mLongAxisStartPadding;
437 final int longAxisEndPadding = mLongAxisEndPadding;
438 final int shortAxisStartPadding = mShortAxisStartPadding;
439 final int shortAxisEndPadding = mShortAxisEndPadding;
440 final int cellWidth = mCellWidth;
441 final int cellHeight = mCellHeight;
442
443 mPortrait = heightSpecSize > widthSpecSize;
444
445 int numShortGaps = shortAxisCells - 1;
446 int numLongGaps = longAxisCells - 1;
447
448 if (mPortrait) {
449 int vSpaceLeft = heightSpecSize - longAxisStartPadding - longAxisEndPadding
450 - (cellHeight * longAxisCells);
451 mHeightGap = vSpaceLeft / numLongGaps;
452
453 int hSpaceLeft = widthSpecSize - shortAxisStartPadding - shortAxisEndPadding
454 - (cellWidth * shortAxisCells);
455 if (numShortGaps > 0) {
456 mWidthGap = hSpaceLeft / numShortGaps;
457 } else {
458 mWidthGap = 0;
459 }
460 } else {
461 int hSpaceLeft = widthSpecSize - longAxisStartPadding - longAxisEndPadding
462 - (cellWidth * longAxisCells);
463 mWidthGap = hSpaceLeft / numLongGaps;
464
465 int vSpaceLeft = heightSpecSize - shortAxisStartPadding - shortAxisEndPadding
466 - (cellHeight * shortAxisCells);
467 if (numShortGaps > 0) {
468 mHeightGap = vSpaceLeft / numShortGaps;
469 } else {
470 mHeightGap = 0;
471 }
472 }
473
474 int count = getChildCount();
475
476 for (int i = 0; i < count; i++) {
477 View child = getChildAt(i);
478 LayoutParams lp = (LayoutParams) child.getLayoutParams();
479
480 if (mPortrait) {
481 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, shortAxisStartPadding,
482 longAxisStartPadding);
483 } else {
484 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, longAxisStartPadding,
485 shortAxisStartPadding);
486 }
487
488 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
489 int childheightMeasureSpec =
490 MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
491 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
492 }
493
494 setMeasuredDimension(widthSpecSize, heightSpecSize);
495 }
496
497 @Override
498 protected void onLayout(boolean changed, int l, int t, int r, int b) {
499 int count = getChildCount();
500
501 for (int i = 0; i < count; i++) {
502 View child = getChildAt(i);
503 if (child.getVisibility() != GONE) {
504
505 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
506
507 int childLeft = lp.x;
508 int childTop = lp.y;
509 child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
510 }
511 }
512 }
513
514 @Override
515 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
516 final int count = getChildCount();
517 for (int i = 0; i < count; i++) {
518 final View view = getChildAt(i);
519 view.setDrawingCacheEnabled(enabled);
520 // Update the drawing caches
521 view.buildDrawingCache();
522 }
523 }
524
525 @Override
526 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
527 super.setChildrenDrawnWithCacheEnabled(enabled);
528 }
529
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800530 /**
Jeff Sharkey70864282009-04-07 21:08:40 -0700531 * Find a vacant area that will fit the given bounds nearest the requested
532 * cell location. Uses Euclidean distance to score multiple vacant areas.
533 *
534 * @param cellX The X location of the desired location.
535 * @param cellY The Y location of the desired location.
536 * @param spanX Horizontal span of the object.
537 * @param spanY Vertical span of the object.
538 * @param vacantCells Pre-computed set of vacant cells to search.
539 * @param recycle Previously returned value to possibly recycle.
540 * @return The X, Y cell of a vacant area that can contain this object,
541 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800542 */
Jeff Sharkey70864282009-04-07 21:08:40 -0700543 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
544 CellInfo vacantCells, int[] recycle) {
545
546 // Keep track of best-scoring drop area
547 final int[] bestXY = recycle != null ? recycle : new int[2];
548 final int[] cellXY = mCellXY;
549 double bestDistance = Double.MAX_VALUE;
550
551 // Bail early if vacant cells aren't valid
552 if (!vacantCells.valid) {
553 return null;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800554 }
555
Jeff Sharkey70864282009-04-07 21:08:40 -0700556 // Look across all vacant cells for best fit
557 final int size = vacantCells.vacantCells.size();
558 for (int i = 0; i < size; i++) {
559 final CellInfo.VacantCell cell = vacantCells.vacantCells.get(i);
560
561 // Reject if vacant cell isn't our exact size
562 if (cell.spanX != spanX || cell.spanY != spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800563 continue;
564 }
Jeff Sharkey70864282009-04-07 21:08:40 -0700565
566 // Score is center distance from requested pixel
567 cellToPoint(cell.cellX, cell.cellY, cellXY);
568
569 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) +
570 Math.pow(cellXY[1] - pixelY, 2));
571 if (distance <= bestDistance) {
572 bestDistance = distance;
573 bestXY[0] = cell.cellX;
574 bestXY[1] = cell.cellY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800575 }
576 }
577
Jeff Sharkey70864282009-04-07 21:08:40 -0700578 // Return null if no suitable location found
579 if (bestDistance < Double.MAX_VALUE) {
580 return bestXY;
581 } else {
582 return null;
583 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800584 }
Jeff Sharkey70864282009-04-07 21:08:40 -0700585
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800586 /**
587 * Drop a child at the specified position
588 *
589 * @param child The child that is being dropped
Jeff Sharkey70864282009-04-07 21:08:40 -0700590 * @param targetXY Destination area to move to
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800591 */
Jeff Sharkey70864282009-04-07 21:08:40 -0700592 void onDropChild(View child, int[] targetXY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800593 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Jeff Sharkey70864282009-04-07 21:08:40 -0700594 lp.cellX = targetXY[0];
595 lp.cellY = targetXY[1];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800596 lp.isDragging = false;
597 mDragRect.setEmpty();
598 child.requestLayout();
599 invalidate();
600 }
601
602 void onDropAborted(View child) {
603 if (child != null) {
604 ((LayoutParams) child.getLayoutParams()).isDragging = false;
605 invalidate();
606 }
607 mDragRect.setEmpty();
608 }
609
610 /**
611 * Start dragging the specified child
612 *
613 * @param child The child that is being dragged
614 */
615 void onDragChild(View child) {
616 LayoutParams lp = (LayoutParams) child.getLayoutParams();
617 lp.isDragging = true;
618 mDragRect.setEmpty();
619 }
620
621 /**
622 * Drag a child over the specified position
623 *
624 * @param child The child that is being dropped
625 * @param cellX The child's new x cell location
626 * @param cellY The child's new y cell location
627 */
628 void onDragOverChild(View child, int cellX, int cellY) {
629 int[] cellXY = mCellXY;
630 pointToCellRounded(cellX, cellY, cellXY);
631 LayoutParams lp = (LayoutParams) child.getLayoutParams();
632 cellToRect(cellXY[0], cellXY[1], lp.cellHSpan, lp.cellVSpan, mDragRect);
633 invalidate();
634 }
635
636 /**
637 * Computes a bounding rectangle for a range of cells
638 *
639 * @param cellX X coordinate of upper left corner expressed as a cell position
640 * @param cellY Y coordinate of upper left corner expressed as a cell position
641 * @param cellHSpan Width in cells
642 * @param cellVSpan Height in cells
643 * @param dragRect Rectnagle into which to put the results
644 */
645 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF dragRect) {
646 final boolean portrait = mPortrait;
647 final int cellWidth = mCellWidth;
648 final int cellHeight = mCellHeight;
649 final int widthGap = mWidthGap;
650 final int heightGap = mHeightGap;
651
652 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
653 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
654
655 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
656 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
657
658 int x = hStartPadding + cellX * (cellWidth + widthGap);
659 int y = vStartPadding + cellY * (cellHeight + heightGap);
660
661 dragRect.set(x, y, x + width, y + height);
662 }
663
664 /**
665 * Computes the required horizontal and vertical cell spans to always
666 * fit the given rectangle.
667 *
668 * @param width Width in pixels
669 * @param height Height in pixels
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800670 */
671 public int[] rectToCell(int width, int height) {
672 // Always assume we're working with the smallest span to make sure we
673 // reserve enough space in both orientations.
674 int actualWidth = mCellWidth + mWidthGap;
675 int actualHeight = mCellHeight + mHeightGap;
676 int smallerSize = Math.min(actualWidth, actualHeight);
677
678 // Always round up to next largest cell
679 int spanX = (width + smallerSize) / smallerSize;
680 int spanY = (height + smallerSize) / smallerSize;
681 return new int[] { spanX, spanY };
682 }
683
684 /**
685 * Find the first vacant cell, if there is one.
686 *
687 * @param vacant Holds the x and y coordinate of the vacant cell
688 * @param spanX Horizontal cell span.
689 * @param spanY Vertical cell span.
690 *
691 * @return True if a vacant cell was found
692 */
693 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
694 final boolean portrait = mPortrait;
695 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
696 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
697 final boolean[][] occupied = mOccupied;
698
Jeff Sharkey70864282009-04-07 21:08:40 -0700699 findOccupiedCells(xCount, yCount, occupied, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800700
701 return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
702 }
703
704 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
705 int xCount, int yCount, boolean[][] occupied) {
706
707 for (int x = 0; x < xCount; x++) {
708 for (int y = 0; y < yCount; y++) {
709 boolean available = !occupied[x][y];
710out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
711 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
712 available = available && !occupied[i][j];
713 if (!available) break out;
714 }
715 }
716
717 if (available) {
718 vacant[0] = x;
719 vacant[1] = y;
720 return true;
721 }
722 }
723 }
724
725 return false;
726 }
727
728 boolean[] getOccupiedCells() {
729 final boolean portrait = mPortrait;
730 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
731 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
732 final boolean[][] occupied = mOccupied;
733
Jeff Sharkey70864282009-04-07 21:08:40 -0700734 findOccupiedCells(xCount, yCount, occupied, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800735
736 final boolean[] flat = new boolean[xCount * yCount];
737 for (int y = 0; y < yCount; y++) {
738 for (int x = 0; x < xCount; x++) {
739 flat[y * xCount + x] = occupied[x][y];
740 }
741 }
742
743 return flat;
744 }
745
Jeff Sharkey70864282009-04-07 21:08:40 -0700746 private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied, View ignoreView) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800747 for (int x = 0; x < xCount; x++) {
748 for (int y = 0; y < yCount; y++) {
749 occupied[x][y] = false;
750 }
751 }
752
753 int count = getChildCount();
754 for (int i = 0; i < count; i++) {
755 View child = getChildAt(i);
Jeff Sharkey70864282009-04-07 21:08:40 -0700756 if (child instanceof Folder || child.equals(ignoreView)) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800757 continue;
758 }
759 LayoutParams lp = (LayoutParams) child.getLayoutParams();
760
761 for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan && x < xCount; x++) {
762 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan && y < yCount; y++) {
763 occupied[x][y] = true;
764 }
765 }
766 }
767 }
768
769 @Override
770 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
771 return new CellLayout.LayoutParams(getContext(), attrs);
772 }
773
774 @Override
775 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
776 return p instanceof CellLayout.LayoutParams;
777 }
778
779 @Override
780 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
781 return new CellLayout.LayoutParams(p);
782 }
783
784 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
785 /**
786 * Horizontal location of the item in the grid.
787 */
788 @ViewDebug.ExportedProperty
789 public int cellX;
790
791 /**
792 * Vertical location of the item in the grid.
793 */
794 @ViewDebug.ExportedProperty
795 public int cellY;
796
797 /**
798 * Number of cells spanned horizontally by the item.
799 */
800 @ViewDebug.ExportedProperty
801 public int cellHSpan;
802
803 /**
804 * Number of cells spanned vertically by the item.
805 */
806 @ViewDebug.ExportedProperty
807 public int cellVSpan;
808
809 /**
810 * Is this item currently being dragged
811 */
812 public boolean isDragging;
813
814 // X coordinate of the view in the layout.
815 @ViewDebug.ExportedProperty
816 int x;
817 // Y coordinate of the view in the layout.
818 @ViewDebug.ExportedProperty
819 int y;
820
821 public LayoutParams(Context c, AttributeSet attrs) {
822 super(c, attrs);
823 cellHSpan = 1;
824 cellVSpan = 1;
825 }
826
827 public LayoutParams(ViewGroup.LayoutParams source) {
828 super(source);
829 cellHSpan = 1;
830 cellVSpan = 1;
831 }
832
833 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
834 super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
835 this.cellX = cellX;
836 this.cellY = cellY;
837 this.cellHSpan = cellHSpan;
838 this.cellVSpan = cellVSpan;
839 }
840
841 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
842 int hStartPadding, int vStartPadding) {
843
844 final int myCellHSpan = cellHSpan;
845 final int myCellVSpan = cellVSpan;
846 final int myCellX = cellX;
847 final int myCellY = cellY;
848
849 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
850 leftMargin - rightMargin;
851 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
852 topMargin - bottomMargin;
853
854 x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
855 y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
856 }
857 }
858
859 static final class CellInfo implements ContextMenu.ContextMenuInfo {
860 /**
861 * See View.AttachInfo.InvalidateInfo for futher explanations about
862 * the recycling mechanism. In this case, we recycle the vacant cells
863 * instances because up to several hundreds can be instanciated when
864 * the user long presses an empty cell.
865 */
866 static final class VacantCell {
867 int cellX;
868 int cellY;
869 int spanX;
870 int spanY;
871
872 // We can create up to 523 vacant cells on a 4x4 grid, 100 seems
873 // like a reasonable compromise given the size of a VacantCell and
874 // the fact that the user is not likely to touch an empty 4x4 grid
875 // very often
876 private static final int POOL_LIMIT = 100;
877 private static final Object sLock = new Object();
878
879 private static int sAcquiredCount = 0;
880 private static VacantCell sRoot;
881
882 private VacantCell next;
883
884 static VacantCell acquire() {
885 synchronized (sLock) {
886 if (sRoot == null) {
887 return new VacantCell();
888 }
889
890 VacantCell info = sRoot;
891 sRoot = info.next;
892 sAcquiredCount--;
893
894 return info;
895 }
896 }
897
898 void release() {
899 synchronized (sLock) {
900 if (sAcquiredCount < POOL_LIMIT) {
901 sAcquiredCount++;
902 next = sRoot;
903 sRoot = this;
904 }
905 }
906 }
907
908 @Override
909 public String toString() {
910 return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + spanX +
911 ", spanY=" + spanY + "]";
912 }
913 }
914
915 View cell;
916 int cellX;
917 int cellY;
918 int spanX;
919 int spanY;
920 int screen;
921 boolean valid;
922
923 final ArrayList<VacantCell> vacantCells = new ArrayList<VacantCell>(VacantCell.POOL_LIMIT);
924 int maxVacantSpanX;
925 int maxVacantSpanXSpanY;
926 int maxVacantSpanY;
927 int maxVacantSpanYSpanX;
928 final Rect current = new Rect();
929
930 private void clearVacantCells() {
931 final ArrayList<VacantCell> list = vacantCells;
932 final int count = list.size();
933
934 for (int i = 0; i < count; i++) list.get(i).release();
935
936 list.clear();
937 }
938
939 void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) {
940 if (cellX < 0 || cellY < 0) {
941 maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE;
942 maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE;
943 clearVacantCells();
944 return;
945 }
946
947 final boolean[][] unflattened = new boolean[xCount][yCount];
948 for (int y = 0; y < yCount; y++) {
949 for (int x = 0; x < xCount; x++) {
950 unflattened[x][y] = occupied[y * xCount + x];
951 }
952 }
953 CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened);
954 }
955
956 /**
957 * This method can be called only once! Calling #findVacantCellsFromOccupied will
958 * restore the ability to call this method.
959 *
960 * Finds the upper-left coordinate of the first rectangle in the grid that can
961 * hold a cell of the specified dimensions.
962 *
963 * @param cellXY The array that will contain the position of a vacant cell if such a cell
964 * can be found.
965 * @param spanX The horizontal span of the cell we want to find.
966 * @param spanY The vertical span of the cell we want to find.
967 *
968 * @return True if a vacant cell of the specified dimension was found, false otherwise.
969 */
970 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
971 final ArrayList<VacantCell> list = vacantCells;
972 final int count = list.size();
973
974 boolean found = false;
975
976 if (this.spanX >= spanX && this.spanY >= spanY) {
977 cellXY[0] = cellX;
978 cellXY[1] = cellY;
979 found = true;
980 }
981
982 // Look for an exact match first
983 for (int i = 0; i < count; i++) {
984 VacantCell cell = list.get(i);
985 if (cell.spanX == spanX && cell.spanY == spanY) {
986 cellXY[0] = cell.cellX;
987 cellXY[1] = cell.cellY;
988 found = true;
989 break;
990 }
991 }
992
993 // Look for the first cell large enough
994 for (int i = 0; i < count; i++) {
995 VacantCell cell = list.get(i);
996 if (cell.spanX >= spanX && cell.spanY >= spanY) {
997 cellXY[0] = cell.cellX;
998 cellXY[1] = cell.cellY;
999 found = true;
1000 break;
1001 }
1002 }
1003
1004 clearVacantCells();
1005
1006 return found;
1007 }
1008
1009 @Override
1010 public String toString() {
1011 return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + ", x=" + cellX +
1012 ", y=" + cellY + "]";
1013 }
1014 }
1015}
1016
1017