blob: ccc842e5ea101760b49f11136a8ea7a06d33d359 [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;
Michael Jurka629758f2012-06-14 16:18:21 -070020import android.animation.AnimatorListenerAdapter;
Brandon Keely50e6e562012-05-08 16:28:49 -070021import android.animation.AnimatorSet;
Chet Haase00397b12010-10-07 11:13:10 -070022import android.animation.TimeInterpolator;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070023import android.animation.ValueAnimator;
24import android.animation.ValueAnimator.AnimatorUpdateListener;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080025import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040026import android.content.res.Resources;
Winson Chungaafa03c2010-06-11 17:34:16 -070027import android.content.res.TypedArray;
Joe Onorato4be866d2010-10-10 11:26:02 -070028import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070029import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080030import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070031import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070032import android.graphics.Point;
Adam Cohenb5ba0972011-09-07 18:02:31 -070033import android.graphics.PorterDuff;
34import android.graphics.PorterDuffXfermode;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080035import android.graphics.Rect;
Adam Cohen482ed822012-03-02 14:15:13 -080036import android.graphics.drawable.ColorDrawable;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070037import android.graphics.drawable.Drawable;
Adam Cohenb5ba0972011-09-07 18:02:31 -070038import android.graphics.drawable.NinePatchDrawable;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080039import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070040import android.util.Log;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080041import android.view.MotionEvent;
42import android.view.View;
43import android.view.ViewDebug;
44import android.view.ViewGroup;
Winson Chungaafa03c2010-06-11 17:34:16 -070045import android.view.animation.Animation;
Winson Chung150fbab2010-09-29 17:14:26 -070046import android.view.animation.DecelerateInterpolator;
Winson Chungaafa03c2010-06-11 17:34:16 -070047import android.view.animation.LayoutAnimationController;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080048
Adam Cohen66396872011-04-15 17:50:36 -070049import com.android.launcher.R;
Adam Cohen69ce2e52011-07-03 19:25:21 -070050import com.android.launcher2.FolderIcon.FolderRingAnimator;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070051
Adam Cohen69ce2e52011-07-03 19:25:21 -070052import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070053import java.util.Arrays;
Adam Cohenbfbfd262011-06-13 16:55:12 -070054import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080055import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070056
Michael Jurkabdb5c532011-02-01 15:05:06 -080057public class CellLayout extends ViewGroup {
Winson Chungaafa03c2010-06-11 17:34:16 -070058 static final String TAG = "CellLayout";
59
Adam Cohen2acce882012-03-28 19:03:19 -070060 private Launcher mLauncher;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080061 private int mCellWidth;
62 private int mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070063
Adam Cohend22015c2010-07-26 22:02:18 -070064 private int mCountX;
65 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080066
Adam Cohen234c4cd2011-07-17 21:03:04 -070067 private int mOriginalWidthGap;
68 private int mOriginalHeightGap;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080069 private int mWidthGap;
70 private int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070071 private int mMaxGap;
Adam Cohenebea84d2011-11-09 17:20:41 -080072 private boolean mScrollingTransformsDirty = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080073
74 private final Rect mRect = new Rect();
75 private final CellInfo mCellInfo = new CellInfo();
Winson Chungaafa03c2010-06-11 17:34:16 -070076
Patrick Dubroyde7658b2010-09-27 11:15:43 -070077 // These are temporary variables to prevent having to allocate a new object just to
78 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Winson Chung0be025d2011-05-23 17:45:09 -070079 private final int[] mTmpXY = new int[2];
Patrick Dubroyde7658b2010-09-27 11:15:43 -070080 private final int[] mTmpPoint = new int[2];
Adam Cohen69ce2e52011-07-03 19:25:21 -070081 int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070082
The Android Open Source Project31dd5032009-03-03 19:32:27 -080083 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -080084 boolean[][] mTmpOccupied;
Michael Jurkad771c962011-08-09 15:00:48 -070085 private boolean mLastDownOnOccupiedCell = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080086
Michael Jurkadee05892010-07-27 10:01:56 -070087 private OnTouchListener mInterceptTouchListener;
88
Adam Cohen69ce2e52011-07-03 19:25:21 -070089 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -070090 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -070091
Adam Cohenb5ba0972011-09-07 18:02:31 -070092 private int mForegroundAlpha = 0;
Michael Jurka5f1c5092010-09-03 14:15:02 -070093 private float mBackgroundAlpha;
Adam Cohen1b0aaac2010-10-28 11:11:18 -070094 private float mBackgroundAlphaMultiplier = 1.0f;
Adam Cohenf34bab52010-09-30 14:11:56 -070095
Michael Jurka33945b22010-12-21 18:19:38 -080096 private Drawable mNormalBackground;
Michael Jurka33945b22010-12-21 18:19:38 -080097 private Drawable mActiveGlowBackground;
Adam Cohenb5ba0972011-09-07 18:02:31 -070098 private Drawable mOverScrollForegroundDrawable;
99 private Drawable mOverScrollLeft;
100 private Drawable mOverScrollRight;
Michael Jurka18014792010-10-14 09:01:34 -0700101 private Rect mBackgroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700102 private Rect mForegroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700103 private int mForegroundPadding;
Patrick Dubroy1262e362010-10-06 15:49:50 -0700104
Michael Jurka33945b22010-12-21 18:19:38 -0800105 // If we're actively dragging something over this screen, mIsDragOverlapping is true
106 private boolean mIsDragOverlapping = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700107 private final Point mDragCenter = new Point();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700108
Winson Chung150fbab2010-09-29 17:14:26 -0700109 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700110 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohend41fbf52012-02-16 23:53:59 -0800111 private Rect[] mDragOutlines = new Rect[4];
Chet Haase472b2812010-10-14 07:02:04 -0700112 private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700113 private InterruptibleInOutAnimator[] mDragOutlineAnims =
114 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700115
116 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700117 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700118 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700119
Patrick Dubroy96864c32011-03-10 17:17:23 -0800120 private BubbleTextView mPressedOrFocusedIcon;
121
Adam Cohen482ed822012-03-02 14:15:13 -0800122 private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
123 HashMap<CellLayout.LayoutParams, Animator>();
Adam Cohen19f37922012-03-21 11:59:11 -0700124 private HashMap<View, ReorderHintAnimation>
125 mShakeAnimators = new HashMap<View, ReorderHintAnimation>();
126
127 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700128
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700129 // When a drag operation is in progress, holds the nearest cell to the touch point
130 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800131
Joe Onorato4be866d2010-10-10 11:26:02 -0700132 private boolean mDragging = false;
133
Patrick Dubroyce34a972010-10-19 10:34:32 -0700134 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700135 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700136
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800137 private boolean mIsHotseat = false;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800138
Adam Cohen482ed822012-03-02 14:15:13 -0800139 public static final int MODE_DRAG_OVER = 0;
140 public static final int MODE_ON_DROP = 1;
141 public static final int MODE_ON_DROP_EXTERNAL = 2;
142 public static final int MODE_ACCEPT_DROP = 3;
Adam Cohen19f37922012-03-21 11:59:11 -0700143 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800144 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
145
Adam Cohena897f392012-04-27 18:12:05 -0700146 static final int LANDSCAPE = 0;
147 static final int PORTRAIT = 1;
148
Adam Cohen7bdfc972012-05-22 16:50:35 -0700149 private static final float REORDER_HINT_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700150 private static final int REORDER_ANIMATION_DURATION = 150;
151 private float mReorderHintAnimationMagnitude;
152
Adam Cohen482ed822012-03-02 14:15:13 -0800153 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
154 private Rect mOccupiedRect = new Rect();
155 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700156 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700157 private static final int INVALID_DIRECTION = -100;
Adam Cohenc6cc61d2012-04-04 12:47:08 -0700158 private DropTarget.DragEnforcer mDragEnforcer;
Adam Cohen482ed822012-03-02 14:15:13 -0800159
Romain Guy8a0bff52012-05-06 13:14:33 -0700160 private final static PorterDuffXfermode sAddBlendMode =
161 new PorterDuffXfermode(PorterDuff.Mode.ADD);
162
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800163 public CellLayout(Context context) {
164 this(context, null);
165 }
166
167 public CellLayout(Context context, AttributeSet attrs) {
168 this(context, attrs, 0);
169 }
170
171 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
172 super(context, attrs, defStyle);
Michael Jurka8b805b12012-04-18 14:23:14 -0700173 mDragEnforcer = new DropTarget.DragEnforcer(context);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700174
175 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
176 // the user where a dragged item will land when dropped.
177 setWillNotDraw(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700178 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700179
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800180 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
181
Adam Cohenf4bd5792012-04-27 11:35:29 -0700182 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
183 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700184 mWidthGap = mOriginalWidthGap = a.getDimensionPixelSize(R.styleable.CellLayout_widthGap, 0);
185 mHeightGap = mOriginalHeightGap = a.getDimensionPixelSize(R.styleable.CellLayout_heightGap, 0);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700186 mMaxGap = a.getDimensionPixelSize(R.styleable.CellLayout_maxGap, 0);
Adam Cohend22015c2010-07-26 22:02:18 -0700187 mCountX = LauncherModel.getCellCountX();
188 mCountY = LauncherModel.getCellCountY();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700189 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800190 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700191 mPreviousReorderDirection[0] = INVALID_DIRECTION;
192 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800193
194 a.recycle();
195
196 setAlwaysDrawnWithCacheEnabled(false);
197
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700198 final Resources res = getResources();
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700199
Winson Chung967289b2011-06-30 18:09:30 -0700200 mNormalBackground = res.getDrawable(R.drawable.homescreen_blue_normal_holo);
Winson Chungdea74b72011-09-13 18:06:43 -0700201 mActiveGlowBackground = res.getDrawable(R.drawable.homescreen_blue_strong_holo);
Michael Jurka33945b22010-12-21 18:19:38 -0800202
Adam Cohenb5ba0972011-09-07 18:02:31 -0700203 mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
204 mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
205 mForegroundPadding =
206 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
Michael Jurka33945b22010-12-21 18:19:38 -0800207
Adam Cohen19f37922012-03-21 11:59:11 -0700208 mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE *
209 res.getDimensionPixelSize(R.dimen.app_icon_size));
210
Winson Chungb26f3d62011-06-02 10:49:29 -0700211 mNormalBackground.setFilterBitmap(true);
Winson Chungb26f3d62011-06-02 10:49:29 -0700212 mActiveGlowBackground.setFilterBitmap(true);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700213
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700214 // Initialize the data structures used for the drag visualization.
Winson Chung150fbab2010-09-29 17:14:26 -0700215
Patrick Dubroyce34a972010-10-19 10:34:32 -0700216 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700217
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700218
Winson Chungb8c69f32011-10-19 21:36:08 -0700219 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700220 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800221 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700222 }
223
224 // When dragging things around the home screens, we show a green outline of
225 // where the item will land. The outlines gradually fade out, leaving a trail
226 // behind the drag path.
227 // Set up all the animations that are used to implement this fading.
228 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700229 final float fromAlphaValue = 0;
230 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700231
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700232 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700233
234 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700235 final InterruptibleInOutAnimator anim =
236 new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700237 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700238 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700239 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700240 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700241 final Bitmap outline = (Bitmap)anim.getTag();
242
243 // If an animation is started and then stopped very quickly, we can still
244 // get spurious updates we've cleared the tag. Guard against this.
245 if (outline == null) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700246 @SuppressWarnings("all") // suppress dead code warning
247 final boolean debug = false;
248 if (debug) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700249 Object val = animation.getAnimatedValue();
250 Log.d(TAG, "anim " + thisIndex + " update: " + val +
251 ", isStopped " + anim.isStopped());
252 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700253 // Try to prevent it from continuing to run
254 animation.cancel();
255 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700256 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800257 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700258 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700259 }
260 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700261 // The animation holds a reference to the drag outline bitmap as long is it's
262 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700263 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700264 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700265 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700266 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700267 anim.setTag(null);
268 }
269 }
270 });
271 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700272 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700273
Michael Jurka18014792010-10-14 09:01:34 -0700274 mBackgroundRect = new Rect();
Adam Cohenb5ba0972011-09-07 18:02:31 -0700275 mForegroundRect = new Rect();
Michael Jurkabea15192010-11-17 12:33:46 -0800276
Michael Jurkaa52570f2012-03-20 03:18:20 -0700277 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
278 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
279 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700280 }
281
Michael Jurkaf6440da2011-04-05 14:50:34 -0700282 static int widthInPortrait(Resources r, int numCells) {
283 // We use this method from Workspace to figure out how many rows/columns Launcher should
284 // have. We ignore the left/right padding on CellLayout because it turns out in our design
285 // the padding extends outside the visible screen size, but it looked fine anyway.
Michael Jurkaf6440da2011-04-05 14:50:34 -0700286 int cellWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700287 int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
288 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
Michael Jurkaf6440da2011-04-05 14:50:34 -0700289
Winson Chung4b825dcd2011-06-19 12:41:22 -0700290 return minGap * (numCells - 1) + cellWidth * numCells;
Michael Jurkaf6440da2011-04-05 14:50:34 -0700291 }
292
Michael Jurkaf6440da2011-04-05 14:50:34 -0700293 static int heightInLandscape(Resources r, int numCells) {
294 // We use this method from Workspace to figure out how many rows/columns Launcher should
295 // have. We ignore the left/right padding on CellLayout because it turns out in our design
296 // the padding extends outside the visible screen size, but it looked fine anyway.
Michael Jurkaf6440da2011-04-05 14:50:34 -0700297 int cellHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700298 int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
299 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
Michael Jurkaf6440da2011-04-05 14:50:34 -0700300
Winson Chung4b825dcd2011-06-19 12:41:22 -0700301 return minGap * (numCells - 1) + cellHeight * numCells;
Michael Jurkaf6440da2011-04-05 14:50:34 -0700302 }
303
Adam Cohen2801caf2011-05-13 20:57:39 -0700304 public void enableHardwareLayers() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700305 mShortcutsAndWidgets.enableHardwareLayers();
Adam Cohen2801caf2011-05-13 20:57:39 -0700306 }
307
308 public void setGridSize(int x, int y) {
309 mCountX = x;
310 mCountY = y;
311 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800312 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700313 mTempRectStack.clear();
Adam Cohen76fc0852011-06-17 13:26:23 -0700314 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700315 }
316
Patrick Dubroy96864c32011-03-10 17:17:23 -0800317 private void invalidateBubbleTextView(BubbleTextView icon) {
318 final int padding = icon.getPressedOrFocusedBackgroundPadding();
Winson Chung4b825dcd2011-06-19 12:41:22 -0700319 invalidate(icon.getLeft() + getPaddingLeft() - padding,
320 icon.getTop() + getPaddingTop() - padding,
321 icon.getRight() + getPaddingLeft() + padding,
322 icon.getBottom() + getPaddingTop() + padding);
Patrick Dubroy96864c32011-03-10 17:17:23 -0800323 }
324
Adam Cohenb5ba0972011-09-07 18:02:31 -0700325 void setOverScrollAmount(float r, boolean left) {
326 if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
327 mOverScrollForegroundDrawable = mOverScrollLeft;
328 } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
329 mOverScrollForegroundDrawable = mOverScrollRight;
330 }
331
332 mForegroundAlpha = (int) Math.round((r * 255));
333 mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
334 invalidate();
335 }
336
Patrick Dubroy96864c32011-03-10 17:17:23 -0800337 void setPressedOrFocusedIcon(BubbleTextView icon) {
338 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
339 // requires an expanded clip rect (due to the glow's blur radius)
340 BubbleTextView oldIcon = mPressedOrFocusedIcon;
341 mPressedOrFocusedIcon = icon;
342 if (oldIcon != null) {
343 invalidateBubbleTextView(oldIcon);
344 }
345 if (mPressedOrFocusedIcon != null) {
346 invalidateBubbleTextView(mPressedOrFocusedIcon);
347 }
348 }
349
Michael Jurka33945b22010-12-21 18:19:38 -0800350 void setIsDragOverlapping(boolean isDragOverlapping) {
351 if (mIsDragOverlapping != isDragOverlapping) {
352 mIsDragOverlapping = isDragOverlapping;
353 invalidate();
354 }
355 }
356
357 boolean getIsDragOverlapping() {
358 return mIsDragOverlapping;
359 }
360
Adam Cohenebea84d2011-11-09 17:20:41 -0800361 protected void setOverscrollTransformsDirty(boolean dirty) {
362 mScrollingTransformsDirty = dirty;
363 }
364
365 protected void resetOverscrollTransforms() {
366 if (mScrollingTransformsDirty) {
367 setOverscrollTransformsDirty(false);
368 setTranslationX(0);
369 setRotationY(0);
370 // It doesn't matter if we pass true or false here, the important thing is that we
371 // pass 0, which results in the overscroll drawable not being drawn any more.
372 setOverScrollAmount(0, false);
373 setPivotX(getMeasuredWidth() / 2);
374 setPivotY(getMeasuredHeight() / 2);
375 }
376 }
377
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700378 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700379 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700380 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
381 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
382 // When we're small, we are either drawn normally or in the "accepts drops" state (during
383 // a drag). However, we also drag the mini hover background *over* one of those two
384 // backgrounds
Winson Chungb26f3d62011-06-02 10:49:29 -0700385 if (mBackgroundAlpha > 0.0f) {
Adam Cohenf34bab52010-09-30 14:11:56 -0700386 Drawable bg;
Michael Jurka33945b22010-12-21 18:19:38 -0800387
388 if (mIsDragOverlapping) {
389 // In the mini case, we draw the active_glow bg *over* the active background
Michael Jurkabdf78552011-10-31 14:34:25 -0700390 bg = mActiveGlowBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700391 } else {
Michael Jurkabdf78552011-10-31 14:34:25 -0700392 bg = mNormalBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700393 }
Michael Jurka33945b22010-12-21 18:19:38 -0800394
395 bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
396 bg.setBounds(mBackgroundRect);
397 bg.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700398 }
Romain Guya6abce82009-11-10 02:54:41 -0800399
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700400 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700401 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700402 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700403 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800404 final Rect r = mDragOutlines[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700405 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700406 paint.setAlpha((int)(alpha + .5f));
Adam Cohend41fbf52012-02-16 23:53:59 -0800407 canvas.drawBitmap(b, null, r, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700408 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700409 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800410
411 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
412 // requires an expanded clip rect (due to the glow's blur radius)
413 if (mPressedOrFocusedIcon != null) {
414 final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
415 final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
416 if (b != null) {
417 canvas.drawBitmap(b,
Winson Chung4b825dcd2011-06-19 12:41:22 -0700418 mPressedOrFocusedIcon.getLeft() + getPaddingLeft() - padding,
419 mPressedOrFocusedIcon.getTop() + getPaddingTop() - padding,
Patrick Dubroy96864c32011-03-10 17:17:23 -0800420 null);
421 }
422 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700423
Adam Cohen482ed822012-03-02 14:15:13 -0800424 if (DEBUG_VISUALIZE_OCCUPIED) {
425 int[] pt = new int[2];
426 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700427 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800428 for (int i = 0; i < mCountX; i++) {
429 for (int j = 0; j < mCountY; j++) {
430 if (mOccupied[i][j]) {
431 cellToPoint(i, j, pt);
432 canvas.save();
433 canvas.translate(pt[0], pt[1]);
434 cd.draw(canvas);
435 canvas.restore();
436 }
437 }
438 }
439 }
440
Andrew Flynn850d2e72012-04-26 16:51:20 -0700441 int previewOffset = FolderRingAnimator.sPreviewSize;
442
Adam Cohen69ce2e52011-07-03 19:25:21 -0700443 // The folder outer / inner ring image(s)
444 for (int i = 0; i < mFolderOuterRings.size(); i++) {
445 FolderRingAnimator fra = mFolderOuterRings.get(i);
446
447 // Draw outer ring
448 Drawable d = FolderRingAnimator.sSharedOuterRingDrawable;
449 int width = (int) fra.getOuterRingSize();
450 int height = width;
451 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
452
453 int centerX = mTempLocation[0] + mCellWidth / 2;
Andrew Flynn850d2e72012-04-26 16:51:20 -0700454 int centerY = mTempLocation[1] + previewOffset / 2;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700455
456 canvas.save();
457 canvas.translate(centerX - width / 2, centerY - height / 2);
458 d.setBounds(0, 0, width, height);
459 d.draw(canvas);
460 canvas.restore();
461
462 // Draw inner ring
463 d = FolderRingAnimator.sSharedInnerRingDrawable;
464 width = (int) fra.getInnerRingSize();
465 height = width;
466 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
467
468 centerX = mTempLocation[0] + mCellWidth / 2;
Andrew Flynn850d2e72012-04-26 16:51:20 -0700469 centerY = mTempLocation[1] + previewOffset / 2;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700470 canvas.save();
471 canvas.translate(centerX - width / 2, centerY - width / 2);
472 d.setBounds(0, 0, width, height);
473 d.draw(canvas);
474 canvas.restore();
475 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700476
477 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
478 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
479 int width = d.getIntrinsicWidth();
480 int height = d.getIntrinsicHeight();
481
482 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
483 int centerX = mTempLocation[0] + mCellWidth / 2;
Andrew Flynn850d2e72012-04-26 16:51:20 -0700484 int centerY = mTempLocation[1] + previewOffset / 2;
Adam Cohenc51934b2011-07-26 21:07:43 -0700485
486 canvas.save();
487 canvas.translate(centerX - width / 2, centerY - width / 2);
488 d.setBounds(0, 0, width, height);
489 d.draw(canvas);
490 canvas.restore();
491 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700492 }
493
Adam Cohenb5ba0972011-09-07 18:02:31 -0700494 @Override
495 protected void dispatchDraw(Canvas canvas) {
496 super.dispatchDraw(canvas);
497 if (mForegroundAlpha > 0) {
498 mOverScrollForegroundDrawable.setBounds(mForegroundRect);
499 Paint p = ((NinePatchDrawable) mOverScrollForegroundDrawable).getPaint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700500 p.setXfermode(sAddBlendMode);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700501 mOverScrollForegroundDrawable.draw(canvas);
502 p.setXfermode(null);
503 }
504 }
505
Adam Cohen69ce2e52011-07-03 19:25:21 -0700506 public void showFolderAccept(FolderRingAnimator fra) {
507 mFolderOuterRings.add(fra);
508 }
509
510 public void hideFolderAccept(FolderRingAnimator fra) {
511 if (mFolderOuterRings.contains(fra)) {
512 mFolderOuterRings.remove(fra);
513 }
514 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700515 }
516
Adam Cohenc51934b2011-07-26 21:07:43 -0700517 public void setFolderLeaveBehindCell(int x, int y) {
518 mFolderLeaveBehindCell[0] = x;
519 mFolderLeaveBehindCell[1] = y;
520 invalidate();
521 }
522
523 public void clearFolderLeaveBehind() {
524 mFolderLeaveBehindCell[0] = -1;
525 mFolderLeaveBehindCell[1] = -1;
526 invalidate();
527 }
528
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700529 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700530 public boolean shouldDelayChildPressedState() {
531 return false;
532 }
533
534 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700535 public void cancelLongPress() {
536 super.cancelLongPress();
537
538 // Cancel long press for all children
539 final int count = getChildCount();
540 for (int i = 0; i < count; i++) {
541 final View child = getChildAt(i);
542 child.cancelLongPress();
543 }
544 }
545
Michael Jurkadee05892010-07-27 10:01:56 -0700546 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
547 mInterceptTouchListener = listener;
548 }
549
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800550 int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700551 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800552 }
553
554 int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700555 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800556 }
557
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800558 public void setIsHotseat(boolean isHotseat) {
559 mIsHotseat = isHotseat;
560 }
561
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800562 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Andrew Flynn850d2e72012-04-26 16:51:20 -0700563 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700564 final LayoutParams lp = params;
565
Andrew Flynnde38e422012-05-08 11:22:15 -0700566 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800567 if (child instanceof BubbleTextView) {
568 BubbleTextView bubbleChild = (BubbleTextView) child;
569
Andrew Flynnde38e422012-05-08 11:22:15 -0700570 Resources res = getResources();
571 if (mIsHotseat) {
572 bubbleChild.setTextColor(res.getColor(android.R.color.transparent));
573 } else {
574 bubbleChild.setTextColor(res.getColor(R.color.workspace_icon_text_color));
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800575 }
576 }
577
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800578 // Generate an id for each view, this assumes we have at most 256x256 cells
579 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700580 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700581 // If the horizontal or vertical span is set to -1, it is taken to
582 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700583 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
584 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800585
Winson Chungaafa03c2010-06-11 17:34:16 -0700586 child.setId(childId);
587
Michael Jurkaa52570f2012-03-20 03:18:20 -0700588 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700589
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700590 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700591
Winson Chungaafa03c2010-06-11 17:34:16 -0700592 return true;
593 }
594 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800595 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700596
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800597 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700598 public void removeAllViews() {
599 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700600 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700601 }
602
603 @Override
604 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700605 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700606 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700607 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700608 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700609 }
610
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700611 public void removeViewWithoutMarkingCells(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700612 mShortcutsAndWidgets.removeView(view);
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700613 }
614
Michael Jurka0280c3b2010-09-17 15:00:07 -0700615 @Override
616 public void removeView(View view) {
617 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700618 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700619 }
620
621 @Override
622 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700623 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
624 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700625 }
626
627 @Override
628 public void removeViewInLayout(View view) {
629 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700630 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700631 }
632
633 @Override
634 public void removeViews(int start, int count) {
635 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700636 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700637 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700638 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700639 }
640
641 @Override
642 public void removeViewsInLayout(int start, int count) {
643 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700644 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700645 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700646 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800647 }
648
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800649 @Override
650 protected void onAttachedToWindow() {
651 super.onAttachedToWindow();
652 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
653 }
654
Michael Jurkaaf442092010-06-10 17:01:57 -0700655 public void setTagToCellInfoForPoint(int touchX, int touchY) {
656 final CellInfo cellInfo = mCellInfo;
Winson Chungeecf02d2012-03-02 17:14:58 -0800657 Rect frame = mRect;
Michael Jurka8b805b12012-04-18 14:23:14 -0700658 final int x = touchX + getScrollX();
659 final int y = touchY + getScrollY();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700660 final int count = mShortcutsAndWidgets.getChildCount();
Michael Jurkaaf442092010-06-10 17:01:57 -0700661
662 boolean found = false;
663 for (int i = count - 1; i >= 0; i--) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700664 final View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohend4844c32011-02-18 19:25:06 -0800665 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Michael Jurkaaf442092010-06-10 17:01:57 -0700666
Adam Cohen1b607ed2011-03-03 17:26:50 -0800667 if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
668 lp.isLockedToGrid) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700669 child.getHitRect(frame);
Winson Chung0be025d2011-05-23 17:45:09 -0700670
Winson Chungeecf02d2012-03-02 17:14:58 -0800671 float scale = child.getScaleX();
672 frame = new Rect(child.getLeft(), child.getTop(), child.getRight(),
673 child.getBottom());
Winson Chung0be025d2011-05-23 17:45:09 -0700674 // The child hit rect is relative to the CellLayoutChildren parent, so we need to
675 // offset that by this CellLayout's padding to test an (x,y) point that is relative
676 // to this view.
Michael Jurka8b805b12012-04-18 14:23:14 -0700677 frame.offset(getPaddingLeft(), getPaddingTop());
Winson Chungeecf02d2012-03-02 17:14:58 -0800678 frame.inset((int) (frame.width() * (1f - scale) / 2),
679 (int) (frame.height() * (1f - scale) / 2));
Winson Chung0be025d2011-05-23 17:45:09 -0700680
Michael Jurkaaf442092010-06-10 17:01:57 -0700681 if (frame.contains(x, y)) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700682 cellInfo.cell = child;
683 cellInfo.cellX = lp.cellX;
684 cellInfo.cellY = lp.cellY;
685 cellInfo.spanX = lp.cellHSpan;
686 cellInfo.spanY = lp.cellVSpan;
Michael Jurkaaf442092010-06-10 17:01:57 -0700687 found = true;
Michael Jurkaaf442092010-06-10 17:01:57 -0700688 break;
689 }
690 }
691 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700692
Michael Jurkad771c962011-08-09 15:00:48 -0700693 mLastDownOnOccupiedCell = found;
694
Michael Jurkaaf442092010-06-10 17:01:57 -0700695 if (!found) {
Winson Chung0be025d2011-05-23 17:45:09 -0700696 final int cellXY[] = mTmpXY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700697 pointToCellExact(x, y, cellXY);
698
Michael Jurkaaf442092010-06-10 17:01:57 -0700699 cellInfo.cell = null;
700 cellInfo.cellX = cellXY[0];
701 cellInfo.cellY = cellXY[1];
702 cellInfo.spanX = 1;
703 cellInfo.spanY = 1;
Michael Jurkaaf442092010-06-10 17:01:57 -0700704 }
705 setTag(cellInfo);
706 }
707
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800708 @Override
709 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Cohenc1997fd2011-08-15 18:26:39 -0700710 // First we clear the tag to ensure that on every touch down we start with a fresh slate,
711 // even in the case where we return early. Not clearing here was causing bugs whereby on
712 // long-press we'd end up picking up an item from a previous drag operation.
713 final int action = ev.getAction();
714
715 if (action == MotionEvent.ACTION_DOWN) {
716 clearTagCellInfo();
717 }
718
Michael Jurkadee05892010-07-27 10:01:56 -0700719 if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
720 return true;
721 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800722
723 if (action == MotionEvent.ACTION_DOWN) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700724 setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800725 }
Winson Chungeecf02d2012-03-02 17:14:58 -0800726
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800727 return false;
728 }
729
Adam Cohenc1997fd2011-08-15 18:26:39 -0700730 private void clearTagCellInfo() {
731 final CellInfo cellInfo = mCellInfo;
732 cellInfo.cell = null;
733 cellInfo.cellX = -1;
734 cellInfo.cellY = -1;
735 cellInfo.spanX = 0;
736 cellInfo.spanY = 0;
737 setTag(cellInfo);
738 }
739
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800740 public CellInfo getTag() {
Michael Jurka0280c3b2010-09-17 15:00:07 -0700741 return (CellInfo) super.getTag();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800742 }
743
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700744 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700745 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800746 * @param x X coordinate of the point
747 * @param y Y coordinate of the point
748 * @param result Array of 2 ints to hold the x and y coordinate of the cell
749 */
750 void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700751 final int hStartPadding = getPaddingLeft();
752 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800753
754 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
755 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
756
Adam Cohend22015c2010-07-26 22:02:18 -0700757 final int xAxis = mCountX;
758 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800759
760 if (result[0] < 0) result[0] = 0;
761 if (result[0] >= xAxis) result[0] = xAxis - 1;
762 if (result[1] < 0) result[1] = 0;
763 if (result[1] >= yAxis) result[1] = yAxis - 1;
764 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700765
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800766 /**
767 * Given a point, return the cell that most closely encloses that point
768 * @param x X coordinate of the point
769 * @param y Y coordinate of the point
770 * @param result Array of 2 ints to hold the x and y coordinate of the cell
771 */
772 void pointToCellRounded(int x, int y, int[] result) {
773 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
774 }
775
776 /**
777 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700778 *
779 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800780 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700781 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800782 * @param result Array of 2 ints to hold the x and y coordinate of the point
783 */
784 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700785 final int hStartPadding = getPaddingLeft();
786 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800787
788 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
789 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
790 }
791
Adam Cohene3e27a82011-04-15 12:07:39 -0700792 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800793 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700794 *
795 * @param cellX X coordinate of the cell
796 * @param cellY Y coordinate of the cell
797 *
798 * @param result Array of 2 ints to hold the x and y coordinate of the point
799 */
800 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700801 regionToCenterPoint(cellX, cellY, 1, 1, result);
802 }
803
804 /**
805 * Given a cell coordinate and span return the point that represents the center of the regio
806 *
807 * @param cellX X coordinate of the cell
808 * @param cellY Y coordinate of the cell
809 *
810 * @param result Array of 2 ints to hold the x and y coordinate of the point
811 */
812 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700813 final int hStartPadding = getPaddingLeft();
814 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700815 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
816 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
817 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
818 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700819 }
820
Adam Cohen19f37922012-03-21 11:59:11 -0700821 /**
822 * Given a cell coordinate and span fills out a corresponding pixel rect
823 *
824 * @param cellX X coordinate of the cell
825 * @param cellY Y coordinate of the cell
826 * @param result Rect in which to write the result
827 */
828 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
829 final int hStartPadding = getPaddingLeft();
830 final int vStartPadding = getPaddingTop();
831 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
832 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
833 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
834 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
835 }
836
Adam Cohen482ed822012-03-02 14:15:13 -0800837 public float getDistanceFromCell(float x, float y, int[] cell) {
838 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
839 float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) +
840 Math.pow(y - mTmpPoint[1], 2));
841 return distance;
842 }
843
Romain Guy84f296c2009-11-04 15:00:44 -0800844 int getCellWidth() {
845 return mCellWidth;
846 }
847
848 int getCellHeight() {
849 return mCellHeight;
850 }
851
Adam Cohend4844c32011-02-18 19:25:06 -0800852 int getWidthGap() {
853 return mWidthGap;
854 }
855
856 int getHeightGap() {
857 return mHeightGap;
858 }
859
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700860 Rect getContentRect(Rect r) {
861 if (r == null) {
862 r = new Rect();
863 }
864 int left = getPaddingLeft();
865 int top = getPaddingTop();
Michael Jurka8b805b12012-04-18 14:23:14 -0700866 int right = left + getWidth() - getPaddingLeft() - getPaddingRight();
867 int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom();
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700868 r.set(left, top, right, bottom);
869 return r;
870 }
871
Adam Cohena897f392012-04-27 18:12:05 -0700872 static void getMetrics(Rect metrics, Resources res, int measureWidth, int measureHeight,
873 int countX, int countY, int orientation) {
874 int numWidthGaps = countX - 1;
875 int numHeightGaps = countY - 1;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700876
877 int widthGap;
878 int heightGap;
879 int cellWidth;
880 int cellHeight;
881 int paddingLeft;
882 int paddingRight;
883 int paddingTop;
884 int paddingBottom;
885
Adam Cohena897f392012-04-27 18:12:05 -0700886 int maxGap = res.getDimensionPixelSize(R.dimen.workspace_max_gap);
Adam Cohenf4bd5792012-04-27 11:35:29 -0700887 if (orientation == LANDSCAPE) {
888 cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_land);
889 cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_land);
890 widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_land);
891 heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_land);
892 paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_land);
893 paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_land);
894 paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_land);
895 paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_land);
896 } else {
897 // PORTRAIT
898 cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_port);
899 cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_port);
900 widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_port);
901 heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_port);
902 paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_port);
903 paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_port);
904 paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_port);
905 paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_port);
906 }
907
908 if (widthGap < 0 || heightGap < 0) {
909 int hSpace = measureWidth - paddingLeft - paddingRight;
910 int vSpace = measureHeight - paddingTop - paddingBottom;
Adam Cohena897f392012-04-27 18:12:05 -0700911 int hFreeSpace = hSpace - (countX * cellWidth);
912 int vFreeSpace = vSpace - (countY * cellHeight);
913 widthGap = Math.min(maxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
914 heightGap = Math.min(maxGap, numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Adam Cohenf4bd5792012-04-27 11:35:29 -0700915 }
916 metrics.set(cellWidth, cellHeight, widthGap, heightGap);
917 }
918
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800919 @Override
920 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800921 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700922 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
923
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800924 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
925 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700926
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800927 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
928 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
929 }
930
Adam Cohend22015c2010-07-26 22:02:18 -0700931 int numWidthGaps = mCountX - 1;
932 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800933
Adam Cohen234c4cd2011-07-17 21:03:04 -0700934 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Michael Jurkadd13e3d2012-05-01 12:38:17 -0700935 int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight();
936 int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom();
Adam Cohenf4bd5792012-04-27 11:35:29 -0700937 int hFreeSpace = hSpace - (mCountX * mCellWidth);
938 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700939 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
940 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700941 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700942 } else {
943 mWidthGap = mOriginalWidthGap;
944 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700945 }
Michael Jurka5f1c5092010-09-03 14:15:02 -0700946
Michael Jurka8c920dd2011-01-20 14:16:56 -0800947 // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
948 int newWidth = widthSpecSize;
949 int newHeight = heightSpecSize;
Michael Jurka5f1c5092010-09-03 14:15:02 -0700950 if (widthSpecMode == MeasureSpec.AT_MOST) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700951 newWidth = getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Winson Chungece7f5b2010-10-22 14:54:12 -0700952 ((mCountX - 1) * mWidthGap);
Michael Jurka8b805b12012-04-18 14:23:14 -0700953 newHeight = getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Winson Chungece7f5b2010-10-22 14:54:12 -0700954 ((mCountY - 1) * mHeightGap);
Michael Jurka5f1c5092010-09-03 14:15:02 -0700955 setMeasuredDimension(newWidth, newHeight);
Michael Jurka5f1c5092010-09-03 14:15:02 -0700956 }
Michael Jurka8c920dd2011-01-20 14:16:56 -0800957
958 int count = getChildCount();
959 for (int i = 0; i < count; i++) {
960 View child = getChildAt(i);
Michael Jurka8b805b12012-04-18 14:23:14 -0700961 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() -
962 getPaddingRight(), MeasureSpec.EXACTLY);
963 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() -
964 getPaddingBottom(), MeasureSpec.EXACTLY);
Michael Jurka8c920dd2011-01-20 14:16:56 -0800965 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
966 }
967 setMeasuredDimension(newWidth, newHeight);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800968 }
969
970 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700971 protected void onLayout(boolean changed, int l, int t, int r, int b) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800972 int count = getChildCount();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800973 for (int i = 0; i < count; i++) {
Michael Jurka8c920dd2011-01-20 14:16:56 -0800974 View child = getChildAt(i);
Michael Jurka8b805b12012-04-18 14:23:14 -0700975 child.layout(getPaddingLeft(), getPaddingTop(),
976 r - l - getPaddingRight(), b - t - getPaddingBottom());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800977 }
978 }
979
980 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700981 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
982 super.onSizeChanged(w, h, oldw, oldh);
Michael Jurka18014792010-10-14 09:01:34 -0700983 mBackgroundRect.set(0, 0, w, h);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700984 mForegroundRect.set(mForegroundPadding, mForegroundPadding,
985 w - 2 * mForegroundPadding, h - 2 * mForegroundPadding);
Michael Jurkadee05892010-07-27 10:01:56 -0700986 }
987
988 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800989 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700990 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800991 }
992
993 @Override
994 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700995 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800996 }
997
Michael Jurka5f1c5092010-09-03 14:15:02 -0700998 public float getBackgroundAlpha() {
999 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -07001000 }
1001
Adam Cohen1b0aaac2010-10-28 11:11:18 -07001002 public void setBackgroundAlphaMultiplier(float multiplier) {
Michael Jurkaa3d30ad2012-05-08 13:43:43 -07001003 if (mBackgroundAlphaMultiplier != multiplier) {
1004 mBackgroundAlphaMultiplier = multiplier;
1005 invalidate();
1006 }
Adam Cohen1b0aaac2010-10-28 11:11:18 -07001007 }
1008
Adam Cohenddb82192010-11-10 16:32:54 -08001009 public float getBackgroundAlphaMultiplier() {
1010 return mBackgroundAlphaMultiplier;
1011 }
1012
Michael Jurka5f1c5092010-09-03 14:15:02 -07001013 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -08001014 if (mBackgroundAlpha != alpha) {
1015 mBackgroundAlpha = alpha;
1016 invalidate();
1017 }
Michael Jurkadee05892010-07-27 10:01:56 -07001018 }
1019
Michael Jurkaa52570f2012-03-20 03:18:20 -07001020 public void setShortcutAndWidgetAlpha(float alpha) {
Michael Jurka0142d492010-08-25 17:46:15 -07001021 final int childCount = getChildCount();
1022 for (int i = 0; i < childCount; i++) {
Michael Jurkadee05892010-07-27 10:01:56 -07001023 getChildAt(i).setAlpha(alpha);
1024 }
1025 }
1026
Michael Jurkaa52570f2012-03-20 03:18:20 -07001027 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
1028 if (getChildCount() > 0) {
1029 return (ShortcutAndWidgetContainer) getChildAt(0);
1030 }
1031 return null;
1032 }
1033
Patrick Dubroy440c3602010-07-13 17:50:32 -07001034 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001035 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -07001036 }
1037
Adam Cohen76fc0852011-06-17 13:26:23 -07001038 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -08001039 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001040 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -08001041 boolean[][] occupied = mOccupied;
1042 if (!permanent) {
1043 occupied = mTmpOccupied;
1044 }
1045
Adam Cohen19f37922012-03-21 11:59:11 -07001046 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001047 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1048 final ItemInfo info = (ItemInfo) child.getTag();
1049
1050 // We cancel any existing animations
1051 if (mReorderAnimators.containsKey(lp)) {
1052 mReorderAnimators.get(lp).cancel();
1053 mReorderAnimators.remove(lp);
1054 }
1055
Adam Cohen482ed822012-03-02 14:15:13 -08001056 final int oldX = lp.x;
1057 final int oldY = lp.y;
1058 if (adjustOccupied) {
1059 occupied[lp.cellX][lp.cellY] = false;
1060 occupied[cellX][cellY] = true;
1061 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001062 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001063 if (permanent) {
1064 lp.cellX = info.cellX = cellX;
1065 lp.cellY = info.cellY = cellY;
1066 } else {
1067 lp.tmpCellX = cellX;
1068 lp.tmpCellY = cellY;
1069 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001070 clc.setupLp(lp);
1071 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001072 final int newX = lp.x;
1073 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001074
Adam Cohen76fc0852011-06-17 13:26:23 -07001075 lp.x = oldX;
1076 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001077
Adam Cohen482ed822012-03-02 14:15:13 -08001078 // Exit early if we're not actually moving the view
1079 if (oldX == newX && oldY == newY) {
1080 lp.isLockedToGrid = true;
1081 return true;
1082 }
1083
Michael Jurka2ecf9952012-06-18 12:52:28 -07001084 ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001085 va.setDuration(duration);
1086 mReorderAnimators.put(lp, va);
1087
1088 va.addUpdateListener(new AnimatorUpdateListener() {
1089 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001090 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001091 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001092 lp.x = (int) ((1 - r) * oldX + r * newX);
1093 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001094 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001095 }
1096 });
Adam Cohen482ed822012-03-02 14:15:13 -08001097 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001098 boolean cancelled = false;
1099 public void onAnimationEnd(Animator animation) {
1100 // If the animation was cancelled, it means that another animation
1101 // has interrupted this one, and we don't want to lock the item into
1102 // place just yet.
1103 if (!cancelled) {
1104 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001105 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001106 }
1107 if (mReorderAnimators.containsKey(lp)) {
1108 mReorderAnimators.remove(lp);
1109 }
1110 }
1111 public void onAnimationCancel(Animator animation) {
1112 cancelled = true;
1113 }
1114 });
Adam Cohen482ed822012-03-02 14:15:13 -08001115 va.setStartDelay(delay);
1116 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001117 return true;
1118 }
1119 return false;
1120 }
1121
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001122 /**
1123 * Estimate where the top left cell of the dragged item will land if it is dropped.
1124 *
1125 * @param originX The X value of the top left corner of the item
1126 * @param originY The Y value of the top left corner of the item
1127 * @param spanX The number of horizontal cells that the item spans
1128 * @param spanY The number of vertical cells that the item spans
1129 * @param result The estimated drop cell X and Y.
1130 */
1131 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
Adam Cohend22015c2010-07-26 22:02:18 -07001132 final int countX = mCountX;
1133 final int countY = mCountY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001134
Michael Jurkaa63c4522010-08-19 13:52:27 -07001135 // pointToCellRounded takes the top left of a cell but will pad that with
1136 // cellWidth/2 and cellHeight/2 when finding the matching cell
1137 pointToCellRounded(originX, originY, result);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001138
1139 // If the item isn't fully on this screen, snap to the edges
1140 int rightOverhang = result[0] + spanX - countX;
1141 if (rightOverhang > 0) {
1142 result[0] -= rightOverhang; // Snap to right
1143 }
1144 result[0] = Math.max(0, result[0]); // Snap to left
1145 int bottomOverhang = result[1] + spanY - countY;
1146 if (bottomOverhang > 0) {
1147 result[1] -= bottomOverhang; // Snap to bottom
1148 }
1149 result[1] = Math.max(0, result[1]); // Snap to top
1150 }
1151
Adam Cohen482ed822012-03-02 14:15:13 -08001152 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1153 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001154 final int oldDragCellX = mDragCell[0];
1155 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001156
Winson Chungb8c69f32011-10-19 21:36:08 -07001157 if (v != null && dragOffset == null) {
Winson Chunga9abd0e2010-10-27 17:18:37 -07001158 mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
1159 } else {
1160 mDragCenter.set(originX, originY);
1161 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001162
Adam Cohen2801caf2011-05-13 20:57:39 -07001163 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001164 return;
1165 }
1166
Adam Cohen482ed822012-03-02 14:15:13 -08001167 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1168 mDragCell[0] = cellX;
1169 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001170 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001171 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001172 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001173
Joe Onorato4be866d2010-10-10 11:26:02 -07001174 int left = topLeft[0];
1175 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001176
Winson Chungb8c69f32011-10-19 21:36:08 -07001177 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001178 // When drawing the drag outline, it did not account for margin offsets
1179 // added by the view's parent.
1180 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1181 left += lp.leftMargin;
1182 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001183
Adam Cohen99e8b402011-03-25 19:23:43 -07001184 // Offsets due to the size difference between the View and the dragOutline.
1185 // There is a size difference to account for the outer blur, which may lie
1186 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001187 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001188 // We center about the x axis
1189 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1190 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001191 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001192 if (dragOffset != null && dragRegion != null) {
1193 // Center the drag region *horizontally* in the cell and apply a drag
1194 // outline offset
1195 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1196 - dragRegion.width()) / 2;
1197 top += dragOffset.y;
1198 } else {
1199 // Center the drag outline in the cell
1200 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1201 - dragOutline.getWidth()) / 2;
1202 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1203 - dragOutline.getHeight()) / 2;
1204 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001205 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001206 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001207 mDragOutlineAnims[oldIndex].animateOut();
1208 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001209 Rect r = mDragOutlines[mDragOutlineCurrent];
1210 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1211 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001212 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001213 }
Winson Chung150fbab2010-09-29 17:14:26 -07001214
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001215 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1216 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001217 }
1218 }
1219
Adam Cohene0310962011-04-18 16:15:31 -07001220 public void clearDragOutlines() {
1221 final int oldIndex = mDragOutlineCurrent;
1222 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001223 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001224 }
1225
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001226 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001227 * Find a vacant area that will fit the given bounds nearest the requested
1228 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001229 *
Romain Guy51afc022009-05-04 18:03:43 -07001230 * @param pixelX The X location at which you want to search for a vacant area.
1231 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001232 * @param spanX Horizontal span of the object.
1233 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001234 * @param result Array in which to place the result, or null (in which case a new array will
1235 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001236 * @return The X, Y cell of a vacant area that can contain this object,
1237 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001238 */
Adam Cohend41fbf52012-02-16 23:53:59 -08001239 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
1240 int[] result) {
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001241 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001242 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001243
Michael Jurka6a1435d2010-09-27 17:35:12 -07001244 /**
1245 * Find a vacant area that will fit the given bounds nearest the requested
1246 * cell location. Uses Euclidean distance to score multiple vacant areas.
1247 *
1248 * @param pixelX The X location at which you want to search for a vacant area.
1249 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001250 * @param minSpanX The minimum horizontal span required
1251 * @param minSpanY The minimum vertical span required
1252 * @param spanX Horizontal span of the object.
1253 * @param spanY Vertical span of the object.
1254 * @param result Array in which to place the result, or null (in which case a new array will
1255 * be allocated)
1256 * @return The X, Y cell of a vacant area that can contain this object,
1257 * nearest the requested location.
1258 */
1259 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1260 int spanY, int[] result, int[] resultSpan) {
1261 return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
1262 result, resultSpan);
1263 }
1264
1265 /**
1266 * Find a vacant area that will fit the given bounds nearest the requested
1267 * cell location. Uses Euclidean distance to score multiple vacant areas.
1268 *
1269 * @param pixelX The X location at which you want to search for a vacant area.
1270 * @param pixelY The Y location at which you want to search for a vacant area.
Michael Jurka6a1435d2010-09-27 17:35:12 -07001271 * @param spanX Horizontal span of the object.
1272 * @param spanY Vertical span of the object.
Adam Cohendf035382011-04-11 17:22:04 -07001273 * @param ignoreOccupied If true, the result can be an occupied cell
1274 * @param result Array in which to place the result, or null (in which case a new array will
1275 * be allocated)
Michael Jurka6a1435d2010-09-27 17:35:12 -07001276 * @return The X, Y cell of a vacant area that can contain this object,
1277 * nearest the requested location.
1278 */
Adam Cohendf035382011-04-11 17:22:04 -07001279 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
1280 boolean ignoreOccupied, int[] result) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001281 return findNearestArea(pixelX, pixelY, spanX, spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001282 spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08001283 }
1284
1285 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1286 private void lazyInitTempRectStack() {
1287 if (mTempRectStack.isEmpty()) {
1288 for (int i = 0; i < mCountX * mCountY; i++) {
1289 mTempRectStack.push(new Rect());
1290 }
1291 }
1292 }
Adam Cohen482ed822012-03-02 14:15:13 -08001293
Adam Cohend41fbf52012-02-16 23:53:59 -08001294 private void recycleTempRects(Stack<Rect> used) {
1295 while (!used.isEmpty()) {
1296 mTempRectStack.push(used.pop());
1297 }
1298 }
1299
1300 /**
1301 * Find a vacant area that will fit the given bounds nearest the requested
1302 * cell location. Uses Euclidean distance to score multiple vacant areas.
1303 *
1304 * @param pixelX The X location at which you want to search for a vacant area.
1305 * @param pixelY The Y location at which you want to search for a vacant area.
1306 * @param minSpanX The minimum horizontal span required
1307 * @param minSpanY The minimum vertical span required
1308 * @param spanX Horizontal span of the object.
1309 * @param spanY Vertical span of the object.
1310 * @param ignoreOccupied If true, the result can be an occupied cell
1311 * @param result Array in which to place the result, or null (in which case a new array will
1312 * be allocated)
1313 * @return The X, Y cell of a vacant area that can contain this object,
1314 * nearest the requested location.
1315 */
1316 int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001317 View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
1318 boolean[][] occupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001319 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001320 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08001321 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001322
Adam Cohene3e27a82011-04-15 12:07:39 -07001323 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1324 // to the center of the item, but we are searching based on the top-left cell, so
1325 // we translate the point over to correspond to the top-left.
1326 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1327 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1328
Jeff Sharkey70864282009-04-07 21:08:40 -07001329 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001330 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001331 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001332 final Rect bestRect = new Rect(-1, -1, -1, -1);
1333 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001334
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001335 final int countX = mCountX;
1336 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001337
Adam Cohend41fbf52012-02-16 23:53:59 -08001338 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1339 spanX < minSpanX || spanY < minSpanY) {
1340 return bestXY;
1341 }
1342
1343 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001344 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001345 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1346 int ySize = -1;
1347 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001348 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001349 // First, let's see if this thing fits anywhere
1350 for (int i = 0; i < minSpanX; i++) {
1351 for (int j = 0; j < minSpanY; j++) {
Adam Cohendf035382011-04-11 17:22:04 -07001352 if (occupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001353 continue inner;
1354 }
Michael Jurkac28de512010-08-13 11:27:44 -07001355 }
1356 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001357 xSize = minSpanX;
1358 ySize = minSpanY;
1359
1360 // We know that the item will fit at _some_ acceptable size, now let's see
1361 // how big we can make it. We'll alternate between incrementing x and y spans
1362 // until we hit a limit.
1363 boolean incX = true;
1364 boolean hitMaxX = xSize >= spanX;
1365 boolean hitMaxY = ySize >= spanY;
1366 while (!(hitMaxX && hitMaxY)) {
1367 if (incX && !hitMaxX) {
1368 for (int j = 0; j < ySize; j++) {
1369 if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
1370 // We can't move out horizontally
1371 hitMaxX = true;
1372 }
1373 }
1374 if (!hitMaxX) {
1375 xSize++;
1376 }
1377 } else if (!hitMaxY) {
1378 for (int i = 0; i < xSize; i++) {
1379 if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
1380 // We can't move out vertically
1381 hitMaxY = true;
1382 }
1383 }
1384 if (!hitMaxY) {
1385 ySize++;
1386 }
1387 }
1388 hitMaxX |= xSize >= spanX;
1389 hitMaxY |= ySize >= spanY;
1390 incX = !incX;
1391 }
1392 incX = true;
1393 hitMaxX = xSize >= spanX;
1394 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001395 }
Winson Chung0be025d2011-05-23 17:45:09 -07001396 final int[] cellXY = mTmpXY;
Adam Cohene3e27a82011-04-15 12:07:39 -07001397 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001398
Adam Cohend41fbf52012-02-16 23:53:59 -08001399 // We verify that the current rect is not a sub-rect of any of our previous
1400 // candidates. In this case, the current rect is disqualified in favour of the
1401 // containing rect.
1402 Rect currentRect = mTempRectStack.pop();
1403 currentRect.set(x, y, x + xSize, y + ySize);
1404 boolean contained = false;
1405 for (Rect r : validRegions) {
1406 if (r.contains(currentRect)) {
1407 contained = true;
1408 break;
1409 }
1410 }
1411 validRegions.push(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001412 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
1413 + Math.pow(cellXY[1] - pixelY, 2));
Adam Cohen482ed822012-03-02 14:15:13 -08001414
Adam Cohend41fbf52012-02-16 23:53:59 -08001415 if ((distance <= bestDistance && !contained) ||
1416 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001417 bestDistance = distance;
1418 bestXY[0] = x;
1419 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001420 if (resultSpan != null) {
1421 resultSpan[0] = xSize;
1422 resultSpan[1] = ySize;
1423 }
1424 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001425 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001426 }
1427 }
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001428 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08001429 markCellsAsOccupiedForView(ignoreView, occupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001430
Adam Cohenc0dcf592011-06-01 15:30:43 -07001431 // Return -1, -1 if no suitable location found
1432 if (bestDistance == Double.MAX_VALUE) {
1433 bestXY[0] = -1;
1434 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001435 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001436 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001437 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001438 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001439
Adam Cohen482ed822012-03-02 14:15:13 -08001440 /**
1441 * Find a vacant area that will fit the given bounds nearest the requested
1442 * cell location, and will also weigh in a suggested direction vector of the
1443 * desired location. This method computers distance based on unit grid distances,
1444 * not pixel distances.
1445 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001446 * @param cellX The X cell nearest to which you want to search for a vacant area.
1447 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001448 * @param spanX Horizontal span of the object.
1449 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001450 * @param direction The favored direction in which the views should move from x, y
1451 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1452 * matches exactly. Otherwise we find the best matching direction.
1453 * @param occoupied The array which represents which cells in the CellLayout are occupied
1454 * @param blockOccupied The array which represents which cells in the specified block (cellX,
1455 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001456 * @param result Array in which to place the result, or null (in which case a new array will
1457 * be allocated)
1458 * @return The X, Y cell of a vacant area that can contain this object,
1459 * nearest the requested location.
1460 */
1461 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001462 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001463 // Keep track of best-scoring drop area
1464 final int[] bestXY = result != null ? result : new int[2];
1465 float bestDistance = Float.MAX_VALUE;
1466 int bestDirectionScore = Integer.MIN_VALUE;
1467
1468 final int countX = mCountX;
1469 final int countY = mCountY;
1470
1471 for (int y = 0; y < countY - (spanY - 1); y++) {
1472 inner:
1473 for (int x = 0; x < countX - (spanX - 1); x++) {
1474 // First, let's see if this thing fits anywhere
1475 for (int i = 0; i < spanX; i++) {
1476 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001477 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001478 continue inner;
1479 }
1480 }
1481 }
1482
1483 float distance = (float)
1484 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1485 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001486 computeDirectionVector(x - cellX, y - cellY, curDirection);
1487 // The direction score is just the dot product of the two candidate direction
1488 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001489 int curDirectionScore = direction[0] * curDirection[0] +
1490 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001491 boolean exactDirectionOnly = false;
1492 boolean directionMatches = direction[0] == curDirection[0] &&
1493 direction[0] == curDirection[0];
1494 if ((directionMatches || !exactDirectionOnly) &&
1495 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001496 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1497 bestDistance = distance;
1498 bestDirectionScore = curDirectionScore;
1499 bestXY[0] = x;
1500 bestXY[1] = y;
1501 }
1502 }
1503 }
1504
1505 // Return -1, -1 if no suitable location found
1506 if (bestDistance == Float.MAX_VALUE) {
1507 bestXY[0] = -1;
1508 bestXY[1] = -1;
1509 }
1510 return bestXY;
1511 }
1512
Adam Cohen47a876d2012-03-19 13:21:41 -07001513 private int[] findNearestAreaInDirection(int cellX, int cellY, int spanX, int spanY,
1514 int[] direction,boolean[][] occupied,
1515 boolean blockOccupied[][], int[] result) {
1516 // Keep track of best-scoring drop area
1517 final int[] bestXY = result != null ? result : new int[2];
1518 bestXY[0] = -1;
1519 bestXY[1] = -1;
1520 float bestDistance = Float.MAX_VALUE;
1521
1522 // We use this to march in a single direction
Adam Cohen5b53f292012-03-29 14:30:35 -07001523 if ((direction[0] != 0 && direction[1] != 0) ||
1524 (direction[0] == 0 && direction[1] == 0)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001525 return bestXY;
1526 }
1527
1528 // This will only incrememnet one of x or y based on the assertion above
1529 int x = cellX + direction[0];
1530 int y = cellY + direction[1];
1531 while (x >= 0 && x + spanX <= mCountX && y >= 0 && y + spanY <= mCountY) {
1532
1533 boolean fail = false;
1534 for (int i = 0; i < spanX; i++) {
1535 for (int j = 0; j < spanY; j++) {
1536 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
1537 fail = true;
1538 }
1539 }
1540 }
1541 if (!fail) {
1542 float distance = (float)
1543 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1544 if (Float.compare(distance, bestDistance) < 0) {
1545 bestDistance = distance;
1546 bestXY[0] = x;
1547 bestXY[1] = y;
1548 }
1549 }
1550 x += direction[0];
1551 y += direction[1];
1552 }
1553 return bestXY;
1554 }
1555
Adam Cohen482ed822012-03-02 14:15:13 -08001556 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001557 int[] direction, ItemConfiguration currentState) {
1558 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001559 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001560 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001561 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1562
Adam Cohen8baab352012-03-20 17:39:21 -07001563 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001564
1565 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001566 c.x = mTempLocation[0];
1567 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001568 success = true;
1569
1570 }
Adam Cohen8baab352012-03-20 17:39:21 -07001571 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001572 return success;
1573 }
1574
Adam Cohen47a876d2012-03-19 13:21:41 -07001575 // This method looks in the specified direction to see if there is an additional view
1576 // immediately adjecent in that direction
1577 private boolean addViewInDirection(ArrayList<View> views, Rect boundingRect, int[] direction,
Adam Cohen19f37922012-03-21 11:59:11 -07001578 boolean[][] occupied, View dragView, ItemConfiguration currentState) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001579 boolean found = false;
1580
Michael Jurkaa52570f2012-03-20 03:18:20 -07001581 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen47a876d2012-03-19 13:21:41 -07001582 Rect r0 = new Rect(boundingRect);
1583 Rect r1 = new Rect();
1584
1585 int deltaX = 0;
1586 int deltaY = 0;
1587 if (direction[1] < 0) {
1588 r0.set(r0.left, r0.top - 1, r0.right, r0.bottom);
1589 deltaY = -1;
1590 } else if (direction[1] > 0) {
1591 r0.set(r0.left, r0.top, r0.right, r0.bottom + 1);
1592 deltaY = 1;
1593 } else if (direction[0] < 0) {
1594 r0.set(r0.left - 1, r0.top, r0.right, r0.bottom);
1595 deltaX = -1;
1596 } else if (direction[0] > 0) {
1597 r0.set(r0.left, r0.top, r0.right + 1, r0.bottom);
1598 deltaX = 1;
1599 }
1600
1601 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001602 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen19f37922012-03-21 11:59:11 -07001603 if (views.contains(child) || child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001604 CellAndSpan c = currentState.map.get(child);
Adam Cohen47a876d2012-03-19 13:21:41 -07001605
Adam Cohen8baab352012-03-20 17:39:21 -07001606 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1607 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen47a876d2012-03-19 13:21:41 -07001608 if (Rect.intersects(r0, r1)) {
1609 if (!lp.canReorder) {
1610 return false;
1611 }
1612 boolean pushed = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001613 for (int x = c.x; x < c.x + c.spanX; x++) {
1614 for (int y = c.y; y < c.y + c.spanY; y++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001615 boolean inBounds = x - deltaX >= 0 && x -deltaX < mCountX
1616 && y - deltaY >= 0 && y - deltaY < mCountY;
1617 if (inBounds && occupied[x - deltaX][y - deltaY]) {
1618 pushed = true;
1619 }
1620 }
1621 }
1622 if (pushed) {
1623 views.add(child);
Adam Cohen8baab352012-03-20 17:39:21 -07001624 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen47a876d2012-03-19 13:21:41 -07001625 found = true;
1626 }
1627 }
1628 }
1629 return found;
1630 }
1631
Adam Cohen482ed822012-03-02 14:15:13 -08001632 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohen19f37922012-03-21 11:59:11 -07001633 int[] direction, boolean push, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001634 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001635
Adam Cohen8baab352012-03-20 17:39:21 -07001636 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001637 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001638 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001639 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001640 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001641 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001642 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001643 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001644 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001645 }
1646 }
Adam Cohen8baab352012-03-20 17:39:21 -07001647
1648 @SuppressWarnings("unchecked")
1649 ArrayList<View> dup = (ArrayList<View>) views.clone();
1650 // We try and expand the group of views in the direction vector passed, based on
1651 // whether they are physically adjacent, ie. based on "push mechanics".
Adam Cohen19f37922012-03-21 11:59:11 -07001652 while (push && addViewInDirection(dup, boundingRect, direction, mTmpOccupied, dragView,
Adam Cohen8baab352012-03-20 17:39:21 -07001653 currentState)) {
1654 }
1655
1656 // Mark the occupied state as false for the group of views we want to move.
1657 for (View v: dup) {
1658 CellAndSpan c = currentState.map.get(v);
1659 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1660 }
1661
Adam Cohen47a876d2012-03-19 13:21:41 -07001662 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1663 int top = boundingRect.top;
1664 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001665 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
1666 // for tetris-style interlocking.
1667 for (View v: dup) {
1668 CellAndSpan c = currentState.map.get(v);
1669 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001670 }
1671
Adam Cohen482ed822012-03-02 14:15:13 -08001672 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1673
Adam Cohen8baab352012-03-20 17:39:21 -07001674 if (push) {
1675 findNearestAreaInDirection(boundingRect.left, boundingRect.top, boundingRect.width(),
1676 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
1677 } else {
1678 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1679 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
1680 }
Adam Cohen482ed822012-03-02 14:15:13 -08001681
Adam Cohen8baab352012-03-20 17:39:21 -07001682 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001683 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001684 int deltaX = mTempLocation[0] - boundingRect.left;
1685 int deltaY = mTempLocation[1] - boundingRect.top;
1686 for (View v: dup) {
1687 CellAndSpan c = currentState.map.get(v);
1688 c.x += deltaX;
1689 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001690 }
1691 success = true;
1692 }
Adam Cohen8baab352012-03-20 17:39:21 -07001693
1694 // In either case, we set the occupied array as marked for the location of the views
1695 for (View v: dup) {
1696 CellAndSpan c = currentState.map.get(v);
1697 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001698 }
1699 return success;
1700 }
1701
1702 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1703 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1704 }
1705
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001706 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1707 // to push items in each of the cardinal directions, in an order based on the direction vector
1708 // passed.
1709 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1710 int[] direction, View ignoreView, ItemConfiguration solution) {
1711 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
1712 // If the direction vector has two non-zero components, we try pushing
1713 // separately in each of the components.
1714 int temp = direction[1];
1715 direction[1] = 0;
1716 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1717 ignoreView, solution)) {
1718 return true;
1719 }
1720 direction[1] = temp;
1721 temp = direction[0];
1722 direction[0] = 0;
1723 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1724 ignoreView, solution)) {
1725 return true;
1726 }
1727 // Revert the direction
1728 direction[0] = temp;
1729
1730 // Now we try pushing in each component of the opposite direction
1731 direction[0] *= -1;
1732 direction[1] *= -1;
1733 temp = direction[1];
1734 direction[1] = 0;
1735 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1736 ignoreView, solution)) {
1737 return true;
1738 }
1739
1740 direction[1] = temp;
1741 temp = direction[0];
1742 direction[0] = 0;
1743 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1744 ignoreView, solution)) {
1745 return true;
1746 }
1747 // revert the direction
1748 direction[0] = temp;
1749 direction[0] *= -1;
1750 direction[1] *= -1;
1751
1752 } else {
1753 // If the direction vector has a single non-zero component, we push first in the
1754 // direction of the vector
1755 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1756 ignoreView, solution)) {
1757 return true;
1758 }
1759
1760 // Then we try the opposite direction
1761 direction[0] *= -1;
1762 direction[1] *= -1;
1763 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1764 ignoreView, solution)) {
1765 return true;
1766 }
1767 // Switch the direction back
1768 direction[0] *= -1;
1769 direction[1] *= -1;
1770
1771 // If we have failed to find a push solution with the above, then we try
1772 // to find a solution by pushing along the perpendicular axis.
1773
1774 // Swap the components
1775 int temp = direction[1];
1776 direction[1] = direction[0];
1777 direction[0] = temp;
1778 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1779 ignoreView, solution)) {
1780 return true;
1781 }
1782
1783 // Then we try the opposite direction
1784 direction[0] *= -1;
1785 direction[1] *= -1;
1786 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1787 ignoreView, solution)) {
1788 return true;
1789 }
1790 // Switch the direction back
1791 direction[0] *= -1;
1792 direction[1] *= -1;
1793
1794 // Swap the components back
1795 temp = direction[1];
1796 direction[1] = direction[0];
1797 direction[0] = temp;
1798 }
1799 return false;
1800 }
1801
Adam Cohen482ed822012-03-02 14:15:13 -08001802 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001803 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001804 // Return early if get invalid cell positions
1805 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001806
Adam Cohen8baab352012-03-20 17:39:21 -07001807 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001808 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001809
Adam Cohen8baab352012-03-20 17:39:21 -07001810 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001811 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001812 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001813 if (c != null) {
1814 c.x = cellX;
1815 c.y = cellY;
1816 }
Adam Cohen482ed822012-03-02 14:15:13 -08001817 }
Adam Cohen482ed822012-03-02 14:15:13 -08001818 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1819 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001820 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001821 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001822 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001823 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001824 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001825 if (Rect.intersects(r0, r1)) {
1826 if (!lp.canReorder) {
1827 return false;
1828 }
1829 mIntersectingViews.add(child);
1830 }
1831 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001832
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001833 // First we try to find a solution which respects the push mechanic. That is,
1834 // we try to find a solution such that no displaced item travels through another item
1835 // without also displacing that item.
1836 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001837 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001838 return true;
1839 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001840
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001841 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohen19f37922012-03-21 11:59:11 -07001842 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, false, ignoreView,
1843 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001844 return true;
1845 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001846
Adam Cohen482ed822012-03-02 14:15:13 -08001847 // Ok, they couldn't move as a block, let's move them individually
1848 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001849 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001850 return false;
1851 }
1852 }
1853 return true;
1854 }
1855
1856 /*
1857 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1858 * the provided point and the provided cell
1859 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001860 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001861 double angle = Math.atan(((float) deltaY) / deltaX);
1862
1863 result[0] = 0;
1864 result[1] = 0;
1865 if (Math.abs(Math.cos(angle)) > 0.5f) {
1866 result[0] = (int) Math.signum(deltaX);
1867 }
1868 if (Math.abs(Math.sin(angle)) > 0.5f) {
1869 result[1] = (int) Math.signum(deltaY);
1870 }
1871 }
1872
Adam Cohen8baab352012-03-20 17:39:21 -07001873 private void copyOccupiedArray(boolean[][] occupied) {
1874 for (int i = 0; i < mCountX; i++) {
1875 for (int j = 0; j < mCountY; j++) {
1876 occupied[i][j] = mOccupied[i][j];
1877 }
1878 }
1879 }
1880
Adam Cohen482ed822012-03-02 14:15:13 -08001881 ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1882 int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001883 // Copy the current state into the solution. This solution will be manipulated as necessary.
1884 copyCurrentStateToSolution(solution, false);
1885 // Copy the current occupied array into the temporary occupied array. This array will be
1886 // manipulated as necessary to find a solution.
1887 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001888
1889 // We find the nearest cell into which we would place the dragged item, assuming there's
1890 // nothing in its way.
1891 int result[] = new int[2];
1892 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1893
1894 boolean success = false;
1895 // First we try the exact nearest position of the item being dragged,
1896 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001897 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1898 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001899
1900 if (!success) {
1901 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1902 // x, then 1 in y etc.
1903 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
1904 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
1905 dragView, false, solution);
1906 } else if (spanY > minSpanY) {
1907 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
1908 dragView, true, solution);
1909 }
1910 solution.isSolution = false;
1911 } else {
1912 solution.isSolution = true;
1913 solution.dragViewX = result[0];
1914 solution.dragViewY = result[1];
1915 solution.dragViewSpanX = spanX;
1916 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08001917 }
1918 return solution;
1919 }
1920
1921 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001922 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001923 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001924 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001925 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001926 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08001927 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07001928 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001929 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001930 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001931 }
Adam Cohen8baab352012-03-20 17:39:21 -07001932 solution.map.put(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08001933 }
1934 }
1935
1936 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
1937 for (int i = 0; i < mCountX; i++) {
1938 for (int j = 0; j < mCountY; j++) {
1939 mTmpOccupied[i][j] = false;
1940 }
1941 }
1942
Michael Jurkaa52570f2012-03-20 03:18:20 -07001943 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001944 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001945 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001946 if (child == dragView) continue;
1947 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001948 CellAndSpan c = solution.map.get(child);
1949 if (c != null) {
1950 lp.tmpCellX = c.x;
1951 lp.tmpCellY = c.y;
1952 lp.cellHSpan = c.spanX;
1953 lp.cellVSpan = c.spanY;
1954 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001955 }
1956 }
1957 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
1958 solution.dragViewSpanY, mTmpOccupied, true);
1959 }
1960
1961 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
1962 commitDragView) {
1963
1964 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
1965 for (int i = 0; i < mCountX; i++) {
1966 for (int j = 0; j < mCountY; j++) {
1967 occupied[i][j] = false;
1968 }
1969 }
1970
Michael Jurkaa52570f2012-03-20 03:18:20 -07001971 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001972 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001973 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001974 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001975 CellAndSpan c = solution.map.get(child);
1976 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07001977 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
1978 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07001979 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001980 }
1981 }
1982 if (commitDragView) {
1983 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
1984 solution.dragViewSpanY, occupied, true);
1985 }
1986 }
1987
Adam Cohen19f37922012-03-21 11:59:11 -07001988 // This method starts or changes the reorder hint animations
1989 private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
1990 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07001991 for (int i = 0; i < childCount; i++) {
1992 View child = mShortcutsAndWidgets.getChildAt(i);
1993 if (child == dragView) continue;
1994 CellAndSpan c = solution.map.get(child);
1995 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1996 if (c != null) {
1997 ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
1998 c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07001999 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002000 }
2001 }
2002 }
2003
2004 // Class which represents the reorder hint animations. These animations show that an item is
2005 // in a temporary state, and hint at where the item will return to.
2006 class ReorderHintAnimation {
2007 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002008 float finalDeltaX;
2009 float finalDeltaY;
2010 float initDeltaX;
2011 float initDeltaY;
2012 float finalScale;
2013 float initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002014 private static final int DURATION = 300;
Adam Cohene7587d22012-05-24 18:50:02 -07002015 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002016
2017 public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
2018 int spanX, int spanY) {
2019 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2020 final int x0 = mTmpPoint[0];
2021 final int y0 = mTmpPoint[1];
2022 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2023 final int x1 = mTmpPoint[0];
2024 final int y1 = mTmpPoint[1];
2025 final int dX = x1 - x0;
2026 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002027 finalDeltaX = 0;
2028 finalDeltaY = 0;
Adam Cohen19f37922012-03-21 11:59:11 -07002029 if (dX == dY && dX == 0) {
2030 } else {
2031 if (dY == 0) {
Adam Cohend024f982012-05-23 18:26:45 -07002032 finalDeltaX = - Math.signum(dX) * mReorderHintAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002033 } else if (dX == 0) {
Adam Cohend024f982012-05-23 18:26:45 -07002034 finalDeltaY = - Math.signum(dY) * mReorderHintAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002035 } else {
2036 double angle = Math.atan( (float) (dY) / dX);
Adam Cohend024f982012-05-23 18:26:45 -07002037 finalDeltaX = (int) (- Math.signum(dX) *
Adam Cohenfe41ac62012-05-23 14:00:37 -07002038 Math.abs(Math.cos(angle) * mReorderHintAnimationMagnitude));
Adam Cohend024f982012-05-23 18:26:45 -07002039 finalDeltaY = (int) (- Math.signum(dY) *
Adam Cohenfe41ac62012-05-23 14:00:37 -07002040 Math.abs(Math.sin(angle) * mReorderHintAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002041 }
2042 }
Adam Cohend024f982012-05-23 18:26:45 -07002043 initDeltaX = child.getTranslationX();
2044 initDeltaY = child.getTranslationY();
2045 finalScale = 1.0f - 4.0f / child.getWidth();
2046 initScale = child.getScaleX();
2047
Brandon Keely50e6e562012-05-08 16:28:49 -07002048 child.setPivotY(child.getMeasuredHeight() * 0.5f);
2049 child.setPivotX(child.getMeasuredWidth() * 0.5f);
Adam Cohen19f37922012-03-21 11:59:11 -07002050 this.child = child;
2051 }
2052
Adam Cohend024f982012-05-23 18:26:45 -07002053 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002054 if (mShakeAnimators.containsKey(child)) {
2055 ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002056 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002057 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002058 if (finalDeltaX == 0 && finalDeltaY == 0) {
2059 completeAnimationImmediately();
2060 return;
2061 }
Adam Cohen19f37922012-03-21 11:59:11 -07002062 }
Adam Cohend024f982012-05-23 18:26:45 -07002063 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002064 return;
2065 }
Michael Jurka2ecf9952012-06-18 12:52:28 -07002066 ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002067 a = va;
Adam Cohen19f37922012-03-21 11:59:11 -07002068 va.setRepeatMode(ValueAnimator.REVERSE);
2069 va.setRepeatCount(ValueAnimator.INFINITE);
Adam Cohen7bdfc972012-05-22 16:50:35 -07002070 va.setDuration(DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002071 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002072 va.addUpdateListener(new AnimatorUpdateListener() {
2073 @Override
2074 public void onAnimationUpdate(ValueAnimator animation) {
2075 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohend024f982012-05-23 18:26:45 -07002076 float x = r * finalDeltaX + (1 - r) * initDeltaX;
2077 float y = r * finalDeltaY + (1 - r) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002078 child.setTranslationX(x);
2079 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002080 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002081 child.setScaleX(s);
2082 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002083 }
2084 });
2085 va.addListener(new AnimatorListenerAdapter() {
2086 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002087 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002088 initDeltaX = 0;
2089 initDeltaY = 0;
2090 initScale = 1.0f;
Adam Cohen19f37922012-03-21 11:59:11 -07002091 }
2092 });
Adam Cohen19f37922012-03-21 11:59:11 -07002093 mShakeAnimators.put(child, this);
2094 va.start();
2095 }
2096
Adam Cohend024f982012-05-23 18:26:45 -07002097 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002098 if (a != null) {
2099 a.cancel();
2100 }
Adam Cohen19f37922012-03-21 11:59:11 -07002101 }
Adam Cohene7587d22012-05-24 18:50:02 -07002102
Brandon Keely50e6e562012-05-08 16:28:49 -07002103 private void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002104 if (a != null) {
2105 a.cancel();
2106 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002107
Michael Jurka2ecf9952012-06-18 12:52:28 -07002108 AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002109 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002110 s.playTogether(
Michael Jurka2ecf9952012-06-18 12:52:28 -07002111 LauncherAnimUtils.ofFloat(child, "scaleX", 1f),
2112 LauncherAnimUtils.ofFloat(child, "scaleY", 1f),
2113 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2114 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002115 );
2116 s.setDuration(REORDER_ANIMATION_DURATION);
2117 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2118 s.start();
2119 }
Adam Cohen19f37922012-03-21 11:59:11 -07002120 }
2121
2122 private void completeAndClearReorderHintAnimations() {
2123 for (ReorderHintAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002124 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002125 }
2126 mShakeAnimators.clear();
2127 }
2128
Adam Cohen482ed822012-03-02 14:15:13 -08002129 private void commitTempPlacement() {
2130 for (int i = 0; i < mCountX; i++) {
2131 for (int j = 0; j < mCountY; j++) {
2132 mOccupied[i][j] = mTmpOccupied[i][j];
2133 }
2134 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002135 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002136 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002137 View child = mShortcutsAndWidgets.getChildAt(i);
2138 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2139 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002140 // We do a null check here because the item info can be null in the case of the
2141 // AllApps button in the hotseat.
2142 if (info != null) {
2143 info.cellX = lp.cellX = lp.tmpCellX;
2144 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002145 info.spanX = lp.cellHSpan;
2146 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002147 }
Adam Cohen482ed822012-03-02 14:15:13 -08002148 }
Adam Cohen2acce882012-03-28 19:03:19 -07002149 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002150 }
2151
2152 public void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002153 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002154 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002155 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002156 lp.useTmpCoords = useTempCoords;
2157 }
2158 }
2159
Adam Cohen482ed822012-03-02 14:15:13 -08002160 ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
2161 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2162 int[] result = new int[2];
2163 int[] resultSpan = new int[2];
2164 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
2165 resultSpan);
2166 if (result[0] >= 0 && result[1] >= 0) {
2167 copyCurrentStateToSolution(solution, false);
2168 solution.dragViewX = result[0];
2169 solution.dragViewY = result[1];
2170 solution.dragViewSpanX = resultSpan[0];
2171 solution.dragViewSpanY = resultSpan[1];
2172 solution.isSolution = true;
2173 } else {
2174 solution.isSolution = false;
2175 }
2176 return solution;
2177 }
2178
2179 public void prepareChildForDrag(View child) {
2180 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002181 }
2182
Adam Cohen19f37922012-03-21 11:59:11 -07002183 /* This seems like it should be obvious and straight-forward, but when the direction vector
2184 needs to match with the notion of the dragView pushing other views, we have to employ
2185 a slightly more subtle notion of the direction vector. The question is what two points is
2186 the vector between? The center of the dragView and its desired destination? Not quite, as
2187 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2188 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2189 or right, which helps make pushing feel right.
2190 */
2191 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2192 int spanY, View dragView, int[] resultDirection) {
2193 int[] targetDestination = new int[2];
2194
2195 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2196 Rect dragRect = new Rect();
2197 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2198 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2199
2200 Rect dropRegionRect = new Rect();
2201 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2202 dragView, dropRegionRect, mIntersectingViews);
2203
2204 int dropRegionSpanX = dropRegionRect.width();
2205 int dropRegionSpanY = dropRegionRect.height();
2206
2207 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2208 dropRegionRect.height(), dropRegionRect);
2209
2210 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2211 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2212
2213 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2214 deltaX = 0;
2215 }
2216 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2217 deltaY = 0;
2218 }
2219
2220 if (deltaX == 0 && deltaY == 0) {
2221 // No idea what to do, give a random direction.
2222 resultDirection[0] = 1;
2223 resultDirection[1] = 0;
2224 } else {
2225 computeDirectionVector(deltaX, deltaY, resultDirection);
2226 }
2227 }
2228
2229 // For a given cell and span, fetch the set of views intersecting the region.
2230 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2231 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2232 if (boundingRect != null) {
2233 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2234 }
2235 intersectingViews.clear();
2236 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2237 Rect r1 = new Rect();
2238 final int count = mShortcutsAndWidgets.getChildCount();
2239 for (int i = 0; i < count; i++) {
2240 View child = mShortcutsAndWidgets.getChildAt(i);
2241 if (child == dragView) continue;
2242 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2243 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2244 if (Rect.intersects(r0, r1)) {
2245 mIntersectingViews.add(child);
2246 if (boundingRect != null) {
2247 boundingRect.union(r1);
2248 }
2249 }
2250 }
2251 }
2252
2253 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2254 View dragView, int[] result) {
2255 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2256 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2257 mIntersectingViews);
2258 return !mIntersectingViews.isEmpty();
2259 }
2260
2261 void revertTempState() {
2262 if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
2263 final int count = mShortcutsAndWidgets.getChildCount();
2264 for (int i = 0; i < count; i++) {
2265 View child = mShortcutsAndWidgets.getChildAt(i);
2266 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2267 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2268 lp.tmpCellX = lp.cellX;
2269 lp.tmpCellY = lp.cellY;
2270 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2271 0, false, false);
2272 }
2273 }
2274 completeAndClearReorderHintAnimations();
2275 setItemPlacementDirty(false);
2276 }
2277
Adam Cohenbebf0422012-04-11 18:06:28 -07002278 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2279 View dragView, int[] direction, boolean commit) {
2280 int[] pixelXY = new int[2];
2281 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2282
2283 // First we determine if things have moved enough to cause a different layout
2284 ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY,
2285 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2286
2287 setUseTempCoords(true);
2288 if (swapSolution != null && swapSolution.isSolution) {
2289 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2290 // committing anything or animating anything as we just want to determine if a solution
2291 // exists
2292 copySolutionToTempState(swapSolution, dragView);
2293 setItemPlacementDirty(true);
2294 animateItemsToSolution(swapSolution, dragView, commit);
2295
2296 if (commit) {
2297 commitTempPlacement();
2298 completeAndClearReorderHintAnimations();
2299 setItemPlacementDirty(false);
2300 } else {
2301 beginOrAdjustHintAnimations(swapSolution, dragView,
2302 REORDER_ANIMATION_DURATION);
2303 }
2304 mShortcutsAndWidgets.requestLayout();
2305 }
2306 return swapSolution.isSolution;
2307 }
2308
Adam Cohen482ed822012-03-02 14:15:13 -08002309 int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
2310 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002311 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002312 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002313
2314 if (resultSpan == null) {
2315 resultSpan = new int[2];
2316 }
2317
Adam Cohen19f37922012-03-21 11:59:11 -07002318 // When we are checking drop validity or actually dropping, we don't recompute the
2319 // direction vector, since we want the solution to match the preview, and it's possible
2320 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002321 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2322 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002323 mDirectionVector[0] = mPreviousReorderDirection[0];
2324 mDirectionVector[1] = mPreviousReorderDirection[1];
2325 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002326 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2327 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2328 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002329 }
2330 } else {
2331 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2332 mPreviousReorderDirection[0] = mDirectionVector[0];
2333 mPreviousReorderDirection[1] = mDirectionVector[1];
2334 }
2335
Adam Cohen482ed822012-03-02 14:15:13 -08002336 ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
2337 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2338
2339 // We attempt the approach which doesn't shuffle views at all
2340 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2341 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2342
2343 ItemConfiguration finalSolution = null;
2344 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2345 finalSolution = swapSolution;
2346 } else if (noShuffleSolution.isSolution) {
2347 finalSolution = noShuffleSolution;
2348 }
2349
2350 boolean foundSolution = true;
2351 if (!DESTRUCTIVE_REORDER) {
2352 setUseTempCoords(true);
2353 }
2354
2355 if (finalSolution != null) {
2356 result[0] = finalSolution.dragViewX;
2357 result[1] = finalSolution.dragViewY;
2358 resultSpan[0] = finalSolution.dragViewSpanX;
2359 resultSpan[1] = finalSolution.dragViewSpanY;
2360
2361 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2362 // committing anything or animating anything as we just want to determine if a solution
2363 // exists
2364 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2365 if (!DESTRUCTIVE_REORDER) {
2366 copySolutionToTempState(finalSolution, dragView);
2367 }
2368 setItemPlacementDirty(true);
2369 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2370
Adam Cohen19f37922012-03-21 11:59:11 -07002371 if (!DESTRUCTIVE_REORDER &&
2372 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002373 commitTempPlacement();
Adam Cohen19f37922012-03-21 11:59:11 -07002374 completeAndClearReorderHintAnimations();
2375 setItemPlacementDirty(false);
2376 } else {
2377 beginOrAdjustHintAnimations(finalSolution, dragView,
2378 REORDER_ANIMATION_DURATION);
Adam Cohen482ed822012-03-02 14:15:13 -08002379 }
2380 }
2381 } else {
2382 foundSolution = false;
2383 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2384 }
2385
2386 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2387 setUseTempCoords(false);
2388 }
Adam Cohen482ed822012-03-02 14:15:13 -08002389
Michael Jurkaa52570f2012-03-20 03:18:20 -07002390 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002391 return result;
2392 }
2393
Adam Cohen19f37922012-03-21 11:59:11 -07002394 void setItemPlacementDirty(boolean dirty) {
2395 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002396 }
Adam Cohen19f37922012-03-21 11:59:11 -07002397 boolean isItemPlacementDirty() {
2398 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002399 }
2400
2401 private class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002402 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohen482ed822012-03-02 14:15:13 -08002403 boolean isSolution = false;
2404 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2405
2406 int area() {
2407 return dragViewSpanX * dragViewSpanY;
2408 }
Adam Cohen8baab352012-03-20 17:39:21 -07002409 }
2410
2411 private class CellAndSpan {
2412 int x, y;
2413 int spanX, spanY;
2414
2415 public CellAndSpan(int x, int y, int spanX, int spanY) {
2416 this.x = x;
2417 this.y = y;
2418 this.spanX = spanX;
2419 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002420 }
2421 }
2422
Adam Cohendf035382011-04-11 17:22:04 -07002423 /**
2424 * Find a vacant area that will fit the given bounds nearest the requested
2425 * cell location. Uses Euclidean distance to score multiple vacant areas.
2426 *
2427 * @param pixelX The X location at which you want to search for a vacant area.
2428 * @param pixelY The Y location at which you want to search for a vacant area.
2429 * @param spanX Horizontal span of the object.
2430 * @param spanY Vertical span of the object.
2431 * @param ignoreView Considers space occupied by this view as unoccupied
2432 * @param result Previously returned value to possibly recycle.
2433 * @return The X, Y cell of a vacant area that can contain this object,
2434 * nearest the requested location.
2435 */
2436 int[] findNearestVacantArea(
2437 int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
2438 return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
2439 }
2440
2441 /**
Adam Cohend41fbf52012-02-16 23:53:59 -08002442 * Find a vacant area that will fit the given bounds nearest the requested
2443 * cell location. Uses Euclidean distance to score multiple vacant areas.
2444 *
2445 * @param pixelX The X location at which you want to search for a vacant area.
2446 * @param pixelY The Y location at which you want to search for a vacant area.
2447 * @param minSpanX The minimum horizontal span required
2448 * @param minSpanY The minimum vertical span required
2449 * @param spanX Horizontal span of the object.
2450 * @param spanY Vertical span of the object.
2451 * @param ignoreView Considers space occupied by this view as unoccupied
2452 * @param result Previously returned value to possibly recycle.
2453 * @return The X, Y cell of a vacant area that can contain this object,
2454 * nearest the requested location.
2455 */
2456 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
2457 int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
Adam Cohen482ed822012-03-02 14:15:13 -08002458 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
2459 result, resultSpan, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08002460 }
2461
2462 /**
Adam Cohendf035382011-04-11 17:22:04 -07002463 * Find a starting cell position that will fit the given bounds nearest the requested
2464 * cell location. Uses Euclidean distance to score multiple vacant areas.
2465 *
2466 * @param pixelX The X location at which you want to search for a vacant area.
2467 * @param pixelY The Y location at which you want to search for a vacant area.
2468 * @param spanX Horizontal span of the object.
2469 * @param spanY Vertical span of the object.
2470 * @param ignoreView Considers space occupied by this view as unoccupied
2471 * @param result Previously returned value to possibly recycle.
2472 * @return The X, Y cell of a vacant area that can contain this object,
2473 * nearest the requested location.
2474 */
2475 int[] findNearestArea(
2476 int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2477 return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
2478 }
2479
Michael Jurka0280c3b2010-09-17 15:00:07 -07002480 boolean existsEmptyCell() {
2481 return findCellForSpan(null, 1, 1);
2482 }
2483
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002484 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002485 * Finds the upper-left coordinate of the first rectangle in the grid that can
2486 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2487 * then this method will only return coordinates for rectangles that contain the cell
2488 * (intersectX, intersectY)
2489 *
2490 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2491 * can be found.
2492 * @param spanX The horizontal span of the cell we want to find.
2493 * @param spanY The vertical span of the cell we want to find.
2494 *
2495 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002496 */
Michael Jurka0280c3b2010-09-17 15:00:07 -07002497 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Adam Cohen482ed822012-03-02 14:15:13 -08002498 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002499 }
2500
2501 /**
2502 * Like above, but ignores any cells occupied by the item "ignoreView"
2503 *
2504 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2505 * can be found.
2506 * @param spanX The horizontal span of the cell we want to find.
2507 * @param spanY The vertical span of the cell we want to find.
2508 * @param ignoreView The home screen item we should treat as not occupying any space
2509 * @return
2510 */
2511 boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
Adam Cohen482ed822012-03-02 14:15:13 -08002512 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
2513 ignoreView, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002514 }
2515
2516 /**
2517 * Like above, but if intersectX and intersectY are not -1, then this method will try to
2518 * return coordinates for rectangles that contain the cell [intersectX, intersectY]
2519 *
2520 * @param spanX The horizontal span of the cell we want to find.
2521 * @param spanY The vertical span of the cell we want to find.
2522 * @param ignoreView The home screen item we should treat as not occupying any space
2523 * @param intersectX The X coordinate of the cell that we should try to overlap
2524 * @param intersectX The Y coordinate of the cell that we should try to overlap
2525 *
2526 * @return True if a vacant cell of the specified dimension was found, false otherwise.
2527 */
2528 boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
2529 int intersectX, int intersectY) {
2530 return findCellForSpanThatIntersectsIgnoring(
Adam Cohen482ed822012-03-02 14:15:13 -08002531 cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002532 }
2533
2534 /**
2535 * The superset of the above two methods
2536 */
2537 boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002538 int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002539 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08002540 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002541
Michael Jurka28750fb2010-09-24 17:43:49 -07002542 boolean foundCell = false;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002543 while (true) {
2544 int startX = 0;
2545 if (intersectX >= 0) {
2546 startX = Math.max(startX, intersectX - (spanX - 1));
2547 }
2548 int endX = mCountX - (spanX - 1);
2549 if (intersectX >= 0) {
2550 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
2551 }
2552 int startY = 0;
2553 if (intersectY >= 0) {
2554 startY = Math.max(startY, intersectY - (spanY - 1));
2555 }
2556 int endY = mCountY - (spanY - 1);
2557 if (intersectY >= 0) {
2558 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
2559 }
2560
Winson Chungbbc60d82010-11-11 16:34:41 -08002561 for (int y = startY; y < endY && !foundCell; y++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002562 inner:
Winson Chungbbc60d82010-11-11 16:34:41 -08002563 for (int x = startX; x < endX; x++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002564 for (int i = 0; i < spanX; i++) {
2565 for (int j = 0; j < spanY; j++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002566 if (occupied[x + i][y + j]) {
Winson Chungbbc60d82010-11-11 16:34:41 -08002567 // small optimization: we can skip to after the column we just found
Michael Jurka0280c3b2010-09-17 15:00:07 -07002568 // an occupied cell
Winson Chungbbc60d82010-11-11 16:34:41 -08002569 x += i;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002570 continue inner;
2571 }
2572 }
2573 }
2574 if (cellXY != null) {
2575 cellXY[0] = x;
2576 cellXY[1] = y;
2577 }
Michael Jurka28750fb2010-09-24 17:43:49 -07002578 foundCell = true;
2579 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002580 }
2581 }
2582 if (intersectX == -1 && intersectY == -1) {
2583 break;
2584 } else {
2585 // if we failed to find anything, try again but without any requirements of
2586 // intersecting
2587 intersectX = -1;
2588 intersectY = -1;
2589 continue;
2590 }
2591 }
2592
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002593 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08002594 markCellsAsOccupiedForView(ignoreView, occupied);
Michael Jurka28750fb2010-09-24 17:43:49 -07002595 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002596 }
2597
2598 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002599 * A drag event has begun over this layout.
2600 * It may have begun over this layout (in which case onDragChild is called first),
2601 * or it may have begun on another layout.
2602 */
2603 void onDragEnter() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002604 mDragEnforcer.onDragEnter();
Winson Chungc07918d2011-07-01 15:35:26 -07002605 mDragging = true;
2606 }
2607
2608 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002609 * Called when drag has left this CellLayout or has been completed (successfully or not)
2610 */
2611 void onDragExit() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002612 mDragEnforcer.onDragExit();
Joe Onorato4be866d2010-10-10 11:26:02 -07002613 // This can actually be called when we aren't in a drag, e.g. when adding a new
2614 // item to this layout via the customize drawer.
2615 // Guard against that case.
2616 if (mDragging) {
2617 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002618 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002619
2620 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002621 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002622 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2623 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002624 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002625 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002626 }
2627
2628 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002629 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002630 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002631 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002632 *
2633 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002634 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002635 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002636 if (child != null) {
2637 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002638 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002639 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002640 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002641 }
2642
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002643 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002644 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002645 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002646 * @param cellX X coordinate of upper left corner expressed as a cell position
2647 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002648 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002649 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002650 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002651 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002652 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002653 final int cellWidth = mCellWidth;
2654 final int cellHeight = mCellHeight;
2655 final int widthGap = mWidthGap;
2656 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002657
Winson Chung4b825dcd2011-06-19 12:41:22 -07002658 final int hStartPadding = getPaddingLeft();
2659 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002660
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002661 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2662 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2663
2664 int x = hStartPadding + cellX * (cellWidth + widthGap);
2665 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002666
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002667 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002668 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002669
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002670 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002671 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002672 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002673 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002674 * @param width Width in pixels
2675 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002676 * @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 -08002677 */
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002678 public int[] rectToCell(int width, int height, int[] result) {
Michael Jurka9987a5c2010-10-08 16:58:12 -07002679 return rectToCell(getResources(), width, height, result);
2680 }
2681
2682 public static int[] rectToCell(Resources resources, int width, int height, int[] result) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002683 // Always assume we're working with the smallest span to make sure we
2684 // reserve enough space in both orientations.
Joe Onorato79e56262009-09-21 15:23:04 -04002685 int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
2686 int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002687 int smallerSize = Math.min(actualWidth, actualHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002688
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002689 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002690 int spanX = (int) Math.ceil(width / (float) smallerSize);
2691 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002692
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002693 if (result == null) {
2694 return new int[] { spanX, spanY };
2695 }
2696 result[0] = spanX;
2697 result[1] = spanY;
2698 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002699 }
2700
Michael Jurkaf12c75c2011-01-25 22:41:40 -08002701 public int[] cellSpansToSize(int hSpans, int vSpans) {
2702 int[] size = new int[2];
2703 size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
2704 size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
2705 return size;
2706 }
2707
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002708 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08002709 * Calculate the grid spans needed to fit given item
2710 */
2711 public void calculateSpans(ItemInfo info) {
2712 final int minWidth;
2713 final int minHeight;
2714
2715 if (info instanceof LauncherAppWidgetInfo) {
2716 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
2717 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
2718 } else if (info instanceof PendingAddWidgetInfo) {
2719 minWidth = ((PendingAddWidgetInfo) info).minWidth;
2720 minHeight = ((PendingAddWidgetInfo) info).minHeight;
2721 } else {
2722 // It's not a widget, so it must be 1x1
2723 info.spanX = info.spanY = 1;
2724 return;
2725 }
2726 int[] spans = rectToCell(minWidth, minHeight, null);
2727 info.spanX = spans[0];
2728 info.spanY = spans[1];
2729 }
2730
2731 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002732 * Find the first vacant cell, if there is one.
2733 *
2734 * @param vacant Holds the x and y coordinate of the vacant cell
2735 * @param spanX Horizontal cell span.
2736 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -07002737 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002738 * @return True if a vacant cell was found
2739 */
2740 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002741
Michael Jurka0280c3b2010-09-17 15:00:07 -07002742 return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002743 }
2744
2745 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
2746 int xCount, int yCount, boolean[][] occupied) {
2747
Adam Cohen2801caf2011-05-13 20:57:39 -07002748 for (int y = 0; y < yCount; y++) {
2749 for (int x = 0; x < xCount; x++) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002750 boolean available = !occupied[x][y];
2751out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
2752 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
2753 available = available && !occupied[i][j];
2754 if (!available) break out;
2755 }
2756 }
2757
2758 if (available) {
2759 vacant[0] = x;
2760 vacant[1] = y;
2761 return true;
2762 }
2763 }
2764 }
2765
2766 return false;
2767 }
2768
Michael Jurka0280c3b2010-09-17 15:00:07 -07002769 private void clearOccupiedCells() {
2770 for (int x = 0; x < mCountX; x++) {
2771 for (int y = 0; y < mCountY; y++) {
2772 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002773 }
2774 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002775 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002776
Adam Cohend41fbf52012-02-16 23:53:59 -08002777 public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002778 markCellsAsUnoccupiedForView(view);
Adam Cohen482ed822012-03-02 14:15:13 -08002779 markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002780 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002781
Adam Cohend4844c32011-02-18 19:25:06 -08002782 public void markCellsAsOccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08002783 markCellsAsOccupiedForView(view, mOccupied);
2784 }
2785 public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002786 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002787 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002788 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002789 }
2790
Adam Cohend4844c32011-02-18 19:25:06 -08002791 public void markCellsAsUnoccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08002792 markCellsAsUnoccupiedForView(view, mOccupied);
2793 }
2794 public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002795 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002796 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002797 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002798 }
2799
Adam Cohen482ed822012-03-02 14:15:13 -08002800 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2801 boolean value) {
2802 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002803 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2804 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002805 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002806 }
2807 }
2808 }
2809
Adam Cohen2801caf2011-05-13 20:57:39 -07002810 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002811 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002812 (Math.max((mCountX - 1), 0) * mWidthGap);
2813 }
2814
2815 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002816 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002817 (Math.max((mCountY - 1), 0) * mHeightGap);
2818 }
2819
Michael Jurka66d72172011-04-12 16:29:25 -07002820 public boolean isOccupied(int x, int y) {
2821 if (x < mCountX && y < mCountY) {
2822 return mOccupied[x][y];
2823 } else {
2824 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2825 }
2826 }
2827
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002828 @Override
2829 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2830 return new CellLayout.LayoutParams(getContext(), attrs);
2831 }
2832
2833 @Override
2834 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2835 return p instanceof CellLayout.LayoutParams;
2836 }
2837
2838 @Override
2839 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2840 return new CellLayout.LayoutParams(p);
2841 }
2842
Winson Chungaafa03c2010-06-11 17:34:16 -07002843 public static class CellLayoutAnimationController extends LayoutAnimationController {
2844 public CellLayoutAnimationController(Animation animation, float delay) {
2845 super(animation, delay);
2846 }
2847
2848 @Override
2849 protected long getDelayForView(View view) {
2850 return (int) (Math.random() * 150);
2851 }
2852 }
2853
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002854 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2855 /**
2856 * Horizontal location of the item in the grid.
2857 */
2858 @ViewDebug.ExportedProperty
2859 public int cellX;
2860
2861 /**
2862 * Vertical location of the item in the grid.
2863 */
2864 @ViewDebug.ExportedProperty
2865 public int cellY;
2866
2867 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002868 * Temporary horizontal location of the item in the grid during reorder
2869 */
2870 public int tmpCellX;
2871
2872 /**
2873 * Temporary vertical location of the item in the grid during reorder
2874 */
2875 public int tmpCellY;
2876
2877 /**
2878 * Indicates that the temporary coordinates should be used to layout the items
2879 */
2880 public boolean useTmpCoords;
2881
2882 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002883 * Number of cells spanned horizontally by the item.
2884 */
2885 @ViewDebug.ExportedProperty
2886 public int cellHSpan;
2887
2888 /**
2889 * Number of cells spanned vertically by the item.
2890 */
2891 @ViewDebug.ExportedProperty
2892 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002893
Adam Cohen1b607ed2011-03-03 17:26:50 -08002894 /**
2895 * Indicates whether the item will set its x, y, width and height parameters freely,
2896 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2897 */
Adam Cohend4844c32011-02-18 19:25:06 -08002898 public boolean isLockedToGrid = true;
2899
Adam Cohen482ed822012-03-02 14:15:13 -08002900 /**
2901 * Indicates whether this item can be reordered. Always true except in the case of the
2902 * the AllApps button.
2903 */
2904 public boolean canReorder = true;
2905
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002906 // X coordinate of the view in the layout.
2907 @ViewDebug.ExportedProperty
2908 int x;
2909 // Y coordinate of the view in the layout.
2910 @ViewDebug.ExportedProperty
2911 int y;
2912
Romain Guy84f296c2009-11-04 15:00:44 -08002913 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002914
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002915 public LayoutParams(Context c, AttributeSet attrs) {
2916 super(c, attrs);
2917 cellHSpan = 1;
2918 cellVSpan = 1;
2919 }
2920
2921 public LayoutParams(ViewGroup.LayoutParams source) {
2922 super(source);
2923 cellHSpan = 1;
2924 cellVSpan = 1;
2925 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002926
2927 public LayoutParams(LayoutParams source) {
2928 super(source);
2929 this.cellX = source.cellX;
2930 this.cellY = source.cellY;
2931 this.cellHSpan = source.cellHSpan;
2932 this.cellVSpan = source.cellVSpan;
2933 }
2934
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002935 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002936 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002937 this.cellX = cellX;
2938 this.cellY = cellY;
2939 this.cellHSpan = cellHSpan;
2940 this.cellVSpan = cellVSpan;
2941 }
2942
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002943 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap) {
Adam Cohend4844c32011-02-18 19:25:06 -08002944 if (isLockedToGrid) {
2945 final int myCellHSpan = cellHSpan;
2946 final int myCellVSpan = cellVSpan;
Adam Cohen482ed822012-03-02 14:15:13 -08002947 final int myCellX = useTmpCoords ? tmpCellX : cellX;
2948 final int myCellY = useTmpCoords ? tmpCellY : cellY;
Adam Cohen1b607ed2011-03-03 17:26:50 -08002949
Adam Cohend4844c32011-02-18 19:25:06 -08002950 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2951 leftMargin - rightMargin;
2952 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2953 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002954 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2955 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002956 }
2957 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002958
Winson Chungaafa03c2010-06-11 17:34:16 -07002959 public String toString() {
2960 return "(" + this.cellX + ", " + this.cellY + ")";
2961 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002962
2963 public void setWidth(int width) {
2964 this.width = width;
2965 }
2966
2967 public int getWidth() {
2968 return width;
2969 }
2970
2971 public void setHeight(int height) {
2972 this.height = height;
2973 }
2974
2975 public int getHeight() {
2976 return height;
2977 }
2978
2979 public void setX(int x) {
2980 this.x = x;
2981 }
2982
2983 public int getX() {
2984 return x;
2985 }
2986
2987 public void setY(int y) {
2988 this.y = y;
2989 }
2990
2991 public int getY() {
2992 return y;
2993 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002994 }
2995
Michael Jurka0280c3b2010-09-17 15:00:07 -07002996 // This class stores info for two purposes:
2997 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2998 // its spanX, spanY, and the screen it is on
2999 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
3000 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
3001 // the CellLayout that was long clicked
Michael Jurkae5fb0f22011-04-11 13:27:46 -07003002 static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003003 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07003004 int cellX = -1;
3005 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003006 int spanX;
3007 int spanY;
3008 int screen;
Winson Chung3d503fb2011-07-13 17:25:49 -07003009 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003010
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003011 @Override
3012 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07003013 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
3014 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003015 }
3016 }
Michael Jurkad771c962011-08-09 15:00:48 -07003017
3018 public boolean lastDownOnOccupiedCell() {
3019 return mLastDownOnOccupiedCell;
3020 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003021}