blob: 4c08ca62738e8374cc9864549cb26be091108223 [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
Joe Onoratoa5902522009-07-30 13:37:37 -070017package com.android.launcher2;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080018
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070019import com.android.launcher.R;
Winson Chungaafa03c2010-06-11 17:34:16 -070020
21import android.app.WallpaperManager;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080022import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040023import android.content.res.Resources;
Winson Chungaafa03c2010-06-11 17:34:16 -070024import android.content.res.TypedArray;
Michael Jurkadee05892010-07-27 10:01:56 -070025import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070026import android.graphics.Canvas;
Michael Jurkaa63c4522010-08-19 13:52:27 -070027import android.graphics.Color;
Michael Jurkadee05892010-07-27 10:01:56 -070028import android.graphics.Matrix;
29import android.graphics.Paint;
30import android.graphics.PorterDuff;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080031import android.graphics.Rect;
32import android.graphics.RectF;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070033import android.graphics.drawable.Drawable;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080034import android.util.AttributeSet;
35import android.view.ContextMenu;
36import android.view.MotionEvent;
37import android.view.View;
38import android.view.ViewDebug;
39import android.view.ViewGroup;
Michael Jurkadee05892010-07-27 10:01:56 -070040import android.view.View.OnTouchListener;
Winson Chungaafa03c2010-06-11 17:34:16 -070041import android.view.animation.Animation;
42import android.view.animation.LayoutAnimationController;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080043
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070044import java.util.ArrayList;
45import java.util.Arrays;
Romain Guyedcce092010-03-04 13:03:17 -080046
The Android Open Source Project31dd5032009-03-03 19:32:27 -080047public class CellLayout extends ViewGroup {
Winson Chungaafa03c2010-06-11 17:34:16 -070048 static final String TAG = "CellLayout";
Michael Jurkadee05892010-07-27 10:01:56 -070049 // we make the dimmed bitmap smaller than the screen itself for memory + perf reasons
50 static final float DIMMED_BITMAP_SCALE = 0.25f;
Winson Chungaafa03c2010-06-11 17:34:16 -070051
The Android Open Source Project31dd5032009-03-03 19:32:27 -080052 private boolean mPortrait;
53
54 private int mCellWidth;
55 private int mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070056
Winson Chungaafa03c2010-06-11 17:34:16 -070057 private int mLeftPadding;
58 private int mRightPadding;
59 private int mTopPadding;
60 private int mBottomPadding;
61
Adam Cohend22015c2010-07-26 22:02:18 -070062 private int mCountX;
63 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080064
65 private int mWidthGap;
66 private int mHeightGap;
67
68 private final Rect mRect = new Rect();
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -070069 private final RectF mRectF = new RectF();
The Android Open Source Project31dd5032009-03-03 19:32:27 -080070 private final CellInfo mCellInfo = new CellInfo();
Winson Chungaafa03c2010-06-11 17:34:16 -070071
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070072 // This is a temporary variable to prevent having to allocate a new object just to
73 // return an (x, y) value from helper functions. Do NOT use it to maintain other state.
74 private final int[] mTmpCellXY = new int[2];
75
The Android Open Source Project31dd5032009-03-03 19:32:27 -080076 boolean[][] mOccupied;
77
Michael Jurkadee05892010-07-27 10:01:56 -070078 private OnTouchListener mInterceptTouchListener;
79
80 // this is what the home screen fades to when it shrinks
81 // (ie in all apps and in home screen customize mode)
82 private Bitmap mDimmedBitmap;
83 private Canvas mDimmedBitmapCanvas;
84 private float mDimmedBitmapAlpha;
Michael Jurkaa63c4522010-08-19 13:52:27 -070085 private boolean mDimmedBitmapDirty = false;
Michael Jurkadee05892010-07-27 10:01:56 -070086 private final Paint mDimmedBitmapPaint = new Paint();
87 private final Rect mLayoutRect = new Rect();
88 private final Rect mDimmedBitmapRect = new Rect();
Michael Jurkaa63c4522010-08-19 13:52:27 -070089 private Drawable mDimmedBitmapBackground;
90 private Drawable mDimmedBitmapBackgroundHover;
91 // If we're actively dragging something over this screen and it's small,
92 // mHover is true
93 private boolean mHover = false;
Michael Jurkadee05892010-07-27 10:01:56 -070094
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070095 private final RectF mDragRect = new RectF();
96
97 // When dragging, used to indicate a vacant drop location
98 private Drawable mVacantDrawable;
99
100 // When dragging, used to indicate an occupied drop location
101 private Drawable mOccupiedDrawable;
102
103 // Updated to point to mVacantDrawable or mOccupiedDrawable, as appropriate
104 private Drawable mDragRectDrawable;
105
106 // When a drag operation is in progress, holds the nearest cell to the touch point
107 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800108
109 private boolean mDirtyTag;
Mike Cleronf8bbd342009-10-23 16:15:16 -0700110 private boolean mLastDownOnOccupiedCell = false;
Winson Chungaafa03c2010-06-11 17:34:16 -0700111
112 private final WallpaperManager mWallpaperManager;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800113
114 public CellLayout(Context context) {
115 this(context, null);
116 }
117
118 public CellLayout(Context context, AttributeSet attrs) {
119 this(context, attrs, 0);
120 }
121
122 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
123 super(context, attrs, defStyle);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700124
125 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
126 // the user where a dragged item will land when dropped.
127 setWillNotDraw(false);
128 mVacantDrawable = getResources().getDrawable(R.drawable.rounded_rect_green);
129 mOccupiedDrawable = getResources().getDrawable(R.drawable.rounded_rect_red);
130
Michael Jurkaa63c4522010-08-19 13:52:27 -0700131 if (LauncherApplication.isScreenXLarge()) {
132 mDimmedBitmapBackground = getResources().getDrawable(
133 R.drawable.mini_home_screen_bg);
134
135 mDimmedBitmapBackgroundHover = getResources().getDrawable(
136 R.drawable.mini_home_screen_bg_hover);
137 }
138
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800139 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
140
141 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
142 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
Winson Chungaafa03c2010-06-11 17:34:16 -0700143
Adam Cohend22015c2010-07-26 22:02:18 -0700144 mLeftPadding =
145 a.getDimensionPixelSize(R.styleable.CellLayout_xAxisStartPadding, 10);
146 mRightPadding =
147 a.getDimensionPixelSize(R.styleable.CellLayout_xAxisEndPadding, 10);
148 mTopPadding =
149 a.getDimensionPixelSize(R.styleable.CellLayout_yAxisStartPadding, 10);
150 mBottomPadding =
151 a.getDimensionPixelSize(R.styleable.CellLayout_yAxisEndPadding, 10);
Winson Chungaafa03c2010-06-11 17:34:16 -0700152
Adam Cohend22015c2010-07-26 22:02:18 -0700153 mCountX = LauncherModel.getCellCountX();
154 mCountY = LauncherModel.getCellCountY();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800155
156 a.recycle();
157
158 setAlwaysDrawnWithCacheEnabled(false);
159
Romain Guy84f296c2009-11-04 15:00:44 -0800160 mWallpaperManager = WallpaperManager.getInstance(getContext());
Michael Jurkadee05892010-07-27 10:01:56 -0700161
162 mDimmedBitmapPaint.setFilterBitmap(true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800163 }
164
Michael Jurkaa63c4522010-08-19 13:52:27 -0700165 public void setHover(boolean value) {
166 if (mHover != value) {
167 invalidate();
168 }
169 mHover = value;
170 }
171
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700172 @Override
Romain Guya6abce82009-11-10 02:54:41 -0800173 public void dispatchDraw(Canvas canvas) {
Michael Jurkaa63c4522010-08-19 13:52:27 -0700174 if (mDimmedBitmapAlpha > 0.0f) {
175 final Drawable bg = mHover ? mDimmedBitmapBackgroundHover : mDimmedBitmapBackground;
176 bg.setAlpha((int) (mDimmedBitmapAlpha * 255));
177 bg.draw(canvas);
178 }
Romain Guya6abce82009-11-10 02:54:41 -0800179 super.dispatchDraw(canvas);
180 }
181
182 @Override
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700183 protected void onDraw(Canvas canvas) {
184 if (!mDragRect.isEmpty()) {
185 mDragRectDrawable.setBounds(
186 (int)mDragRect.left,
187 (int)mDragRect.top,
188 (int)mDragRect.right,
189 (int)mDragRect.bottom);
190 mDragRectDrawable.draw(canvas);
191 }
Michael Jurkadee05892010-07-27 10:01:56 -0700192 if (mDimmedBitmap != null && mDimmedBitmapAlpha > 0.0f) {
193 if (mDimmedBitmapDirty) {
194 updateDimmedBitmap();
195 mDimmedBitmapDirty = false;
196 }
197 mDimmedBitmapPaint.setAlpha((int) (mDimmedBitmapAlpha * 255));
198
199 canvas.drawBitmap(mDimmedBitmap, mDimmedBitmapRect, mLayoutRect, mDimmedBitmapPaint);
200 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700201 }
202
203 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700204 public void cancelLongPress() {
205 super.cancelLongPress();
206
207 // Cancel long press for all children
208 final int count = getChildCount();
209 for (int i = 0; i < count; i++) {
210 final View child = getChildAt(i);
211 child.cancelLongPress();
212 }
213 }
214
Michael Jurkadee05892010-07-27 10:01:56 -0700215 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
216 mInterceptTouchListener = listener;
217 }
218
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800219 int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700220 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800221 }
222
223 int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700224 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800225 }
226
Winson Chungaafa03c2010-06-11 17:34:16 -0700227 // Takes canonical layout parameters
228 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params) {
229 final LayoutParams lp = params;
230
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800231 // Generate an id for each view, this assumes we have at most 256x256 cells
232 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700233 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700234 // If the horizontal or vertical span is set to -1, it is taken to
235 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700236 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
237 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800238
Winson Chungaafa03c2010-06-11 17:34:16 -0700239 child.setId(childId);
240
Michael Jurkadee05892010-07-27 10:01:56 -0700241 // We might be in the middle or end of shrinking/fading to a dimmed view
242 // Make sure this view's alpha is set the same as all the rest of the views
243 child.setAlpha(1.0f - mDimmedBitmapAlpha);
244
Winson Chungaafa03c2010-06-11 17:34:16 -0700245 addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700246
247 // next time we draw the dimmed bitmap we need to update it
248 mDimmedBitmapDirty = true;
Winson Chungaafa03c2010-06-11 17:34:16 -0700249 return true;
250 }
251 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800252 }
253
254 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700255 public void removeView(View view) {
256 super.removeView(view);
257 mDimmedBitmapDirty = true;
258 }
259
260 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800261 public void requestChildFocus(View child, View focused) {
262 super.requestChildFocus(child, focused);
263 if (child != null) {
264 Rect r = new Rect();
265 child.getDrawingRect(r);
266 requestRectangleOnScreen(r);
267 }
268 }
269
270 @Override
271 protected void onAttachedToWindow() {
272 super.onAttachedToWindow();
273 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
274 }
275
Michael Jurkaaf442092010-06-10 17:01:57 -0700276 public void setTagToCellInfoForPoint(int touchX, int touchY) {
277 final CellInfo cellInfo = mCellInfo;
278 final Rect frame = mRect;
279 final int x = touchX + mScrollX;
280 final int y = touchY + mScrollY;
281 final int count = getChildCount();
282
283 boolean found = false;
284 for (int i = count - 1; i >= 0; i--) {
285 final View child = getChildAt(i);
286
287 if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
288 child.getHitRect(frame);
289 if (frame.contains(x, y)) {
290 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
291 cellInfo.cell = child;
292 cellInfo.cellX = lp.cellX;
293 cellInfo.cellY = lp.cellY;
294 cellInfo.spanX = lp.cellHSpan;
295 cellInfo.spanY = lp.cellVSpan;
Michael Jurkac28de512010-08-13 11:27:44 -0700296 cellInfo.intersectX = lp.cellX;
297 cellInfo.intersectY = lp.cellY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700298 cellInfo.valid = true;
299 found = true;
300 mDirtyTag = false;
301 break;
302 }
303 }
304 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700305
Michael Jurkaaf442092010-06-10 17:01:57 -0700306 mLastDownOnOccupiedCell = found;
307
308 if (!found) {
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700309 final int cellXY[] = mTmpCellXY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700310 pointToCellExact(x, y, cellXY);
311
312 final boolean portrait = mPortrait;
Adam Cohend22015c2010-07-26 22:02:18 -0700313 final int xCount = mCountX;
314 final int yCount = mCountY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700315
316 final boolean[][] occupied = mOccupied;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700317 findOccupiedCells(xCount, yCount, occupied, null, true);
Michael Jurkaaf442092010-06-10 17:01:57 -0700318
319 cellInfo.cell = null;
320 cellInfo.cellX = cellXY[0];
321 cellInfo.cellY = cellXY[1];
322 cellInfo.spanX = 1;
323 cellInfo.spanY = 1;
Michael Jurkac28de512010-08-13 11:27:44 -0700324 cellInfo.intersectX = cellXY[0];
325 cellInfo.intersectY = cellXY[1];
Michael Jurkaaf442092010-06-10 17:01:57 -0700326 cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount &&
327 cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]];
328
329 // Instead of finding the interesting vacant cells here, wait until a
330 // caller invokes getTag() to retrieve the result. Finding the vacant
331 // cells is a bit expensive and can generate many new objects, it's
332 // therefore better to defer it until we know we actually need it.
333
334 mDirtyTag = true;
335 }
336 setTag(cellInfo);
337 }
338
Winson Chungaafa03c2010-06-11 17:34:16 -0700339
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800340 @Override
341 public boolean onInterceptTouchEvent(MotionEvent ev) {
Michael Jurkadee05892010-07-27 10:01:56 -0700342 if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
343 return true;
344 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800345 final int action = ev.getAction();
346 final CellInfo cellInfo = mCellInfo;
347
348 if (action == MotionEvent.ACTION_DOWN) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700349 setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800350 } else if (action == MotionEvent.ACTION_UP) {
351 cellInfo.cell = null;
352 cellInfo.cellX = -1;
353 cellInfo.cellY = -1;
354 cellInfo.spanX = 0;
355 cellInfo.spanY = 0;
356 cellInfo.valid = false;
357 mDirtyTag = false;
358 setTag(cellInfo);
359 }
360
361 return false;
362 }
363
364 @Override
365 public CellInfo getTag() {
366 final CellInfo info = (CellInfo) super.getTag();
367 if (mDirtyTag && info.valid) {
Adam Cohend22015c2010-07-26 22:02:18 -0700368 final int xCount = mCountX;
369 final int yCount = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800370
371 final boolean[][] occupied = mOccupied;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700372 findOccupiedCells(xCount, yCount, occupied, null, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800373
Michael Jurkac28de512010-08-13 11:27:44 -0700374 info.updateOccupiedCells(occupied, mCountX, mCountY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800375
376 mDirtyTag = false;
377 }
378 return info;
379 }
380
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700381 /**
382 * Check if the column 'x' is empty from rows 'top' to 'bottom', inclusive.
383 */
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800384 private static boolean isColumnEmpty(int x, int top, int bottom, boolean[][] occupied) {
385 for (int y = top; y <= bottom; y++) {
386 if (occupied[x][y]) {
387 return false;
388 }
389 }
390 return true;
391 }
392
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700393 /**
394 * Check if the row 'y' is empty from columns 'left' to 'right', inclusive.
395 */
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800396 private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) {
397 for (int x = left; x <= right; x++) {
398 if (occupied[x][y]) {
399 return false;
400 }
401 }
402 return true;
403 }
404
Michael Jurkac28de512010-08-13 11:27:44 -0700405 CellInfo updateOccupiedCells(boolean[] occupiedCells, View ignoreView) {
Adam Cohend22015c2010-07-26 22:02:18 -0700406 final int xCount = mCountX;
407 final int yCount = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800408
409 boolean[][] occupied = mOccupied;
410
411 if (occupiedCells != null) {
412 for (int y = 0; y < yCount; y++) {
413 for (int x = 0; x < xCount; x++) {
414 occupied[x][y] = occupiedCells[y * xCount + x];
415 }
416 }
417 } else {
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700418 findOccupiedCells(xCount, yCount, occupied, ignoreView, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800419 }
420
421 CellInfo cellInfo = new CellInfo();
422
423 cellInfo.cellX = -1;
424 cellInfo.cellY = -1;
Michael Jurkac28de512010-08-13 11:27:44 -0700425 cellInfo.intersectX = -1;
426 cellInfo.intersectY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800427 cellInfo.spanY = 0;
428 cellInfo.spanX = 0;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800429 cellInfo.screen = mCellInfo.screen;
430
Michael Jurkac28de512010-08-13 11:27:44 -0700431 cellInfo.updateOccupiedCells(occupied, mCountX, mCountY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800432
Michael Jurkac28de512010-08-13 11:27:44 -0700433 cellInfo.valid = cellInfo.existsEmptyCell();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800434
435 // Assume the caller will perform their own cell searching, otherwise we
436 // risk causing an unnecessary rebuild after findCellForSpan()
Winson Chungaafa03c2010-06-11 17:34:16 -0700437
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800438 return cellInfo;
439 }
440
441 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700442 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800443 * @param x X coordinate of the point
444 * @param y Y coordinate of the point
445 * @param result Array of 2 ints to hold the x and y coordinate of the cell
446 */
447 void pointToCellExact(int x, int y, int[] result) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700448 final int hStartPadding = getLeftPadding();
449 final int vStartPadding = getTopPadding();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800450
451 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
452 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
453
Adam Cohend22015c2010-07-26 22:02:18 -0700454 final int xAxis = mCountX;
455 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800456
457 if (result[0] < 0) result[0] = 0;
458 if (result[0] >= xAxis) result[0] = xAxis - 1;
459 if (result[1] < 0) result[1] = 0;
460 if (result[1] >= yAxis) result[1] = yAxis - 1;
461 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700462
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800463 /**
464 * Given a point, return the cell that most closely encloses that point
465 * @param x X coordinate of the point
466 * @param y Y coordinate of the point
467 * @param result Array of 2 ints to hold the x and y coordinate of the cell
468 */
469 void pointToCellRounded(int x, int y, int[] result) {
470 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
471 }
472
473 /**
474 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700475 *
476 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800477 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700478 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800479 * @param result Array of 2 ints to hold the x and y coordinate of the point
480 */
481 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700482 final int hStartPadding = getLeftPadding();
483 final int vStartPadding = getTopPadding();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800484
485 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
486 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
487 }
488
Romain Guy84f296c2009-11-04 15:00:44 -0800489 int getCellWidth() {
490 return mCellWidth;
491 }
492
493 int getCellHeight() {
494 return mCellHeight;
495 }
496
Romain Guy1a304a12009-11-10 00:02:32 -0800497 int getLeftPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700498 return mLeftPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800499 }
500
501 int getTopPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700502 return mTopPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800503 }
504
505 int getRightPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700506 return mRightPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800507 }
508
509 int getBottomPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700510 return mBottomPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800511 }
512
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800513 @Override
514 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
515 // TODO: currently ignoring padding
Winson Chungaafa03c2010-06-11 17:34:16 -0700516
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800517 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700518 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
519
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800520 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
521 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700522
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800523 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
524 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
525 }
526
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800527 final int cellWidth = mCellWidth;
528 final int cellHeight = mCellHeight;
529
Adam Cohend22015c2010-07-26 22:02:18 -0700530 if (mOccupied == null) {
531 mOccupied = new boolean[mCountX][mCountY];
Winson Chungaafa03c2010-06-11 17:34:16 -0700532 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800533
Adam Cohend22015c2010-07-26 22:02:18 -0700534 int numWidthGaps = mCountX - 1;
535 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800536
Adam Cohend22015c2010-07-26 22:02:18 -0700537 int vSpaceLeft = heightSpecSize - mTopPadding
538 - mBottomPadding - (cellHeight * mCountY);
539 mHeightGap = vSpaceLeft / numHeightGaps;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800540
Adam Cohend22015c2010-07-26 22:02:18 -0700541 int hSpaceLeft = widthSpecSize - mLeftPadding
542 - mRightPadding - (cellWidth * mCountX);
543 mWidthGap = hSpaceLeft / numWidthGaps;
Winson Chungaafa03c2010-06-11 17:34:16 -0700544
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800545 int count = getChildCount();
546
547 for (int i = 0; i < count; i++) {
548 View child = getChildAt(i);
549 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Winson Chungaafa03c2010-06-11 17:34:16 -0700550 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap,
551 mLeftPadding, mTopPadding);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800552
Winson Chungaafa03c2010-06-11 17:34:16 -0700553 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width,
554 MeasureSpec.EXACTLY);
555 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
556 MeasureSpec.EXACTLY);
557
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800558 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
559 }
560
561 setMeasuredDimension(widthSpecSize, heightSpecSize);
562 }
563
564 @Override
565 protected void onLayout(boolean changed, int l, int t, int r, int b) {
566 int count = getChildCount();
567
568 for (int i = 0; i < count; i++) {
569 View child = getChildAt(i);
570 if (child.getVisibility() != GONE) {
571
572 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
573
574 int childLeft = lp.x;
575 int childTop = lp.y;
576 child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
Romain Guy84f296c2009-11-04 15:00:44 -0800577
578 if (lp.dropped) {
579 lp.dropped = false;
580
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700581 final int[] cellXY = mTmpCellXY;
Romain Guy06762ab2010-01-25 16:51:08 -0800582 getLocationOnScreen(cellXY);
Romain Guy84f296c2009-11-04 15:00:44 -0800583 mWallpaperManager.sendWallpaperCommand(getWindowToken(), "android.home.drop",
Romain Guy06762ab2010-01-25 16:51:08 -0800584 cellXY[0] + childLeft + lp.width / 2,
585 cellXY[1] + childTop + lp.height / 2, 0, null);
Romain Guy84f296c2009-11-04 15:00:44 -0800586 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800587 }
588 }
589 }
590
591 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700592 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
593 super.onSizeChanged(w, h, oldw, oldh);
594 mLayoutRect.set(0, 0, w, h);
595 mDimmedBitmapRect.set(0, 0, (int) (DIMMED_BITMAP_SCALE * w), (int) (DIMMED_BITMAP_SCALE * h));
Michael Jurkaa63c4522010-08-19 13:52:27 -0700596 if (mDimmedBitmapBackground != null) {
597 mDimmedBitmapBackground.setBounds(mLayoutRect);
598 }
599 if (mDimmedBitmapBackgroundHover != null) {
600 mDimmedBitmapBackgroundHover.setBounds(mLayoutRect);
601 }
Michael Jurkadee05892010-07-27 10:01:56 -0700602 }
603
604 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800605 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
606 final int count = getChildCount();
607 for (int i = 0; i < count; i++) {
608 final View view = getChildAt(i);
609 view.setDrawingCacheEnabled(enabled);
610 // Update the drawing caches
Adam Powellfefa0ce2010-05-03 10:23:50 -0700611 view.buildDrawingCache(true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800612 }
613 }
614
615 @Override
616 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
617 super.setChildrenDrawnWithCacheEnabled(enabled);
618 }
619
Michael Jurkadee05892010-07-27 10:01:56 -0700620 public float getDimmedBitmapAlpha() {
621 return mDimmedBitmapAlpha;
622 }
623
624 public void setDimmedBitmapAlpha(float alpha) {
625 // If we're dimming the screen after it was not dimmed, refresh
626 // to allow for updated widgets. We don't continually refresh it
627 // after this point, however, as an optimization
628 if (mDimmedBitmapAlpha == 0.0f && alpha > 0.0f) {
629 updateDimmedBitmap();
630 }
631 mDimmedBitmapAlpha = alpha;
632 setChildrenAlpha(1.0f - mDimmedBitmapAlpha);
633 }
634
635 private void setChildrenAlpha(float alpha) {
636 for (int i = 0; i < getChildCount(); i++) {
637 getChildAt(i).setAlpha(alpha);
638 }
639 }
640
641 public void updateDimmedBitmap() {
642 if (mDimmedBitmap == null) {
643 mDimmedBitmap = Bitmap.createBitmap((int) (getWidth() * DIMMED_BITMAP_SCALE),
644 (int) (getHeight() * DIMMED_BITMAP_SCALE), Bitmap.Config.ARGB_8888);
645 mDimmedBitmapCanvas = new Canvas(mDimmedBitmap);
646 mDimmedBitmapCanvas.scale(DIMMED_BITMAP_SCALE, DIMMED_BITMAP_SCALE);
647 }
648 // clear the canvas
649 mDimmedBitmapCanvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
650
651 // draw the screen into the bitmap
652 // just for drawing to the bitmap, make all the items on the screen opaque
653 setChildrenAlpha(1.0f);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700654 // call our superclass's dispatchdraw so we don't draw the background
655 super.dispatchDraw(mDimmedBitmapCanvas);
Michael Jurkadee05892010-07-27 10:01:56 -0700656 setChildrenAlpha(1.0f - mDimmedBitmapAlpha);
657
Michael Jurkaa63c4522010-08-19 13:52:27 -0700658 // replace all colored areas with a dark (semi-transparent black)
659 mDimmedBitmapCanvas.drawColor(Color.argb(160, 0, 0, 0), PorterDuff.Mode.SRC_IN);
Michael Jurkadee05892010-07-27 10:01:56 -0700660 }
661
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700662 private boolean isVacant(int originX, int originY, int spanX, int spanY) {
663 for (int i = 0; i < spanY; i++) {
664 if (!isRowEmpty(originY + i, originX, originX + spanX - 1, mOccupied)) {
665 return false;
666 }
667 }
668 return true;
669 }
670
Patrick Dubroy440c3602010-07-13 17:50:32 -0700671 public View getChildAt(int x, int y) {
672 final int count = getChildCount();
673 for (int i = 0; i < count; i++) {
674 View child = getChildAt(i);
675 LayoutParams lp = (LayoutParams) child.getLayoutParams();
676
677 if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) &&
678 (lp.cellY <= y) && (y < lp.cellY + lp.cellHSpan)) {
679 return child;
680 }
681 }
682 return null;
683 }
684
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700685 /**
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -0700686 * Estimate the size that a child with the given dimensions will take in the layout.
687 */
688 void estimateChildSize(int minWidth, int minHeight, int[] result) {
689 // Assuming it's placed at 0, 0, find where the bottom right cell will land
690 rectToCell(minWidth, minHeight, result);
691
692 // Then figure out the rect it will occupy
693 cellToRect(0, 0, result[0], result[1], mRectF);
694 result[0] = (int)mRectF.width();
695 result[1] = (int)mRectF.height();
696 }
697
698 /**
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700699 * Estimate where the top left cell of the dragged item will land if it is dropped.
700 *
701 * @param originX The X value of the top left corner of the item
702 * @param originY The Y value of the top left corner of the item
703 * @param spanX The number of horizontal cells that the item spans
704 * @param spanY The number of vertical cells that the item spans
705 * @param result The estimated drop cell X and Y.
706 */
707 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
Adam Cohend22015c2010-07-26 22:02:18 -0700708 final int countX = mCountX;
709 final int countY = mCountY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700710
Michael Jurkaa63c4522010-08-19 13:52:27 -0700711 // pointToCellRounded takes the top left of a cell but will pad that with
712 // cellWidth/2 and cellHeight/2 when finding the matching cell
713 pointToCellRounded(originX, originY, result);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700714
715 // If the item isn't fully on this screen, snap to the edges
716 int rightOverhang = result[0] + spanX - countX;
717 if (rightOverhang > 0) {
718 result[0] -= rightOverhang; // Snap to right
719 }
720 result[0] = Math.max(0, result[0]); // Snap to left
721 int bottomOverhang = result[1] + spanY - countY;
722 if (bottomOverhang > 0) {
723 result[1] -= bottomOverhang; // Snap to bottom
724 }
725 result[1] = Math.max(0, result[1]); // Snap to top
726 }
727
728 void visualizeDropLocation(View view, int originX, int originY, int spanX, int spanY) {
729 final int[] originCell = mDragCell;
730 final int[] cellXY = mTmpCellXY;
731 estimateDropCell(originX, originY, spanX, spanY, cellXY);
732
733 // Only recalculate the bounding rect when necessary
734 if (!Arrays.equals(cellXY, originCell)) {
735 originCell[0] = cellXY[0];
736 originCell[1] = cellXY[1];
737
738 // Find the top left corner of the rect the object will occupy
739 final int[] topLeft = mTmpCellXY;
740 cellToPoint(originCell[0], originCell[1], topLeft);
741 final int left = topLeft[0];
742 final int top = topLeft[1];
743
744 // Now find the bottom right
745 final int[] bottomRight = mTmpCellXY;
746 cellToPoint(originCell[0] + spanX - 1, originCell[1] + spanY - 1, bottomRight);
747 bottomRight[0] += mCellWidth;
748 bottomRight[1] += mCellHeight;
749
Adam Cohend22015c2010-07-26 22:02:18 -0700750 final int countX = mCountX;
751 final int countY = mCountY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700752 // TODO: It's not necessary to do this every time, but it's not especially expensive
753 findOccupiedCells(countX, countY, mOccupied, view, false);
754
755 boolean vacant = isVacant(originCell[0], originCell[1], spanX, spanY);
756 mDragRectDrawable = vacant ? mVacantDrawable : mOccupiedDrawable;
757
758 // mDragRect will be rendered in onDraw()
759 mDragRect.set(left, top, bottomRight[0], bottomRight[1]);
760 invalidate();
761 }
762 }
763
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800764 /**
Jeff Sharkey70864282009-04-07 21:08:40 -0700765 * Find a vacant area that will fit the given bounds nearest the requested
766 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -0700767 *
Romain Guy51afc022009-05-04 18:03:43 -0700768 * @param pixelX The X location at which you want to search for a vacant area.
769 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -0700770 * @param spanX Horizontal span of the object.
771 * @param spanY Vertical span of the object.
772 * @param vacantCells Pre-computed set of vacant cells to search.
773 * @param recycle Previously returned value to possibly recycle.
774 * @return The X, Y cell of a vacant area that can contain this object,
775 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800776 */
Jeff Sharkey70864282009-04-07 21:08:40 -0700777 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
778 CellInfo vacantCells, int[] recycle) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700779
Jeff Sharkey70864282009-04-07 21:08:40 -0700780 // Keep track of best-scoring drop area
781 final int[] bestXY = recycle != null ? recycle : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -0700782 double bestDistance = Double.MAX_VALUE;
Winson Chungaafa03c2010-06-11 17:34:16 -0700783
Michael Jurkac28de512010-08-13 11:27:44 -0700784 for (int x = 0; x < mCountX - (spanX - 1); x++) {
785 inner:
786 for (int y = 0; y < mCountY - (spanY - 1); y++) {
787 for (int i = 0; i < spanX; i++) {
788 for (int j = 0; j < spanY; j++) {
789 if (mOccupied[x + i][y + j]) {
790 // small optimization: we can skip to below the row we just found
791 // an occupied cell
792 y += j;
793 continue inner;
794 }
795 }
796 }
797 final int[] cellXY = mTmpCellXY;
798 cellToPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800799
Michael Jurkac28de512010-08-13 11:27:44 -0700800 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
801 + Math.pow(cellXY[1] - pixelY, 2));
802 if (distance <= bestDistance) {
803 bestDistance = distance;
804 bestXY[0] = x;
805 bestXY[1] = y;
806 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800807 }
808 }
809
Winson Chungaafa03c2010-06-11 17:34:16 -0700810 // Return null if no suitable location found
Jeff Sharkey70864282009-04-07 21:08:40 -0700811 if (bestDistance < Double.MAX_VALUE) {
812 return bestXY;
813 } else {
814 return null;
815 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800816 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700817
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800818 /**
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700819 * Called when a drag and drop operation has finished (successfully or not).
820 */
821 void onDragComplete() {
822 // Invalidate the drag data
823 mDragCell[0] = -1;
824 mDragCell[1] = -1;
825
Michael Jurkaa63c4522010-08-19 13:52:27 -0700826 setHover(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700827 mDragRect.setEmpty();
828 invalidate();
829 }
830
831 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700832 * Mark a child as having been dropped.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800833 *
834 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800835 */
Winson Chungaafa03c2010-06-11 17:34:16 -0700836 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -0700837 if (child != null) {
838 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guyd94533d2009-08-17 10:01:15 -0700839 lp.isDragging = false;
Romain Guy84f296c2009-11-04 15:00:44 -0800840 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -0700841 mDragRect.setEmpty();
842 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -0700843 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700844 onDragComplete();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800845 }
846
847 void onDropAborted(View child) {
848 if (child != null) {
849 ((LayoutParams) child.getLayoutParams()).isDragging = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800850 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700851 onDragComplete();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800852 }
853
854 /**
855 * Start dragging the specified child
Winson Chungaafa03c2010-06-11 17:34:16 -0700856 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800857 * @param child The child that is being dragged
858 */
859 void onDragChild(View child) {
860 LayoutParams lp = (LayoutParams) child.getLayoutParams();
861 lp.isDragging = true;
862 mDragRect.setEmpty();
863 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700864
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800865 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800866 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -0700867 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800868 * @param cellX X coordinate of upper left corner expressed as a cell position
869 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -0700870 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800871 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700872 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800873 */
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700874 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800875 final int cellWidth = mCellWidth;
876 final int cellHeight = mCellHeight;
877 final int widthGap = mWidthGap;
878 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -0700879
880 final int hStartPadding = getLeftPadding();
881 final int vStartPadding = getTopPadding();
882
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800883 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
884 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
885
886 int x = hStartPadding + cellX * (cellWidth + widthGap);
887 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -0700888
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700889 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800890 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700891
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800892 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700893 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800894 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -0700895 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800896 * @param width Width in pixels
897 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -0700898 * @param result An array of length 2 in which to store the result (may be null).
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800899 */
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -0700900 public int[] rectToCell(int width, int height, int[] result) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800901 // Always assume we're working with the smallest span to make sure we
902 // reserve enough space in both orientations.
Joe Onorato79e56262009-09-21 15:23:04 -0400903 final Resources resources = getResources();
904 int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
905 int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800906 int smallerSize = Math.min(actualWidth, actualHeight);
Joe Onorato79e56262009-09-21 15:23:04 -0400907
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800908 // Always round up to next largest cell
909 int spanX = (width + smallerSize) / smallerSize;
910 int spanY = (height + smallerSize) / smallerSize;
Joe Onorato79e56262009-09-21 15:23:04 -0400911
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -0700912 if (result == null) {
913 return new int[] { spanX, spanY };
914 }
915 result[0] = spanX;
916 result[1] = spanY;
917 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800918 }
919
920 /**
921 * Find the first vacant cell, if there is one.
922 *
923 * @param vacant Holds the x and y coordinate of the vacant cell
924 * @param spanX Horizontal cell span.
925 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -0700926 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800927 * @return True if a vacant cell was found
928 */
929 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
Adam Cohend22015c2010-07-26 22:02:18 -0700930 final int xCount = mCountX;
931 final int yCount = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800932 final boolean[][] occupied = mOccupied;
933
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700934 findOccupiedCells(xCount, yCount, occupied, null, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800935
936 return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
937 }
938
939 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
940 int xCount, int yCount, boolean[][] occupied) {
941
942 for (int x = 0; x < xCount; x++) {
943 for (int y = 0; y < yCount; y++) {
944 boolean available = !occupied[x][y];
945out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
946 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
947 available = available && !occupied[i][j];
948 if (!available) break out;
949 }
950 }
951
952 if (available) {
953 vacant[0] = x;
954 vacant[1] = y;
955 return true;
956 }
957 }
958 }
959
960 return false;
961 }
962
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700963 /**
964 * Update the array of occupied cells (mOccupied), and return a flattened copy of the array.
965 */
966 boolean[] getOccupiedCellsFlattened() {
Adam Cohend22015c2010-07-26 22:02:18 -0700967 final int xCount = mCountX;
968 final int yCount = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800969 final boolean[][] occupied = mOccupied;
970
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700971 findOccupiedCells(xCount, yCount, occupied, null, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800972
973 final boolean[] flat = new boolean[xCount * yCount];
974 for (int y = 0; y < yCount; y++) {
975 for (int x = 0; x < xCount; x++) {
976 flat[y * xCount + x] = occupied[x][y];
977 }
978 }
979
980 return flat;
981 }
982
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700983 /**
984 * Update the array of occupied cells.
985 * @param ignoreView If non-null, the space occupied by this View is treated as vacant
986 * @param ignoreFolders If true, a cell occupied by a Folder is treated as vacant
987 */
988 private void findOccupiedCells(
989 int xCount, int yCount, boolean[][] occupied, View ignoreView, boolean ignoreFolders) {
990
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800991 for (int x = 0; x < xCount; x++) {
992 for (int y = 0; y < yCount; y++) {
993 occupied[x][y] = false;
994 }
995 }
996
997 int count = getChildCount();
998 for (int i = 0; i < count; i++) {
999 View child = getChildAt(i);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001000 if ((ignoreFolders && child instanceof Folder) || child.equals(ignoreView)) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001001 continue;
1002 }
1003 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1004
1005 for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan && x < xCount; x++) {
1006 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan && y < yCount; y++) {
1007 occupied[x][y] = true;
1008 }
1009 }
1010 }
1011 }
1012
1013 @Override
1014 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1015 return new CellLayout.LayoutParams(getContext(), attrs);
1016 }
1017
1018 @Override
1019 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1020 return p instanceof CellLayout.LayoutParams;
1021 }
1022
1023 @Override
1024 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1025 return new CellLayout.LayoutParams(p);
1026 }
1027
Winson Chungaafa03c2010-06-11 17:34:16 -07001028 public static class CellLayoutAnimationController extends LayoutAnimationController {
1029 public CellLayoutAnimationController(Animation animation, float delay) {
1030 super(animation, delay);
1031 }
1032
1033 @Override
1034 protected long getDelayForView(View view) {
1035 return (int) (Math.random() * 150);
1036 }
1037 }
1038
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001039 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1040 /**
1041 * Horizontal location of the item in the grid.
1042 */
1043 @ViewDebug.ExportedProperty
1044 public int cellX;
1045
1046 /**
1047 * Vertical location of the item in the grid.
1048 */
1049 @ViewDebug.ExportedProperty
1050 public int cellY;
1051
1052 /**
1053 * Number of cells spanned horizontally by the item.
1054 */
1055 @ViewDebug.ExportedProperty
1056 public int cellHSpan;
1057
1058 /**
1059 * Number of cells spanned vertically by the item.
1060 */
1061 @ViewDebug.ExportedProperty
1062 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07001063
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001064 /**
1065 * Is this item currently being dragged
1066 */
1067 public boolean isDragging;
1068
1069 // X coordinate of the view in the layout.
1070 @ViewDebug.ExportedProperty
1071 int x;
1072 // Y coordinate of the view in the layout.
1073 @ViewDebug.ExportedProperty
1074 int y;
1075
Romain Guy84f296c2009-11-04 15:00:44 -08001076 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07001077
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001078 public LayoutParams(Context c, AttributeSet attrs) {
1079 super(c, attrs);
1080 cellHSpan = 1;
1081 cellVSpan = 1;
1082 }
1083
1084 public LayoutParams(ViewGroup.LayoutParams source) {
1085 super(source);
1086 cellHSpan = 1;
1087 cellVSpan = 1;
1088 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001089
1090 public LayoutParams(LayoutParams source) {
1091 super(source);
1092 this.cellX = source.cellX;
1093 this.cellY = source.cellY;
1094 this.cellHSpan = source.cellHSpan;
1095 this.cellVSpan = source.cellVSpan;
1096 }
1097
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001098 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08001099 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001100 this.cellX = cellX;
1101 this.cellY = cellY;
1102 this.cellHSpan = cellHSpan;
1103 this.cellVSpan = cellVSpan;
1104 }
1105
1106 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
1107 int hStartPadding, int vStartPadding) {
Winson Chungaafa03c2010-06-11 17:34:16 -07001108
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001109 final int myCellHSpan = cellHSpan;
1110 final int myCellVSpan = cellVSpan;
1111 final int myCellX = cellX;
1112 final int myCellY = cellY;
Winson Chungaafa03c2010-06-11 17:34:16 -07001113
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001114 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
1115 leftMargin - rightMargin;
1116 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
1117 topMargin - bottomMargin;
1118
1119 x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
1120 y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
1121 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001122
1123 public String toString() {
1124 return "(" + this.cellX + ", " + this.cellY + ")";
1125 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001126 }
1127
1128 static final class CellInfo implements ContextMenu.ContextMenuInfo {
Michael Jurkac28de512010-08-13 11:27:44 -07001129 private boolean[][] mOccupied;
1130 private int mCountX;
1131 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001132 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07001133 int cellX = -1;
1134 int cellY = -1;
Michael Jurkac28de512010-08-13 11:27:44 -07001135 // intersectX and intersectY constrain the results of findCellForSpan; any empty space
1136 // it results must include this point (unless intersectX and intersectY are -1)
Michael Jurkaa63c4522010-08-19 13:52:27 -07001137 int intersectX = -1;
1138 int intersectY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001139 int spanX;
1140 int spanY;
1141 int screen;
1142 boolean valid;
1143
Michael Jurkac28de512010-08-13 11:27:44 -07001144 void updateOccupiedCells(boolean[][] occupied, int xCount, int yCount) {
1145 mOccupied = occupied.clone();
1146 mCountX = xCount;
1147 mCountY = yCount;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001148 }
1149
Michael Jurkac28de512010-08-13 11:27:44 -07001150 void updateOccupiedCells(boolean[] occupied, int xCount, int yCount) {
1151 if (mOccupied == null || mCountX != xCount || mCountY != yCount) {
1152 mOccupied = new boolean[xCount][yCount];
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001153 }
Michael Jurkac28de512010-08-13 11:27:44 -07001154 mCountX = xCount;
1155 mCountY = yCount;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001156 for (int y = 0; y < yCount; y++) {
1157 for (int x = 0; x < xCount; x++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001158 mOccupied[x][y] = occupied[y * xCount + x];
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001159 }
1160 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001161 }
1162
Michael Jurkac28de512010-08-13 11:27:44 -07001163 boolean existsEmptyCell() {
1164 return findCellForSpan(null, 1, 1);
1165 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001166 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001167 * Finds the upper-left coordinate of the first rectangle in the grid that can
Michael Jurkac28de512010-08-13 11:27:44 -07001168 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
1169 * then this method will only return coordinates for rectangles that contain the cell
1170 * (intersectX, intersectY)
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001171 *
1172 * @param cellXY The array that will contain the position of a vacant cell if such a cell
1173 * can be found.
1174 * @param spanX The horizontal span of the cell we want to find.
1175 * @param spanY The vertical span of the cell we want to find.
1176 *
1177 * @return True if a vacant cell of the specified dimension was found, false otherwise.
1178 */
1179 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Michael Jurka0e260592010-06-30 17:07:39 -07001180 // return the span represented by the CellInfo only there is no view there
1181 // (this.cell == null) and there is enough space
Michael Jurkac28de512010-08-13 11:27:44 -07001182
Michael Jurka0e260592010-06-30 17:07:39 -07001183 if (this.cell == null && this.spanX >= spanX && this.spanY >= spanY) {
Michael Jurkac28de512010-08-13 11:27:44 -07001184 if (cellXY != null) {
1185 cellXY[0] = cellX;
1186 cellXY[1] = cellY;
1187 }
1188 return true;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001189 }
1190
Michael Jurkac28de512010-08-13 11:27:44 -07001191 int startX = 0;
1192 if (intersectX >= 0) {
1193 startX = Math.max(startX, intersectX - (spanX - 1));
1194 }
1195 int endX = mCountX - (spanX - 1);
1196 if (intersectX >= 0) {
1197 endX = Math.min(endX, intersectX + (spanX - 1));
1198 }
1199 int startY = 0;
1200 if (intersectY >= 0) {
1201 startY = Math.max(startY, intersectY - (spanY - 1));
1202 }
1203 int endY = mCountY - (spanY - 1);
1204 if (intersectY >= 0) {
1205 endY = Math.min(endY, intersectY + (spanY - 1));
1206 }
1207
Michael Jurkaa63c4522010-08-19 13:52:27 -07001208 for (int x = startX; x < endX; x++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001209 inner:
1210 for (int y = startY; y < endY; y++) {
1211 for (int i = 0; i < spanX; i++) {
1212 for (int j = 0; j < spanY; j++) {
1213 if (mOccupied[x + i][y + j]) {
1214 // small optimization: we can skip to below the row we just found
1215 // an occupied cell
1216 y += j;
1217 continue inner;
1218 }
1219 }
1220 }
1221 if (cellXY != null) {
1222 cellXY[0] = x;
1223 cellXY[1] = y;
1224 }
1225 return true;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001226 }
1227 }
Michael Jurkac28de512010-08-13 11:27:44 -07001228 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001229 }
1230
1231 @Override
1232 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07001233 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
1234 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001235 }
1236 }
Mike Cleronf8bbd342009-10-23 16:15:16 -07001237
1238 public boolean lastDownOnOccupiedCell() {
1239 return mLastDownOnOccupiedCell;
1240 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001241}