blob: 3e5a74ac6d411122d1d478e3e7e168d616bb156a [file] [log] [blame]
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -07001/*
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
The Android Open Source Project15a88802009-02-10 15:44:05 -080059 private boolean mDirtyTag;
60
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -070061 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
The Android Open Source Projectd097a182008-12-17 18:05:58 -0800110 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
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700121 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;
The Android Open Source Project15a88802009-02-10 15:44:05 -0800162 mDirtyTag = false;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700163 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;
177 findOccupiedCells(xCount, yCount, occupied);
178
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
The Android Open Source Project15a88802009-02-10 15:44:05 -0800187 // 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;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700193 }
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;
The Android Open Source Project15a88802009-02-10 15:44:05 -0800202 mDirtyTag = false;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700203 setTag(cellInfo);
204 }
205
206 return false;
207 }
208
The Android Open Source Project15a88802009-02-10 15:44:05 -0800209 @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;
218 findOccupiedCells(xCount, yCount, occupied);
219
220 findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied);
221
222 mDirtyTag = false;
223 }
224 return info;
225 }
226
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700227 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;
The Android Open Source Project15a88802009-02-10 15:44:05 -0800234 cellInfo.clearVacantCells();
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700235
236 if (occupied[x][y]) {
237 return;
238 }
239
The Android Open Source Project15a88802009-02-10 15:44:05 -0800240 cellInfo.current.set(x, y, x, y);
241
242 findVacantCell(cellInfo.current, xCount, yCount, occupied, cellInfo);
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700243 }
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) {
The Android Open Source Project15a88802009-02-10 15:44:05 -0800284 CellInfo.VacantCell cell = CellInfo.VacantCell.acquire();
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700285 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
318 CellInfo findAllVacantCells(boolean[] occupiedCells) {
319 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 {
332 findOccupiedCells(xCount, yCount, occupied);
333 }
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;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700345 cellInfo.screen = mCellInfo.screen;
346
The Android Open Source Project15a88802009-02-10 15:44:05 -0800347 Rect current = cellInfo.current;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700348
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 if (cellInfo.valid) {
361 int[] xy = new int[2];
362 if (cellInfo.findCellForSpan(xy, 1, 1)) {
363 cellInfo.cellX = xy[0];
364 cellInfo.cellY = xy[1];
365 cellInfo.spanY = 1;
366 cellInfo.spanX = 1;
367 }
368 }
369
370 return cellInfo;
371 }
372
373 /**
374 * Given a point, return the cell that strictly encloses that point
375 * @param x X coordinate of the point
376 * @param y Y coordinate of the point
377 * @param result Array of 2 ints to hold the x and y coordinate of the cell
378 */
379 void pointToCellExact(int x, int y, int[] result) {
380 final boolean portrait = mPortrait;
381
382 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
383 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
384
385 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
386 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
387
388 final int xAxis = portrait ? mShortAxisCells : mLongAxisCells;
389 final int yAxis = portrait ? mLongAxisCells : mShortAxisCells;
390
391 if (result[0] < 0) result[0] = 0;
392 if (result[0] >= xAxis) result[0] = xAxis - 1;
393 if (result[1] < 0) result[1] = 0;
394 if (result[1] >= yAxis) result[1] = yAxis - 1;
395 }
396
397 /**
398 * Given a point, return the cell that most closely encloses that point
399 * @param x X coordinate of the point
400 * @param y Y coordinate of the point
401 * @param result Array of 2 ints to hold the x and y coordinate of the cell
402 */
403 void pointToCellRounded(int x, int y, int[] result) {
404 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
405 }
406
407 /**
408 * Given a cell coordinate, return the point that represents the upper left corner of that cell
409 *
410 * @param cellX X coordinate of the cell
411 * @param cellY Y coordinate of the cell
412 *
413 * @param result Array of 2 ints to hold the x and y coordinate of the point
414 */
415 void cellToPoint(int cellX, int cellY, int[] result) {
416 final boolean portrait = mPortrait;
417
418 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
419 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
420
421
422 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
423 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
424 }
425
426 @Override
427 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
428 // TODO: currently ignoring padding
429
430 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
431 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
432
433 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
434 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
435
436 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
437 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
438 }
439
440 final int shortAxisCells = mShortAxisCells;
441 final int longAxisCells = mLongAxisCells;
442 final int longAxisStartPadding = mLongAxisStartPadding;
443 final int longAxisEndPadding = mLongAxisEndPadding;
444 final int shortAxisStartPadding = mShortAxisStartPadding;
445 final int shortAxisEndPadding = mShortAxisEndPadding;
446 final int cellWidth = mCellWidth;
447 final int cellHeight = mCellHeight;
448
449 mPortrait = heightSpecSize > widthSpecSize;
450
451 int numShortGaps = shortAxisCells - 1;
452 int numLongGaps = longAxisCells - 1;
453
454 if (mPortrait) {
455 int vSpaceLeft = heightSpecSize - longAxisStartPadding - longAxisEndPadding
456 - (cellHeight * longAxisCells);
457 mHeightGap = vSpaceLeft / numLongGaps;
458
459 int hSpaceLeft = widthSpecSize - shortAxisStartPadding - shortAxisEndPadding
460 - (cellWidth * shortAxisCells);
461 if (numShortGaps > 0) {
462 mWidthGap = hSpaceLeft / numShortGaps;
463 } else {
464 mWidthGap = 0;
465 }
466 } else {
467 int hSpaceLeft = widthSpecSize - longAxisStartPadding - longAxisEndPadding
468 - (cellWidth * longAxisCells);
469 mWidthGap = hSpaceLeft / numLongGaps;
470
471 int vSpaceLeft = heightSpecSize - shortAxisStartPadding - shortAxisEndPadding
472 - (cellHeight * shortAxisCells);
473 if (numShortGaps > 0) {
474 mHeightGap = vSpaceLeft / numShortGaps;
475 } else {
476 mHeightGap = 0;
477 }
478 }
479
480 int count = getChildCount();
481
482 for (int i = 0; i < count; i++) {
483 View child = getChildAt(i);
484 LayoutParams lp = (LayoutParams) child.getLayoutParams();
485
486 if (mPortrait) {
487 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, shortAxisStartPadding,
488 longAxisStartPadding);
489 } else {
490 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, longAxisStartPadding,
491 shortAxisStartPadding);
492 }
493
494 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
495 int childheightMeasureSpec =
496 MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
497 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
498 }
499
500 setMeasuredDimension(widthSpecSize, heightSpecSize);
501 }
502
503 @Override
504 protected void onLayout(boolean changed, int l, int t, int r, int b) {
505 int count = getChildCount();
506
507 for (int i = 0; i < count; i++) {
508 View child = getChildAt(i);
509 if (child.getVisibility() != GONE) {
510
511 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
512
513 int childLeft = lp.x;
514 int childTop = lp.y;
515 child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
516 }
517 }
518 }
519
520 @Override
521 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
522 final int count = getChildCount();
523 for (int i = 0; i < count; i++) {
524 final View view = getChildAt(i);
525 view.setDrawingCacheEnabled(enabled);
526 // Update the drawing caches
527 view.buildDrawingCache();
528 }
529 }
530
531 @Override
532 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
533 super.setChildrenDrawnWithCacheEnabled(enabled);
534 }
535
536 boolean acceptChildDrop(int x, int y, int cellHSpan, int cellVSpan, View cell) {
537 int[] cellXY = mCellXY;
538 pointToCellRounded(x, y, cellXY);
539 int cellX = cellXY[0];
540 int cellY = cellXY[1];
541
542 return findCell(cellX, cellY, cellHSpan, cellVSpan, cell) == null;
543 }
544
545 /**
546 * Finds the first View intersecting with the specified cell. If the cell is outside
547 * of the layout, this is returned.
548 *
549 * @param cellX The X location of the cell to test.
550 * @param cellY The Y location of the cell to test.
551 * @param cellHSpan The horizontal span of the cell to test.
552 * @param cellVSpan The vertical span of the cell to test.
553 * @param ignoreCell View to ignore during the test.
554 *
555 * @return Returns the first View intersecting with the specified cell, this if the cell
556 * lies outside of this layout's grid or null if no View was found.
557 */
558 View findCell(int cellX, int cellY, int cellHSpan, int cellVSpan, View ignoreCell) {
559 if (cellX < 0 || cellX + cellHSpan > (mPortrait ? mShortAxisCells : mLongAxisCells) ||
560 cellY < 0 || cellY + cellVSpan > (mPortrait ? mLongAxisCells : mShortAxisCells)) {
561 return this;
562 }
563
564 final int count = getChildCount();
565 for (int i = 0; i < count; i++) {
566 final View view = getChildAt(i);
567 if (view == ignoreCell) {
568 continue;
569 }
570
571 final LayoutParams lp = (LayoutParams) view.getLayoutParams();
572 if (cellX < lp.cellX + lp.cellHSpan && lp.cellX < cellX + cellHSpan &&
573 cellY < lp.cellY + lp.cellVSpan && lp.cellY < cellY + cellVSpan) {
574 return view;
575 }
576 }
577
578 return null;
579 }
580
581 /**
582 * Drop a child at the specified position
583 *
584 * @param child The child that is being dropped
585 * @param cellX The child's new x location
586 * @param cellY The child's new y location
587 */
588 void onDropChild(View child, int cellX, int cellY) {
589 int[] cellXY = mCellXY;
590 pointToCellRounded(cellX, cellY, cellXY);
591 LayoutParams lp = (LayoutParams) child.getLayoutParams();
592 lp.cellX = cellXY[0];
593 lp.cellY = cellXY[1];
594 lp.isDragging = false;
595 mDragRect.setEmpty();
596 child.requestLayout();
597 invalidate();
598 }
599
600 void onDropAborted(View child) {
601 if (child != null) {
602 ((LayoutParams) child.getLayoutParams()).isDragging = false;
603 invalidate();
604 }
605 mDragRect.setEmpty();
606 }
607
608 /**
609 * Start dragging the specified child
610 *
611 * @param child The child that is being dragged
612 */
613 void onDragChild(View child) {
614 LayoutParams lp = (LayoutParams) child.getLayoutParams();
615 lp.isDragging = true;
616 mDragRect.setEmpty();
617 }
618
619 /**
620 * Drag a child over the specified position
621 *
622 * @param child The child that is being dropped
623 * @param cellX The child's new x cell location
624 * @param cellY The child's new y cell location
625 */
626 void onDragOverChild(View child, int cellX, int cellY) {
627 int[] cellXY = mCellXY;
628 pointToCellRounded(cellX, cellY, cellXY);
629 LayoutParams lp = (LayoutParams) child.getLayoutParams();
630 cellToRect(cellXY[0], cellXY[1], lp.cellHSpan, lp.cellVSpan, mDragRect);
631 invalidate();
632 }
633
634 /**
635 * Computes a bounding rectangle for a range of cells
636 *
637 * @param cellX X coordinate of upper left corner expressed as a cell position
638 * @param cellY Y coordinate of upper left corner expressed as a cell position
639 * @param cellHSpan Width in cells
640 * @param cellVSpan Height in cells
641 * @param dragRect Rectnagle into which to put the results
642 */
643 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF dragRect) {
644 final boolean portrait = mPortrait;
645 final int cellWidth = mCellWidth;
646 final int cellHeight = mCellHeight;
647 final int widthGap = mWidthGap;
648 final int heightGap = mHeightGap;
649
650 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
651 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
652
653 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
654 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
655
656 int x = hStartPadding + cellX * (cellWidth + widthGap);
657 int y = vStartPadding + cellY * (cellHeight + heightGap);
658
659 dragRect.set(x, y, x + width, y + height);
660 }
The Android Open Source Project15a88802009-02-10 15:44:05 -0800661
662 /**
663 * Computes the required horizontal and vertical cell spans to always
664 * fit the given rectangle.
665 *
666 * @param width Width in pixels
667 * @param height Height in pixels
668 * @param cellInfo {@link CellInfo} to fill with calculated span parameters
669 */
670 public void rectToCell(int width, int height, CellInfo cellInfo) {
671 // Always assume we're working with the smallest span to make sure we
672 // reserve enough space in both orientations.
673 int actualWidth = mCellWidth + mWidthGap;
674 int actualHeight = mCellHeight + mHeightGap;
675 int smallerSize = Math.min(actualWidth, actualHeight);
676
677 // Always round up to next largest cell
678 cellInfo.spanX = (width + smallerSize) / smallerSize;
679 cellInfo.spanY = (height + smallerSize) / smallerSize;
680 }
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700681
682 /**
683 * Find the first vacant cell, if there is one.
684 *
685 * @param vacant Holds the x and y coordinate of the vacant cell
686 * @param spanX Horizontal cell span.
687 * @param spanY Vertical cell span.
688 *
689 * @return True if a vacant cell was found
690 */
691 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
692 final boolean portrait = mPortrait;
693 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
694 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
695 final boolean[][] occupied = mOccupied;
696
697 findOccupiedCells(xCount, yCount, occupied);
698
699 return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
700 }
701
702 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
703 int xCount, int yCount, boolean[][] occupied) {
704
705 for (int x = 0; x < xCount; x++) {
706 for (int y = 0; y < yCount; y++) {
707 boolean available = !occupied[x][y];
708out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
709 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
710 available = available && !occupied[i][j];
711 if (!available) break out;
712 }
713 }
714
715 if (available) {
716 vacant[0] = x;
717 vacant[1] = y;
718 return true;
719 }
720 }
721 }
722
723 return false;
724 }
725
726 boolean[] getOccupiedCells() {
727 final boolean portrait = mPortrait;
728 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
729 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
730 final boolean[][] occupied = mOccupied;
731
732 findOccupiedCells(xCount, yCount, occupied);
733
734 final boolean[] flat = new boolean[xCount * yCount];
735 for (int y = 0; y < yCount; y++) {
736 for (int x = 0; x < xCount; x++) {
737 flat[y * xCount + x] = occupied[x][y];
738 }
739 }
740
741 return flat;
742 }
743
744 private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied) {
745 for (int x = 0; x < xCount; x++) {
746 for (int y = 0; y < yCount; y++) {
747 occupied[x][y] = false;
748 }
749 }
750
751 int count = getChildCount();
752 for (int i = 0; i < count; i++) {
753 View child = getChildAt(i);
754 if (child instanceof Folder) {
755 continue;
756 }
757 LayoutParams lp = (LayoutParams) child.getLayoutParams();
758
759 for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan && x < xCount; x++) {
760 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan && y < yCount; y++) {
761 occupied[x][y] = true;
762 }
763 }
764 }
765 }
766
767 @Override
768 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
769 return new CellLayout.LayoutParams(getContext(), attrs);
770 }
771
772 @Override
773 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
774 return p instanceof CellLayout.LayoutParams;
775 }
776
777 @Override
778 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
779 return new CellLayout.LayoutParams(p);
780 }
781
782 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
783 /**
784 * Horizontal location of the item in the grid.
785 */
786 @ViewDebug.ExportedProperty
787 public int cellX;
788
789 /**
790 * Vertical location of the item in the grid.
791 */
792 @ViewDebug.ExportedProperty
793 public int cellY;
794
795 /**
796 * Number of cells spanned horizontally by the item.
797 */
798 @ViewDebug.ExportedProperty
799 public int cellHSpan;
800
801 /**
802 * Number of cells spanned vertically by the item.
803 */
804 @ViewDebug.ExportedProperty
805 public int cellVSpan;
806
807 /**
808 * Is this item currently being dragged
809 */
810 public boolean isDragging;
811
812 // X coordinate of the view in the layout.
813 @ViewDebug.ExportedProperty
814 int x;
815 // Y coordinate of the view in the layout.
816 @ViewDebug.ExportedProperty
817 int y;
818
819 public LayoutParams(Context c, AttributeSet attrs) {
820 super(c, attrs);
821 cellHSpan = 1;
822 cellVSpan = 1;
823 }
824
825 public LayoutParams(ViewGroup.LayoutParams source) {
826 super(source);
827 cellHSpan = 1;
828 cellVSpan = 1;
829 }
830
831 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
832 super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
833 this.cellX = cellX;
834 this.cellY = cellY;
835 this.cellHSpan = cellHSpan;
836 this.cellVSpan = cellVSpan;
837 }
838
839 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
840 int hStartPadding, int vStartPadding) {
841
842 final int myCellHSpan = cellHSpan;
843 final int myCellVSpan = cellVSpan;
844 final int myCellX = cellX;
845 final int myCellY = cellY;
846
847 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
848 leftMargin - rightMargin;
849 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
850 topMargin - bottomMargin;
851
852 x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
853 y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
854 }
855 }
856
857 static final class CellInfo implements ContextMenu.ContextMenuInfo {
The Android Open Source Project15a88802009-02-10 15:44:05 -0800858 /**
859 * See View.AttachInfo.InvalidateInfo for futher explanations about
860 * the recycling mechanism. In this case, we recycle the vacant cells
861 * instances because up to several hundreds can be instanciated when
862 * the user long presses an empty cell.
863 */
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700864 static final class VacantCell {
865 int cellX;
866 int cellY;
867 int spanX;
868 int spanY;
869
The Android Open Source Project15a88802009-02-10 15:44:05 -0800870 // We can create up to 523 vacant cells on a 4x4 grid, 100 seems
871 // like a reasonable compromise given the size of a VacantCell and
872 // the fact that the user is not likely to touch an empty 4x4 grid
873 // very often
874 private static final int POOL_LIMIT = 100;
875 private static final Object sLock = new Object();
876
877 private static int sAcquiredCount = 0;
878 private static VacantCell sRoot;
879
880 private VacantCell next;
881
882 static VacantCell acquire() {
883 synchronized (sLock) {
884 if (sRoot == null) {
885 return new VacantCell();
886 }
887
888 VacantCell info = sRoot;
889 sRoot = info.next;
890 sAcquiredCount--;
891
892 return info;
893 }
894 }
895
896 void release() {
897 synchronized (sLock) {
898 if (sAcquiredCount < POOL_LIMIT) {
899 sAcquiredCount++;
900 next = sRoot;
901 sRoot = this;
902 }
903 }
904 }
905
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700906 @Override
907 public String toString() {
908 return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + spanX +
909 ", spanY=" + spanY + "]";
910 }
911 }
912
913 View cell;
914 int cellX;
915 int cellY;
916 int spanX;
917 int spanY;
918 int screen;
919 boolean valid;
920
The Android Open Source Project15a88802009-02-10 15:44:05 -0800921 final ArrayList<VacantCell> vacantCells = new ArrayList<VacantCell>(VacantCell.POOL_LIMIT);
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700922 int maxVacantSpanX;
923 int maxVacantSpanXSpanY;
924 int maxVacantSpanY;
925 int maxVacantSpanYSpanX;
The Android Open Source Project15a88802009-02-10 15:44:05 -0800926 final Rect current = new Rect();
927
928 private void clearVacantCells() {
929 final ArrayList<VacantCell> list = vacantCells;
930 final int count = list.size();
931
932 for (int i = 0; i < count; i++) list.get(i).release();
933
934 list.clear();
935 }
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700936
937 void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) {
938 if (cellX < 0 || cellY < 0) {
939 maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE;
940 maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE;
The Android Open Source Project15a88802009-02-10 15:44:05 -0800941 clearVacantCells();
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700942 return;
943 }
944
945 final boolean[][] unflattened = new boolean[xCount][yCount];
946 for (int y = 0; y < yCount; y++) {
947 for (int x = 0; x < xCount; x++) {
948 unflattened[x][y] = occupied[y * xCount + x];
949 }
950 }
951 CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened);
952 }
953
The Android Open Source Project15a88802009-02-10 15:44:05 -0800954 /**
955 * This method can be called only once! Calling #findVacantCellsFromOccupied will
956 * restore the ability to call this method.
957 *
958 * Finds the upper-left coordinate of the first rectangle in the grid that can
959 * hold a cell of the specified dimensions.
960 *
961 * @param cellXY The array that will contain the position of a vacant cell if such a cell
962 * can be found.
963 * @param spanX The horizontal span of the cell we want to find.
964 * @param spanY The vertical span of the cell we want to find.
965 *
966 * @return True if a vacant cell of the specified dimension was found, false otherwise.
967 */
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700968 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
The Android Open Source Project15a88802009-02-10 15:44:05 -0800969 final ArrayList<VacantCell> list = vacantCells;
970 final int count = list.size();
971
972 boolean found = false;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700973
974 if (this.spanX >= spanX && this.spanY >= spanY) {
975 cellXY[0] = cellX;
976 cellXY[1] = cellY;
The Android Open Source Project15a88802009-02-10 15:44:05 -0800977 found = true;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700978 }
979
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700980 // Look for an exact match first
981 for (int i = 0; i < count; i++) {
982 VacantCell cell = list.get(i);
983 if (cell.spanX == spanX && cell.spanY == spanY) {
984 cellXY[0] = cell.cellX;
985 cellXY[1] = cell.cellY;
The Android Open Source Project15a88802009-02-10 15:44:05 -0800986 found = true;
987 break;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700988 }
989 }
990
991 // Look for the first cell large enough
992 for (int i = 0; i < count; i++) {
993 VacantCell cell = list.get(i);
994 if (cell.spanX >= spanX && cell.spanY >= spanY) {
995 cellXY[0] = cell.cellX;
996 cellXY[1] = cell.cellY;
The Android Open Source Project15a88802009-02-10 15:44:05 -0800997 found = true;
998 break;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700999 }
1000 }
1001
The Android Open Source Project15a88802009-02-10 15:44:05 -08001002 clearVacantCells();
1003
1004 return found;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -07001005 }
1006
1007 @Override
1008 public String toString() {
1009 return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + ", x=" + cellX +
1010 ", y=" + cellY + "]";
1011 }
1012 }
1013}
1014
1015