blob: ee759a217f214948d29fac1a60c7b52a21c44eb3 [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
Joe Onorato4be866d2010-10-10 11:26:02 -070019import android.animation.Animator;
Brandon Keely50e6e562012-05-08 16:28:49 -070020import android.animation.AnimatorSet;
21import android.animation.ObjectAnimator;
Joe Onorato4be866d2010-10-10 11:26:02 -070022import android.animation.AnimatorListenerAdapter;
Chet Haase00397b12010-10-07 11:13:10 -070023import android.animation.TimeInterpolator;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070024import android.animation.ValueAnimator;
25import android.animation.ValueAnimator.AnimatorUpdateListener;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080026import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040027import android.content.res.Resources;
Winson Chungaafa03c2010-06-11 17:34:16 -070028import android.content.res.TypedArray;
Joe Onorato4be866d2010-10-10 11:26:02 -070029import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070030import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080031import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070032import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070033import android.graphics.Point;
34import android.graphics.PointF;
Adam Cohenb5ba0972011-09-07 18:02:31 -070035import android.graphics.PorterDuff;
36import android.graphics.PorterDuffXfermode;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080037import android.graphics.Rect;
Adam Cohen482ed822012-03-02 14:15:13 -080038import android.graphics.drawable.ColorDrawable;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070039import android.graphics.drawable.Drawable;
Adam Cohenb5ba0972011-09-07 18:02:31 -070040import android.graphics.drawable.NinePatchDrawable;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080041import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070042import android.util.Log;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080043import android.view.MotionEvent;
44import android.view.View;
45import android.view.ViewDebug;
46import android.view.ViewGroup;
Winson Chungaafa03c2010-06-11 17:34:16 -070047import android.view.animation.Animation;
Winson Chung150fbab2010-09-29 17:14:26 -070048import android.view.animation.DecelerateInterpolator;
Winson Chungaafa03c2010-06-11 17:34:16 -070049import android.view.animation.LayoutAnimationController;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080050
Adam Cohen66396872011-04-15 17:50:36 -070051import com.android.launcher.R;
Adam Cohen69ce2e52011-07-03 19:25:21 -070052import com.android.launcher2.FolderIcon.FolderRingAnimator;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070053
Adam Cohen69ce2e52011-07-03 19:25:21 -070054import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070055import java.util.Arrays;
Adam Cohenbfbfd262011-06-13 16:55:12 -070056import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080057import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070058
Michael Jurkabdb5c532011-02-01 15:05:06 -080059public class CellLayout extends ViewGroup {
Winson Chungaafa03c2010-06-11 17:34:16 -070060 static final String TAG = "CellLayout";
61
Adam Cohen2acce882012-03-28 19:03:19 -070062 private Launcher mLauncher;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080063 private int mCellWidth;
64 private int mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070065
Adam Cohend22015c2010-07-26 22:02:18 -070066 private int mCountX;
67 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080068
Adam Cohen234c4cd2011-07-17 21:03:04 -070069 private int mOriginalWidthGap;
70 private int mOriginalHeightGap;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080071 private int mWidthGap;
72 private int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070073 private int mMaxGap;
Adam Cohenebea84d2011-11-09 17:20:41 -080074 private boolean mScrollingTransformsDirty = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080075
76 private final Rect mRect = new Rect();
77 private final CellInfo mCellInfo = new CellInfo();
Winson Chungaafa03c2010-06-11 17:34:16 -070078
Patrick Dubroyde7658b2010-09-27 11:15:43 -070079 // These are temporary variables to prevent having to allocate a new object just to
80 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Winson Chung0be025d2011-05-23 17:45:09 -070081 private final int[] mTmpXY = new int[2];
Patrick Dubroyde7658b2010-09-27 11:15:43 -070082 private final int[] mTmpPoint = new int[2];
83 private final PointF mTmpPointF = new PointF();
Adam Cohen69ce2e52011-07-03 19:25:21 -070084 int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070085
The Android Open Source Project31dd5032009-03-03 19:32:27 -080086 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -080087 boolean[][] mTmpOccupied;
Michael Jurkad771c962011-08-09 15:00:48 -070088 private boolean mLastDownOnOccupiedCell = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080089
Michael Jurkadee05892010-07-27 10:01:56 -070090 private OnTouchListener mInterceptTouchListener;
91
Adam Cohen69ce2e52011-07-03 19:25:21 -070092 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -070093 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -070094
Adam Cohenb5ba0972011-09-07 18:02:31 -070095 private int mForegroundAlpha = 0;
Michael Jurka5f1c5092010-09-03 14:15:02 -070096 private float mBackgroundAlpha;
Adam Cohen1b0aaac2010-10-28 11:11:18 -070097 private float mBackgroundAlphaMultiplier = 1.0f;
Adam Cohenf34bab52010-09-30 14:11:56 -070098
Michael Jurka33945b22010-12-21 18:19:38 -080099 private Drawable mNormalBackground;
Michael Jurka33945b22010-12-21 18:19:38 -0800100 private Drawable mActiveGlowBackground;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700101 private Drawable mOverScrollForegroundDrawable;
102 private Drawable mOverScrollLeft;
103 private Drawable mOverScrollRight;
Michael Jurka18014792010-10-14 09:01:34 -0700104 private Rect mBackgroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700105 private Rect mForegroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700106 private int mForegroundPadding;
Patrick Dubroy1262e362010-10-06 15:49:50 -0700107
Michael Jurka33945b22010-12-21 18:19:38 -0800108 // If we're actively dragging something over this screen, mIsDragOverlapping is true
109 private boolean mIsDragOverlapping = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700110 private final Point mDragCenter = new Point();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700111
Winson Chung150fbab2010-09-29 17:14:26 -0700112 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700113 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohend41fbf52012-02-16 23:53:59 -0800114 private Rect[] mDragOutlines = new Rect[4];
Chet Haase472b2812010-10-14 07:02:04 -0700115 private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700116 private InterruptibleInOutAnimator[] mDragOutlineAnims =
117 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700118
119 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700120 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700121 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700122
Patrick Dubroy96864c32011-03-10 17:17:23 -0800123 private BubbleTextView mPressedOrFocusedIcon;
124
Adam Cohen482ed822012-03-02 14:15:13 -0800125 private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
126 HashMap<CellLayout.LayoutParams, Animator>();
Adam Cohen19f37922012-03-21 11:59:11 -0700127 private HashMap<View, ReorderHintAnimation>
128 mShakeAnimators = new HashMap<View, ReorderHintAnimation>();
129
130 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700131
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700132 // When a drag operation is in progress, holds the nearest cell to the touch point
133 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800134
Joe Onorato4be866d2010-10-10 11:26:02 -0700135 private boolean mDragging = false;
136
Patrick Dubroyce34a972010-10-19 10:34:32 -0700137 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700138 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700139
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800140 private boolean mIsHotseat = false;
Winson Chungeecf02d2012-03-02 17:14:58 -0800141 private float mChildScale = 1f;
142 private float mHotseatChildScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800143
Adam Cohen482ed822012-03-02 14:15:13 -0800144 public static final int MODE_DRAG_OVER = 0;
145 public static final int MODE_ON_DROP = 1;
146 public static final int MODE_ON_DROP_EXTERNAL = 2;
147 public static final int MODE_ACCEPT_DROP = 3;
Adam Cohen19f37922012-03-21 11:59:11 -0700148 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800149 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
150
Adam Cohena897f392012-04-27 18:12:05 -0700151 static final int LANDSCAPE = 0;
152 static final int PORTRAIT = 1;
153
Brandon Keely50e6e562012-05-08 16:28:49 -0700154 private static final float REORDER_HINT_MAGNITUDE = 0.10f;
Adam Cohen19f37922012-03-21 11:59:11 -0700155 private static final int REORDER_ANIMATION_DURATION = 150;
156 private float mReorderHintAnimationMagnitude;
157
Adam Cohen482ed822012-03-02 14:15:13 -0800158 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
159 private Rect mOccupiedRect = new Rect();
160 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700161 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700162 private static final int INVALID_DIRECTION = -100;
Adam Cohenc6cc61d2012-04-04 12:47:08 -0700163 private DropTarget.DragEnforcer mDragEnforcer;
Adam Cohen482ed822012-03-02 14:15:13 -0800164
Romain Guy8a0bff52012-05-06 13:14:33 -0700165 private final static PorterDuffXfermode sAddBlendMode =
166 new PorterDuffXfermode(PorterDuff.Mode.ADD);
167
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800168 public CellLayout(Context context) {
169 this(context, null);
170 }
171
172 public CellLayout(Context context, AttributeSet attrs) {
173 this(context, attrs, 0);
174 }
175
176 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
177 super(context, attrs, defStyle);
Michael Jurka8b805b12012-04-18 14:23:14 -0700178 mDragEnforcer = new DropTarget.DragEnforcer(context);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700179
180 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
181 // the user where a dragged item will land when dropped.
182 setWillNotDraw(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700183 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700184
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800185 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
186
Adam Cohenf4bd5792012-04-27 11:35:29 -0700187 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
188 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700189 mWidthGap = mOriginalWidthGap = a.getDimensionPixelSize(R.styleable.CellLayout_widthGap, 0);
190 mHeightGap = mOriginalHeightGap = a.getDimensionPixelSize(R.styleable.CellLayout_heightGap, 0);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700191 mMaxGap = a.getDimensionPixelSize(R.styleable.CellLayout_maxGap, 0);
Adam Cohend22015c2010-07-26 22:02:18 -0700192 mCountX = LauncherModel.getCellCountX();
193 mCountY = LauncherModel.getCellCountY();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700194 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800195 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700196 mPreviousReorderDirection[0] = INVALID_DIRECTION;
197 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800198
199 a.recycle();
200
201 setAlwaysDrawnWithCacheEnabled(false);
202
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700203 final Resources res = getResources();
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700204
Winson Chung967289b2011-06-30 18:09:30 -0700205 mNormalBackground = res.getDrawable(R.drawable.homescreen_blue_normal_holo);
Winson Chungdea74b72011-09-13 18:06:43 -0700206 mActiveGlowBackground = res.getDrawable(R.drawable.homescreen_blue_strong_holo);
Michael Jurka33945b22010-12-21 18:19:38 -0800207
Adam Cohenb5ba0972011-09-07 18:02:31 -0700208 mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
209 mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
210 mForegroundPadding =
211 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
Michael Jurka33945b22010-12-21 18:19:38 -0800212
Adam Cohen19f37922012-03-21 11:59:11 -0700213 mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE *
214 res.getDimensionPixelSize(R.dimen.app_icon_size));
215
Winson Chungb26f3d62011-06-02 10:49:29 -0700216 mNormalBackground.setFilterBitmap(true);
Winson Chungb26f3d62011-06-02 10:49:29 -0700217 mActiveGlowBackground.setFilterBitmap(true);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700218
Winson Chungeecf02d2012-03-02 17:14:58 -0800219 int iconScale = res.getInteger(R.integer.app_icon_scale_percent);
220 if (iconScale >= 0) {
221 mChildScale = iconScale / 100f;
222 }
223 int hotseatIconScale = res.getInteger(R.integer.app_icon_hotseat_scale_percent);
224 if (hotseatIconScale >= 0) {
225 mHotseatChildScale = hotseatIconScale / 100f;
226 }
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800227
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700228 // Initialize the data structures used for the drag visualization.
Winson Chung150fbab2010-09-29 17:14:26 -0700229
Patrick Dubroyce34a972010-10-19 10:34:32 -0700230 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700231
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700232
Winson Chungb8c69f32011-10-19 21:36:08 -0700233 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700234 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800235 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700236 }
237
238 // When dragging things around the home screens, we show a green outline of
239 // where the item will land. The outlines gradually fade out, leaving a trail
240 // behind the drag path.
241 // Set up all the animations that are used to implement this fading.
242 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700243 final float fromAlphaValue = 0;
244 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700245
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700246 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700247
248 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700249 final InterruptibleInOutAnimator anim =
250 new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700251 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700252 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700253 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700254 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700255 final Bitmap outline = (Bitmap)anim.getTag();
256
257 // If an animation is started and then stopped very quickly, we can still
258 // get spurious updates we've cleared the tag. Guard against this.
259 if (outline == null) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700260 @SuppressWarnings("all") // suppress dead code warning
261 final boolean debug = false;
262 if (debug) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700263 Object val = animation.getAnimatedValue();
264 Log.d(TAG, "anim " + thisIndex + " update: " + val +
265 ", isStopped " + anim.isStopped());
266 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700267 // Try to prevent it from continuing to run
268 animation.cancel();
269 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700270 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800271 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700272 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700273 }
274 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700275 // The animation holds a reference to the drag outline bitmap as long is it's
276 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700277 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700278 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700279 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700280 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700281 anim.setTag(null);
282 }
283 }
284 });
285 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700286 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700287
Michael Jurka18014792010-10-14 09:01:34 -0700288 mBackgroundRect = new Rect();
Adam Cohenb5ba0972011-09-07 18:02:31 -0700289 mForegroundRect = new Rect();
Michael Jurkabea15192010-11-17 12:33:46 -0800290
Michael Jurkaa52570f2012-03-20 03:18:20 -0700291 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
292 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
293 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700294 }
295
Michael Jurkaf6440da2011-04-05 14:50:34 -0700296 static int widthInPortrait(Resources r, int numCells) {
297 // We use this method from Workspace to figure out how many rows/columns Launcher should
298 // have. We ignore the left/right padding on CellLayout because it turns out in our design
299 // the padding extends outside the visible screen size, but it looked fine anyway.
Michael Jurkaf6440da2011-04-05 14:50:34 -0700300 int cellWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700301 int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
302 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
Michael Jurkaf6440da2011-04-05 14:50:34 -0700303
Winson Chung4b825dcd2011-06-19 12:41:22 -0700304 return minGap * (numCells - 1) + cellWidth * numCells;
Michael Jurkaf6440da2011-04-05 14:50:34 -0700305 }
306
Michael Jurkaf6440da2011-04-05 14:50:34 -0700307 static int heightInLandscape(Resources r, int numCells) {
308 // We use this method from Workspace to figure out how many rows/columns Launcher should
309 // have. We ignore the left/right padding on CellLayout because it turns out in our design
310 // the padding extends outside the visible screen size, but it looked fine anyway.
Michael Jurkaf6440da2011-04-05 14:50:34 -0700311 int cellHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700312 int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
313 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
Michael Jurkaf6440da2011-04-05 14:50:34 -0700314
Winson Chung4b825dcd2011-06-19 12:41:22 -0700315 return minGap * (numCells - 1) + cellHeight * numCells;
Michael Jurkaf6440da2011-04-05 14:50:34 -0700316 }
317
Adam Cohen2801caf2011-05-13 20:57:39 -0700318 public void enableHardwareLayers() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700319 mShortcutsAndWidgets.enableHardwareLayers();
Adam Cohen2801caf2011-05-13 20:57:39 -0700320 }
321
322 public void setGridSize(int x, int y) {
323 mCountX = x;
324 mCountY = y;
325 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800326 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700327 mTempRectStack.clear();
Adam Cohen76fc0852011-06-17 13:26:23 -0700328 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700329 }
330
Patrick Dubroy96864c32011-03-10 17:17:23 -0800331 private void invalidateBubbleTextView(BubbleTextView icon) {
332 final int padding = icon.getPressedOrFocusedBackgroundPadding();
Winson Chung4b825dcd2011-06-19 12:41:22 -0700333 invalidate(icon.getLeft() + getPaddingLeft() - padding,
334 icon.getTop() + getPaddingTop() - padding,
335 icon.getRight() + getPaddingLeft() + padding,
336 icon.getBottom() + getPaddingTop() + padding);
Patrick Dubroy96864c32011-03-10 17:17:23 -0800337 }
338
Adam Cohenb5ba0972011-09-07 18:02:31 -0700339 void setOverScrollAmount(float r, boolean left) {
340 if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
341 mOverScrollForegroundDrawable = mOverScrollLeft;
342 } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
343 mOverScrollForegroundDrawable = mOverScrollRight;
344 }
345
346 mForegroundAlpha = (int) Math.round((r * 255));
347 mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
348 invalidate();
349 }
350
Patrick Dubroy96864c32011-03-10 17:17:23 -0800351 void setPressedOrFocusedIcon(BubbleTextView icon) {
352 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
353 // requires an expanded clip rect (due to the glow's blur radius)
354 BubbleTextView oldIcon = mPressedOrFocusedIcon;
355 mPressedOrFocusedIcon = icon;
356 if (oldIcon != null) {
357 invalidateBubbleTextView(oldIcon);
358 }
359 if (mPressedOrFocusedIcon != null) {
360 invalidateBubbleTextView(mPressedOrFocusedIcon);
361 }
362 }
363
Michael Jurka33945b22010-12-21 18:19:38 -0800364 void setIsDragOverlapping(boolean isDragOverlapping) {
365 if (mIsDragOverlapping != isDragOverlapping) {
366 mIsDragOverlapping = isDragOverlapping;
367 invalidate();
368 }
369 }
370
371 boolean getIsDragOverlapping() {
372 return mIsDragOverlapping;
373 }
374
Adam Cohenebea84d2011-11-09 17:20:41 -0800375 protected void setOverscrollTransformsDirty(boolean dirty) {
376 mScrollingTransformsDirty = dirty;
377 }
378
379 protected void resetOverscrollTransforms() {
380 if (mScrollingTransformsDirty) {
381 setOverscrollTransformsDirty(false);
382 setTranslationX(0);
383 setRotationY(0);
384 // It doesn't matter if we pass true or false here, the important thing is that we
385 // pass 0, which results in the overscroll drawable not being drawn any more.
386 setOverScrollAmount(0, false);
387 setPivotX(getMeasuredWidth() / 2);
388 setPivotY(getMeasuredHeight() / 2);
389 }
390 }
391
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700392 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700393 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700394 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
395 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
396 // When we're small, we are either drawn normally or in the "accepts drops" state (during
397 // a drag). However, we also drag the mini hover background *over* one of those two
398 // backgrounds
Winson Chungb26f3d62011-06-02 10:49:29 -0700399 if (mBackgroundAlpha > 0.0f) {
Adam Cohenf34bab52010-09-30 14:11:56 -0700400 Drawable bg;
Michael Jurka33945b22010-12-21 18:19:38 -0800401
402 if (mIsDragOverlapping) {
403 // In the mini case, we draw the active_glow bg *over* the active background
Michael Jurkabdf78552011-10-31 14:34:25 -0700404 bg = mActiveGlowBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700405 } else {
Michael Jurkabdf78552011-10-31 14:34:25 -0700406 bg = mNormalBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700407 }
Michael Jurka33945b22010-12-21 18:19:38 -0800408
409 bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
410 bg.setBounds(mBackgroundRect);
411 bg.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700412 }
Romain Guya6abce82009-11-10 02:54:41 -0800413
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700414 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700415 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700416 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700417 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800418 final Rect r = mDragOutlines[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700419 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700420 paint.setAlpha((int)(alpha + .5f));
Adam Cohend41fbf52012-02-16 23:53:59 -0800421 canvas.drawBitmap(b, null, r, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700422 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700423 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800424
425 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
426 // requires an expanded clip rect (due to the glow's blur radius)
427 if (mPressedOrFocusedIcon != null) {
428 final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
429 final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
430 if (b != null) {
431 canvas.drawBitmap(b,
Winson Chung4b825dcd2011-06-19 12:41:22 -0700432 mPressedOrFocusedIcon.getLeft() + getPaddingLeft() - padding,
433 mPressedOrFocusedIcon.getTop() + getPaddingTop() - padding,
Patrick Dubroy96864c32011-03-10 17:17:23 -0800434 null);
435 }
436 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700437
Adam Cohen482ed822012-03-02 14:15:13 -0800438 if (DEBUG_VISUALIZE_OCCUPIED) {
439 int[] pt = new int[2];
440 ColorDrawable cd = new ColorDrawable(Color.RED);
441 cd.setBounds(0, 0, 80, 80);
442 for (int i = 0; i < mCountX; i++) {
443 for (int j = 0; j < mCountY; j++) {
444 if (mOccupied[i][j]) {
445 cellToPoint(i, j, pt);
446 canvas.save();
447 canvas.translate(pt[0], pt[1]);
448 cd.draw(canvas);
449 canvas.restore();
450 }
451 }
452 }
453 }
454
Andrew Flynn850d2e72012-04-26 16:51:20 -0700455 int previewOffset = FolderRingAnimator.sPreviewSize;
456
Adam Cohen69ce2e52011-07-03 19:25:21 -0700457 // The folder outer / inner ring image(s)
458 for (int i = 0; i < mFolderOuterRings.size(); i++) {
459 FolderRingAnimator fra = mFolderOuterRings.get(i);
460
461 // Draw outer ring
462 Drawable d = FolderRingAnimator.sSharedOuterRingDrawable;
463 int width = (int) fra.getOuterRingSize();
464 int height = width;
465 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
466
467 int centerX = mTempLocation[0] + mCellWidth / 2;
Andrew Flynn850d2e72012-04-26 16:51:20 -0700468 int centerY = mTempLocation[1] + previewOffset / 2;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700469
470 canvas.save();
471 canvas.translate(centerX - width / 2, centerY - height / 2);
472 d.setBounds(0, 0, width, height);
473 d.draw(canvas);
474 canvas.restore();
475
476 // Draw inner ring
477 d = FolderRingAnimator.sSharedInnerRingDrawable;
478 width = (int) fra.getInnerRingSize();
479 height = width;
480 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
481
482 centerX = mTempLocation[0] + mCellWidth / 2;
Andrew Flynn850d2e72012-04-26 16:51:20 -0700483 centerY = mTempLocation[1] + previewOffset / 2;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700484 canvas.save();
485 canvas.translate(centerX - width / 2, centerY - width / 2);
486 d.setBounds(0, 0, width, height);
487 d.draw(canvas);
488 canvas.restore();
489 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700490
491 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
492 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
493 int width = d.getIntrinsicWidth();
494 int height = d.getIntrinsicHeight();
495
496 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
497 int centerX = mTempLocation[0] + mCellWidth / 2;
Andrew Flynn850d2e72012-04-26 16:51:20 -0700498 int centerY = mTempLocation[1] + previewOffset / 2;
Adam Cohenc51934b2011-07-26 21:07:43 -0700499
500 canvas.save();
501 canvas.translate(centerX - width / 2, centerY - width / 2);
502 d.setBounds(0, 0, width, height);
503 d.draw(canvas);
504 canvas.restore();
505 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700506 }
507
Adam Cohenb5ba0972011-09-07 18:02:31 -0700508 @Override
509 protected void dispatchDraw(Canvas canvas) {
510 super.dispatchDraw(canvas);
511 if (mForegroundAlpha > 0) {
512 mOverScrollForegroundDrawable.setBounds(mForegroundRect);
513 Paint p = ((NinePatchDrawable) mOverScrollForegroundDrawable).getPaint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700514 p.setXfermode(sAddBlendMode);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700515 mOverScrollForegroundDrawable.draw(canvas);
516 p.setXfermode(null);
517 }
518 }
519
Adam Cohen69ce2e52011-07-03 19:25:21 -0700520 public void showFolderAccept(FolderRingAnimator fra) {
521 mFolderOuterRings.add(fra);
522 }
523
524 public void hideFolderAccept(FolderRingAnimator fra) {
525 if (mFolderOuterRings.contains(fra)) {
526 mFolderOuterRings.remove(fra);
527 }
528 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700529 }
530
Adam Cohenc51934b2011-07-26 21:07:43 -0700531 public void setFolderLeaveBehindCell(int x, int y) {
532 mFolderLeaveBehindCell[0] = x;
533 mFolderLeaveBehindCell[1] = y;
534 invalidate();
535 }
536
537 public void clearFolderLeaveBehind() {
538 mFolderLeaveBehindCell[0] = -1;
539 mFolderLeaveBehindCell[1] = -1;
540 invalidate();
541 }
542
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700543 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700544 public boolean shouldDelayChildPressedState() {
545 return false;
546 }
547
548 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700549 public void cancelLongPress() {
550 super.cancelLongPress();
551
552 // Cancel long press for all children
553 final int count = getChildCount();
554 for (int i = 0; i < count; i++) {
555 final View child = getChildAt(i);
556 child.cancelLongPress();
557 }
558 }
559
Michael Jurkadee05892010-07-27 10:01:56 -0700560 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
561 mInterceptTouchListener = listener;
562 }
563
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800564 int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700565 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800566 }
567
568 int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700569 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800570 }
571
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800572 public void setIsHotseat(boolean isHotseat) {
573 mIsHotseat = isHotseat;
574 }
575
Winson Chungeecf02d2012-03-02 17:14:58 -0800576 public float getChildrenScale() {
577 return mIsHotseat ? mHotseatChildScale : mChildScale;
578 }
579
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800580
Andrew Flynn850d2e72012-04-26 16:51:20 -0700581 private void scaleChild(BubbleTextView bubbleChild, float scale) {
Andrew Flynnbc239a12012-03-06 11:39:49 -0800582 // If we haven't measured the child yet, do it now
583 // (this happens if we're being dropped from all-apps
584 if (bubbleChild.getLayoutParams() instanceof LayoutParams &&
585 (bubbleChild.getMeasuredWidth() | bubbleChild.getMeasuredHeight()) == 0) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700586 getShortcutsAndWidgets().measureChild(bubbleChild);
Andrew Flynnbc239a12012-03-06 11:39:49 -0800587 }
Andrew Flynnbc239a12012-03-06 11:39:49 -0800588
Andrew Flynnbc239a12012-03-06 11:39:49 -0800589 bubbleChild.setScaleX(scale);
590 bubbleChild.setScaleY(scale);
Andrew Flynnbc239a12012-03-06 11:39:49 -0800591 }
592
593 private void resetChild(BubbleTextView bubbleChild) {
594 bubbleChild.setScaleX(1f);
595 bubbleChild.setScaleY(1f);
Andrew Flynnbc239a12012-03-06 11:39:49 -0800596
597 bubbleChild.setTextColor(getResources().getColor(R.color.workspace_icon_text_color));
598 }
599
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800600 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Andrew Flynn850d2e72012-04-26 16:51:20 -0700601 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700602 final LayoutParams lp = params;
603
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800604 // Hotseat icons - scale down and remove text
605 // Don't scale the all apps button
606 // scale percent set to -1 means do not scale
607 // Only scale BubbleTextViews
608 if (child instanceof BubbleTextView) {
609 BubbleTextView bubbleChild = (BubbleTextView) child;
610
Andrew Flynnbc239a12012-03-06 11:39:49 -0800611 // Start the child with 100% scale and visible text
612 resetChild(bubbleChild);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800613
Andrew Flynn850d2e72012-04-26 16:51:20 -0700614 if (mIsHotseat && mHotseatChildScale >= 0) {
Andrew Flynnbc239a12012-03-06 11:39:49 -0800615 // Scale/make transparent for a hotseat
Andrew Flynn850d2e72012-04-26 16:51:20 -0700616 scaleChild(bubbleChild, mHotseatChildScale);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800617
Andrew Flynnbc239a12012-03-06 11:39:49 -0800618 bubbleChild.setTextColor(getResources().getColor(android.R.color.transparent));
Winson Chungeecf02d2012-03-02 17:14:58 -0800619 } else if (mChildScale >= 0) {
Andrew Flynnbc239a12012-03-06 11:39:49 -0800620 // Else possibly still scale it if we need to for smaller icons
Andrew Flynn850d2e72012-04-26 16:51:20 -0700621 scaleChild(bubbleChild, mChildScale);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800622 }
623 }
624
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800625 // Generate an id for each view, this assumes we have at most 256x256 cells
626 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700627 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700628 // If the horizontal or vertical span is set to -1, it is taken to
629 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700630 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
631 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800632
Winson Chungaafa03c2010-06-11 17:34:16 -0700633 child.setId(childId);
634
Michael Jurkaa52570f2012-03-20 03:18:20 -0700635 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700636
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700637 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700638
Winson Chungaafa03c2010-06-11 17:34:16 -0700639 return true;
640 }
641 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800642 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700643
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800644 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700645 public void removeAllViews() {
646 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700647 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700648 }
649
650 @Override
651 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700652 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700653 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700654 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700655 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700656 }
657
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700658 public void removeViewWithoutMarkingCells(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700659 mShortcutsAndWidgets.removeView(view);
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700660 }
661
Michael Jurka0280c3b2010-09-17 15:00:07 -0700662 @Override
663 public void removeView(View view) {
664 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700665 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700666 }
667
668 @Override
669 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700670 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
671 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700672 }
673
674 @Override
675 public void removeViewInLayout(View view) {
676 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700677 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700678 }
679
680 @Override
681 public void removeViews(int start, int count) {
682 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700683 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700684 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700685 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700686 }
687
688 @Override
689 public void removeViewsInLayout(int start, int count) {
690 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700691 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700692 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700693 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800694 }
695
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800696 @Override
697 protected void onAttachedToWindow() {
698 super.onAttachedToWindow();
699 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
700 }
701
Michael Jurkaaf442092010-06-10 17:01:57 -0700702 public void setTagToCellInfoForPoint(int touchX, int touchY) {
703 final CellInfo cellInfo = mCellInfo;
Winson Chungeecf02d2012-03-02 17:14:58 -0800704 Rect frame = mRect;
Michael Jurka8b805b12012-04-18 14:23:14 -0700705 final int x = touchX + getScrollX();
706 final int y = touchY + getScrollY();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700707 final int count = mShortcutsAndWidgets.getChildCount();
Michael Jurkaaf442092010-06-10 17:01:57 -0700708
709 boolean found = false;
710 for (int i = count - 1; i >= 0; i--) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700711 final View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohend4844c32011-02-18 19:25:06 -0800712 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Michael Jurkaaf442092010-06-10 17:01:57 -0700713
Adam Cohen1b607ed2011-03-03 17:26:50 -0800714 if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
715 lp.isLockedToGrid) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700716 child.getHitRect(frame);
Winson Chung0be025d2011-05-23 17:45:09 -0700717
Winson Chungeecf02d2012-03-02 17:14:58 -0800718 float scale = child.getScaleX();
719 frame = new Rect(child.getLeft(), child.getTop(), child.getRight(),
720 child.getBottom());
Winson Chung0be025d2011-05-23 17:45:09 -0700721 // The child hit rect is relative to the CellLayoutChildren parent, so we need to
722 // offset that by this CellLayout's padding to test an (x,y) point that is relative
723 // to this view.
Michael Jurka8b805b12012-04-18 14:23:14 -0700724 frame.offset(getPaddingLeft(), getPaddingTop());
Winson Chungeecf02d2012-03-02 17:14:58 -0800725 frame.inset((int) (frame.width() * (1f - scale) / 2),
726 (int) (frame.height() * (1f - scale) / 2));
Winson Chung0be025d2011-05-23 17:45:09 -0700727
Michael Jurkaaf442092010-06-10 17:01:57 -0700728 if (frame.contains(x, y)) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700729 cellInfo.cell = child;
730 cellInfo.cellX = lp.cellX;
731 cellInfo.cellY = lp.cellY;
732 cellInfo.spanX = lp.cellHSpan;
733 cellInfo.spanY = lp.cellVSpan;
Michael Jurkaaf442092010-06-10 17:01:57 -0700734 found = true;
Michael Jurkaaf442092010-06-10 17:01:57 -0700735 break;
736 }
737 }
738 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700739
Michael Jurkad771c962011-08-09 15:00:48 -0700740 mLastDownOnOccupiedCell = found;
741
Michael Jurkaaf442092010-06-10 17:01:57 -0700742 if (!found) {
Winson Chung0be025d2011-05-23 17:45:09 -0700743 final int cellXY[] = mTmpXY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700744 pointToCellExact(x, y, cellXY);
745
Michael Jurkaaf442092010-06-10 17:01:57 -0700746 cellInfo.cell = null;
747 cellInfo.cellX = cellXY[0];
748 cellInfo.cellY = cellXY[1];
749 cellInfo.spanX = 1;
750 cellInfo.spanY = 1;
Michael Jurkaaf442092010-06-10 17:01:57 -0700751 }
752 setTag(cellInfo);
753 }
754
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800755 @Override
756 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Cohenc1997fd2011-08-15 18:26:39 -0700757 // First we clear the tag to ensure that on every touch down we start with a fresh slate,
758 // even in the case where we return early. Not clearing here was causing bugs whereby on
759 // long-press we'd end up picking up an item from a previous drag operation.
760 final int action = ev.getAction();
761
762 if (action == MotionEvent.ACTION_DOWN) {
763 clearTagCellInfo();
764 }
765
Michael Jurkadee05892010-07-27 10:01:56 -0700766 if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
767 return true;
768 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800769
770 if (action == MotionEvent.ACTION_DOWN) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700771 setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800772 }
Winson Chungeecf02d2012-03-02 17:14:58 -0800773
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800774 return false;
775 }
776
Adam Cohenc1997fd2011-08-15 18:26:39 -0700777 private void clearTagCellInfo() {
778 final CellInfo cellInfo = mCellInfo;
779 cellInfo.cell = null;
780 cellInfo.cellX = -1;
781 cellInfo.cellY = -1;
782 cellInfo.spanX = 0;
783 cellInfo.spanY = 0;
784 setTag(cellInfo);
785 }
786
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800787 public CellInfo getTag() {
Michael Jurka0280c3b2010-09-17 15:00:07 -0700788 return (CellInfo) super.getTag();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800789 }
790
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700791 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700792 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800793 * @param x X coordinate of the point
794 * @param y Y coordinate of the point
795 * @param result Array of 2 ints to hold the x and y coordinate of the cell
796 */
797 void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700798 final int hStartPadding = getPaddingLeft();
799 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800800
801 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
802 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
803
Adam Cohend22015c2010-07-26 22:02:18 -0700804 final int xAxis = mCountX;
805 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800806
807 if (result[0] < 0) result[0] = 0;
808 if (result[0] >= xAxis) result[0] = xAxis - 1;
809 if (result[1] < 0) result[1] = 0;
810 if (result[1] >= yAxis) result[1] = yAxis - 1;
811 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700812
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800813 /**
814 * Given a point, return the cell that most closely encloses that point
815 * @param x X coordinate of the point
816 * @param y Y coordinate of the point
817 * @param result Array of 2 ints to hold the x and y coordinate of the cell
818 */
819 void pointToCellRounded(int x, int y, int[] result) {
820 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
821 }
822
823 /**
824 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700825 *
826 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800827 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700828 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800829 * @param result Array of 2 ints to hold the x and y coordinate of the point
830 */
831 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700832 final int hStartPadding = getPaddingLeft();
833 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800834
835 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
836 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
837 }
838
Adam Cohene3e27a82011-04-15 12:07:39 -0700839 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800840 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700841 *
842 * @param cellX X coordinate of the cell
843 * @param cellY Y coordinate of the cell
844 *
845 * @param result Array of 2 ints to hold the x and y coordinate of the point
846 */
847 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700848 regionToCenterPoint(cellX, cellY, 1, 1, result);
849 }
850
851 /**
852 * Given a cell coordinate and span return the point that represents the center of the regio
853 *
854 * @param cellX X coordinate of the cell
855 * @param cellY Y coordinate of the cell
856 *
857 * @param result Array of 2 ints to hold the x and y coordinate of the point
858 */
859 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700860 final int hStartPadding = getPaddingLeft();
861 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700862 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
863 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
864 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
865 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700866 }
867
Adam Cohen19f37922012-03-21 11:59:11 -0700868 /**
869 * Given a cell coordinate and span fills out a corresponding pixel rect
870 *
871 * @param cellX X coordinate of the cell
872 * @param cellY Y coordinate of the cell
873 * @param result Rect in which to write the result
874 */
875 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
876 final int hStartPadding = getPaddingLeft();
877 final int vStartPadding = getPaddingTop();
878 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
879 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
880 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
881 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
882 }
883
Adam Cohen482ed822012-03-02 14:15:13 -0800884 public float getDistanceFromCell(float x, float y, int[] cell) {
885 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
886 float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) +
887 Math.pow(y - mTmpPoint[1], 2));
888 return distance;
889 }
890
Romain Guy84f296c2009-11-04 15:00:44 -0800891 int getCellWidth() {
892 return mCellWidth;
893 }
894
895 int getCellHeight() {
896 return mCellHeight;
897 }
898
Adam Cohend4844c32011-02-18 19:25:06 -0800899 int getWidthGap() {
900 return mWidthGap;
901 }
902
903 int getHeightGap() {
904 return mHeightGap;
905 }
906
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700907 Rect getContentRect(Rect r) {
908 if (r == null) {
909 r = new Rect();
910 }
911 int left = getPaddingLeft();
912 int top = getPaddingTop();
Michael Jurka8b805b12012-04-18 14:23:14 -0700913 int right = left + getWidth() - getPaddingLeft() - getPaddingRight();
914 int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom();
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700915 r.set(left, top, right, bottom);
916 return r;
917 }
918
Adam Cohena897f392012-04-27 18:12:05 -0700919 static void getMetrics(Rect metrics, Resources res, int measureWidth, int measureHeight,
920 int countX, int countY, int orientation) {
921 int numWidthGaps = countX - 1;
922 int numHeightGaps = countY - 1;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700923
924 int widthGap;
925 int heightGap;
926 int cellWidth;
927 int cellHeight;
928 int paddingLeft;
929 int paddingRight;
930 int paddingTop;
931 int paddingBottom;
932
Adam Cohena897f392012-04-27 18:12:05 -0700933 int maxGap = res.getDimensionPixelSize(R.dimen.workspace_max_gap);
Adam Cohenf4bd5792012-04-27 11:35:29 -0700934 if (orientation == LANDSCAPE) {
935 cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_land);
936 cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_land);
937 widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_land);
938 heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_land);
939 paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_land);
940 paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_land);
941 paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_land);
942 paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_land);
943 } else {
944 // PORTRAIT
945 cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_port);
946 cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_port);
947 widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_port);
948 heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_port);
949 paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_port);
950 paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_port);
951 paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_port);
952 paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_port);
953 }
954
955 if (widthGap < 0 || heightGap < 0) {
956 int hSpace = measureWidth - paddingLeft - paddingRight;
957 int vSpace = measureHeight - paddingTop - paddingBottom;
Adam Cohena897f392012-04-27 18:12:05 -0700958 int hFreeSpace = hSpace - (countX * cellWidth);
959 int vFreeSpace = vSpace - (countY * cellHeight);
960 widthGap = Math.min(maxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
961 heightGap = Math.min(maxGap, numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Adam Cohenf4bd5792012-04-27 11:35:29 -0700962 }
963 metrics.set(cellWidth, cellHeight, widthGap, heightGap);
964 }
965
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800966 @Override
967 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800968 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700969 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
970
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800971 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
972 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700973
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800974 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
975 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
976 }
977
Adam Cohend22015c2010-07-26 22:02:18 -0700978 int numWidthGaps = mCountX - 1;
979 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800980
Adam Cohen234c4cd2011-07-17 21:03:04 -0700981 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Michael Jurkadd13e3d2012-05-01 12:38:17 -0700982 int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight();
983 int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom();
Adam Cohenf4bd5792012-04-27 11:35:29 -0700984 int hFreeSpace = hSpace - (mCountX * mCellWidth);
985 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700986 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
987 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700988 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700989 } else {
990 mWidthGap = mOriginalWidthGap;
991 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700992 }
Michael Jurka5f1c5092010-09-03 14:15:02 -0700993
Michael Jurka8c920dd2011-01-20 14:16:56 -0800994 // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
995 int newWidth = widthSpecSize;
996 int newHeight = heightSpecSize;
Michael Jurka5f1c5092010-09-03 14:15:02 -0700997 if (widthSpecMode == MeasureSpec.AT_MOST) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700998 newWidth = getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Winson Chungece7f5b2010-10-22 14:54:12 -0700999 ((mCountX - 1) * mWidthGap);
Michael Jurka8b805b12012-04-18 14:23:14 -07001000 newHeight = getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Winson Chungece7f5b2010-10-22 14:54:12 -07001001 ((mCountY - 1) * mHeightGap);
Michael Jurka5f1c5092010-09-03 14:15:02 -07001002 setMeasuredDimension(newWidth, newHeight);
Michael Jurka5f1c5092010-09-03 14:15:02 -07001003 }
Michael Jurka8c920dd2011-01-20 14:16:56 -08001004
1005 int count = getChildCount();
1006 for (int i = 0; i < count; i++) {
1007 View child = getChildAt(i);
Michael Jurka8b805b12012-04-18 14:23:14 -07001008 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() -
1009 getPaddingRight(), MeasureSpec.EXACTLY);
1010 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() -
1011 getPaddingBottom(), MeasureSpec.EXACTLY);
Michael Jurka8c920dd2011-01-20 14:16:56 -08001012 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
1013 }
1014 setMeasuredDimension(newWidth, newHeight);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001015 }
1016
1017 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -07001018 protected void onLayout(boolean changed, int l, int t, int r, int b) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001019 int count = getChildCount();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001020 for (int i = 0; i < count; i++) {
Michael Jurka8c920dd2011-01-20 14:16:56 -08001021 View child = getChildAt(i);
Michael Jurka8b805b12012-04-18 14:23:14 -07001022 child.layout(getPaddingLeft(), getPaddingTop(),
1023 r - l - getPaddingRight(), b - t - getPaddingBottom());
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001024 }
1025 }
1026
1027 @Override
Michael Jurkadee05892010-07-27 10:01:56 -07001028 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1029 super.onSizeChanged(w, h, oldw, oldh);
Michael Jurka18014792010-10-14 09:01:34 -07001030 mBackgroundRect.set(0, 0, w, h);
Adam Cohenb5ba0972011-09-07 18:02:31 -07001031 mForegroundRect.set(mForegroundPadding, mForegroundPadding,
1032 w - 2 * mForegroundPadding, h - 2 * mForegroundPadding);
Michael Jurkadee05892010-07-27 10:01:56 -07001033 }
1034
1035 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001036 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001037 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001038 }
1039
1040 @Override
1041 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001042 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001043 }
1044
Michael Jurka5f1c5092010-09-03 14:15:02 -07001045 public float getBackgroundAlpha() {
1046 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -07001047 }
1048
Adam Cohen1b0aaac2010-10-28 11:11:18 -07001049 public void setBackgroundAlphaMultiplier(float multiplier) {
1050 mBackgroundAlphaMultiplier = multiplier;
1051 }
1052
Adam Cohenddb82192010-11-10 16:32:54 -08001053 public float getBackgroundAlphaMultiplier() {
1054 return mBackgroundAlphaMultiplier;
1055 }
1056
Michael Jurka5f1c5092010-09-03 14:15:02 -07001057 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -08001058 if (mBackgroundAlpha != alpha) {
1059 mBackgroundAlpha = alpha;
1060 invalidate();
1061 }
Michael Jurkadee05892010-07-27 10:01:56 -07001062 }
1063
Michael Jurkaa52570f2012-03-20 03:18:20 -07001064 public void setShortcutAndWidgetAlpha(float alpha) {
Michael Jurka0142d492010-08-25 17:46:15 -07001065 final int childCount = getChildCount();
1066 for (int i = 0; i < childCount; i++) {
Michael Jurkadee05892010-07-27 10:01:56 -07001067 getChildAt(i).setAlpha(alpha);
1068 }
1069 }
1070
Michael Jurkaa52570f2012-03-20 03:18:20 -07001071 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
1072 if (getChildCount() > 0) {
1073 return (ShortcutAndWidgetContainer) getChildAt(0);
1074 }
1075 return null;
1076 }
1077
Patrick Dubroy440c3602010-07-13 17:50:32 -07001078 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001079 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -07001080 }
1081
Adam Cohen76fc0852011-06-17 13:26:23 -07001082 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -08001083 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001084 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -08001085 boolean[][] occupied = mOccupied;
1086 if (!permanent) {
1087 occupied = mTmpOccupied;
1088 }
1089
Adam Cohen19f37922012-03-21 11:59:11 -07001090 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001091 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1092 final ItemInfo info = (ItemInfo) child.getTag();
1093
1094 // We cancel any existing animations
1095 if (mReorderAnimators.containsKey(lp)) {
1096 mReorderAnimators.get(lp).cancel();
1097 mReorderAnimators.remove(lp);
1098 }
1099
Adam Cohen482ed822012-03-02 14:15:13 -08001100 final int oldX = lp.x;
1101 final int oldY = lp.y;
1102 if (adjustOccupied) {
1103 occupied[lp.cellX][lp.cellY] = false;
1104 occupied[cellX][cellY] = true;
1105 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001106 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001107 if (permanent) {
1108 lp.cellX = info.cellX = cellX;
1109 lp.cellY = info.cellY = cellY;
1110 } else {
1111 lp.tmpCellX = cellX;
1112 lp.tmpCellY = cellY;
1113 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001114 clc.setupLp(lp);
1115 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001116 final int newX = lp.x;
1117 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001118
Adam Cohen76fc0852011-06-17 13:26:23 -07001119 lp.x = oldX;
1120 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001121
Adam Cohen482ed822012-03-02 14:15:13 -08001122 // Exit early if we're not actually moving the view
1123 if (oldX == newX && oldY == newY) {
1124 lp.isLockedToGrid = true;
1125 return true;
1126 }
1127
1128 ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
1129 va.setDuration(duration);
1130 mReorderAnimators.put(lp, va);
1131
1132 va.addUpdateListener(new AnimatorUpdateListener() {
1133 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001134 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001135 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001136 lp.x = (int) ((1 - r) * oldX + r * newX);
1137 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001138 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001139 }
1140 });
Adam Cohen482ed822012-03-02 14:15:13 -08001141 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001142 boolean cancelled = false;
1143 public void onAnimationEnd(Animator animation) {
1144 // If the animation was cancelled, it means that another animation
1145 // has interrupted this one, and we don't want to lock the item into
1146 // place just yet.
1147 if (!cancelled) {
1148 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001149 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001150 }
1151 if (mReorderAnimators.containsKey(lp)) {
1152 mReorderAnimators.remove(lp);
1153 }
1154 }
1155 public void onAnimationCancel(Animator animation) {
1156 cancelled = true;
1157 }
1158 });
Adam Cohen482ed822012-03-02 14:15:13 -08001159 va.setStartDelay(delay);
1160 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001161 return true;
1162 }
1163 return false;
1164 }
1165
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001166 /**
1167 * Estimate where the top left cell of the dragged item will land if it is dropped.
1168 *
1169 * @param originX The X value of the top left corner of the item
1170 * @param originY The Y value of the top left corner of the item
1171 * @param spanX The number of horizontal cells that the item spans
1172 * @param spanY The number of vertical cells that the item spans
1173 * @param result The estimated drop cell X and Y.
1174 */
1175 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
Adam Cohend22015c2010-07-26 22:02:18 -07001176 final int countX = mCountX;
1177 final int countY = mCountY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001178
Michael Jurkaa63c4522010-08-19 13:52:27 -07001179 // pointToCellRounded takes the top left of a cell but will pad that with
1180 // cellWidth/2 and cellHeight/2 when finding the matching cell
1181 pointToCellRounded(originX, originY, result);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001182
1183 // If the item isn't fully on this screen, snap to the edges
1184 int rightOverhang = result[0] + spanX - countX;
1185 if (rightOverhang > 0) {
1186 result[0] -= rightOverhang; // Snap to right
1187 }
1188 result[0] = Math.max(0, result[0]); // Snap to left
1189 int bottomOverhang = result[1] + spanY - countY;
1190 if (bottomOverhang > 0) {
1191 result[1] -= bottomOverhang; // Snap to bottom
1192 }
1193 result[1] = Math.max(0, result[1]); // Snap to top
1194 }
1195
Adam Cohen482ed822012-03-02 14:15:13 -08001196 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1197 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001198 final int oldDragCellX = mDragCell[0];
1199 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001200
Winson Chungb8c69f32011-10-19 21:36:08 -07001201 if (v != null && dragOffset == null) {
Winson Chunga9abd0e2010-10-27 17:18:37 -07001202 mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
1203 } else {
1204 mDragCenter.set(originX, originY);
1205 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001206
Adam Cohen2801caf2011-05-13 20:57:39 -07001207 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001208 return;
1209 }
1210
Adam Cohen482ed822012-03-02 14:15:13 -08001211 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1212 mDragCell[0] = cellX;
1213 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001214 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001215 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001216 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001217
Joe Onorato4be866d2010-10-10 11:26:02 -07001218 int left = topLeft[0];
1219 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001220
Winson Chungb8c69f32011-10-19 21:36:08 -07001221 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001222 // When drawing the drag outline, it did not account for margin offsets
1223 // added by the view's parent.
1224 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1225 left += lp.leftMargin;
1226 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001227
Adam Cohen99e8b402011-03-25 19:23:43 -07001228 // Offsets due to the size difference between the View and the dragOutline.
1229 // There is a size difference to account for the outer blur, which may lie
1230 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001231 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001232 // We center about the x axis
1233 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1234 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001235 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001236 if (dragOffset != null && dragRegion != null) {
1237 // Center the drag region *horizontally* in the cell and apply a drag
1238 // outline offset
1239 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1240 - dragRegion.width()) / 2;
1241 top += dragOffset.y;
1242 } else {
1243 // Center the drag outline in the cell
1244 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1245 - dragOutline.getWidth()) / 2;
1246 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1247 - dragOutline.getHeight()) / 2;
1248 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001249 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001250 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001251 mDragOutlineAnims[oldIndex].animateOut();
1252 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001253 Rect r = mDragOutlines[mDragOutlineCurrent];
1254 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1255 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001256 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001257 }
Winson Chung150fbab2010-09-29 17:14:26 -07001258
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001259 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1260 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001261 }
1262 }
1263
Adam Cohene0310962011-04-18 16:15:31 -07001264 public void clearDragOutlines() {
1265 final int oldIndex = mDragOutlineCurrent;
1266 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001267 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001268 }
1269
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001270 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001271 * Find a vacant area that will fit the given bounds nearest the requested
1272 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001273 *
Romain Guy51afc022009-05-04 18:03:43 -07001274 * @param pixelX The X location at which you want to search for a vacant area.
1275 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001276 * @param spanX Horizontal span of the object.
1277 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001278 * @param result Array in which to place the result, or null (in which case a new array will
1279 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001280 * @return The X, Y cell of a vacant area that can contain this object,
1281 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001282 */
Adam Cohend41fbf52012-02-16 23:53:59 -08001283 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
1284 int[] result) {
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001285 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001286 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001287
Michael Jurka6a1435d2010-09-27 17:35:12 -07001288 /**
1289 * Find a vacant area that will fit the given bounds nearest the requested
1290 * cell location. Uses Euclidean distance to score multiple vacant areas.
1291 *
1292 * @param pixelX The X location at which you want to search for a vacant area.
1293 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001294 * @param minSpanX The minimum horizontal span required
1295 * @param minSpanY The minimum vertical span required
1296 * @param spanX Horizontal span of the object.
1297 * @param spanY Vertical span of the object.
1298 * @param result Array in which to place the result, or null (in which case a new array will
1299 * be allocated)
1300 * @return The X, Y cell of a vacant area that can contain this object,
1301 * nearest the requested location.
1302 */
1303 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1304 int spanY, int[] result, int[] resultSpan) {
1305 return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
1306 result, resultSpan);
1307 }
1308
1309 /**
1310 * Find a vacant area that will fit the given bounds nearest the requested
1311 * cell location. Uses Euclidean distance to score multiple vacant areas.
1312 *
1313 * @param pixelX The X location at which you want to search for a vacant area.
1314 * @param pixelY The Y location at which you want to search for a vacant area.
Michael Jurka6a1435d2010-09-27 17:35:12 -07001315 * @param spanX Horizontal span of the object.
1316 * @param spanY Vertical span of the object.
Adam Cohendf035382011-04-11 17:22:04 -07001317 * @param ignoreOccupied If true, the result can be an occupied cell
1318 * @param result Array in which to place the result, or null (in which case a new array will
1319 * be allocated)
Michael Jurka6a1435d2010-09-27 17:35:12 -07001320 * @return The X, Y cell of a vacant area that can contain this object,
1321 * nearest the requested location.
1322 */
Adam Cohendf035382011-04-11 17:22:04 -07001323 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
1324 boolean ignoreOccupied, int[] result) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001325 return findNearestArea(pixelX, pixelY, spanX, spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001326 spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08001327 }
1328
1329 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1330 private void lazyInitTempRectStack() {
1331 if (mTempRectStack.isEmpty()) {
1332 for (int i = 0; i < mCountX * mCountY; i++) {
1333 mTempRectStack.push(new Rect());
1334 }
1335 }
1336 }
Adam Cohen482ed822012-03-02 14:15:13 -08001337
Adam Cohend41fbf52012-02-16 23:53:59 -08001338 private void recycleTempRects(Stack<Rect> used) {
1339 while (!used.isEmpty()) {
1340 mTempRectStack.push(used.pop());
1341 }
1342 }
1343
1344 /**
1345 * Find a vacant area that will fit the given bounds nearest the requested
1346 * cell location. Uses Euclidean distance to score multiple vacant areas.
1347 *
1348 * @param pixelX The X location at which you want to search for a vacant area.
1349 * @param pixelY The Y location at which you want to search for a vacant area.
1350 * @param minSpanX The minimum horizontal span required
1351 * @param minSpanY The minimum vertical span required
1352 * @param spanX Horizontal span of the object.
1353 * @param spanY Vertical span of the object.
1354 * @param ignoreOccupied If true, the result can be an occupied cell
1355 * @param result Array in which to place the result, or null (in which case a new array will
1356 * be allocated)
1357 * @return The X, Y cell of a vacant area that can contain this object,
1358 * nearest the requested location.
1359 */
1360 int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001361 View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
1362 boolean[][] occupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001363 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001364 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08001365 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001366
Adam Cohene3e27a82011-04-15 12:07:39 -07001367 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1368 // to the center of the item, but we are searching based on the top-left cell, so
1369 // we translate the point over to correspond to the top-left.
1370 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1371 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1372
Jeff Sharkey70864282009-04-07 21:08:40 -07001373 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001374 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001375 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001376 final Rect bestRect = new Rect(-1, -1, -1, -1);
1377 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001378
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001379 final int countX = mCountX;
1380 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001381
Adam Cohend41fbf52012-02-16 23:53:59 -08001382 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1383 spanX < minSpanX || spanY < minSpanY) {
1384 return bestXY;
1385 }
1386
1387 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001388 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001389 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1390 int ySize = -1;
1391 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001392 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001393 // First, let's see if this thing fits anywhere
1394 for (int i = 0; i < minSpanX; i++) {
1395 for (int j = 0; j < minSpanY; j++) {
Adam Cohendf035382011-04-11 17:22:04 -07001396 if (occupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001397 continue inner;
1398 }
Michael Jurkac28de512010-08-13 11:27:44 -07001399 }
1400 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001401 xSize = minSpanX;
1402 ySize = minSpanY;
1403
1404 // We know that the item will fit at _some_ acceptable size, now let's see
1405 // how big we can make it. We'll alternate between incrementing x and y spans
1406 // until we hit a limit.
1407 boolean incX = true;
1408 boolean hitMaxX = xSize >= spanX;
1409 boolean hitMaxY = ySize >= spanY;
1410 while (!(hitMaxX && hitMaxY)) {
1411 if (incX && !hitMaxX) {
1412 for (int j = 0; j < ySize; j++) {
1413 if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
1414 // We can't move out horizontally
1415 hitMaxX = true;
1416 }
1417 }
1418 if (!hitMaxX) {
1419 xSize++;
1420 }
1421 } else if (!hitMaxY) {
1422 for (int i = 0; i < xSize; i++) {
1423 if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
1424 // We can't move out vertically
1425 hitMaxY = true;
1426 }
1427 }
1428 if (!hitMaxY) {
1429 ySize++;
1430 }
1431 }
1432 hitMaxX |= xSize >= spanX;
1433 hitMaxY |= ySize >= spanY;
1434 incX = !incX;
1435 }
1436 incX = true;
1437 hitMaxX = xSize >= spanX;
1438 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001439 }
Winson Chung0be025d2011-05-23 17:45:09 -07001440 final int[] cellXY = mTmpXY;
Adam Cohene3e27a82011-04-15 12:07:39 -07001441 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001442
Adam Cohend41fbf52012-02-16 23:53:59 -08001443 // We verify that the current rect is not a sub-rect of any of our previous
1444 // candidates. In this case, the current rect is disqualified in favour of the
1445 // containing rect.
1446 Rect currentRect = mTempRectStack.pop();
1447 currentRect.set(x, y, x + xSize, y + ySize);
1448 boolean contained = false;
1449 for (Rect r : validRegions) {
1450 if (r.contains(currentRect)) {
1451 contained = true;
1452 break;
1453 }
1454 }
1455 validRegions.push(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001456 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
1457 + Math.pow(cellXY[1] - pixelY, 2));
Adam Cohen482ed822012-03-02 14:15:13 -08001458
Adam Cohend41fbf52012-02-16 23:53:59 -08001459 if ((distance <= bestDistance && !contained) ||
1460 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001461 bestDistance = distance;
1462 bestXY[0] = x;
1463 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001464 if (resultSpan != null) {
1465 resultSpan[0] = xSize;
1466 resultSpan[1] = ySize;
1467 }
1468 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001469 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001470 }
1471 }
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001472 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08001473 markCellsAsOccupiedForView(ignoreView, occupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001474
Adam Cohenc0dcf592011-06-01 15:30:43 -07001475 // Return -1, -1 if no suitable location found
1476 if (bestDistance == Double.MAX_VALUE) {
1477 bestXY[0] = -1;
1478 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001479 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001480 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001481 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001482 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001483
Adam Cohen482ed822012-03-02 14:15:13 -08001484 /**
1485 * Find a vacant area that will fit the given bounds nearest the requested
1486 * cell location, and will also weigh in a suggested direction vector of the
1487 * desired location. This method computers distance based on unit grid distances,
1488 * not pixel distances.
1489 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001490 * @param cellX The X cell nearest to which you want to search for a vacant area.
1491 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001492 * @param spanX Horizontal span of the object.
1493 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001494 * @param direction The favored direction in which the views should move from x, y
1495 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1496 * matches exactly. Otherwise we find the best matching direction.
1497 * @param occoupied The array which represents which cells in the CellLayout are occupied
1498 * @param blockOccupied The array which represents which cells in the specified block (cellX,
1499 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001500 * @param result Array in which to place the result, or null (in which case a new array will
1501 * be allocated)
1502 * @return The X, Y cell of a vacant area that can contain this object,
1503 * nearest the requested location.
1504 */
1505 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001506 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001507 // Keep track of best-scoring drop area
1508 final int[] bestXY = result != null ? result : new int[2];
1509 float bestDistance = Float.MAX_VALUE;
1510 int bestDirectionScore = Integer.MIN_VALUE;
1511
1512 final int countX = mCountX;
1513 final int countY = mCountY;
1514
1515 for (int y = 0; y < countY - (spanY - 1); y++) {
1516 inner:
1517 for (int x = 0; x < countX - (spanX - 1); x++) {
1518 // First, let's see if this thing fits anywhere
1519 for (int i = 0; i < spanX; i++) {
1520 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001521 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001522 continue inner;
1523 }
1524 }
1525 }
1526
1527 float distance = (float)
1528 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1529 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001530 computeDirectionVector(x - cellX, y - cellY, curDirection);
1531 // The direction score is just the dot product of the two candidate direction
1532 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001533 int curDirectionScore = direction[0] * curDirection[0] +
1534 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001535 boolean exactDirectionOnly = false;
1536 boolean directionMatches = direction[0] == curDirection[0] &&
1537 direction[0] == curDirection[0];
1538 if ((directionMatches || !exactDirectionOnly) &&
1539 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001540 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1541 bestDistance = distance;
1542 bestDirectionScore = curDirectionScore;
1543 bestXY[0] = x;
1544 bestXY[1] = y;
1545 }
1546 }
1547 }
1548
1549 // Return -1, -1 if no suitable location found
1550 if (bestDistance == Float.MAX_VALUE) {
1551 bestXY[0] = -1;
1552 bestXY[1] = -1;
1553 }
1554 return bestXY;
1555 }
1556
Adam Cohen47a876d2012-03-19 13:21:41 -07001557 private int[] findNearestAreaInDirection(int cellX, int cellY, int spanX, int spanY,
1558 int[] direction,boolean[][] occupied,
1559 boolean blockOccupied[][], int[] result) {
1560 // Keep track of best-scoring drop area
1561 final int[] bestXY = result != null ? result : new int[2];
1562 bestXY[0] = -1;
1563 bestXY[1] = -1;
1564 float bestDistance = Float.MAX_VALUE;
1565
1566 // We use this to march in a single direction
Adam Cohen5b53f292012-03-29 14:30:35 -07001567 if ((direction[0] != 0 && direction[1] != 0) ||
1568 (direction[0] == 0 && direction[1] == 0)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001569 return bestXY;
1570 }
1571
1572 // This will only incrememnet one of x or y based on the assertion above
1573 int x = cellX + direction[0];
1574 int y = cellY + direction[1];
1575 while (x >= 0 && x + spanX <= mCountX && y >= 0 && y + spanY <= mCountY) {
1576
1577 boolean fail = false;
1578 for (int i = 0; i < spanX; i++) {
1579 for (int j = 0; j < spanY; j++) {
1580 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
1581 fail = true;
1582 }
1583 }
1584 }
1585 if (!fail) {
1586 float distance = (float)
1587 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1588 if (Float.compare(distance, bestDistance) < 0) {
1589 bestDistance = distance;
1590 bestXY[0] = x;
1591 bestXY[1] = y;
1592 }
1593 }
1594 x += direction[0];
1595 y += direction[1];
1596 }
1597 return bestXY;
1598 }
1599
Adam Cohen482ed822012-03-02 14:15:13 -08001600 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001601 int[] direction, ItemConfiguration currentState) {
1602 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001603 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001604 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001605 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1606
Adam Cohen8baab352012-03-20 17:39:21 -07001607 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001608
1609 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001610 c.x = mTempLocation[0];
1611 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001612 success = true;
1613
1614 }
Adam Cohen8baab352012-03-20 17:39:21 -07001615 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001616 return success;
1617 }
1618
Adam Cohen47a876d2012-03-19 13:21:41 -07001619 // This method looks in the specified direction to see if there is an additional view
1620 // immediately adjecent in that direction
1621 private boolean addViewInDirection(ArrayList<View> views, Rect boundingRect, int[] direction,
Adam Cohen19f37922012-03-21 11:59:11 -07001622 boolean[][] occupied, View dragView, ItemConfiguration currentState) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001623 boolean found = false;
1624
Michael Jurkaa52570f2012-03-20 03:18:20 -07001625 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen47a876d2012-03-19 13:21:41 -07001626 Rect r0 = new Rect(boundingRect);
1627 Rect r1 = new Rect();
1628
1629 int deltaX = 0;
1630 int deltaY = 0;
1631 if (direction[1] < 0) {
1632 r0.set(r0.left, r0.top - 1, r0.right, r0.bottom);
1633 deltaY = -1;
1634 } else if (direction[1] > 0) {
1635 r0.set(r0.left, r0.top, r0.right, r0.bottom + 1);
1636 deltaY = 1;
1637 } else if (direction[0] < 0) {
1638 r0.set(r0.left - 1, r0.top, r0.right, r0.bottom);
1639 deltaX = -1;
1640 } else if (direction[0] > 0) {
1641 r0.set(r0.left, r0.top, r0.right + 1, r0.bottom);
1642 deltaX = 1;
1643 }
1644
1645 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001646 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen19f37922012-03-21 11:59:11 -07001647 if (views.contains(child) || child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001648 CellAndSpan c = currentState.map.get(child);
Adam Cohen47a876d2012-03-19 13:21:41 -07001649
Adam Cohen8baab352012-03-20 17:39:21 -07001650 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1651 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen47a876d2012-03-19 13:21:41 -07001652 if (Rect.intersects(r0, r1)) {
1653 if (!lp.canReorder) {
1654 return false;
1655 }
1656 boolean pushed = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001657 for (int x = c.x; x < c.x + c.spanX; x++) {
1658 for (int y = c.y; y < c.y + c.spanY; y++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001659 boolean inBounds = x - deltaX >= 0 && x -deltaX < mCountX
1660 && y - deltaY >= 0 && y - deltaY < mCountY;
1661 if (inBounds && occupied[x - deltaX][y - deltaY]) {
1662 pushed = true;
1663 }
1664 }
1665 }
1666 if (pushed) {
1667 views.add(child);
Adam Cohen8baab352012-03-20 17:39:21 -07001668 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen47a876d2012-03-19 13:21:41 -07001669 found = true;
1670 }
1671 }
1672 }
1673 return found;
1674 }
1675
Adam Cohen482ed822012-03-02 14:15:13 -08001676 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohen19f37922012-03-21 11:59:11 -07001677 int[] direction, boolean push, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001678 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001679
Adam Cohen8baab352012-03-20 17:39:21 -07001680 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001681 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001682 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001683 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001684 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001685 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001686 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001687 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001688 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001689 }
1690 }
Adam Cohen8baab352012-03-20 17:39:21 -07001691
1692 @SuppressWarnings("unchecked")
1693 ArrayList<View> dup = (ArrayList<View>) views.clone();
1694 // We try and expand the group of views in the direction vector passed, based on
1695 // whether they are physically adjacent, ie. based on "push mechanics".
Adam Cohen19f37922012-03-21 11:59:11 -07001696 while (push && addViewInDirection(dup, boundingRect, direction, mTmpOccupied, dragView,
Adam Cohen8baab352012-03-20 17:39:21 -07001697 currentState)) {
1698 }
1699
1700 // Mark the occupied state as false for the group of views we want to move.
1701 for (View v: dup) {
1702 CellAndSpan c = currentState.map.get(v);
1703 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1704 }
1705
Adam Cohen47a876d2012-03-19 13:21:41 -07001706 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1707 int top = boundingRect.top;
1708 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001709 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
1710 // for tetris-style interlocking.
1711 for (View v: dup) {
1712 CellAndSpan c = currentState.map.get(v);
1713 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001714 }
1715
Adam Cohen482ed822012-03-02 14:15:13 -08001716 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1717
Adam Cohen8baab352012-03-20 17:39:21 -07001718 if (push) {
1719 findNearestAreaInDirection(boundingRect.left, boundingRect.top, boundingRect.width(),
1720 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
1721 } else {
1722 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1723 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
1724 }
Adam Cohen482ed822012-03-02 14:15:13 -08001725
Adam Cohen8baab352012-03-20 17:39:21 -07001726 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001727 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001728 int deltaX = mTempLocation[0] - boundingRect.left;
1729 int deltaY = mTempLocation[1] - boundingRect.top;
1730 for (View v: dup) {
1731 CellAndSpan c = currentState.map.get(v);
1732 c.x += deltaX;
1733 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001734 }
1735 success = true;
1736 }
Adam Cohen8baab352012-03-20 17:39:21 -07001737
1738 // In either case, we set the occupied array as marked for the location of the views
1739 for (View v: dup) {
1740 CellAndSpan c = currentState.map.get(v);
1741 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001742 }
1743 return success;
1744 }
1745
1746 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1747 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1748 }
1749
1750 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001751 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001752 // Return early if get invalid cell positions
1753 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001754
Adam Cohen8baab352012-03-20 17:39:21 -07001755 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001756 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001757
Adam Cohen8baab352012-03-20 17:39:21 -07001758 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001759 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001760 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001761 if (c != null) {
1762 c.x = cellX;
1763 c.y = cellY;
1764 }
Adam Cohen482ed822012-03-02 14:15:13 -08001765 }
Adam Cohen482ed822012-03-02 14:15:13 -08001766 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1767 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001768 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001769 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001770 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001771 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001772 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001773 if (Rect.intersects(r0, r1)) {
1774 if (!lp.canReorder) {
1775 return false;
1776 }
1777 mIntersectingViews.add(child);
1778 }
1779 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001780
Adam Cohen8baab352012-03-20 17:39:21 -07001781 // We try to move the intersecting views as a block using the push mechanic
Adam Cohen19f37922012-03-21 11:59:11 -07001782 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, true, ignoreView,
1783 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001784 return true;
1785 }
1786 // Try the opposite direction
1787 direction[0] *= -1;
1788 direction[1] *= -1;
Adam Cohen19f37922012-03-21 11:59:11 -07001789 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, true, ignoreView,
1790 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001791 return true;
1792 }
1793 // Switch the direction back
1794 direction[0] *= -1;
1795 direction[1] *= -1;
1796
Adam Cohen8baab352012-03-20 17:39:21 -07001797 // Next we try moving the views as a block , but without requiring the push mechanic
Adam Cohen19f37922012-03-21 11:59:11 -07001798 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, false, ignoreView,
1799 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001800 return true;
1801 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001802
Adam Cohen482ed822012-03-02 14:15:13 -08001803 // Ok, they couldn't move as a block, let's move them individually
1804 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001805 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001806 return false;
1807 }
1808 }
1809 return true;
1810 }
1811
1812 /*
1813 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1814 * the provided point and the provided cell
1815 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001816 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001817 double angle = Math.atan(((float) deltaY) / deltaX);
1818
1819 result[0] = 0;
1820 result[1] = 0;
1821 if (Math.abs(Math.cos(angle)) > 0.5f) {
1822 result[0] = (int) Math.signum(deltaX);
1823 }
1824 if (Math.abs(Math.sin(angle)) > 0.5f) {
1825 result[1] = (int) Math.signum(deltaY);
1826 }
1827 }
1828
Adam Cohen8baab352012-03-20 17:39:21 -07001829 private void copyOccupiedArray(boolean[][] occupied) {
1830 for (int i = 0; i < mCountX; i++) {
1831 for (int j = 0; j < mCountY; j++) {
1832 occupied[i][j] = mOccupied[i][j];
1833 }
1834 }
1835 }
1836
Adam Cohen482ed822012-03-02 14:15:13 -08001837 ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1838 int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001839 // Copy the current state into the solution. This solution will be manipulated as necessary.
1840 copyCurrentStateToSolution(solution, false);
1841 // Copy the current occupied array into the temporary occupied array. This array will be
1842 // manipulated as necessary to find a solution.
1843 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001844
1845 // We find the nearest cell into which we would place the dragged item, assuming there's
1846 // nothing in its way.
1847 int result[] = new int[2];
1848 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1849
1850 boolean success = false;
1851 // First we try the exact nearest position of the item being dragged,
1852 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001853 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1854 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001855
1856 if (!success) {
1857 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1858 // x, then 1 in y etc.
1859 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
1860 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
1861 dragView, false, solution);
1862 } else if (spanY > minSpanY) {
1863 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
1864 dragView, true, solution);
1865 }
1866 solution.isSolution = false;
1867 } else {
1868 solution.isSolution = true;
1869 solution.dragViewX = result[0];
1870 solution.dragViewY = result[1];
1871 solution.dragViewSpanX = spanX;
1872 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08001873 }
1874 return solution;
1875 }
1876
1877 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001878 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001879 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001880 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001881 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001882 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08001883 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07001884 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001885 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001886 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001887 }
Adam Cohen8baab352012-03-20 17:39:21 -07001888 solution.map.put(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08001889 }
1890 }
1891
1892 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
1893 for (int i = 0; i < mCountX; i++) {
1894 for (int j = 0; j < mCountY; j++) {
1895 mTmpOccupied[i][j] = false;
1896 }
1897 }
1898
Michael Jurkaa52570f2012-03-20 03:18:20 -07001899 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001900 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001901 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001902 if (child == dragView) continue;
1903 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001904 CellAndSpan c = solution.map.get(child);
1905 if (c != null) {
1906 lp.tmpCellX = c.x;
1907 lp.tmpCellY = c.y;
1908 lp.cellHSpan = c.spanX;
1909 lp.cellVSpan = c.spanY;
1910 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001911 }
1912 }
1913 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
1914 solution.dragViewSpanY, mTmpOccupied, true);
1915 }
1916
1917 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
1918 commitDragView) {
1919
1920 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
1921 for (int i = 0; i < mCountX; i++) {
1922 for (int j = 0; j < mCountY; j++) {
1923 occupied[i][j] = false;
1924 }
1925 }
1926
Michael Jurkaa52570f2012-03-20 03:18:20 -07001927 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001928 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001929 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001930 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001931 CellAndSpan c = solution.map.get(child);
1932 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07001933 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
1934 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07001935 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001936 }
1937 }
1938 if (commitDragView) {
1939 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
1940 solution.dragViewSpanY, occupied, true);
1941 }
1942 }
1943
Adam Cohen19f37922012-03-21 11:59:11 -07001944 // This method starts or changes the reorder hint animations
1945 private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
1946 int childCount = mShortcutsAndWidgets.getChildCount();
1947 int timeForPriorAnimationToComplete = getMaxCompletionTime();
1948 for (int i = 0; i < childCount; i++) {
1949 View child = mShortcutsAndWidgets.getChildAt(i);
1950 if (child == dragView) continue;
1951 CellAndSpan c = solution.map.get(child);
1952 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1953 if (c != null) {
1954 ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
1955 c.x, c.y, c.spanX, c.spanY);
1956 rha.animate(timeForPriorAnimationToComplete);
1957 }
1958 }
1959 }
1960
1961 // Class which represents the reorder hint animations. These animations show that an item is
1962 // in a temporary state, and hint at where the item will return to.
1963 class ReorderHintAnimation {
1964 View child;
1965 float deltaX;
1966 float deltaY;
Brandon Keely50e6e562012-05-08 16:28:49 -07001967 private static final int DURATION = 300;
Adam Cohen19f37922012-03-21 11:59:11 -07001968 private int repeatCount;
1969 private boolean cancelOnCycleComplete = false;
1970 ValueAnimator va;
1971
1972 public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
1973 int spanX, int spanY) {
1974 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
1975 final int x0 = mTmpPoint[0];
1976 final int y0 = mTmpPoint[1];
1977 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
1978 final int x1 = mTmpPoint[0];
1979 final int y1 = mTmpPoint[1];
1980 final int dX = x1 - x0;
1981 final int dY = y1 - y0;
1982 deltaX = 0;
1983 deltaY = 0;
1984 if (dX == dY && dX == 0) {
1985 } else {
1986 if (dY == 0) {
1987 deltaX = mReorderHintAnimationMagnitude;
1988 } else if (dX == 0) {
1989 deltaY = mReorderHintAnimationMagnitude;
1990 } else {
1991 double angle = Math.atan( (float) (dY) / dX);
1992 deltaX = (int) (Math.cos(angle) * mReorderHintAnimationMagnitude);
1993 deltaY = (int) (Math.sin(angle) * mReorderHintAnimationMagnitude);
1994 }
1995 }
Brandon Keely50e6e562012-05-08 16:28:49 -07001996 child.setPivotY(child.getMeasuredHeight() * 0.5f);
1997 child.setPivotX(child.getMeasuredWidth() * 0.5f);
Adam Cohen19f37922012-03-21 11:59:11 -07001998 this.child = child;
1999 }
2000
2001 void animate(int delay) {
2002 if (mShakeAnimators.containsKey(child)) {
2003 ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
2004 oldAnimation.completeAnimation();
2005 mShakeAnimators.remove(child);
2006 }
2007 if (deltaX == 0 && deltaY == 0) {
2008 return;
2009 }
2010 va = ValueAnimator.ofFloat(0f, 1f);
2011 va.setRepeatMode(ValueAnimator.REVERSE);
2012 va.setRepeatCount(ValueAnimator.INFINITE);
Brandon Keely50e6e562012-05-08 16:28:49 -07002013 va.setDuration((int) (DURATION * (1.0f + Math.random()*.08f)));
Adam Cohen19f37922012-03-21 11:59:11 -07002014 va.addUpdateListener(new AnimatorUpdateListener() {
2015 @Override
2016 public void onAnimationUpdate(ValueAnimator animation) {
2017 float r = ((Float) animation.getAnimatedValue()).floatValue();
2018 float x = r * deltaX;
2019 float y = r * deltaY;
2020 child.setTranslationX(x);
2021 child.setTranslationY(y);
Brandon Keely50e6e562012-05-08 16:28:49 -07002022 float sf = 4.0f / child.getWidth();
2023 float s = 1.0f - r * sf;
2024 child.setScaleX(s);
2025 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002026 }
2027 });
2028 va.addListener(new AnimatorListenerAdapter() {
2029 public void onAnimationRepeat(Animator animation) {
2030 repeatCount++;
2031 // We make sure to end only after a full period
2032 if (cancelOnCycleComplete && repeatCount % 2 == 0) {
2033 va.cancel();
2034 }
2035 }
2036 });
2037 va.setStartDelay(Math.max(REORDER_ANIMATION_DURATION, delay));
2038 mShakeAnimators.put(child, this);
2039 va.start();
2040 }
2041
2042
2043 private void completeAnimation() {
2044 cancelOnCycleComplete = true;
2045 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002046 private void completeAnimationImmediately() {
2047 va.cancel();
2048
2049 AnimatorSet s = new AnimatorSet();
2050 s.playTogether(
2051 ObjectAnimator.ofFloat(child, "scaleX", 1f),
2052 ObjectAnimator.ofFloat(child, "scaleY", 1f),
2053 ObjectAnimator.ofFloat(child, "translationX", 0f),
2054 ObjectAnimator.ofFloat(child, "translationX", 0f)
2055 );
2056 s.setDuration(REORDER_ANIMATION_DURATION);
2057 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2058 s.start();
2059 }
2060
Adam Cohen19f37922012-03-21 11:59:11 -07002061
2062 // Returns the time required to complete the current oscillating animation
2063 private int completionTime() {
2064 if (repeatCount % 2 == 0) {
2065 return (int) (va.getDuration() - va.getCurrentPlayTime() + DURATION);
2066 } else {
2067 return (int) (va.getDuration() - va.getCurrentPlayTime());
2068 }
2069 }
2070 }
2071
2072 private void completeAndClearReorderHintAnimations() {
2073 for (ReorderHintAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002074 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002075 }
2076 mShakeAnimators.clear();
2077 }
2078
2079 private int getMaxCompletionTime() {
2080 int maxTime = 0;
2081 for (ReorderHintAnimation a: mShakeAnimators.values()) {
2082 maxTime = Math.max(maxTime, a.completionTime());
2083 }
2084 return maxTime;
2085 }
2086
Adam Cohen482ed822012-03-02 14:15:13 -08002087 private void commitTempPlacement() {
2088 for (int i = 0; i < mCountX; i++) {
2089 for (int j = 0; j < mCountY; j++) {
2090 mOccupied[i][j] = mTmpOccupied[i][j];
2091 }
2092 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002093 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002094 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002095 View child = mShortcutsAndWidgets.getChildAt(i);
2096 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2097 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002098 // We do a null check here because the item info can be null in the case of the
2099 // AllApps button in the hotseat.
2100 if (info != null) {
2101 info.cellX = lp.cellX = lp.tmpCellX;
2102 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002103 info.spanX = lp.cellHSpan;
2104 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002105 }
Adam Cohen482ed822012-03-02 14:15:13 -08002106 }
Adam Cohen2acce882012-03-28 19:03:19 -07002107 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002108 }
2109
2110 public void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002111 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002112 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002113 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002114 lp.useTmpCoords = useTempCoords;
2115 }
2116 }
2117
Adam Cohen482ed822012-03-02 14:15:13 -08002118 ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
2119 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2120 int[] result = new int[2];
2121 int[] resultSpan = new int[2];
2122 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
2123 resultSpan);
2124 if (result[0] >= 0 && result[1] >= 0) {
2125 copyCurrentStateToSolution(solution, false);
2126 solution.dragViewX = result[0];
2127 solution.dragViewY = result[1];
2128 solution.dragViewSpanX = resultSpan[0];
2129 solution.dragViewSpanY = resultSpan[1];
2130 solution.isSolution = true;
2131 } else {
2132 solution.isSolution = false;
2133 }
2134 return solution;
2135 }
2136
2137 public void prepareChildForDrag(View child) {
2138 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002139 }
2140
Adam Cohen19f37922012-03-21 11:59:11 -07002141 /* This seems like it should be obvious and straight-forward, but when the direction vector
2142 needs to match with the notion of the dragView pushing other views, we have to employ
2143 a slightly more subtle notion of the direction vector. The question is what two points is
2144 the vector between? The center of the dragView and its desired destination? Not quite, as
2145 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2146 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2147 or right, which helps make pushing feel right.
2148 */
2149 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2150 int spanY, View dragView, int[] resultDirection) {
2151 int[] targetDestination = new int[2];
2152
2153 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2154 Rect dragRect = new Rect();
2155 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2156 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2157
2158 Rect dropRegionRect = new Rect();
2159 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2160 dragView, dropRegionRect, mIntersectingViews);
2161
2162 int dropRegionSpanX = dropRegionRect.width();
2163 int dropRegionSpanY = dropRegionRect.height();
2164
2165 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2166 dropRegionRect.height(), dropRegionRect);
2167
2168 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2169 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2170
2171 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2172 deltaX = 0;
2173 }
2174 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2175 deltaY = 0;
2176 }
2177
2178 if (deltaX == 0 && deltaY == 0) {
2179 // No idea what to do, give a random direction.
2180 resultDirection[0] = 1;
2181 resultDirection[1] = 0;
2182 } else {
2183 computeDirectionVector(deltaX, deltaY, resultDirection);
2184 }
2185 }
2186
2187 // For a given cell and span, fetch the set of views intersecting the region.
2188 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2189 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2190 if (boundingRect != null) {
2191 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2192 }
2193 intersectingViews.clear();
2194 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2195 Rect r1 = new Rect();
2196 final int count = mShortcutsAndWidgets.getChildCount();
2197 for (int i = 0; i < count; i++) {
2198 View child = mShortcutsAndWidgets.getChildAt(i);
2199 if (child == dragView) continue;
2200 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2201 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2202 if (Rect.intersects(r0, r1)) {
2203 mIntersectingViews.add(child);
2204 if (boundingRect != null) {
2205 boundingRect.union(r1);
2206 }
2207 }
2208 }
2209 }
2210
2211 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2212 View dragView, int[] result) {
2213 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2214 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2215 mIntersectingViews);
2216 return !mIntersectingViews.isEmpty();
2217 }
2218
2219 void revertTempState() {
2220 if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
2221 final int count = mShortcutsAndWidgets.getChildCount();
2222 for (int i = 0; i < count; i++) {
2223 View child = mShortcutsAndWidgets.getChildAt(i);
2224 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2225 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2226 lp.tmpCellX = lp.cellX;
2227 lp.tmpCellY = lp.cellY;
2228 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2229 0, false, false);
2230 }
2231 }
2232 completeAndClearReorderHintAnimations();
2233 setItemPlacementDirty(false);
2234 }
2235
Adam Cohenbebf0422012-04-11 18:06:28 -07002236 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2237 View dragView, int[] direction, boolean commit) {
2238 int[] pixelXY = new int[2];
2239 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2240
2241 // First we determine if things have moved enough to cause a different layout
2242 ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY,
2243 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2244
2245 setUseTempCoords(true);
2246 if (swapSolution != null && swapSolution.isSolution) {
2247 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2248 // committing anything or animating anything as we just want to determine if a solution
2249 // exists
2250 copySolutionToTempState(swapSolution, dragView);
2251 setItemPlacementDirty(true);
2252 animateItemsToSolution(swapSolution, dragView, commit);
2253
2254 if (commit) {
2255 commitTempPlacement();
2256 completeAndClearReorderHintAnimations();
2257 setItemPlacementDirty(false);
2258 } else {
2259 beginOrAdjustHintAnimations(swapSolution, dragView,
2260 REORDER_ANIMATION_DURATION);
2261 }
2262 mShortcutsAndWidgets.requestLayout();
2263 }
2264 return swapSolution.isSolution;
2265 }
2266
Adam Cohen482ed822012-03-02 14:15:13 -08002267 int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
2268 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002269 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002270 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002271
2272 if (resultSpan == null) {
2273 resultSpan = new int[2];
2274 }
2275
Adam Cohen19f37922012-03-21 11:59:11 -07002276 // When we are checking drop validity or actually dropping, we don't recompute the
2277 // direction vector, since we want the solution to match the preview, and it's possible
2278 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002279 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2280 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002281 mDirectionVector[0] = mPreviousReorderDirection[0];
2282 mDirectionVector[1] = mPreviousReorderDirection[1];
2283 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002284 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2285 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2286 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002287 }
2288 } else {
2289 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2290 mPreviousReorderDirection[0] = mDirectionVector[0];
2291 mPreviousReorderDirection[1] = mDirectionVector[1];
2292 }
2293
Adam Cohen482ed822012-03-02 14:15:13 -08002294 ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
2295 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2296
2297 // We attempt the approach which doesn't shuffle views at all
2298 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2299 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2300
2301 ItemConfiguration finalSolution = null;
2302 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2303 finalSolution = swapSolution;
2304 } else if (noShuffleSolution.isSolution) {
2305 finalSolution = noShuffleSolution;
2306 }
2307
2308 boolean foundSolution = true;
2309 if (!DESTRUCTIVE_REORDER) {
2310 setUseTempCoords(true);
2311 }
2312
2313 if (finalSolution != null) {
2314 result[0] = finalSolution.dragViewX;
2315 result[1] = finalSolution.dragViewY;
2316 resultSpan[0] = finalSolution.dragViewSpanX;
2317 resultSpan[1] = finalSolution.dragViewSpanY;
2318
2319 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2320 // committing anything or animating anything as we just want to determine if a solution
2321 // exists
2322 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2323 if (!DESTRUCTIVE_REORDER) {
2324 copySolutionToTempState(finalSolution, dragView);
2325 }
2326 setItemPlacementDirty(true);
2327 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2328
Adam Cohen19f37922012-03-21 11:59:11 -07002329 if (!DESTRUCTIVE_REORDER &&
2330 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002331 commitTempPlacement();
Adam Cohen19f37922012-03-21 11:59:11 -07002332 completeAndClearReorderHintAnimations();
2333 setItemPlacementDirty(false);
2334 } else {
2335 beginOrAdjustHintAnimations(finalSolution, dragView,
2336 REORDER_ANIMATION_DURATION);
Adam Cohen482ed822012-03-02 14:15:13 -08002337 }
2338 }
2339 } else {
2340 foundSolution = false;
2341 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2342 }
2343
2344 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2345 setUseTempCoords(false);
2346 }
Adam Cohen482ed822012-03-02 14:15:13 -08002347
Michael Jurkaa52570f2012-03-20 03:18:20 -07002348 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002349 return result;
2350 }
2351
Adam Cohen19f37922012-03-21 11:59:11 -07002352 void setItemPlacementDirty(boolean dirty) {
2353 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002354 }
Adam Cohen19f37922012-03-21 11:59:11 -07002355 boolean isItemPlacementDirty() {
2356 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002357 }
2358
2359 private class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002360 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohen482ed822012-03-02 14:15:13 -08002361 boolean isSolution = false;
2362 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2363
2364 int area() {
2365 return dragViewSpanX * dragViewSpanY;
2366 }
Adam Cohen8baab352012-03-20 17:39:21 -07002367 }
2368
2369 private class CellAndSpan {
2370 int x, y;
2371 int spanX, spanY;
2372
2373 public CellAndSpan(int x, int y, int spanX, int spanY) {
2374 this.x = x;
2375 this.y = y;
2376 this.spanX = spanX;
2377 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002378 }
2379 }
2380
Adam Cohendf035382011-04-11 17:22:04 -07002381 /**
2382 * Find a vacant area that will fit the given bounds nearest the requested
2383 * cell location. Uses Euclidean distance to score multiple vacant areas.
2384 *
2385 * @param pixelX The X location at which you want to search for a vacant area.
2386 * @param pixelY The Y location at which you want to search for a vacant area.
2387 * @param spanX Horizontal span of the object.
2388 * @param spanY Vertical span of the object.
2389 * @param ignoreView Considers space occupied by this view as unoccupied
2390 * @param result Previously returned value to possibly recycle.
2391 * @return The X, Y cell of a vacant area that can contain this object,
2392 * nearest the requested location.
2393 */
2394 int[] findNearestVacantArea(
2395 int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
2396 return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
2397 }
2398
2399 /**
Adam Cohend41fbf52012-02-16 23:53:59 -08002400 * Find a vacant area that will fit the given bounds nearest the requested
2401 * cell location. Uses Euclidean distance to score multiple vacant areas.
2402 *
2403 * @param pixelX The X location at which you want to search for a vacant area.
2404 * @param pixelY The Y location at which you want to search for a vacant area.
2405 * @param minSpanX The minimum horizontal span required
2406 * @param minSpanY The minimum vertical span required
2407 * @param spanX Horizontal span of the object.
2408 * @param spanY Vertical span of the object.
2409 * @param ignoreView Considers space occupied by this view as unoccupied
2410 * @param result Previously returned value to possibly recycle.
2411 * @return The X, Y cell of a vacant area that can contain this object,
2412 * nearest the requested location.
2413 */
2414 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
2415 int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
Adam Cohen482ed822012-03-02 14:15:13 -08002416 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
2417 result, resultSpan, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08002418 }
2419
2420 /**
Adam Cohendf035382011-04-11 17:22:04 -07002421 * Find a starting cell position that will fit the given bounds nearest the requested
2422 * cell location. Uses Euclidean distance to score multiple vacant areas.
2423 *
2424 * @param pixelX The X location at which you want to search for a vacant area.
2425 * @param pixelY The Y location at which you want to search for a vacant area.
2426 * @param spanX Horizontal span of the object.
2427 * @param spanY Vertical span of the object.
2428 * @param ignoreView Considers space occupied by this view as unoccupied
2429 * @param result Previously returned value to possibly recycle.
2430 * @return The X, Y cell of a vacant area that can contain this object,
2431 * nearest the requested location.
2432 */
2433 int[] findNearestArea(
2434 int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2435 return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
2436 }
2437
Michael Jurka0280c3b2010-09-17 15:00:07 -07002438 boolean existsEmptyCell() {
2439 return findCellForSpan(null, 1, 1);
2440 }
2441
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002442 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002443 * Finds the upper-left coordinate of the first rectangle in the grid that can
2444 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2445 * then this method will only return coordinates for rectangles that contain the cell
2446 * (intersectX, intersectY)
2447 *
2448 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2449 * can be found.
2450 * @param spanX The horizontal span of the cell we want to find.
2451 * @param spanY The vertical span of the cell we want to find.
2452 *
2453 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002454 */
Michael Jurka0280c3b2010-09-17 15:00:07 -07002455 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Adam Cohen482ed822012-03-02 14:15:13 -08002456 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002457 }
2458
2459 /**
2460 * Like above, but ignores any cells occupied by the item "ignoreView"
2461 *
2462 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2463 * can be found.
2464 * @param spanX The horizontal span of the cell we want to find.
2465 * @param spanY The vertical span of the cell we want to find.
2466 * @param ignoreView The home screen item we should treat as not occupying any space
2467 * @return
2468 */
2469 boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
Adam Cohen482ed822012-03-02 14:15:13 -08002470 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
2471 ignoreView, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002472 }
2473
2474 /**
2475 * Like above, but if intersectX and intersectY are not -1, then this method will try to
2476 * return coordinates for rectangles that contain the cell [intersectX, intersectY]
2477 *
2478 * @param spanX The horizontal span of the cell we want to find.
2479 * @param spanY The vertical span of the cell we want to find.
2480 * @param ignoreView The home screen item we should treat as not occupying any space
2481 * @param intersectX The X coordinate of the cell that we should try to overlap
2482 * @param intersectX The Y coordinate of the cell that we should try to overlap
2483 *
2484 * @return True if a vacant cell of the specified dimension was found, false otherwise.
2485 */
2486 boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
2487 int intersectX, int intersectY) {
2488 return findCellForSpanThatIntersectsIgnoring(
Adam Cohen482ed822012-03-02 14:15:13 -08002489 cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002490 }
2491
2492 /**
2493 * The superset of the above two methods
2494 */
2495 boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002496 int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002497 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08002498 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002499
Michael Jurka28750fb2010-09-24 17:43:49 -07002500 boolean foundCell = false;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002501 while (true) {
2502 int startX = 0;
2503 if (intersectX >= 0) {
2504 startX = Math.max(startX, intersectX - (spanX - 1));
2505 }
2506 int endX = mCountX - (spanX - 1);
2507 if (intersectX >= 0) {
2508 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
2509 }
2510 int startY = 0;
2511 if (intersectY >= 0) {
2512 startY = Math.max(startY, intersectY - (spanY - 1));
2513 }
2514 int endY = mCountY - (spanY - 1);
2515 if (intersectY >= 0) {
2516 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
2517 }
2518
Winson Chungbbc60d82010-11-11 16:34:41 -08002519 for (int y = startY; y < endY && !foundCell; y++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002520 inner:
Winson Chungbbc60d82010-11-11 16:34:41 -08002521 for (int x = startX; x < endX; x++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002522 for (int i = 0; i < spanX; i++) {
2523 for (int j = 0; j < spanY; j++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002524 if (occupied[x + i][y + j]) {
Winson Chungbbc60d82010-11-11 16:34:41 -08002525 // small optimization: we can skip to after the column we just found
Michael Jurka0280c3b2010-09-17 15:00:07 -07002526 // an occupied cell
Winson Chungbbc60d82010-11-11 16:34:41 -08002527 x += i;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002528 continue inner;
2529 }
2530 }
2531 }
2532 if (cellXY != null) {
2533 cellXY[0] = x;
2534 cellXY[1] = y;
2535 }
Michael Jurka28750fb2010-09-24 17:43:49 -07002536 foundCell = true;
2537 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002538 }
2539 }
2540 if (intersectX == -1 && intersectY == -1) {
2541 break;
2542 } else {
2543 // if we failed to find anything, try again but without any requirements of
2544 // intersecting
2545 intersectX = -1;
2546 intersectY = -1;
2547 continue;
2548 }
2549 }
2550
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002551 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08002552 markCellsAsOccupiedForView(ignoreView, occupied);
Michael Jurka28750fb2010-09-24 17:43:49 -07002553 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002554 }
2555
2556 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002557 * A drag event has begun over this layout.
2558 * It may have begun over this layout (in which case onDragChild is called first),
2559 * or it may have begun on another layout.
2560 */
2561 void onDragEnter() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002562 mDragEnforcer.onDragEnter();
Winson Chungc07918d2011-07-01 15:35:26 -07002563 mDragging = true;
2564 }
2565
2566 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002567 * Called when drag has left this CellLayout or has been completed (successfully or not)
2568 */
2569 void onDragExit() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002570 mDragEnforcer.onDragExit();
Joe Onorato4be866d2010-10-10 11:26:02 -07002571 // This can actually be called when we aren't in a drag, e.g. when adding a new
2572 // item to this layout via the customize drawer.
2573 // Guard against that case.
2574 if (mDragging) {
2575 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002576 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002577
2578 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002579 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002580 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2581 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002582 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002583 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002584 }
2585
2586 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002587 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002588 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002589 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002590 *
2591 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002592 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002593 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002594 if (child != null) {
2595 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002596 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002597 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002598 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002599 }
2600
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002601 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002602 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002603 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002604 * @param cellX X coordinate of upper left corner expressed as a cell position
2605 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002606 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002607 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002608 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002609 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002610 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002611 final int cellWidth = mCellWidth;
2612 final int cellHeight = mCellHeight;
2613 final int widthGap = mWidthGap;
2614 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002615
Winson Chung4b825dcd2011-06-19 12:41:22 -07002616 final int hStartPadding = getPaddingLeft();
2617 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002618
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002619 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2620 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2621
2622 int x = hStartPadding + cellX * (cellWidth + widthGap);
2623 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002624
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002625 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002626 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002627
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002628 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002629 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002630 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002631 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002632 * @param width Width in pixels
2633 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002634 * @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 -08002635 */
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002636 public int[] rectToCell(int width, int height, int[] result) {
Michael Jurka9987a5c2010-10-08 16:58:12 -07002637 return rectToCell(getResources(), width, height, result);
2638 }
2639
2640 public static int[] rectToCell(Resources resources, int width, int height, int[] result) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002641 // Always assume we're working with the smallest span to make sure we
2642 // reserve enough space in both orientations.
Joe Onorato79e56262009-09-21 15:23:04 -04002643 int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
2644 int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002645 int smallerSize = Math.min(actualWidth, actualHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002646
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002647 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002648 int spanX = (int) Math.ceil(width / (float) smallerSize);
2649 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002650
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002651 if (result == null) {
2652 return new int[] { spanX, spanY };
2653 }
2654 result[0] = spanX;
2655 result[1] = spanY;
2656 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002657 }
2658
Michael Jurkaf12c75c2011-01-25 22:41:40 -08002659 public int[] cellSpansToSize(int hSpans, int vSpans) {
2660 int[] size = new int[2];
2661 size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
2662 size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
2663 return size;
2664 }
2665
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002666 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08002667 * Calculate the grid spans needed to fit given item
2668 */
2669 public void calculateSpans(ItemInfo info) {
2670 final int minWidth;
2671 final int minHeight;
2672
2673 if (info instanceof LauncherAppWidgetInfo) {
2674 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
2675 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
2676 } else if (info instanceof PendingAddWidgetInfo) {
2677 minWidth = ((PendingAddWidgetInfo) info).minWidth;
2678 minHeight = ((PendingAddWidgetInfo) info).minHeight;
2679 } else {
2680 // It's not a widget, so it must be 1x1
2681 info.spanX = info.spanY = 1;
2682 return;
2683 }
2684 int[] spans = rectToCell(minWidth, minHeight, null);
2685 info.spanX = spans[0];
2686 info.spanY = spans[1];
2687 }
2688
2689 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002690 * Find the first vacant cell, if there is one.
2691 *
2692 * @param vacant Holds the x and y coordinate of the vacant cell
2693 * @param spanX Horizontal cell span.
2694 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -07002695 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002696 * @return True if a vacant cell was found
2697 */
2698 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002699
Michael Jurka0280c3b2010-09-17 15:00:07 -07002700 return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002701 }
2702
2703 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
2704 int xCount, int yCount, boolean[][] occupied) {
2705
Adam Cohen2801caf2011-05-13 20:57:39 -07002706 for (int y = 0; y < yCount; y++) {
2707 for (int x = 0; x < xCount; x++) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002708 boolean available = !occupied[x][y];
2709out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
2710 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
2711 available = available && !occupied[i][j];
2712 if (!available) break out;
2713 }
2714 }
2715
2716 if (available) {
2717 vacant[0] = x;
2718 vacant[1] = y;
2719 return true;
2720 }
2721 }
2722 }
2723
2724 return false;
2725 }
2726
Michael Jurka0280c3b2010-09-17 15:00:07 -07002727 private void clearOccupiedCells() {
2728 for (int x = 0; x < mCountX; x++) {
2729 for (int y = 0; y < mCountY; y++) {
2730 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002731 }
2732 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002733 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002734
Adam Cohend41fbf52012-02-16 23:53:59 -08002735 public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002736 markCellsAsUnoccupiedForView(view);
Adam Cohen482ed822012-03-02 14:15:13 -08002737 markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002738 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002739
Adam Cohend4844c32011-02-18 19:25:06 -08002740 public void markCellsAsOccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08002741 markCellsAsOccupiedForView(view, mOccupied);
2742 }
2743 public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002744 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002745 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002746 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002747 }
2748
Adam Cohend4844c32011-02-18 19:25:06 -08002749 public void markCellsAsUnoccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08002750 markCellsAsUnoccupiedForView(view, mOccupied);
2751 }
2752 public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002753 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002754 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002755 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002756 }
2757
Adam Cohen482ed822012-03-02 14:15:13 -08002758 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2759 boolean value) {
2760 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002761 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2762 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002763 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002764 }
2765 }
2766 }
2767
Adam Cohen2801caf2011-05-13 20:57:39 -07002768 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002769 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002770 (Math.max((mCountX - 1), 0) * mWidthGap);
2771 }
2772
2773 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002774 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002775 (Math.max((mCountY - 1), 0) * mHeightGap);
2776 }
2777
Michael Jurka66d72172011-04-12 16:29:25 -07002778 public boolean isOccupied(int x, int y) {
2779 if (x < mCountX && y < mCountY) {
2780 return mOccupied[x][y];
2781 } else {
2782 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2783 }
2784 }
2785
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002786 @Override
2787 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2788 return new CellLayout.LayoutParams(getContext(), attrs);
2789 }
2790
2791 @Override
2792 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2793 return p instanceof CellLayout.LayoutParams;
2794 }
2795
2796 @Override
2797 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2798 return new CellLayout.LayoutParams(p);
2799 }
2800
Winson Chungaafa03c2010-06-11 17:34:16 -07002801 public static class CellLayoutAnimationController extends LayoutAnimationController {
2802 public CellLayoutAnimationController(Animation animation, float delay) {
2803 super(animation, delay);
2804 }
2805
2806 @Override
2807 protected long getDelayForView(View view) {
2808 return (int) (Math.random() * 150);
2809 }
2810 }
2811
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002812 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2813 /**
2814 * Horizontal location of the item in the grid.
2815 */
2816 @ViewDebug.ExportedProperty
2817 public int cellX;
2818
2819 /**
2820 * Vertical location of the item in the grid.
2821 */
2822 @ViewDebug.ExportedProperty
2823 public int cellY;
2824
2825 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002826 * Temporary horizontal location of the item in the grid during reorder
2827 */
2828 public int tmpCellX;
2829
2830 /**
2831 * Temporary vertical location of the item in the grid during reorder
2832 */
2833 public int tmpCellY;
2834
2835 /**
2836 * Indicates that the temporary coordinates should be used to layout the items
2837 */
2838 public boolean useTmpCoords;
2839
2840 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002841 * Number of cells spanned horizontally by the item.
2842 */
2843 @ViewDebug.ExportedProperty
2844 public int cellHSpan;
2845
2846 /**
2847 * Number of cells spanned vertically by the item.
2848 */
2849 @ViewDebug.ExportedProperty
2850 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002851
Adam Cohen1b607ed2011-03-03 17:26:50 -08002852 /**
2853 * Indicates whether the item will set its x, y, width and height parameters freely,
2854 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2855 */
Adam Cohend4844c32011-02-18 19:25:06 -08002856 public boolean isLockedToGrid = true;
2857
Adam Cohen482ed822012-03-02 14:15:13 -08002858 /**
2859 * Indicates whether this item can be reordered. Always true except in the case of the
2860 * the AllApps button.
2861 */
2862 public boolean canReorder = true;
2863
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002864 // X coordinate of the view in the layout.
2865 @ViewDebug.ExportedProperty
2866 int x;
2867 // Y coordinate of the view in the layout.
2868 @ViewDebug.ExportedProperty
2869 int y;
2870
Romain Guy84f296c2009-11-04 15:00:44 -08002871 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002872
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002873 public LayoutParams(Context c, AttributeSet attrs) {
2874 super(c, attrs);
2875 cellHSpan = 1;
2876 cellVSpan = 1;
2877 }
2878
2879 public LayoutParams(ViewGroup.LayoutParams source) {
2880 super(source);
2881 cellHSpan = 1;
2882 cellVSpan = 1;
2883 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002884
2885 public LayoutParams(LayoutParams source) {
2886 super(source);
2887 this.cellX = source.cellX;
2888 this.cellY = source.cellY;
2889 this.cellHSpan = source.cellHSpan;
2890 this.cellVSpan = source.cellVSpan;
2891 }
2892
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002893 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002894 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002895 this.cellX = cellX;
2896 this.cellY = cellY;
2897 this.cellHSpan = cellHSpan;
2898 this.cellVSpan = cellVSpan;
2899 }
2900
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002901 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap) {
Adam Cohend4844c32011-02-18 19:25:06 -08002902 if (isLockedToGrid) {
2903 final int myCellHSpan = cellHSpan;
2904 final int myCellVSpan = cellVSpan;
Adam Cohen482ed822012-03-02 14:15:13 -08002905 final int myCellX = useTmpCoords ? tmpCellX : cellX;
2906 final int myCellY = useTmpCoords ? tmpCellY : cellY;
Adam Cohen1b607ed2011-03-03 17:26:50 -08002907
Adam Cohend4844c32011-02-18 19:25:06 -08002908 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2909 leftMargin - rightMargin;
2910 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2911 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002912 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2913 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002914 }
2915 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002916
Winson Chungaafa03c2010-06-11 17:34:16 -07002917 public String toString() {
2918 return "(" + this.cellX + ", " + this.cellY + ")";
2919 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002920
2921 public void setWidth(int width) {
2922 this.width = width;
2923 }
2924
2925 public int getWidth() {
2926 return width;
2927 }
2928
2929 public void setHeight(int height) {
2930 this.height = height;
2931 }
2932
2933 public int getHeight() {
2934 return height;
2935 }
2936
2937 public void setX(int x) {
2938 this.x = x;
2939 }
2940
2941 public int getX() {
2942 return x;
2943 }
2944
2945 public void setY(int y) {
2946 this.y = y;
2947 }
2948
2949 public int getY() {
2950 return y;
2951 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002952 }
2953
Michael Jurka0280c3b2010-09-17 15:00:07 -07002954 // This class stores info for two purposes:
2955 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2956 // its spanX, spanY, and the screen it is on
2957 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2958 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2959 // the CellLayout that was long clicked
Michael Jurkae5fb0f22011-04-11 13:27:46 -07002960 static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002961 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07002962 int cellX = -1;
2963 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002964 int spanX;
2965 int spanY;
2966 int screen;
Winson Chung3d503fb2011-07-13 17:25:49 -07002967 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002968
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002969 @Override
2970 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002971 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2972 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002973 }
2974 }
Michael Jurkad771c962011-08-09 15:00:48 -07002975
2976 public boolean lastDownOnOccupiedCell() {
2977 return mLastDownOnOccupiedCell;
2978 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002979}