blob: 22492ac31e2f3ba47ea5912c1ec664931ee84d29 [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
Daniel Sandler325dc232013-06-05 22:57:57 -040017package com.android.launcher3;
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;
Adam Cohen1462de32012-07-24 22:34:36 -070039import android.os.Parcelable;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080040import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070041import android.util.Log;
Adam Cohen1462de32012-07-24 22:34:36 -070042import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080043import android.view.MotionEvent;
44import android.view.View;
45import android.view.ViewDebug;
46import android.view.ViewGroup;
Winson Chungaafa03c2010-06-11 17:34:16 -070047import android.view.animation.Animation;
Winson Chung150fbab2010-09-29 17:14:26 -070048import android.view.animation.DecelerateInterpolator;
Winson Chungaafa03c2010-06-11 17:34:16 -070049import android.view.animation.LayoutAnimationController;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080050
Daniel Sandler325dc232013-06-05 22:57:57 -040051import com.android.launcher3.R;
52import com.android.launcher3.FolderIcon.FolderRingAnimator;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070053
Adam Cohen69ce2e52011-07-03 19:25:21 -070054import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070055import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080056import java.util.Collections;
57import java.util.Comparator;
Adam Cohenbfbfd262011-06-13 16:55:12 -070058import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080059import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070060
Michael Jurkabdb5c532011-02-01 15:05:06 -080061public class CellLayout extends ViewGroup {
Winson Chungaafa03c2010-06-11 17:34:16 -070062 static final String TAG = "CellLayout";
63
Adam Cohen2acce882012-03-28 19:03:19 -070064 private Launcher mLauncher;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080065 private int mCellWidth;
66 private int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070067 private int mFixedCellWidth;
68 private int mFixedCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070069
Adam Cohend22015c2010-07-26 22:02:18 -070070 private int mCountX;
71 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080072
Adam Cohen234c4cd2011-07-17 21:03:04 -070073 private int mOriginalWidthGap;
74 private int mOriginalHeightGap;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080075 private int mWidthGap;
76 private int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070077 private int mMaxGap;
Adam Cohenebea84d2011-11-09 17:20:41 -080078 private boolean mScrollingTransformsDirty = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080079
80 private final Rect mRect = new Rect();
81 private final CellInfo mCellInfo = new CellInfo();
Winson Chungaafa03c2010-06-11 17:34:16 -070082
Patrick Dubroyde7658b2010-09-27 11:15:43 -070083 // These are temporary variables to prevent having to allocate a new object just to
84 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Winson Chung0be025d2011-05-23 17:45:09 -070085 private final int[] mTmpXY = new int[2];
Patrick Dubroyde7658b2010-09-27 11:15:43 -070086 private final int[] mTmpPoint = new int[2];
Adam Cohen69ce2e52011-07-03 19:25:21 -070087 int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070088
The Android Open Source Project31dd5032009-03-03 19:32:27 -080089 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -080090 boolean[][] mTmpOccupied;
Michael Jurkad771c962011-08-09 15:00:48 -070091 private boolean mLastDownOnOccupiedCell = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080092
Michael Jurkadee05892010-07-27 10:01:56 -070093 private OnTouchListener mInterceptTouchListener;
94
Adam Cohen69ce2e52011-07-03 19:25:21 -070095 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -070096 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -070097
Adam Cohenb5ba0972011-09-07 18:02:31 -070098 private int mForegroundAlpha = 0;
Michael Jurka5f1c5092010-09-03 14:15:02 -070099 private float mBackgroundAlpha;
Adam Cohen1b0aaac2010-10-28 11:11:18 -0700100 private float mBackgroundAlphaMultiplier = 1.0f;
Adam Cohenf34bab52010-09-30 14:11:56 -0700101
Michael Jurka33945b22010-12-21 18:19:38 -0800102 private Drawable mNormalBackground;
Michael Jurka33945b22010-12-21 18:19:38 -0800103 private Drawable mActiveGlowBackground;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700104 private Drawable mOverScrollForegroundDrawable;
105 private Drawable mOverScrollLeft;
106 private Drawable mOverScrollRight;
Michael Jurka18014792010-10-14 09:01:34 -0700107 private Rect mBackgroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700108 private Rect mForegroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700109 private int mForegroundPadding;
Patrick Dubroy1262e362010-10-06 15:49:50 -0700110
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700111 // These values allow a fixed measurement to be set on the CellLayout.
112 private int mFixedWidth = -1;
113 private int mFixedHeight = -1;
114
Michael Jurka33945b22010-12-21 18:19:38 -0800115 // If we're actively dragging something over this screen, mIsDragOverlapping is true
116 private boolean mIsDragOverlapping = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700117 private final Point mDragCenter = new Point();
Adam Cohendedbd962013-07-11 14:21:49 -0700118 boolean mUseActiveGlowBackground = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700119
Winson Chung150fbab2010-09-29 17:14:26 -0700120 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700121 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohend41fbf52012-02-16 23:53:59 -0800122 private Rect[] mDragOutlines = new Rect[4];
Chet Haase472b2812010-10-14 07:02:04 -0700123 private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700124 private InterruptibleInOutAnimator[] mDragOutlineAnims =
125 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700126
127 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700128 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700129 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700130
Patrick Dubroy96864c32011-03-10 17:17:23 -0800131 private BubbleTextView mPressedOrFocusedIcon;
132
Adam Cohen482ed822012-03-02 14:15:13 -0800133 private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
134 HashMap<CellLayout.LayoutParams, Animator>();
Adam Cohen19f37922012-03-21 11:59:11 -0700135 private HashMap<View, ReorderHintAnimation>
136 mShakeAnimators = new HashMap<View, ReorderHintAnimation>();
137
138 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700139
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700140 // When a drag operation is in progress, holds the nearest cell to the touch point
141 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800142
Joe Onorato4be866d2010-10-10 11:26:02 -0700143 private boolean mDragging = false;
144
Patrick Dubroyce34a972010-10-19 10:34:32 -0700145 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700146 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700147
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800148 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700149 private float mHotseatScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800150
Adam Cohen482ed822012-03-02 14:15:13 -0800151 public static final int MODE_DRAG_OVER = 0;
152 public static final int MODE_ON_DROP = 1;
153 public static final int MODE_ON_DROP_EXTERNAL = 2;
154 public static final int MODE_ACCEPT_DROP = 3;
Adam Cohen19f37922012-03-21 11:59:11 -0700155 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800156 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
157
Adam Cohena897f392012-04-27 18:12:05 -0700158 static final int LANDSCAPE = 0;
159 static final int PORTRAIT = 1;
160
Adam Cohen7bdfc972012-05-22 16:50:35 -0700161 private static final float REORDER_HINT_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700162 private static final int REORDER_ANIMATION_DURATION = 150;
163 private float mReorderHintAnimationMagnitude;
164
Adam Cohen482ed822012-03-02 14:15:13 -0800165 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
166 private Rect mOccupiedRect = new Rect();
167 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700168 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700169 private static final int INVALID_DIRECTION = -100;
Adam Cohenc6cc61d2012-04-04 12:47:08 -0700170 private DropTarget.DragEnforcer mDragEnforcer;
Adam Cohen482ed822012-03-02 14:15:13 -0800171
Romain Guy8a0bff52012-05-06 13:14:33 -0700172 private final static PorterDuffXfermode sAddBlendMode =
173 new PorterDuffXfermode(PorterDuff.Mode.ADD);
Michael Jurkaca993832012-06-29 15:17:04 -0700174 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700175
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800176 public CellLayout(Context context) {
177 this(context, null);
178 }
179
180 public CellLayout(Context context, AttributeSet attrs) {
181 this(context, attrs, 0);
182 }
183
184 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
185 super(context, attrs, defStyle);
Michael Jurka8b805b12012-04-18 14:23:14 -0700186 mDragEnforcer = new DropTarget.DragEnforcer(context);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700187
188 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
189 // the user where a dragged item will land when dropped.
190 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800191 setClipToPadding(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700192 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700193
Winson Chung892c74d2013-08-22 16:15:50 -0700194 LauncherAppState app = LauncherAppState.getInstance();
195 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800196 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
197
Winson Chung11a1a532013-09-13 11:14:45 -0700198 mCellWidth = mCellHeight = -1;
199 mFixedCellHeight = mFixedCellHeight = -1;
Winson Chung5f8afe62013-08-12 16:19:28 -0700200 mWidthGap = mOriginalWidthGap = 0;
201 mHeightGap = mOriginalHeightGap = 0;
202 mMaxGap = Integer.MAX_VALUE;
Winson Chung892c74d2013-08-22 16:15:50 -0700203 mCountX = (int) grid.numColumns;
204 mCountY = (int) grid.numRows;
Michael Jurka0280c3b2010-09-17 15:00:07 -0700205 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800206 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700207 mPreviousReorderDirection[0] = INVALID_DIRECTION;
208 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800209
210 a.recycle();
211
212 setAlwaysDrawnWithCacheEnabled(false);
213
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700214 final Resources res = getResources();
Winson Chung5f8afe62013-08-12 16:19:28 -0700215 mHotseatScale = (float) grid.hotseatIconSize / grid.iconSize;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700216
Adam Cohen410f3cd2013-09-22 12:09:32 -0700217 mNormalBackground = res.getDrawable(R.drawable.screenpanel);
218 mActiveGlowBackground = res.getDrawable(R.drawable.screenpanel_hover);
Michael Jurka33945b22010-12-21 18:19:38 -0800219
Adam Cohenb5ba0972011-09-07 18:02:31 -0700220 mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
221 mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
222 mForegroundPadding =
223 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
Michael Jurka33945b22010-12-21 18:19:38 -0800224
Adam Cohen19f37922012-03-21 11:59:11 -0700225 mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE *
Winson Chung5f8afe62013-08-12 16:19:28 -0700226 grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700227
Winson Chungb26f3d62011-06-02 10:49:29 -0700228 mNormalBackground.setFilterBitmap(true);
Winson Chungb26f3d62011-06-02 10:49:29 -0700229 mActiveGlowBackground.setFilterBitmap(true);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700230
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700231 // Initialize the data structures used for the drag visualization.
Patrick Dubroyce34a972010-10-19 10:34:32 -0700232 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700233 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700234 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800235 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700236 }
237
238 // When dragging things around the home screens, we show a green outline of
239 // where the item will land. The outlines gradually fade out, leaving a trail
240 // behind the drag path.
241 // Set up all the animations that are used to implement this fading.
242 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700243 final float fromAlphaValue = 0;
244 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700245
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700246 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700247
248 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700249 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100250 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700251 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700252 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700253 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700254 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700255 final Bitmap outline = (Bitmap)anim.getTag();
256
257 // If an animation is started and then stopped very quickly, we can still
258 // get spurious updates we've cleared the tag. Guard against this.
259 if (outline == null) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700260 @SuppressWarnings("all") // suppress dead code warning
261 final boolean debug = false;
262 if (debug) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700263 Object val = animation.getAnimatedValue();
264 Log.d(TAG, "anim " + thisIndex + " update: " + val +
265 ", isStopped " + anim.isStopped());
266 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700267 // Try to prevent it from continuing to run
268 animation.cancel();
269 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700270 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800271 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700272 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700273 }
274 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700275 // The animation holds a reference to the drag outline bitmap as long is it's
276 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700277 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700278 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700279 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700280 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700281 anim.setTag(null);
282 }
283 }
284 });
285 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700286 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700287
Michael Jurka18014792010-10-14 09:01:34 -0700288 mBackgroundRect = new Rect();
Adam Cohenb5ba0972011-09-07 18:02:31 -0700289 mForegroundRect = new Rect();
Michael Jurkabea15192010-11-17 12:33:46 -0800290
Michael Jurkaa52570f2012-03-20 03:18:20 -0700291 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
Adam Cohen2374abf2013-04-16 14:56:57 -0700292 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700293 mCountX, mCountY);
Adam Cohen2374abf2013-04-16 14:56:57 -0700294
Michael Jurkaa52570f2012-03-20 03:18:20 -0700295 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700296 }
297
Adam Cohen2801caf2011-05-13 20:57:39 -0700298 public void enableHardwareLayers() {
Michael Jurkaca993832012-06-29 15:17:04 -0700299 mShortcutsAndWidgets.setLayerType(LAYER_TYPE_HARDWARE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700300 }
301
302 public void disableHardwareLayers() {
Michael Jurkaca993832012-06-29 15:17:04 -0700303 mShortcutsAndWidgets.setLayerType(LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700304 }
305
306 public void buildHardwareLayer() {
307 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700308 }
309
Adam Cohen307fe232012-08-16 17:55:58 -0700310 public float getChildrenScale() {
311 return mIsHotseat ? mHotseatScale : 1.0f;
312 }
313
Winson Chung5f8afe62013-08-12 16:19:28 -0700314 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700315 mFixedCellWidth = mCellWidth = width;
316 mFixedCellHeight = mCellHeight = height;
Winson Chung5f8afe62013-08-12 16:19:28 -0700317 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
318 mCountX, mCountY);
319 }
320
Adam Cohen2801caf2011-05-13 20:57:39 -0700321 public void setGridSize(int x, int y) {
322 mCountX = x;
323 mCountY = y;
324 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800325 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700326 mTempRectStack.clear();
Adam Cohen2374abf2013-04-16 14:56:57 -0700327 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700328 mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700329 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700330 }
331
Adam Cohen2374abf2013-04-16 14:56:57 -0700332 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
333 public void setInvertIfRtl(boolean invert) {
334 mShortcutsAndWidgets.setInvertIfRtl(invert);
335 }
336
Patrick Dubroy96864c32011-03-10 17:17:23 -0800337 private void invalidateBubbleTextView(BubbleTextView icon) {
338 final int padding = icon.getPressedOrFocusedBackgroundPadding();
Winson Chung4b825dcd2011-06-19 12:41:22 -0700339 invalidate(icon.getLeft() + getPaddingLeft() - padding,
340 icon.getTop() + getPaddingTop() - padding,
341 icon.getRight() + getPaddingLeft() + padding,
342 icon.getBottom() + getPaddingTop() + padding);
Patrick Dubroy96864c32011-03-10 17:17:23 -0800343 }
344
Adam Cohenb5ba0972011-09-07 18:02:31 -0700345 void setOverScrollAmount(float r, boolean left) {
346 if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
347 mOverScrollForegroundDrawable = mOverScrollLeft;
348 } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
349 mOverScrollForegroundDrawable = mOverScrollRight;
350 }
351
352 mForegroundAlpha = (int) Math.round((r * 255));
353 mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
354 invalidate();
355 }
356
Patrick Dubroy96864c32011-03-10 17:17:23 -0800357 void setPressedOrFocusedIcon(BubbleTextView icon) {
358 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
359 // requires an expanded clip rect (due to the glow's blur radius)
360 BubbleTextView oldIcon = mPressedOrFocusedIcon;
361 mPressedOrFocusedIcon = icon;
362 if (oldIcon != null) {
363 invalidateBubbleTextView(oldIcon);
364 }
365 if (mPressedOrFocusedIcon != null) {
366 invalidateBubbleTextView(mPressedOrFocusedIcon);
367 }
368 }
369
Michael Jurka33945b22010-12-21 18:19:38 -0800370 void setIsDragOverlapping(boolean isDragOverlapping) {
371 if (mIsDragOverlapping != isDragOverlapping) {
372 mIsDragOverlapping = isDragOverlapping;
Adam Cohendedbd962013-07-11 14:21:49 -0700373 setUseActiveGlowBackground(mIsDragOverlapping);
Michael Jurka33945b22010-12-21 18:19:38 -0800374 invalidate();
375 }
376 }
377
Adam Cohendedbd962013-07-11 14:21:49 -0700378 void setUseActiveGlowBackground(boolean use) {
379 mUseActiveGlowBackground = use;
380 }
381
Michael Jurka33945b22010-12-21 18:19:38 -0800382 boolean getIsDragOverlapping() {
383 return mIsDragOverlapping;
384 }
385
Adam Cohenebea84d2011-11-09 17:20:41 -0800386 protected void setOverscrollTransformsDirty(boolean dirty) {
387 mScrollingTransformsDirty = dirty;
388 }
389
390 protected void resetOverscrollTransforms() {
391 if (mScrollingTransformsDirty) {
392 setOverscrollTransformsDirty(false);
393 setTranslationX(0);
394 setRotationY(0);
395 // It doesn't matter if we pass true or false here, the important thing is that we
396 // pass 0, which results in the overscroll drawable not being drawn any more.
397 setOverScrollAmount(0, false);
398 setPivotX(getMeasuredWidth() / 2);
399 setPivotY(getMeasuredHeight() / 2);
400 }
401 }
402
Adam Cohen307fe232012-08-16 17:55:58 -0700403 public void scaleRect(Rect r, float scale) {
404 if (scale != 1.0f) {
405 r.left = (int) (r.left * scale + 0.5f);
406 r.top = (int) (r.top * scale + 0.5f);
407 r.right = (int) (r.right * scale + 0.5f);
408 r.bottom = (int) (r.bottom * scale + 0.5f);
409 }
410 }
411
412 Rect temp = new Rect();
413 void scaleRectAboutCenter(Rect in, Rect out, float scale) {
414 int cx = in.centerX();
415 int cy = in.centerY();
416 out.set(in);
417 out.offset(-cx, -cy);
418 scaleRect(out, scale);
419 out.offset(cx, cy);
420 }
421
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700422 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700423 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700424 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
425 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
426 // When we're small, we are either drawn normally or in the "accepts drops" state (during
427 // a drag). However, we also drag the mini hover background *over* one of those two
428 // backgrounds
Winson Chungb26f3d62011-06-02 10:49:29 -0700429 if (mBackgroundAlpha > 0.0f) {
Adam Cohenf34bab52010-09-30 14:11:56 -0700430 Drawable bg;
Michael Jurka33945b22010-12-21 18:19:38 -0800431
Adam Cohendedbd962013-07-11 14:21:49 -0700432 if (mUseActiveGlowBackground) {
Michael Jurka33945b22010-12-21 18:19:38 -0800433 // In the mini case, we draw the active_glow bg *over* the active background
Michael Jurkabdf78552011-10-31 14:34:25 -0700434 bg = mActiveGlowBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700435 } else {
Michael Jurkabdf78552011-10-31 14:34:25 -0700436 bg = mNormalBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700437 }
Michael Jurka33945b22010-12-21 18:19:38 -0800438
439 bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
440 bg.setBounds(mBackgroundRect);
441 bg.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700442 }
Romain Guya6abce82009-11-10 02:54:41 -0800443
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700444 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700445 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700446 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700447 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800448 final Rect r = mDragOutlines[i];
Adam Cohen307fe232012-08-16 17:55:58 -0700449 scaleRectAboutCenter(r, temp, getChildrenScale());
Joe Onorato4be866d2010-10-10 11:26:02 -0700450 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700451 paint.setAlpha((int)(alpha + .5f));
Adam Cohen307fe232012-08-16 17:55:58 -0700452 canvas.drawBitmap(b, null, temp, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700453 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700454 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800455
456 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
457 // requires an expanded clip rect (due to the glow's blur radius)
458 if (mPressedOrFocusedIcon != null) {
459 final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
460 final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
461 if (b != null) {
462 canvas.drawBitmap(b,
Winson Chung4b825dcd2011-06-19 12:41:22 -0700463 mPressedOrFocusedIcon.getLeft() + getPaddingLeft() - padding,
464 mPressedOrFocusedIcon.getTop() + getPaddingTop() - padding,
Patrick Dubroy96864c32011-03-10 17:17:23 -0800465 null);
466 }
467 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700468
Adam Cohen482ed822012-03-02 14:15:13 -0800469 if (DEBUG_VISUALIZE_OCCUPIED) {
470 int[] pt = new int[2];
471 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700472 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800473 for (int i = 0; i < mCountX; i++) {
474 for (int j = 0; j < mCountY; j++) {
475 if (mOccupied[i][j]) {
476 cellToPoint(i, j, pt);
477 canvas.save();
478 canvas.translate(pt[0], pt[1]);
479 cd.draw(canvas);
480 canvas.restore();
481 }
482 }
483 }
484 }
485
Andrew Flynn850d2e72012-04-26 16:51:20 -0700486 int previewOffset = FolderRingAnimator.sPreviewSize;
487
Adam Cohen69ce2e52011-07-03 19:25:21 -0700488 // The folder outer / inner ring image(s)
Winson Chung5f8afe62013-08-12 16:19:28 -0700489 LauncherAppState app = LauncherAppState.getInstance();
490 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700491 for (int i = 0; i < mFolderOuterRings.size(); i++) {
492 FolderRingAnimator fra = mFolderOuterRings.get(i);
493
Adam Cohen5108bc02013-09-20 17:04:51 -0700494 Drawable d;
495 int width, height;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700496 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700497 View child = getChildAt(fra.mCellX, fra.mCellY);
Adam Cohen5108bc02013-09-20 17:04:51 -0700498 int centerX = mTempLocation[0] + mCellWidth / 2;
499 int centerY = mTempLocation[1] + previewOffset / 2 +
500 child.getPaddingTop() + grid.folderBackgroundOffset;
Winson Chung89f97052013-09-20 11:32:26 -0700501 if (child != null) {
Adam Cohen5108bc02013-09-20 17:04:51 -0700502 // Draw outer ring, if it exists
503 if (FolderIcon.HAS_OUTER_RING) {
504 d = FolderRingAnimator.sSharedOuterRingDrawable;
505 width = (int) (fra.getOuterRingSize() * getChildrenScale());
506 height = width;
507 canvas.save();
508 canvas.translate(centerX - width / 2, centerY - height / 2);
509 d.setBounds(0, 0, width, height);
510 d.draw(canvas);
511 canvas.restore();
512 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700513
Winson Chung89f97052013-09-20 11:32:26 -0700514 // Draw inner ring
515 d = FolderRingAnimator.sSharedInnerRingDrawable;
516 width = (int) (fra.getInnerRingSize() * getChildrenScale());
517 height = width;
Winson Chung89f97052013-09-20 11:32:26 -0700518 canvas.save();
519 canvas.translate(centerX - width / 2, centerY - width / 2);
520 d.setBounds(0, 0, width, height);
521 d.draw(canvas);
522 canvas.restore();
523 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700524 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700525
526 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
527 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
528 int width = d.getIntrinsicWidth();
529 int height = d.getIntrinsicHeight();
530
531 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700532 View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]);
Winson Chung89f97052013-09-20 11:32:26 -0700533 if (child != null) {
534 int centerX = mTempLocation[0] + mCellWidth / 2;
535 int centerY = mTempLocation[1] + previewOffset / 2 +
536 child.getPaddingTop() + grid.folderBackgroundOffset;
Adam Cohenc51934b2011-07-26 21:07:43 -0700537
Winson Chung89f97052013-09-20 11:32:26 -0700538 canvas.save();
539 canvas.translate(centerX - width / 2, centerY - width / 2);
540 d.setBounds(0, 0, width, height);
541 d.draw(canvas);
542 canvas.restore();
543 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700544 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700545 }
546
Adam Cohenb5ba0972011-09-07 18:02:31 -0700547 @Override
548 protected void dispatchDraw(Canvas canvas) {
549 super.dispatchDraw(canvas);
550 if (mForegroundAlpha > 0) {
551 mOverScrollForegroundDrawable.setBounds(mForegroundRect);
552 Paint p = ((NinePatchDrawable) mOverScrollForegroundDrawable).getPaint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700553 p.setXfermode(sAddBlendMode);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700554 mOverScrollForegroundDrawable.draw(canvas);
555 p.setXfermode(null);
556 }
557 }
558
Adam Cohen69ce2e52011-07-03 19:25:21 -0700559 public void showFolderAccept(FolderRingAnimator fra) {
560 mFolderOuterRings.add(fra);
561 }
562
563 public void hideFolderAccept(FolderRingAnimator fra) {
564 if (mFolderOuterRings.contains(fra)) {
565 mFolderOuterRings.remove(fra);
566 }
567 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700568 }
569
Adam Cohenc51934b2011-07-26 21:07:43 -0700570 public void setFolderLeaveBehindCell(int x, int y) {
571 mFolderLeaveBehindCell[0] = x;
572 mFolderLeaveBehindCell[1] = y;
573 invalidate();
574 }
575
576 public void clearFolderLeaveBehind() {
577 mFolderLeaveBehindCell[0] = -1;
578 mFolderLeaveBehindCell[1] = -1;
579 invalidate();
580 }
581
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700582 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700583 public boolean shouldDelayChildPressedState() {
584 return false;
585 }
586
Adam Cohen1462de32012-07-24 22:34:36 -0700587 public void restoreInstanceState(SparseArray<Parcelable> states) {
588 dispatchRestoreInstanceState(states);
589 }
590
Michael Jurkae6235dd2011-10-04 15:02:05 -0700591 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700592 public void cancelLongPress() {
593 super.cancelLongPress();
594
595 // Cancel long press for all children
596 final int count = getChildCount();
597 for (int i = 0; i < count; i++) {
598 final View child = getChildAt(i);
599 child.cancelLongPress();
600 }
601 }
602
Michael Jurkadee05892010-07-27 10:01:56 -0700603 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
604 mInterceptTouchListener = listener;
605 }
606
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800607 int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700608 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800609 }
610
611 int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700612 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800613 }
614
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800615 public void setIsHotseat(boolean isHotseat) {
616 mIsHotseat = isHotseat;
Winson Chung5f8afe62013-08-12 16:19:28 -0700617 mShortcutsAndWidgets.setIsHotseat(isHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800618 }
619
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800620 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Andrew Flynn850d2e72012-04-26 16:51:20 -0700621 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700622 final LayoutParams lp = params;
623
Andrew Flynnde38e422012-05-08 11:22:15 -0700624 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800625 if (child instanceof BubbleTextView) {
626 BubbleTextView bubbleChild = (BubbleTextView) child;
Winson Chung5f8afe62013-08-12 16:19:28 -0700627 bubbleChild.setTextVisibility(!mIsHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800628 }
629
Adam Cohen307fe232012-08-16 17:55:58 -0700630 child.setScaleX(getChildrenScale());
631 child.setScaleY(getChildrenScale());
632
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800633 // Generate an id for each view, this assumes we have at most 256x256 cells
634 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700635 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700636 // If the horizontal or vertical span is set to -1, it is taken to
637 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700638 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
639 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800640
Winson Chungaafa03c2010-06-11 17:34:16 -0700641 child.setId(childId);
642
Michael Jurkaa52570f2012-03-20 03:18:20 -0700643 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700644
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700645 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700646
Winson Chungaafa03c2010-06-11 17:34:16 -0700647 return true;
648 }
649 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800650 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700651
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800652 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700653 public void removeAllViews() {
654 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700655 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700656 }
657
658 @Override
659 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700660 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700661 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700662 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700663 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700664 }
665
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700666 public void removeViewWithoutMarkingCells(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700667 mShortcutsAndWidgets.removeView(view);
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700668 }
669
Michael Jurka0280c3b2010-09-17 15:00:07 -0700670 @Override
671 public void removeView(View view) {
672 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700673 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700674 }
675
676 @Override
677 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700678 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
679 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700680 }
681
682 @Override
683 public void removeViewInLayout(View view) {
684 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700685 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700686 }
687
688 @Override
689 public void removeViews(int start, int count) {
690 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700691 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700692 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700693 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700694 }
695
696 @Override
697 public void removeViewsInLayout(int start, int count) {
698 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700699 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700700 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700701 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800702 }
703
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800704 @Override
705 protected void onAttachedToWindow() {
706 super.onAttachedToWindow();
Adam Cohendcd297f2013-06-18 13:13:40 -0700707 if (getParent() instanceof Workspace) {
708 Workspace workspace = (Workspace) getParent();
709 mCellInfo.screenId = workspace.getIdForScreen(this);
710 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800711 }
712
Michael Jurkaaf442092010-06-10 17:01:57 -0700713 public void setTagToCellInfoForPoint(int touchX, int touchY) {
714 final CellInfo cellInfo = mCellInfo;
Winson Chungeecf02d2012-03-02 17:14:58 -0800715 Rect frame = mRect;
Michael Jurka8b805b12012-04-18 14:23:14 -0700716 final int x = touchX + getScrollX();
717 final int y = touchY + getScrollY();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700718 final int count = mShortcutsAndWidgets.getChildCount();
Michael Jurkaaf442092010-06-10 17:01:57 -0700719
720 boolean found = false;
721 for (int i = count - 1; i >= 0; i--) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700722 final View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohend4844c32011-02-18 19:25:06 -0800723 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Michael Jurkaaf442092010-06-10 17:01:57 -0700724
Adam Cohen1b607ed2011-03-03 17:26:50 -0800725 if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
726 lp.isLockedToGrid) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700727 child.getHitRect(frame);
Winson Chung0be025d2011-05-23 17:45:09 -0700728
Winson Chungeecf02d2012-03-02 17:14:58 -0800729 float scale = child.getScaleX();
730 frame = new Rect(child.getLeft(), child.getTop(), child.getRight(),
731 child.getBottom());
Winson Chung0be025d2011-05-23 17:45:09 -0700732 // The child hit rect is relative to the CellLayoutChildren parent, so we need to
733 // offset that by this CellLayout's padding to test an (x,y) point that is relative
734 // to this view.
Michael Jurka8b805b12012-04-18 14:23:14 -0700735 frame.offset(getPaddingLeft(), getPaddingTop());
Winson Chungeecf02d2012-03-02 17:14:58 -0800736 frame.inset((int) (frame.width() * (1f - scale) / 2),
737 (int) (frame.height() * (1f - scale) / 2));
Winson Chung0be025d2011-05-23 17:45:09 -0700738
Michael Jurkaaf442092010-06-10 17:01:57 -0700739 if (frame.contains(x, y)) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700740 cellInfo.cell = child;
741 cellInfo.cellX = lp.cellX;
742 cellInfo.cellY = lp.cellY;
743 cellInfo.spanX = lp.cellHSpan;
744 cellInfo.spanY = lp.cellVSpan;
Michael Jurkaaf442092010-06-10 17:01:57 -0700745 found = true;
Michael Jurkaaf442092010-06-10 17:01:57 -0700746 break;
747 }
748 }
749 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700750
Michael Jurkad771c962011-08-09 15:00:48 -0700751 mLastDownOnOccupiedCell = found;
752
Michael Jurkaaf442092010-06-10 17:01:57 -0700753 if (!found) {
Winson Chung0be025d2011-05-23 17:45:09 -0700754 final int cellXY[] = mTmpXY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700755 pointToCellExact(x, y, cellXY);
756
Michael Jurkaaf442092010-06-10 17:01:57 -0700757 cellInfo.cell = null;
758 cellInfo.cellX = cellXY[0];
759 cellInfo.cellY = cellXY[1];
760 cellInfo.spanX = 1;
761 cellInfo.spanY = 1;
Michael Jurkaaf442092010-06-10 17:01:57 -0700762 }
763 setTag(cellInfo);
764 }
765
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800766 @Override
767 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Cohenc1997fd2011-08-15 18:26:39 -0700768 // First we clear the tag to ensure that on every touch down we start with a fresh slate,
769 // even in the case where we return early. Not clearing here was causing bugs whereby on
770 // long-press we'd end up picking up an item from a previous drag operation.
771 final int action = ev.getAction();
772
773 if (action == MotionEvent.ACTION_DOWN) {
774 clearTagCellInfo();
775 }
776
Michael Jurkadee05892010-07-27 10:01:56 -0700777 if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
778 return true;
779 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800780
781 if (action == MotionEvent.ACTION_DOWN) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700782 setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800783 }
Winson Chungeecf02d2012-03-02 17:14:58 -0800784
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800785 return false;
786 }
787
Adam Cohenc1997fd2011-08-15 18:26:39 -0700788 private void clearTagCellInfo() {
789 final CellInfo cellInfo = mCellInfo;
790 cellInfo.cell = null;
791 cellInfo.cellX = -1;
792 cellInfo.cellY = -1;
793 cellInfo.spanX = 0;
794 cellInfo.spanY = 0;
795 setTag(cellInfo);
796 }
797
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800798 public CellInfo getTag() {
Michael Jurka0280c3b2010-09-17 15:00:07 -0700799 return (CellInfo) super.getTag();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800800 }
801
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700802 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700803 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800804 * @param x X coordinate of the point
805 * @param y Y coordinate of the point
806 * @param result Array of 2 ints to hold the x and y coordinate of the cell
807 */
808 void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700809 final int hStartPadding = getPaddingLeft();
810 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800811
812 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
813 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
814
Adam Cohend22015c2010-07-26 22:02:18 -0700815 final int xAxis = mCountX;
816 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800817
818 if (result[0] < 0) result[0] = 0;
819 if (result[0] >= xAxis) result[0] = xAxis - 1;
820 if (result[1] < 0) result[1] = 0;
821 if (result[1] >= yAxis) result[1] = yAxis - 1;
822 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700823
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800824 /**
825 * Given a point, return the cell that most closely encloses that point
826 * @param x X coordinate of the point
827 * @param y Y coordinate of the point
828 * @param result Array of 2 ints to hold the x and y coordinate of the cell
829 */
830 void pointToCellRounded(int x, int y, int[] result) {
831 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
832 }
833
834 /**
835 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700836 *
837 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800838 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700839 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800840 * @param result Array of 2 ints to hold the x and y coordinate of the point
841 */
842 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700843 final int hStartPadding = getPaddingLeft();
844 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800845
846 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
847 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
848 }
849
Adam Cohene3e27a82011-04-15 12:07:39 -0700850 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800851 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700852 *
853 * @param cellX X coordinate of the cell
854 * @param cellY Y coordinate of the cell
855 *
856 * @param result Array of 2 ints to hold the x and y coordinate of the point
857 */
858 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700859 regionToCenterPoint(cellX, cellY, 1, 1, result);
860 }
861
862 /**
863 * Given a cell coordinate and span return the point that represents the center of the regio
864 *
865 * @param cellX X coordinate of the cell
866 * @param cellY Y coordinate of the cell
867 *
868 * @param result Array of 2 ints to hold the x and y coordinate of the point
869 */
870 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700871 final int hStartPadding = getPaddingLeft();
872 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700873 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
874 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
875 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
876 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700877 }
878
Adam Cohen19f37922012-03-21 11:59:11 -0700879 /**
880 * Given a cell coordinate and span fills out a corresponding pixel rect
881 *
882 * @param cellX X coordinate of the cell
883 * @param cellY Y coordinate of the cell
884 * @param result Rect in which to write the result
885 */
886 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
887 final int hStartPadding = getPaddingLeft();
888 final int vStartPadding = getPaddingTop();
889 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
890 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
891 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
892 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
893 }
894
Adam Cohen482ed822012-03-02 14:15:13 -0800895 public float getDistanceFromCell(float x, float y, int[] cell) {
896 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
897 float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) +
898 Math.pow(y - mTmpPoint[1], 2));
899 return distance;
900 }
901
Romain Guy84f296c2009-11-04 15:00:44 -0800902 int getCellWidth() {
903 return mCellWidth;
904 }
905
906 int getCellHeight() {
907 return mCellHeight;
908 }
909
Adam Cohend4844c32011-02-18 19:25:06 -0800910 int getWidthGap() {
911 return mWidthGap;
912 }
913
914 int getHeightGap() {
915 return mHeightGap;
916 }
917
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700918 Rect getContentRect(Rect r) {
919 if (r == null) {
920 r = new Rect();
921 }
922 int left = getPaddingLeft();
923 int top = getPaddingTop();
Michael Jurka8b805b12012-04-18 14:23:14 -0700924 int right = left + getWidth() - getPaddingLeft() - getPaddingRight();
925 int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom();
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700926 r.set(left, top, right, bottom);
927 return r;
928 }
929
Winson Chung66700732013-08-20 16:56:15 -0700930 static void getMetrics(Rect metrics, int paddedMeasureWidth,
931 int paddedMeasureHeight, int countX, int countY) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700932 LauncherAppState app = LauncherAppState.getInstance();
933 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Winson Chung66700732013-08-20 16:56:15 -0700934 metrics.set(grid.calculateCellWidth(paddedMeasureWidth, countX),
935 grid.calculateCellHeight(paddedMeasureHeight, countY), 0, 0);
Adam Cohenf4bd5792012-04-27 11:35:29 -0700936 }
937
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700938 public void setFixedSize(int width, int height) {
939 mFixedWidth = width;
940 mFixedHeight = height;
941 }
942
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800943 @Override
944 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700945 LauncherAppState app = LauncherAppState.getInstance();
946 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
947
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800948 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800949 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700950 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
951 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700952 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
953 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700954 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700955 int cw = grid.calculateCellWidth(childWidthSize, mCountX);
956 int ch = grid.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700957 if (cw != mCellWidth || ch != mCellHeight) {
958 mCellWidth = cw;
959 mCellHeight = ch;
960 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
961 mHeightGap, mCountX, mCountY);
962 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700963 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700964
Winson Chung2d75f122013-09-23 16:53:31 -0700965 int newWidth = childWidthSize;
966 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700967 if (mFixedWidth > 0 && mFixedHeight > 0) {
968 newWidth = mFixedWidth;
969 newHeight = mFixedHeight;
970 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800971 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
972 }
973
Adam Cohend22015c2010-07-26 22:02:18 -0700974 int numWidthGaps = mCountX - 1;
975 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800976
Adam Cohen234c4cd2011-07-17 21:03:04 -0700977 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700978 int hSpace = childWidthSize;
979 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700980 int hFreeSpace = hSpace - (mCountX * mCellWidth);
981 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700982 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
983 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -0700984 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
985 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700986 } else {
987 mWidthGap = mOriginalWidthGap;
988 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700989 }
Michael Jurka8c920dd2011-01-20 14:16:56 -0800990 int count = getChildCount();
Winson Chung5f8afe62013-08-12 16:19:28 -0700991 int maxWidth = 0;
992 int maxHeight = 0;
Michael Jurka8c920dd2011-01-20 14:16:56 -0800993 for (int i = 0; i < count; i++) {
994 View child = getChildAt(i);
Winson Chung2d75f122013-09-23 16:53:31 -0700995 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth,
996 MeasureSpec.EXACTLY);
997 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight,
998 MeasureSpec.EXACTLY);
Michael Jurka8c920dd2011-01-20 14:16:56 -0800999 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -07001000 maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
1001 maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
Michael Jurka8c920dd2011-01-20 14:16:56 -08001002 }
Winson Chung2d75f122013-09-23 16:53:31 -07001003 if (mFixedWidth > 0 && mFixedHeight > 0) {
1004 setMeasuredDimension(maxWidth, maxHeight);
1005 } else {
1006 setMeasuredDimension(widthSize, heightSize);
1007 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001008 }
1009
1010 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -07001011 protected void onLayout(boolean changed, int l, int t, int r, int b) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001012 int count = getChildCount();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001013 for (int i = 0; i < count; i++) {
Michael Jurka8c920dd2011-01-20 14:16:56 -08001014 View child = getChildAt(i);
Winson Chung2d75f122013-09-23 16:53:31 -07001015 int left = getPaddingLeft();
1016 int top = getPaddingTop();
1017 child.layout(left, top,
1018 left + r - l,
1019 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001020 }
1021 }
1022
1023 @Override
Michael Jurkadee05892010-07-27 10:01:56 -07001024 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1025 super.onSizeChanged(w, h, oldw, oldh);
Michael Jurka18014792010-10-14 09:01:34 -07001026 mBackgroundRect.set(0, 0, w, h);
Adam Cohenb5ba0972011-09-07 18:02:31 -07001027 mForegroundRect.set(mForegroundPadding, mForegroundPadding,
Adam Cohen215b4162012-08-30 13:14:08 -07001028 w - mForegroundPadding, h - mForegroundPadding);
Michael Jurkadee05892010-07-27 10:01:56 -07001029 }
1030
1031 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001032 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001033 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001034 }
1035
1036 @Override
1037 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001038 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001039 }
1040
Michael Jurka5f1c5092010-09-03 14:15:02 -07001041 public float getBackgroundAlpha() {
1042 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -07001043 }
1044
Adam Cohen1b0aaac2010-10-28 11:11:18 -07001045 public void setBackgroundAlphaMultiplier(float multiplier) {
Michael Jurkaa3d30ad2012-05-08 13:43:43 -07001046 if (mBackgroundAlphaMultiplier != multiplier) {
1047 mBackgroundAlphaMultiplier = multiplier;
1048 invalidate();
1049 }
Adam Cohen1b0aaac2010-10-28 11:11:18 -07001050 }
1051
Adam Cohenddb82192010-11-10 16:32:54 -08001052 public float getBackgroundAlphaMultiplier() {
1053 return mBackgroundAlphaMultiplier;
1054 }
1055
Michael Jurka5f1c5092010-09-03 14:15:02 -07001056 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -08001057 if (mBackgroundAlpha != alpha) {
1058 mBackgroundAlpha = alpha;
1059 invalidate();
1060 }
Michael Jurkadee05892010-07-27 10:01:56 -07001061 }
1062
Michael Jurkaa52570f2012-03-20 03:18:20 -07001063 public void setShortcutAndWidgetAlpha(float alpha) {
Michael Jurka0142d492010-08-25 17:46:15 -07001064 final int childCount = getChildCount();
1065 for (int i = 0; i < childCount; i++) {
Michael Jurkadee05892010-07-27 10:01:56 -07001066 getChildAt(i).setAlpha(alpha);
1067 }
1068 }
1069
Michael Jurkaa52570f2012-03-20 03:18:20 -07001070 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
1071 if (getChildCount() > 0) {
1072 return (ShortcutAndWidgetContainer) getChildAt(0);
1073 }
1074 return null;
1075 }
1076
Patrick Dubroy440c3602010-07-13 17:50:32 -07001077 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001078 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -07001079 }
1080
Adam Cohen76fc0852011-06-17 13:26:23 -07001081 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -08001082 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001083 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -08001084 boolean[][] occupied = mOccupied;
1085 if (!permanent) {
1086 occupied = mTmpOccupied;
1087 }
1088
Adam Cohen19f37922012-03-21 11:59:11 -07001089 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001090 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1091 final ItemInfo info = (ItemInfo) child.getTag();
1092
1093 // We cancel any existing animations
1094 if (mReorderAnimators.containsKey(lp)) {
1095 mReorderAnimators.get(lp).cancel();
1096 mReorderAnimators.remove(lp);
1097 }
1098
Adam Cohen482ed822012-03-02 14:15:13 -08001099 final int oldX = lp.x;
1100 final int oldY = lp.y;
1101 if (adjustOccupied) {
1102 occupied[lp.cellX][lp.cellY] = false;
1103 occupied[cellX][cellY] = true;
1104 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001105 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001106 if (permanent) {
1107 lp.cellX = info.cellX = cellX;
1108 lp.cellY = info.cellY = cellY;
1109 } else {
1110 lp.tmpCellX = cellX;
1111 lp.tmpCellY = cellY;
1112 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001113 clc.setupLp(lp);
1114 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001115 final int newX = lp.x;
1116 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001117
Adam Cohen76fc0852011-06-17 13:26:23 -07001118 lp.x = oldX;
1119 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001120
Adam Cohen482ed822012-03-02 14:15:13 -08001121 // Exit early if we're not actually moving the view
1122 if (oldX == newX && oldY == newY) {
1123 lp.isLockedToGrid = true;
1124 return true;
1125 }
1126
Michael Jurkaf1ad6082013-03-13 12:55:46 +01001127 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001128 va.setDuration(duration);
1129 mReorderAnimators.put(lp, va);
1130
1131 va.addUpdateListener(new AnimatorUpdateListener() {
1132 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001133 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001134 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001135 lp.x = (int) ((1 - r) * oldX + r * newX);
1136 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001137 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001138 }
1139 });
Adam Cohen482ed822012-03-02 14:15:13 -08001140 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001141 boolean cancelled = false;
1142 public void onAnimationEnd(Animator animation) {
1143 // If the animation was cancelled, it means that another animation
1144 // has interrupted this one, and we don't want to lock the item into
1145 // place just yet.
1146 if (!cancelled) {
1147 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001148 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001149 }
1150 if (mReorderAnimators.containsKey(lp)) {
1151 mReorderAnimators.remove(lp);
1152 }
1153 }
1154 public void onAnimationCancel(Animator animation) {
1155 cancelled = true;
1156 }
1157 });
Adam Cohen482ed822012-03-02 14:15:13 -08001158 va.setStartDelay(delay);
1159 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001160 return true;
1161 }
1162 return false;
1163 }
1164
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001165 /**
1166 * Estimate where the top left cell of the dragged item will land if it is dropped.
1167 *
1168 * @param originX The X value of the top left corner of the item
1169 * @param originY The Y value of the top left corner of the item
1170 * @param spanX The number of horizontal cells that the item spans
1171 * @param spanY The number of vertical cells that the item spans
1172 * @param result The estimated drop cell X and Y.
1173 */
1174 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
Adam Cohend22015c2010-07-26 22:02:18 -07001175 final int countX = mCountX;
1176 final int countY = mCountY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001177
Michael Jurkaa63c4522010-08-19 13:52:27 -07001178 // pointToCellRounded takes the top left of a cell but will pad that with
1179 // cellWidth/2 and cellHeight/2 when finding the matching cell
1180 pointToCellRounded(originX, originY, result);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001181
1182 // If the item isn't fully on this screen, snap to the edges
1183 int rightOverhang = result[0] + spanX - countX;
1184 if (rightOverhang > 0) {
1185 result[0] -= rightOverhang; // Snap to right
1186 }
1187 result[0] = Math.max(0, result[0]); // Snap to left
1188 int bottomOverhang = result[1] + spanY - countY;
1189 if (bottomOverhang > 0) {
1190 result[1] -= bottomOverhang; // Snap to bottom
1191 }
1192 result[1] = Math.max(0, result[1]); // Snap to top
1193 }
1194
Adam Cohen482ed822012-03-02 14:15:13 -08001195 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1196 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001197 final int oldDragCellX = mDragCell[0];
1198 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001199
Winson Chungb8c69f32011-10-19 21:36:08 -07001200 if (v != null && dragOffset == null) {
Winson Chunga9abd0e2010-10-27 17:18:37 -07001201 mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
1202 } else {
1203 mDragCenter.set(originX, originY);
1204 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001205
Adam Cohen2801caf2011-05-13 20:57:39 -07001206 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001207 return;
1208 }
1209
Adam Cohen482ed822012-03-02 14:15:13 -08001210 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1211 mDragCell[0] = cellX;
1212 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001213 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001214 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001215 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001216
Joe Onorato4be866d2010-10-10 11:26:02 -07001217 int left = topLeft[0];
1218 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001219
Winson Chung892c74d2013-08-22 16:15:50 -07001220 // Offset icons by their padding
1221 if (v instanceof BubbleTextView) {
1222 top += v.getPaddingTop();
1223 }
1224
Winson Chungb8c69f32011-10-19 21:36:08 -07001225 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001226 // When drawing the drag outline, it did not account for margin offsets
1227 // added by the view's parent.
1228 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1229 left += lp.leftMargin;
1230 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001231
Adam Cohen99e8b402011-03-25 19:23:43 -07001232 // Offsets due to the size difference between the View and the dragOutline.
1233 // There is a size difference to account for the outer blur, which may lie
1234 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001235 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001236 // We center about the x axis
1237 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1238 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001239 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001240 if (dragOffset != null && dragRegion != null) {
1241 // Center the drag region *horizontally* in the cell and apply a drag
1242 // outline offset
1243 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1244 - dragRegion.width()) / 2;
1245 top += dragOffset.y;
1246 } else {
1247 // Center the drag outline in the cell
1248 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1249 - dragOutline.getWidth()) / 2;
1250 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1251 - dragOutline.getHeight()) / 2;
1252 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001253 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001254 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001255 mDragOutlineAnims[oldIndex].animateOut();
1256 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001257 Rect r = mDragOutlines[mDragOutlineCurrent];
1258 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1259 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001260 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001261 }
Winson Chung150fbab2010-09-29 17:14:26 -07001262
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001263 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1264 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001265 }
1266 }
1267
Adam Cohene0310962011-04-18 16:15:31 -07001268 public void clearDragOutlines() {
1269 final int oldIndex = mDragOutlineCurrent;
1270 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001271 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001272 }
1273
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001274 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001275 * Find a vacant area that will fit the given bounds nearest the requested
1276 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001277 *
Romain Guy51afc022009-05-04 18:03:43 -07001278 * @param pixelX The X location at which you want to search for a vacant area.
1279 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001280 * @param spanX Horizontal span of the object.
1281 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001282 * @param result Array in which to place the result, or null (in which case a new array will
1283 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001284 * @return The X, Y cell of a vacant area that can contain this object,
1285 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001286 */
Adam Cohend41fbf52012-02-16 23:53:59 -08001287 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
1288 int[] result) {
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001289 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001290 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001291
Michael Jurka6a1435d2010-09-27 17:35:12 -07001292 /**
1293 * Find a vacant area that will fit the given bounds nearest the requested
1294 * cell location. Uses Euclidean distance to score multiple vacant areas.
1295 *
1296 * @param pixelX The X location at which you want to search for a vacant area.
1297 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001298 * @param minSpanX The minimum horizontal span required
1299 * @param minSpanY The minimum vertical span required
1300 * @param spanX Horizontal span of the object.
1301 * @param spanY Vertical span of the object.
1302 * @param result Array in which to place the result, or null (in which case a new array will
1303 * be allocated)
1304 * @return The X, Y cell of a vacant area that can contain this object,
1305 * nearest the requested location.
1306 */
1307 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1308 int spanY, int[] result, int[] resultSpan) {
1309 return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
1310 result, resultSpan);
1311 }
1312
1313 /**
1314 * Find a vacant area that will fit the given bounds nearest the requested
1315 * cell location. Uses Euclidean distance to score multiple vacant areas.
1316 *
1317 * @param pixelX The X location at which you want to search for a vacant area.
1318 * @param pixelY The Y location at which you want to search for a vacant area.
Michael Jurka6a1435d2010-09-27 17:35:12 -07001319 * @param spanX Horizontal span of the object.
1320 * @param spanY Vertical span of the object.
Adam Cohendf035382011-04-11 17:22:04 -07001321 * @param ignoreOccupied If true, the result can be an occupied cell
1322 * @param result Array in which to place the result, or null (in which case a new array will
1323 * be allocated)
Michael Jurka6a1435d2010-09-27 17:35:12 -07001324 * @return The X, Y cell of a vacant area that can contain this object,
1325 * nearest the requested location.
1326 */
Adam Cohendf035382011-04-11 17:22:04 -07001327 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
1328 boolean ignoreOccupied, int[] result) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001329 return findNearestArea(pixelX, pixelY, spanX, spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001330 spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08001331 }
1332
1333 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1334 private void lazyInitTempRectStack() {
1335 if (mTempRectStack.isEmpty()) {
1336 for (int i = 0; i < mCountX * mCountY; i++) {
1337 mTempRectStack.push(new Rect());
1338 }
1339 }
1340 }
Adam Cohen482ed822012-03-02 14:15:13 -08001341
Adam Cohend41fbf52012-02-16 23:53:59 -08001342 private void recycleTempRects(Stack<Rect> used) {
1343 while (!used.isEmpty()) {
1344 mTempRectStack.push(used.pop());
1345 }
1346 }
1347
1348 /**
1349 * Find a vacant area that will fit the given bounds nearest the requested
1350 * cell location. Uses Euclidean distance to score multiple vacant areas.
1351 *
1352 * @param pixelX The X location at which you want to search for a vacant area.
1353 * @param pixelY The Y location at which you want to search for a vacant area.
1354 * @param minSpanX The minimum horizontal span required
1355 * @param minSpanY The minimum vertical span required
1356 * @param spanX Horizontal span of the object.
1357 * @param spanY Vertical span of the object.
1358 * @param ignoreOccupied If true, the result can be an occupied cell
1359 * @param result Array in which to place the result, or null (in which case a new array will
1360 * be allocated)
1361 * @return The X, Y cell of a vacant area that can contain this object,
1362 * nearest the requested location.
1363 */
1364 int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001365 View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
1366 boolean[][] occupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001367 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001368 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08001369 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001370
Adam Cohene3e27a82011-04-15 12:07:39 -07001371 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1372 // to the center of the item, but we are searching based on the top-left cell, so
1373 // we translate the point over to correspond to the top-left.
1374 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1375 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1376
Jeff Sharkey70864282009-04-07 21:08:40 -07001377 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001378 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001379 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001380 final Rect bestRect = new Rect(-1, -1, -1, -1);
1381 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001382
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001383 final int countX = mCountX;
1384 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001385
Adam Cohend41fbf52012-02-16 23:53:59 -08001386 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1387 spanX < minSpanX || spanY < minSpanY) {
1388 return bestXY;
1389 }
1390
1391 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001392 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001393 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1394 int ySize = -1;
1395 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001396 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001397 // First, let's see if this thing fits anywhere
1398 for (int i = 0; i < minSpanX; i++) {
1399 for (int j = 0; j < minSpanY; j++) {
Adam Cohendf035382011-04-11 17:22:04 -07001400 if (occupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001401 continue inner;
1402 }
Michael Jurkac28de512010-08-13 11:27:44 -07001403 }
1404 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001405 xSize = minSpanX;
1406 ySize = minSpanY;
1407
1408 // We know that the item will fit at _some_ acceptable size, now let's see
1409 // how big we can make it. We'll alternate between incrementing x and y spans
1410 // until we hit a limit.
1411 boolean incX = true;
1412 boolean hitMaxX = xSize >= spanX;
1413 boolean hitMaxY = ySize >= spanY;
1414 while (!(hitMaxX && hitMaxY)) {
1415 if (incX && !hitMaxX) {
1416 for (int j = 0; j < ySize; j++) {
1417 if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
1418 // We can't move out horizontally
1419 hitMaxX = true;
1420 }
1421 }
1422 if (!hitMaxX) {
1423 xSize++;
1424 }
1425 } else if (!hitMaxY) {
1426 for (int i = 0; i < xSize; i++) {
1427 if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
1428 // We can't move out vertically
1429 hitMaxY = true;
1430 }
1431 }
1432 if (!hitMaxY) {
1433 ySize++;
1434 }
1435 }
1436 hitMaxX |= xSize >= spanX;
1437 hitMaxY |= ySize >= spanY;
1438 incX = !incX;
1439 }
1440 incX = true;
1441 hitMaxX = xSize >= spanX;
1442 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001443 }
Winson Chung0be025d2011-05-23 17:45:09 -07001444 final int[] cellXY = mTmpXY;
Adam Cohene3e27a82011-04-15 12:07:39 -07001445 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001446
Adam Cohend41fbf52012-02-16 23:53:59 -08001447 // We verify that the current rect is not a sub-rect of any of our previous
1448 // candidates. In this case, the current rect is disqualified in favour of the
1449 // containing rect.
1450 Rect currentRect = mTempRectStack.pop();
1451 currentRect.set(x, y, x + xSize, y + ySize);
1452 boolean contained = false;
1453 for (Rect r : validRegions) {
1454 if (r.contains(currentRect)) {
1455 contained = true;
1456 break;
1457 }
1458 }
1459 validRegions.push(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001460 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
1461 + Math.pow(cellXY[1] - pixelY, 2));
Adam Cohen482ed822012-03-02 14:15:13 -08001462
Adam Cohend41fbf52012-02-16 23:53:59 -08001463 if ((distance <= bestDistance && !contained) ||
1464 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001465 bestDistance = distance;
1466 bestXY[0] = x;
1467 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001468 if (resultSpan != null) {
1469 resultSpan[0] = xSize;
1470 resultSpan[1] = ySize;
1471 }
1472 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001473 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001474 }
1475 }
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001476 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08001477 markCellsAsOccupiedForView(ignoreView, occupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001478
Adam Cohenc0dcf592011-06-01 15:30:43 -07001479 // Return -1, -1 if no suitable location found
1480 if (bestDistance == Double.MAX_VALUE) {
1481 bestXY[0] = -1;
1482 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001483 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001484 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001485 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001486 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001487
Adam Cohen482ed822012-03-02 14:15:13 -08001488 /**
1489 * Find a vacant area that will fit the given bounds nearest the requested
1490 * cell location, and will also weigh in a suggested direction vector of the
1491 * desired location. This method computers distance based on unit grid distances,
1492 * not pixel distances.
1493 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001494 * @param cellX The X cell nearest to which you want to search for a vacant area.
1495 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001496 * @param spanX Horizontal span of the object.
1497 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001498 * @param direction The favored direction in which the views should move from x, y
1499 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1500 * matches exactly. Otherwise we find the best matching direction.
1501 * @param occoupied The array which represents which cells in the CellLayout are occupied
1502 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001503 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001504 * @param result Array in which to place the result, or null (in which case a new array will
1505 * be allocated)
1506 * @return The X, Y cell of a vacant area that can contain this object,
1507 * nearest the requested location.
1508 */
1509 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001510 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001511 // Keep track of best-scoring drop area
1512 final int[] bestXY = result != null ? result : new int[2];
1513 float bestDistance = Float.MAX_VALUE;
1514 int bestDirectionScore = Integer.MIN_VALUE;
1515
1516 final int countX = mCountX;
1517 final int countY = mCountY;
1518
1519 for (int y = 0; y < countY - (spanY - 1); y++) {
1520 inner:
1521 for (int x = 0; x < countX - (spanX - 1); x++) {
1522 // First, let's see if this thing fits anywhere
1523 for (int i = 0; i < spanX; i++) {
1524 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001525 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001526 continue inner;
1527 }
1528 }
1529 }
1530
1531 float distance = (float)
1532 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1533 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001534 computeDirectionVector(x - cellX, y - cellY, curDirection);
1535 // The direction score is just the dot product of the two candidate direction
1536 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001537 int curDirectionScore = direction[0] * curDirection[0] +
1538 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001539 boolean exactDirectionOnly = false;
1540 boolean directionMatches = direction[0] == curDirection[0] &&
1541 direction[0] == curDirection[0];
1542 if ((directionMatches || !exactDirectionOnly) &&
1543 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001544 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1545 bestDistance = distance;
1546 bestDirectionScore = curDirectionScore;
1547 bestXY[0] = x;
1548 bestXY[1] = y;
1549 }
1550 }
1551 }
1552
1553 // Return -1, -1 if no suitable location found
1554 if (bestDistance == Float.MAX_VALUE) {
1555 bestXY[0] = -1;
1556 bestXY[1] = -1;
1557 }
1558 return bestXY;
1559 }
1560
1561 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001562 int[] direction, ItemConfiguration currentState) {
1563 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001564 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001565 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001566 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1567
Adam Cohen8baab352012-03-20 17:39:21 -07001568 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001569
1570 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001571 c.x = mTempLocation[0];
1572 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001573 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001574 }
Adam Cohen8baab352012-03-20 17:39:21 -07001575 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001576 return success;
1577 }
1578
Adam Cohenf3900c22012-11-16 18:28:11 -08001579 /**
1580 * This helper class defines a cluster of views. It helps with defining complex edges
1581 * of the cluster and determining how those edges interact with other views. The edges
1582 * essentially define a fine-grained boundary around the cluster of views -- like a more
1583 * precise version of a bounding box.
1584 */
1585 private class ViewCluster {
1586 final static int LEFT = 0;
1587 final static int TOP = 1;
1588 final static int RIGHT = 2;
1589 final static int BOTTOM = 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001590
Adam Cohenf3900c22012-11-16 18:28:11 -08001591 ArrayList<View> views;
1592 ItemConfiguration config;
1593 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001594
Adam Cohenf3900c22012-11-16 18:28:11 -08001595 int[] leftEdge = new int[mCountY];
1596 int[] rightEdge = new int[mCountY];
1597 int[] topEdge = new int[mCountX];
1598 int[] bottomEdge = new int[mCountX];
1599 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1600
1601 @SuppressWarnings("unchecked")
1602 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1603 this.views = (ArrayList<View>) views.clone();
1604 this.config = config;
1605 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001606 }
1607
Adam Cohenf3900c22012-11-16 18:28:11 -08001608 void resetEdges() {
1609 for (int i = 0; i < mCountX; i++) {
1610 topEdge[i] = -1;
1611 bottomEdge[i] = -1;
1612 }
1613 for (int i = 0; i < mCountY; i++) {
1614 leftEdge[i] = -1;
1615 rightEdge[i] = -1;
1616 }
1617 leftEdgeDirty = true;
1618 rightEdgeDirty = true;
1619 bottomEdgeDirty = true;
1620 topEdgeDirty = true;
1621 boundingRectDirty = true;
1622 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001623
Adam Cohenf3900c22012-11-16 18:28:11 -08001624 void computeEdge(int which, int[] edge) {
1625 int count = views.size();
1626 for (int i = 0; i < count; i++) {
1627 CellAndSpan cs = config.map.get(views.get(i));
1628 switch (which) {
1629 case LEFT:
1630 int left = cs.x;
1631 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1632 if (left < edge[j] || edge[j] < 0) {
1633 edge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001634 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001635 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001636 break;
1637 case RIGHT:
1638 int right = cs.x + cs.spanX;
1639 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1640 if (right > edge[j]) {
1641 edge[j] = right;
1642 }
1643 }
1644 break;
1645 case TOP:
1646 int top = cs.y;
1647 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1648 if (top < edge[j] || edge[j] < 0) {
1649 edge[j] = top;
1650 }
1651 }
1652 break;
1653 case BOTTOM:
1654 int bottom = cs.y + cs.spanY;
1655 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1656 if (bottom > edge[j]) {
1657 edge[j] = bottom;
1658 }
1659 }
1660 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001661 }
1662 }
1663 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001664
1665 boolean isViewTouchingEdge(View v, int whichEdge) {
1666 CellAndSpan cs = config.map.get(v);
1667
1668 int[] edge = getEdge(whichEdge);
1669
1670 switch (whichEdge) {
1671 case LEFT:
1672 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1673 if (edge[i] == cs.x + cs.spanX) {
1674 return true;
1675 }
1676 }
1677 break;
1678 case RIGHT:
1679 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1680 if (edge[i] == cs.x) {
1681 return true;
1682 }
1683 }
1684 break;
1685 case TOP:
1686 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1687 if (edge[i] == cs.y + cs.spanY) {
1688 return true;
1689 }
1690 }
1691 break;
1692 case BOTTOM:
1693 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1694 if (edge[i] == cs.y) {
1695 return true;
1696 }
1697 }
1698 break;
1699 }
1700 return false;
1701 }
1702
1703 void shift(int whichEdge, int delta) {
1704 for (View v: views) {
1705 CellAndSpan c = config.map.get(v);
1706 switch (whichEdge) {
1707 case LEFT:
1708 c.x -= delta;
1709 break;
1710 case RIGHT:
1711 c.x += delta;
1712 break;
1713 case TOP:
1714 c.y -= delta;
1715 break;
1716 case BOTTOM:
1717 default:
1718 c.y += delta;
1719 break;
1720 }
1721 }
1722 resetEdges();
1723 }
1724
1725 public void addView(View v) {
1726 views.add(v);
1727 resetEdges();
1728 }
1729
1730 public Rect getBoundingRect() {
1731 if (boundingRectDirty) {
1732 boolean first = true;
1733 for (View v: views) {
1734 CellAndSpan c = config.map.get(v);
1735 if (first) {
1736 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1737 first = false;
1738 } else {
1739 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1740 }
1741 }
1742 }
1743 return boundingRect;
1744 }
1745
1746 public int[] getEdge(int which) {
1747 switch (which) {
1748 case LEFT:
1749 return getLeftEdge();
1750 case RIGHT:
1751 return getRightEdge();
1752 case TOP:
1753 return getTopEdge();
1754 case BOTTOM:
1755 default:
1756 return getBottomEdge();
1757 }
1758 }
1759
1760 public int[] getLeftEdge() {
1761 if (leftEdgeDirty) {
1762 computeEdge(LEFT, leftEdge);
1763 }
1764 return leftEdge;
1765 }
1766
1767 public int[] getRightEdge() {
1768 if (rightEdgeDirty) {
1769 computeEdge(RIGHT, rightEdge);
1770 }
1771 return rightEdge;
1772 }
1773
1774 public int[] getTopEdge() {
1775 if (topEdgeDirty) {
1776 computeEdge(TOP, topEdge);
1777 }
1778 return topEdge;
1779 }
1780
1781 public int[] getBottomEdge() {
1782 if (bottomEdgeDirty) {
1783 computeEdge(BOTTOM, bottomEdge);
1784 }
1785 return bottomEdge;
1786 }
1787
1788 PositionComparator comparator = new PositionComparator();
1789 class PositionComparator implements Comparator<View> {
1790 int whichEdge = 0;
1791 public int compare(View left, View right) {
1792 CellAndSpan l = config.map.get(left);
1793 CellAndSpan r = config.map.get(right);
1794 switch (whichEdge) {
1795 case LEFT:
1796 return (r.x + r.spanX) - (l.x + l.spanX);
1797 case RIGHT:
1798 return l.x - r.x;
1799 case TOP:
1800 return (r.y + r.spanY) - (l.y + l.spanY);
1801 case BOTTOM:
1802 default:
1803 return l.y - r.y;
1804 }
1805 }
1806 }
1807
1808 public void sortConfigurationForEdgePush(int edge) {
1809 comparator.whichEdge = edge;
1810 Collections.sort(config.sortedViews, comparator);
1811 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001812 }
1813
Adam Cohenf3900c22012-11-16 18:28:11 -08001814 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1815 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001816
Adam Cohenf3900c22012-11-16 18:28:11 -08001817 ViewCluster cluster = new ViewCluster(views, currentState);
1818 Rect clusterRect = cluster.getBoundingRect();
1819 int whichEdge;
1820 int pushDistance;
1821 boolean fail = false;
1822
1823 // Determine the edge of the cluster that will be leading the push and how far
1824 // the cluster must be shifted.
1825 if (direction[0] < 0) {
1826 whichEdge = ViewCluster.LEFT;
1827 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001828 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001829 whichEdge = ViewCluster.RIGHT;
1830 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1831 } else if (direction[1] < 0) {
1832 whichEdge = ViewCluster.TOP;
1833 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1834 } else {
1835 whichEdge = ViewCluster.BOTTOM;
1836 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001837 }
1838
Adam Cohenf3900c22012-11-16 18:28:11 -08001839 // Break early for invalid push distance.
1840 if (pushDistance <= 0) {
1841 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001842 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001843
1844 // Mark the occupied state as false for the group of views we want to move.
1845 for (View v: views) {
1846 CellAndSpan c = currentState.map.get(v);
1847 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1848 }
1849
1850 // We save the current configuration -- if we fail to find a solution we will revert
1851 // to the initial state. The process of finding a solution modifies the configuration
1852 // in place, hence the need for revert in the failure case.
1853 currentState.save();
1854
1855 // The pushing algorithm is simplified by considering the views in the order in which
1856 // they would be pushed by the cluster. For example, if the cluster is leading with its
1857 // left edge, we consider sort the views by their right edge, from right to left.
1858 cluster.sortConfigurationForEdgePush(whichEdge);
1859
1860 while (pushDistance > 0 && !fail) {
1861 for (View v: currentState.sortedViews) {
1862 // For each view that isn't in the cluster, we see if the leading edge of the
1863 // cluster is contacting the edge of that view. If so, we add that view to the
1864 // cluster.
1865 if (!cluster.views.contains(v) && v != dragView) {
1866 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1867 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1868 if (!lp.canReorder) {
1869 // The push solution includes the all apps button, this is not viable.
1870 fail = true;
1871 break;
1872 }
1873 cluster.addView(v);
1874 CellAndSpan c = currentState.map.get(v);
1875
1876 // Adding view to cluster, mark it as not occupied.
1877 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1878 }
1879 }
1880 }
1881 pushDistance--;
1882
1883 // The cluster has been completed, now we move the whole thing over in the appropriate
1884 // direction.
1885 cluster.shift(whichEdge, 1);
1886 }
1887
1888 boolean foundSolution = false;
1889 clusterRect = cluster.getBoundingRect();
1890
1891 // Due to the nature of the algorithm, the only check required to verify a valid solution
1892 // is to ensure that completed shifted cluster lies completely within the cell layout.
1893 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1894 clusterRect.bottom <= mCountY) {
1895 foundSolution = true;
1896 } else {
1897 currentState.restore();
1898 }
1899
1900 // In either case, we set the occupied array as marked for the location of the views
1901 for (View v: cluster.views) {
1902 CellAndSpan c = currentState.map.get(v);
1903 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1904 }
1905
1906 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001907 }
1908
Adam Cohen482ed822012-03-02 14:15:13 -08001909 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001910 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001911 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001912
Adam Cohen8baab352012-03-20 17:39:21 -07001913 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001914 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001915 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001916 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001917 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001918 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001919 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001920 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001921 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001922 }
1923 }
Adam Cohen8baab352012-03-20 17:39:21 -07001924
Adam Cohen8baab352012-03-20 17:39:21 -07001925 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001926 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001927 CellAndSpan c = currentState.map.get(v);
1928 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1929 }
1930
Adam Cohen47a876d2012-03-19 13:21:41 -07001931 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1932 int top = boundingRect.top;
1933 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001934 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001935 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001936 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001937 CellAndSpan c = currentState.map.get(v);
1938 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001939 }
1940
Adam Cohen482ed822012-03-02 14:15:13 -08001941 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1942
Adam Cohenf3900c22012-11-16 18:28:11 -08001943 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1944 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001945
Adam Cohen8baab352012-03-20 17:39:21 -07001946 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001947 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001948 int deltaX = mTempLocation[0] - boundingRect.left;
1949 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001950 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001951 CellAndSpan c = currentState.map.get(v);
1952 c.x += deltaX;
1953 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001954 }
1955 success = true;
1956 }
Adam Cohen8baab352012-03-20 17:39:21 -07001957
1958 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001959 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001960 CellAndSpan c = currentState.map.get(v);
1961 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001962 }
1963 return success;
1964 }
1965
1966 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1967 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1968 }
1969
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001970 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1971 // to push items in each of the cardinal directions, in an order based on the direction vector
1972 // passed.
1973 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1974 int[] direction, View ignoreView, ItemConfiguration solution) {
1975 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001976 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001977 // separately in each of the components.
1978 int temp = direction[1];
1979 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001980
1981 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001982 ignoreView, solution)) {
1983 return true;
1984 }
1985 direction[1] = temp;
1986 temp = direction[0];
1987 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001988
1989 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001990 ignoreView, solution)) {
1991 return true;
1992 }
1993 // Revert the direction
1994 direction[0] = temp;
1995
1996 // Now we try pushing in each component of the opposite direction
1997 direction[0] *= -1;
1998 direction[1] *= -1;
1999 temp = direction[1];
2000 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08002001 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002002 ignoreView, solution)) {
2003 return true;
2004 }
2005
2006 direction[1] = temp;
2007 temp = direction[0];
2008 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08002009 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002010 ignoreView, solution)) {
2011 return true;
2012 }
2013 // revert the direction
2014 direction[0] = temp;
2015 direction[0] *= -1;
2016 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07002017
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002018 } else {
2019 // If the direction vector has a single non-zero component, we push first in the
2020 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08002021 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002022 ignoreView, solution)) {
2023 return true;
2024 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002025 // Then we try the opposite direction
2026 direction[0] *= -1;
2027 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08002028 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002029 ignoreView, solution)) {
2030 return true;
2031 }
2032 // Switch the direction back
2033 direction[0] *= -1;
2034 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07002035
2036 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002037 // to find a solution by pushing along the perpendicular axis.
2038
2039 // Swap the components
2040 int temp = direction[1];
2041 direction[1] = direction[0];
2042 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08002043 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002044 ignoreView, solution)) {
2045 return true;
2046 }
2047
2048 // Then we try the opposite direction
2049 direction[0] *= -1;
2050 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08002051 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002052 ignoreView, solution)) {
2053 return true;
2054 }
2055 // Switch the direction back
2056 direction[0] *= -1;
2057 direction[1] *= -1;
2058
2059 // Swap the components back
2060 temp = direction[1];
2061 direction[1] = direction[0];
2062 direction[0] = temp;
2063 }
2064 return false;
2065 }
2066
Adam Cohen482ed822012-03-02 14:15:13 -08002067 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07002068 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07002069 // Return early if get invalid cell positions
2070 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08002071
Adam Cohen8baab352012-03-20 17:39:21 -07002072 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08002073 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08002074
Adam Cohen8baab352012-03-20 17:39:21 -07002075 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08002076 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07002077 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07002078 if (c != null) {
2079 c.x = cellX;
2080 c.y = cellY;
2081 }
Adam Cohen482ed822012-03-02 14:15:13 -08002082 }
Adam Cohen482ed822012-03-02 14:15:13 -08002083 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2084 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07002085 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08002086 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002087 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002088 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002089 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08002090 if (Rect.intersects(r0, r1)) {
2091 if (!lp.canReorder) {
2092 return false;
2093 }
2094 mIntersectingViews.add(child);
2095 }
2096 }
Adam Cohen47a876d2012-03-19 13:21:41 -07002097
Winson Chung5f8afe62013-08-12 16:19:28 -07002098 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002099 // we try to find a solution such that no displaced item travels through another item
2100 // without also displacing that item.
2101 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07002102 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07002103 return true;
2104 }
Adam Cohen47a876d2012-03-19 13:21:41 -07002105
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002106 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08002107 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07002108 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002109 return true;
2110 }
Adam Cohen47a876d2012-03-19 13:21:41 -07002111
Adam Cohen482ed822012-03-02 14:15:13 -08002112 // Ok, they couldn't move as a block, let's move them individually
2113 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07002114 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002115 return false;
2116 }
2117 }
2118 return true;
2119 }
2120
2121 /*
2122 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
2123 * the provided point and the provided cell
2124 */
Adam Cohen47a876d2012-03-19 13:21:41 -07002125 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08002126 double angle = Math.atan(((float) deltaY) / deltaX);
2127
2128 result[0] = 0;
2129 result[1] = 0;
2130 if (Math.abs(Math.cos(angle)) > 0.5f) {
2131 result[0] = (int) Math.signum(deltaX);
2132 }
2133 if (Math.abs(Math.sin(angle)) > 0.5f) {
2134 result[1] = (int) Math.signum(deltaY);
2135 }
2136 }
2137
Adam Cohen8baab352012-03-20 17:39:21 -07002138 private void copyOccupiedArray(boolean[][] occupied) {
2139 for (int i = 0; i < mCountX; i++) {
2140 for (int j = 0; j < mCountY; j++) {
2141 occupied[i][j] = mOccupied[i][j];
2142 }
2143 }
2144 }
2145
Adam Cohen482ed822012-03-02 14:15:13 -08002146 ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
2147 int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07002148 // Copy the current state into the solution. This solution will be manipulated as necessary.
2149 copyCurrentStateToSolution(solution, false);
2150 // Copy the current occupied array into the temporary occupied array. This array will be
2151 // manipulated as necessary to find a solution.
2152 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08002153
2154 // We find the nearest cell into which we would place the dragged item, assuming there's
2155 // nothing in its way.
2156 int result[] = new int[2];
2157 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2158
2159 boolean success = false;
2160 // First we try the exact nearest position of the item being dragged,
2161 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07002162 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
2163 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002164
2165 if (!success) {
2166 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
2167 // x, then 1 in y etc.
2168 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
2169 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
2170 dragView, false, solution);
2171 } else if (spanY > minSpanY) {
2172 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
2173 dragView, true, solution);
2174 }
2175 solution.isSolution = false;
2176 } else {
2177 solution.isSolution = true;
2178 solution.dragViewX = result[0];
2179 solution.dragViewY = result[1];
2180 solution.dragViewSpanX = spanX;
2181 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002182 }
2183 return solution;
2184 }
2185
2186 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002187 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002188 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002189 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002190 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002191 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08002192 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07002193 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002194 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07002195 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002196 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002197 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08002198 }
2199 }
2200
2201 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
2202 for (int i = 0; i < mCountX; i++) {
2203 for (int j = 0; j < mCountY; j++) {
2204 mTmpOccupied[i][j] = false;
2205 }
2206 }
2207
Michael Jurkaa52570f2012-03-20 03:18:20 -07002208 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002209 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002210 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002211 if (child == dragView) continue;
2212 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002213 CellAndSpan c = solution.map.get(child);
2214 if (c != null) {
2215 lp.tmpCellX = c.x;
2216 lp.tmpCellY = c.y;
2217 lp.cellHSpan = c.spanX;
2218 lp.cellVSpan = c.spanY;
2219 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002220 }
2221 }
2222 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2223 solution.dragViewSpanY, mTmpOccupied, true);
2224 }
2225
2226 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2227 commitDragView) {
2228
2229 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2230 for (int i = 0; i < mCountX; i++) {
2231 for (int j = 0; j < mCountY; j++) {
2232 occupied[i][j] = false;
2233 }
2234 }
2235
Michael Jurkaa52570f2012-03-20 03:18:20 -07002236 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002237 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002238 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002239 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002240 CellAndSpan c = solution.map.get(child);
2241 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002242 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2243 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002244 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002245 }
2246 }
2247 if (commitDragView) {
2248 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2249 solution.dragViewSpanY, occupied, true);
2250 }
2251 }
2252
Adam Cohen19f37922012-03-21 11:59:11 -07002253 // This method starts or changes the reorder hint animations
2254 private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
2255 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002256 for (int i = 0; i < childCount; i++) {
2257 View child = mShortcutsAndWidgets.getChildAt(i);
2258 if (child == dragView) continue;
2259 CellAndSpan c = solution.map.get(child);
2260 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2261 if (c != null) {
2262 ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
2263 c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002264 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002265 }
2266 }
2267 }
2268
2269 // Class which represents the reorder hint animations. These animations show that an item is
2270 // in a temporary state, and hint at where the item will return to.
2271 class ReorderHintAnimation {
2272 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002273 float finalDeltaX;
2274 float finalDeltaY;
2275 float initDeltaX;
2276 float initDeltaY;
2277 float finalScale;
2278 float initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002279 private static final int DURATION = 300;
Adam Cohene7587d22012-05-24 18:50:02 -07002280 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002281
2282 public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
2283 int spanX, int spanY) {
2284 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2285 final int x0 = mTmpPoint[0];
2286 final int y0 = mTmpPoint[1];
2287 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2288 final int x1 = mTmpPoint[0];
2289 final int y1 = mTmpPoint[1];
2290 final int dX = x1 - x0;
2291 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002292 finalDeltaX = 0;
2293 finalDeltaY = 0;
Adam Cohen19f37922012-03-21 11:59:11 -07002294 if (dX == dY && dX == 0) {
2295 } else {
2296 if (dY == 0) {
Adam Cohend024f982012-05-23 18:26:45 -07002297 finalDeltaX = - Math.signum(dX) * mReorderHintAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002298 } else if (dX == 0) {
Adam Cohend024f982012-05-23 18:26:45 -07002299 finalDeltaY = - Math.signum(dY) * mReorderHintAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002300 } else {
2301 double angle = Math.atan( (float) (dY) / dX);
Adam Cohend024f982012-05-23 18:26:45 -07002302 finalDeltaX = (int) (- Math.signum(dX) *
Adam Cohenfe41ac62012-05-23 14:00:37 -07002303 Math.abs(Math.cos(angle) * mReorderHintAnimationMagnitude));
Adam Cohend024f982012-05-23 18:26:45 -07002304 finalDeltaY = (int) (- Math.signum(dY) *
Adam Cohenfe41ac62012-05-23 14:00:37 -07002305 Math.abs(Math.sin(angle) * mReorderHintAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002306 }
2307 }
Adam Cohend024f982012-05-23 18:26:45 -07002308 initDeltaX = child.getTranslationX();
2309 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002310 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002311 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002312 this.child = child;
2313 }
2314
Adam Cohend024f982012-05-23 18:26:45 -07002315 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002316 if (mShakeAnimators.containsKey(child)) {
2317 ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002318 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002319 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002320 if (finalDeltaX == 0 && finalDeltaY == 0) {
2321 completeAnimationImmediately();
2322 return;
2323 }
Adam Cohen19f37922012-03-21 11:59:11 -07002324 }
Adam Cohend024f982012-05-23 18:26:45 -07002325 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002326 return;
2327 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002328 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002329 a = va;
Adam Cohen19f37922012-03-21 11:59:11 -07002330 va.setRepeatMode(ValueAnimator.REVERSE);
2331 va.setRepeatCount(ValueAnimator.INFINITE);
Adam Cohen7bdfc972012-05-22 16:50:35 -07002332 va.setDuration(DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002333 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002334 va.addUpdateListener(new AnimatorUpdateListener() {
2335 @Override
2336 public void onAnimationUpdate(ValueAnimator animation) {
2337 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohend024f982012-05-23 18:26:45 -07002338 float x = r * finalDeltaX + (1 - r) * initDeltaX;
2339 float y = r * finalDeltaY + (1 - r) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002340 child.setTranslationX(x);
2341 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002342 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002343 child.setScaleX(s);
2344 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002345 }
2346 });
2347 va.addListener(new AnimatorListenerAdapter() {
2348 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002349 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002350 initDeltaX = 0;
2351 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002352 initScale = getChildrenScale();
Adam Cohen19f37922012-03-21 11:59:11 -07002353 }
2354 });
Adam Cohen19f37922012-03-21 11:59:11 -07002355 mShakeAnimators.put(child, this);
2356 va.start();
2357 }
2358
Adam Cohend024f982012-05-23 18:26:45 -07002359 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002360 if (a != null) {
2361 a.cancel();
2362 }
Adam Cohen19f37922012-03-21 11:59:11 -07002363 }
Adam Cohene7587d22012-05-24 18:50:02 -07002364
Brandon Keely50e6e562012-05-08 16:28:49 -07002365 private void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002366 if (a != null) {
2367 a.cancel();
2368 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002369
Michael Jurka2ecf9952012-06-18 12:52:28 -07002370 AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002371 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002372 s.playTogether(
Adam Cohen307fe232012-08-16 17:55:58 -07002373 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
2374 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
Michael Jurka2ecf9952012-06-18 12:52:28 -07002375 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2376 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002377 );
2378 s.setDuration(REORDER_ANIMATION_DURATION);
2379 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2380 s.start();
2381 }
Adam Cohen19f37922012-03-21 11:59:11 -07002382 }
2383
2384 private void completeAndClearReorderHintAnimations() {
2385 for (ReorderHintAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002386 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002387 }
2388 mShakeAnimators.clear();
2389 }
2390
Adam Cohen482ed822012-03-02 14:15:13 -08002391 private void commitTempPlacement() {
2392 for (int i = 0; i < mCountX; i++) {
2393 for (int j = 0; j < mCountY; j++) {
2394 mOccupied[i][j] = mTmpOccupied[i][j];
2395 }
2396 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002397 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002398 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002399 View child = mShortcutsAndWidgets.getChildAt(i);
2400 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2401 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002402 // We do a null check here because the item info can be null in the case of the
2403 // AllApps button in the hotseat.
2404 if (info != null) {
Adam Cohen487f7dd2012-06-28 18:12:10 -07002405 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
2406 info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
2407 info.requiresDbUpdate = true;
2408 }
Adam Cohen2acce882012-03-28 19:03:19 -07002409 info.cellX = lp.cellX = lp.tmpCellX;
2410 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002411 info.spanX = lp.cellHSpan;
2412 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002413 }
Adam Cohen482ed822012-03-02 14:15:13 -08002414 }
Adam Cohen2acce882012-03-28 19:03:19 -07002415 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002416 }
2417
2418 public void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002419 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002420 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002421 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002422 lp.useTmpCoords = useTempCoords;
2423 }
2424 }
2425
Adam Cohen482ed822012-03-02 14:15:13 -08002426 ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
2427 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2428 int[] result = new int[2];
2429 int[] resultSpan = new int[2];
2430 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
2431 resultSpan);
2432 if (result[0] >= 0 && result[1] >= 0) {
2433 copyCurrentStateToSolution(solution, false);
2434 solution.dragViewX = result[0];
2435 solution.dragViewY = result[1];
2436 solution.dragViewSpanX = resultSpan[0];
2437 solution.dragViewSpanY = resultSpan[1];
2438 solution.isSolution = true;
2439 } else {
2440 solution.isSolution = false;
2441 }
2442 return solution;
2443 }
2444
2445 public void prepareChildForDrag(View child) {
2446 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002447 }
2448
Adam Cohen19f37922012-03-21 11:59:11 -07002449 /* This seems like it should be obvious and straight-forward, but when the direction vector
2450 needs to match with the notion of the dragView pushing other views, we have to employ
2451 a slightly more subtle notion of the direction vector. The question is what two points is
2452 the vector between? The center of the dragView and its desired destination? Not quite, as
2453 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2454 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2455 or right, which helps make pushing feel right.
2456 */
2457 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2458 int spanY, View dragView, int[] resultDirection) {
2459 int[] targetDestination = new int[2];
2460
2461 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2462 Rect dragRect = new Rect();
2463 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2464 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2465
2466 Rect dropRegionRect = new Rect();
2467 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2468 dragView, dropRegionRect, mIntersectingViews);
2469
2470 int dropRegionSpanX = dropRegionRect.width();
2471 int dropRegionSpanY = dropRegionRect.height();
2472
2473 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2474 dropRegionRect.height(), dropRegionRect);
2475
2476 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2477 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2478
2479 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2480 deltaX = 0;
2481 }
2482 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2483 deltaY = 0;
2484 }
2485
2486 if (deltaX == 0 && deltaY == 0) {
2487 // No idea what to do, give a random direction.
2488 resultDirection[0] = 1;
2489 resultDirection[1] = 0;
2490 } else {
2491 computeDirectionVector(deltaX, deltaY, resultDirection);
2492 }
2493 }
2494
2495 // For a given cell and span, fetch the set of views intersecting the region.
2496 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2497 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2498 if (boundingRect != null) {
2499 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2500 }
2501 intersectingViews.clear();
2502 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2503 Rect r1 = new Rect();
2504 final int count = mShortcutsAndWidgets.getChildCount();
2505 for (int i = 0; i < count; i++) {
2506 View child = mShortcutsAndWidgets.getChildAt(i);
2507 if (child == dragView) continue;
2508 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2509 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2510 if (Rect.intersects(r0, r1)) {
2511 mIntersectingViews.add(child);
2512 if (boundingRect != null) {
2513 boundingRect.union(r1);
2514 }
2515 }
2516 }
2517 }
2518
2519 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2520 View dragView, int[] result) {
2521 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2522 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2523 mIntersectingViews);
2524 return !mIntersectingViews.isEmpty();
2525 }
2526
2527 void revertTempState() {
2528 if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
2529 final int count = mShortcutsAndWidgets.getChildCount();
2530 for (int i = 0; i < count; i++) {
2531 View child = mShortcutsAndWidgets.getChildAt(i);
2532 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2533 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2534 lp.tmpCellX = lp.cellX;
2535 lp.tmpCellY = lp.cellY;
2536 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2537 0, false, false);
2538 }
2539 }
2540 completeAndClearReorderHintAnimations();
2541 setItemPlacementDirty(false);
2542 }
2543
Adam Cohenbebf0422012-04-11 18:06:28 -07002544 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2545 View dragView, int[] direction, boolean commit) {
2546 int[] pixelXY = new int[2];
2547 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2548
2549 // First we determine if things have moved enough to cause a different layout
2550 ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY,
2551 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2552
2553 setUseTempCoords(true);
2554 if (swapSolution != null && swapSolution.isSolution) {
2555 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2556 // committing anything or animating anything as we just want to determine if a solution
2557 // exists
2558 copySolutionToTempState(swapSolution, dragView);
2559 setItemPlacementDirty(true);
2560 animateItemsToSolution(swapSolution, dragView, commit);
2561
2562 if (commit) {
2563 commitTempPlacement();
2564 completeAndClearReorderHintAnimations();
2565 setItemPlacementDirty(false);
2566 } else {
2567 beginOrAdjustHintAnimations(swapSolution, dragView,
2568 REORDER_ANIMATION_DURATION);
2569 }
2570 mShortcutsAndWidgets.requestLayout();
2571 }
2572 return swapSolution.isSolution;
2573 }
2574
Adam Cohen482ed822012-03-02 14:15:13 -08002575 int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
2576 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002577 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002578 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002579
2580 if (resultSpan == null) {
2581 resultSpan = new int[2];
2582 }
2583
Adam Cohen19f37922012-03-21 11:59:11 -07002584 // When we are checking drop validity or actually dropping, we don't recompute the
2585 // direction vector, since we want the solution to match the preview, and it's possible
2586 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002587 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2588 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002589 mDirectionVector[0] = mPreviousReorderDirection[0];
2590 mDirectionVector[1] = mPreviousReorderDirection[1];
2591 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002592 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2593 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2594 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002595 }
2596 } else {
2597 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2598 mPreviousReorderDirection[0] = mDirectionVector[0];
2599 mPreviousReorderDirection[1] = mDirectionVector[1];
2600 }
2601
Adam Cohen482ed822012-03-02 14:15:13 -08002602 ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
2603 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2604
2605 // We attempt the approach which doesn't shuffle views at all
2606 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2607 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2608
2609 ItemConfiguration finalSolution = null;
2610 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2611 finalSolution = swapSolution;
2612 } else if (noShuffleSolution.isSolution) {
2613 finalSolution = noShuffleSolution;
2614 }
2615
2616 boolean foundSolution = true;
2617 if (!DESTRUCTIVE_REORDER) {
2618 setUseTempCoords(true);
2619 }
2620
2621 if (finalSolution != null) {
2622 result[0] = finalSolution.dragViewX;
2623 result[1] = finalSolution.dragViewY;
2624 resultSpan[0] = finalSolution.dragViewSpanX;
2625 resultSpan[1] = finalSolution.dragViewSpanY;
2626
2627 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2628 // committing anything or animating anything as we just want to determine if a solution
2629 // exists
2630 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2631 if (!DESTRUCTIVE_REORDER) {
2632 copySolutionToTempState(finalSolution, dragView);
2633 }
2634 setItemPlacementDirty(true);
2635 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2636
Adam Cohen19f37922012-03-21 11:59:11 -07002637 if (!DESTRUCTIVE_REORDER &&
2638 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002639 commitTempPlacement();
Adam Cohen19f37922012-03-21 11:59:11 -07002640 completeAndClearReorderHintAnimations();
2641 setItemPlacementDirty(false);
2642 } else {
2643 beginOrAdjustHintAnimations(finalSolution, dragView,
2644 REORDER_ANIMATION_DURATION);
Adam Cohen482ed822012-03-02 14:15:13 -08002645 }
2646 }
2647 } else {
2648 foundSolution = false;
2649 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2650 }
2651
2652 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2653 setUseTempCoords(false);
2654 }
Adam Cohen482ed822012-03-02 14:15:13 -08002655
Michael Jurkaa52570f2012-03-20 03:18:20 -07002656 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002657 return result;
2658 }
2659
Adam Cohen19f37922012-03-21 11:59:11 -07002660 void setItemPlacementDirty(boolean dirty) {
2661 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002662 }
Adam Cohen19f37922012-03-21 11:59:11 -07002663 boolean isItemPlacementDirty() {
2664 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002665 }
2666
2667 private class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002668 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002669 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2670 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohen482ed822012-03-02 14:15:13 -08002671 boolean isSolution = false;
2672 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2673
Adam Cohenf3900c22012-11-16 18:28:11 -08002674 void save() {
2675 // Copy current state into savedMap
2676 for (View v: map.keySet()) {
2677 map.get(v).copy(savedMap.get(v));
2678 }
2679 }
2680
2681 void restore() {
2682 // Restore current state from savedMap
2683 for (View v: savedMap.keySet()) {
2684 savedMap.get(v).copy(map.get(v));
2685 }
2686 }
2687
2688 void add(View v, CellAndSpan cs) {
2689 map.put(v, cs);
2690 savedMap.put(v, new CellAndSpan());
2691 sortedViews.add(v);
2692 }
2693
Adam Cohen482ed822012-03-02 14:15:13 -08002694 int area() {
2695 return dragViewSpanX * dragViewSpanY;
2696 }
Adam Cohen8baab352012-03-20 17:39:21 -07002697 }
2698
2699 private class CellAndSpan {
2700 int x, y;
2701 int spanX, spanY;
2702
Adam Cohenf3900c22012-11-16 18:28:11 -08002703 public CellAndSpan() {
2704 }
2705
2706 public void copy(CellAndSpan copy) {
2707 copy.x = x;
2708 copy.y = y;
2709 copy.spanX = spanX;
2710 copy.spanY = spanY;
2711 }
2712
Adam Cohen8baab352012-03-20 17:39:21 -07002713 public CellAndSpan(int x, int y, int spanX, int spanY) {
2714 this.x = x;
2715 this.y = y;
2716 this.spanX = spanX;
2717 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002718 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002719
2720 public String toString() {
2721 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2722 }
2723
Adam Cohen482ed822012-03-02 14:15:13 -08002724 }
2725
Adam Cohendf035382011-04-11 17:22:04 -07002726 /**
2727 * Find a vacant area that will fit the given bounds nearest the requested
2728 * cell location. Uses Euclidean distance to score multiple vacant areas.
2729 *
2730 * @param pixelX The X location at which you want to search for a vacant area.
2731 * @param pixelY The Y location at which you want to search for a vacant area.
2732 * @param spanX Horizontal span of the object.
2733 * @param spanY Vertical span of the object.
2734 * @param ignoreView Considers space occupied by this view as unoccupied
2735 * @param result Previously returned value to possibly recycle.
2736 * @return The X, Y cell of a vacant area that can contain this object,
2737 * nearest the requested location.
2738 */
2739 int[] findNearestVacantArea(
2740 int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
2741 return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
2742 }
2743
2744 /**
Adam Cohend41fbf52012-02-16 23:53:59 -08002745 * Find a vacant area that will fit the given bounds nearest the requested
2746 * cell location. Uses Euclidean distance to score multiple vacant areas.
2747 *
2748 * @param pixelX The X location at which you want to search for a vacant area.
2749 * @param pixelY The Y location at which you want to search for a vacant area.
2750 * @param minSpanX The minimum horizontal span required
2751 * @param minSpanY The minimum vertical span required
2752 * @param spanX Horizontal span of the object.
2753 * @param spanY Vertical span of the object.
2754 * @param ignoreView Considers space occupied by this view as unoccupied
2755 * @param result Previously returned value to possibly recycle.
2756 * @return The X, Y cell of a vacant area that can contain this object,
2757 * nearest the requested location.
2758 */
2759 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
2760 int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
Adam Cohen482ed822012-03-02 14:15:13 -08002761 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
2762 result, resultSpan, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08002763 }
2764
2765 /**
Adam Cohendf035382011-04-11 17:22:04 -07002766 * Find a starting cell position that will fit the given bounds nearest the requested
2767 * cell location. Uses Euclidean distance to score multiple vacant areas.
2768 *
2769 * @param pixelX The X location at which you want to search for a vacant area.
2770 * @param pixelY The Y location at which you want to search for a vacant area.
2771 * @param spanX Horizontal span of the object.
2772 * @param spanY Vertical span of the object.
2773 * @param ignoreView Considers space occupied by this view as unoccupied
2774 * @param result Previously returned value to possibly recycle.
2775 * @return The X, Y cell of a vacant area that can contain this object,
2776 * nearest the requested location.
2777 */
2778 int[] findNearestArea(
2779 int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2780 return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
2781 }
2782
Michael Jurka0280c3b2010-09-17 15:00:07 -07002783 boolean existsEmptyCell() {
2784 return findCellForSpan(null, 1, 1);
2785 }
2786
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002787 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002788 * Finds the upper-left coordinate of the first rectangle in the grid that can
2789 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2790 * then this method will only return coordinates for rectangles that contain the cell
2791 * (intersectX, intersectY)
2792 *
2793 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2794 * can be found.
2795 * @param spanX The horizontal span of the cell we want to find.
2796 * @param spanY The vertical span of the cell we want to find.
2797 *
2798 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002799 */
Michael Jurka0280c3b2010-09-17 15:00:07 -07002800 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Adam Cohen482ed822012-03-02 14:15:13 -08002801 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002802 }
2803
2804 /**
2805 * Like above, but ignores any cells occupied by the item "ignoreView"
2806 *
2807 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2808 * can be found.
2809 * @param spanX The horizontal span of the cell we want to find.
2810 * @param spanY The vertical span of the cell we want to find.
2811 * @param ignoreView The home screen item we should treat as not occupying any space
2812 * @return
2813 */
2814 boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
Adam Cohen482ed822012-03-02 14:15:13 -08002815 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
2816 ignoreView, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002817 }
2818
2819 /**
2820 * Like above, but if intersectX and intersectY are not -1, then this method will try to
2821 * return coordinates for rectangles that contain the cell [intersectX, intersectY]
2822 *
2823 * @param spanX The horizontal span of the cell we want to find.
2824 * @param spanY The vertical span of the cell we want to find.
2825 * @param ignoreView The home screen item we should treat as not occupying any space
2826 * @param intersectX The X coordinate of the cell that we should try to overlap
2827 * @param intersectX The Y coordinate of the cell that we should try to overlap
2828 *
2829 * @return True if a vacant cell of the specified dimension was found, false otherwise.
2830 */
2831 boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
2832 int intersectX, int intersectY) {
2833 return findCellForSpanThatIntersectsIgnoring(
Adam Cohen482ed822012-03-02 14:15:13 -08002834 cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002835 }
2836
2837 /**
2838 * The superset of the above two methods
2839 */
2840 boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002841 int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002842 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08002843 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002844
Michael Jurka28750fb2010-09-24 17:43:49 -07002845 boolean foundCell = false;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002846 while (true) {
2847 int startX = 0;
2848 if (intersectX >= 0) {
2849 startX = Math.max(startX, intersectX - (spanX - 1));
2850 }
2851 int endX = mCountX - (spanX - 1);
2852 if (intersectX >= 0) {
2853 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
2854 }
2855 int startY = 0;
2856 if (intersectY >= 0) {
2857 startY = Math.max(startY, intersectY - (spanY - 1));
2858 }
2859 int endY = mCountY - (spanY - 1);
2860 if (intersectY >= 0) {
2861 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
2862 }
2863
Winson Chungbbc60d82010-11-11 16:34:41 -08002864 for (int y = startY; y < endY && !foundCell; y++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002865 inner:
Winson Chungbbc60d82010-11-11 16:34:41 -08002866 for (int x = startX; x < endX; x++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002867 for (int i = 0; i < spanX; i++) {
2868 for (int j = 0; j < spanY; j++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002869 if (occupied[x + i][y + j]) {
Winson Chungbbc60d82010-11-11 16:34:41 -08002870 // small optimization: we can skip to after the column we just found
Michael Jurka0280c3b2010-09-17 15:00:07 -07002871 // an occupied cell
Winson Chungbbc60d82010-11-11 16:34:41 -08002872 x += i;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002873 continue inner;
2874 }
2875 }
2876 }
2877 if (cellXY != null) {
2878 cellXY[0] = x;
2879 cellXY[1] = y;
2880 }
Michael Jurka28750fb2010-09-24 17:43:49 -07002881 foundCell = true;
2882 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002883 }
2884 }
2885 if (intersectX == -1 && intersectY == -1) {
2886 break;
2887 } else {
2888 // if we failed to find anything, try again but without any requirements of
2889 // intersecting
2890 intersectX = -1;
2891 intersectY = -1;
2892 continue;
2893 }
2894 }
2895
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002896 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08002897 markCellsAsOccupiedForView(ignoreView, occupied);
Michael Jurka28750fb2010-09-24 17:43:49 -07002898 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002899 }
2900
2901 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002902 * A drag event has begun over this layout.
2903 * It may have begun over this layout (in which case onDragChild is called first),
2904 * or it may have begun on another layout.
2905 */
2906 void onDragEnter() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002907 mDragEnforcer.onDragEnter();
Winson Chungc07918d2011-07-01 15:35:26 -07002908 mDragging = true;
2909 }
2910
2911 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002912 * Called when drag has left this CellLayout or has been completed (successfully or not)
2913 */
2914 void onDragExit() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002915 mDragEnforcer.onDragExit();
Joe Onorato4be866d2010-10-10 11:26:02 -07002916 // This can actually be called when we aren't in a drag, e.g. when adding a new
2917 // item to this layout via the customize drawer.
2918 // Guard against that case.
2919 if (mDragging) {
2920 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002921 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002922
2923 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002924 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002925 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2926 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002927 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002928 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002929 }
2930
2931 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002932 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002933 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002934 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002935 *
2936 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002937 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002938 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002939 if (child != null) {
2940 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002941 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002942 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002943 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002944 }
2945
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002946 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002947 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002948 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002949 * @param cellX X coordinate of upper left corner expressed as a cell position
2950 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002951 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002952 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002953 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002954 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002955 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002956 final int cellWidth = mCellWidth;
2957 final int cellHeight = mCellHeight;
2958 final int widthGap = mWidthGap;
2959 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002960
Winson Chung4b825dcd2011-06-19 12:41:22 -07002961 final int hStartPadding = getPaddingLeft();
2962 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002963
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002964 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2965 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2966
2967 int x = hStartPadding + cellX * (cellWidth + widthGap);
2968 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002969
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002970 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002971 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002972
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002973 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002974 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002975 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002976 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002977 * @param width Width in pixels
2978 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002979 * @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 -08002980 */
Winson Chung66700732013-08-20 16:56:15 -07002981 public static int[] rectToCell(int width, int height, int[] result) {
Winson Chung5f8afe62013-08-12 16:19:28 -07002982 LauncherAppState app = LauncherAppState.getInstance();
2983 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Winson Chung66700732013-08-20 16:56:15 -07002984 Rect padding = grid.getWorkspacePadding(grid.isLandscape ?
2985 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
Winson Chung5f8afe62013-08-12 16:19:28 -07002986
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002987 // Always assume we're working with the smallest span to make sure we
2988 // reserve enough space in both orientations.
Winson Chung66700732013-08-20 16:56:15 -07002989 int parentWidth = grid.calculateCellWidth(grid.widthPx
2990 - padding.left - padding.right, (int) grid.numColumns);
2991 int parentHeight = grid.calculateCellHeight(grid.heightPx
2992 - padding.top - padding.bottom, (int) grid.numRows);
2993 int smallerSize = Math.min(parentWidth, parentHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002994
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002995 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002996 int spanX = (int) Math.ceil(width / (float) smallerSize);
2997 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002998
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002999 if (result == null) {
3000 return new int[] { spanX, spanY };
3001 }
3002 result[0] = spanX;
3003 result[1] = spanY;
3004 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003005 }
3006
Michael Jurkaf12c75c2011-01-25 22:41:40 -08003007 public int[] cellSpansToSize(int hSpans, int vSpans) {
3008 int[] size = new int[2];
3009 size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
3010 size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
3011 return size;
3012 }
3013
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003014 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08003015 * Calculate the grid spans needed to fit given item
3016 */
3017 public void calculateSpans(ItemInfo info) {
3018 final int minWidth;
3019 final int minHeight;
3020
3021 if (info instanceof LauncherAppWidgetInfo) {
3022 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
3023 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
3024 } else if (info instanceof PendingAddWidgetInfo) {
3025 minWidth = ((PendingAddWidgetInfo) info).minWidth;
3026 minHeight = ((PendingAddWidgetInfo) info).minHeight;
3027 } else {
3028 // It's not a widget, so it must be 1x1
3029 info.spanX = info.spanY = 1;
3030 return;
3031 }
3032 int[] spans = rectToCell(minWidth, minHeight, null);
3033 info.spanX = spans[0];
3034 info.spanY = spans[1];
3035 }
3036
3037 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003038 * Find the first vacant cell, if there is one.
3039 *
3040 * @param vacant Holds the x and y coordinate of the vacant cell
3041 * @param spanX Horizontal cell span.
3042 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -07003043 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003044 * @return True if a vacant cell was found
3045 */
3046 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003047
Michael Jurka0280c3b2010-09-17 15:00:07 -07003048 return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003049 }
3050
3051 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
3052 int xCount, int yCount, boolean[][] occupied) {
3053
Adam Cohen2801caf2011-05-13 20:57:39 -07003054 for (int y = 0; y < yCount; y++) {
3055 for (int x = 0; x < xCount; x++) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003056 boolean available = !occupied[x][y];
3057out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
3058 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
3059 available = available && !occupied[i][j];
3060 if (!available) break out;
3061 }
3062 }
3063
3064 if (available) {
3065 vacant[0] = x;
3066 vacant[1] = y;
3067 return true;
3068 }
3069 }
3070 }
3071
3072 return false;
3073 }
3074
Michael Jurka0280c3b2010-09-17 15:00:07 -07003075 private void clearOccupiedCells() {
3076 for (int x = 0; x < mCountX; x++) {
3077 for (int y = 0; y < mCountY; y++) {
3078 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003079 }
3080 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07003081 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003082
Adam Cohend41fbf52012-02-16 23:53:59 -08003083 public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07003084 markCellsAsUnoccupiedForView(view);
Adam Cohen482ed822012-03-02 14:15:13 -08003085 markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003086 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003087
Adam Cohend4844c32011-02-18 19:25:06 -08003088 public void markCellsAsOccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08003089 markCellsAsOccupiedForView(view, mOccupied);
3090 }
3091 public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07003092 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003093 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08003094 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003095 }
3096
Adam Cohend4844c32011-02-18 19:25:06 -08003097 public void markCellsAsUnoccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08003098 markCellsAsUnoccupiedForView(view, mOccupied);
3099 }
3100 public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07003101 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003102 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08003103 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003104 }
3105
Adam Cohen482ed822012-03-02 14:15:13 -08003106 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
3107 boolean value) {
3108 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003109 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
3110 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08003111 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003112 }
3113 }
3114 }
3115
Adam Cohen2801caf2011-05-13 20:57:39 -07003116 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07003117 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07003118 (Math.max((mCountX - 1), 0) * mWidthGap);
3119 }
3120
3121 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07003122 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07003123 (Math.max((mCountY - 1), 0) * mHeightGap);
3124 }
3125
Michael Jurka66d72172011-04-12 16:29:25 -07003126 public boolean isOccupied(int x, int y) {
3127 if (x < mCountX && y < mCountY) {
3128 return mOccupied[x][y];
3129 } else {
3130 throw new RuntimeException("Position exceeds the bound of this CellLayout");
3131 }
3132 }
3133
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003134 @Override
3135 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
3136 return new CellLayout.LayoutParams(getContext(), attrs);
3137 }
3138
3139 @Override
3140 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
3141 return p instanceof CellLayout.LayoutParams;
3142 }
3143
3144 @Override
3145 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
3146 return new CellLayout.LayoutParams(p);
3147 }
3148
Winson Chungaafa03c2010-06-11 17:34:16 -07003149 public static class CellLayoutAnimationController extends LayoutAnimationController {
3150 public CellLayoutAnimationController(Animation animation, float delay) {
3151 super(animation, delay);
3152 }
3153
3154 @Override
3155 protected long getDelayForView(View view) {
3156 return (int) (Math.random() * 150);
3157 }
3158 }
3159
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003160 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
3161 /**
3162 * Horizontal location of the item in the grid.
3163 */
3164 @ViewDebug.ExportedProperty
3165 public int cellX;
3166
3167 /**
3168 * Vertical location of the item in the grid.
3169 */
3170 @ViewDebug.ExportedProperty
3171 public int cellY;
3172
3173 /**
Adam Cohen482ed822012-03-02 14:15:13 -08003174 * Temporary horizontal location of the item in the grid during reorder
3175 */
3176 public int tmpCellX;
3177
3178 /**
3179 * Temporary vertical location of the item in the grid during reorder
3180 */
3181 public int tmpCellY;
3182
3183 /**
3184 * Indicates that the temporary coordinates should be used to layout the items
3185 */
3186 public boolean useTmpCoords;
3187
3188 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003189 * Number of cells spanned horizontally by the item.
3190 */
3191 @ViewDebug.ExportedProperty
3192 public int cellHSpan;
3193
3194 /**
3195 * Number of cells spanned vertically by the item.
3196 */
3197 @ViewDebug.ExportedProperty
3198 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07003199
Adam Cohen1b607ed2011-03-03 17:26:50 -08003200 /**
3201 * Indicates whether the item will set its x, y, width and height parameters freely,
3202 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
3203 */
Adam Cohend4844c32011-02-18 19:25:06 -08003204 public boolean isLockedToGrid = true;
3205
Adam Cohen482ed822012-03-02 14:15:13 -08003206 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07003207 * Indicates that this item should use the full extents of its parent.
3208 */
3209 public boolean isFullscreen = false;
3210
3211 /**
Adam Cohen482ed822012-03-02 14:15:13 -08003212 * Indicates whether this item can be reordered. Always true except in the case of the
3213 * the AllApps button.
3214 */
3215 public boolean canReorder = true;
3216
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003217 // X coordinate of the view in the layout.
3218 @ViewDebug.ExportedProperty
3219 int x;
3220 // Y coordinate of the view in the layout.
3221 @ViewDebug.ExportedProperty
3222 int y;
3223
Romain Guy84f296c2009-11-04 15:00:44 -08003224 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07003225
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003226 public LayoutParams(Context c, AttributeSet attrs) {
3227 super(c, attrs);
3228 cellHSpan = 1;
3229 cellVSpan = 1;
3230 }
3231
3232 public LayoutParams(ViewGroup.LayoutParams source) {
3233 super(source);
3234 cellHSpan = 1;
3235 cellVSpan = 1;
3236 }
Winson Chungaafa03c2010-06-11 17:34:16 -07003237
3238 public LayoutParams(LayoutParams source) {
3239 super(source);
3240 this.cellX = source.cellX;
3241 this.cellY = source.cellY;
3242 this.cellHSpan = source.cellHSpan;
3243 this.cellVSpan = source.cellVSpan;
3244 }
3245
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003246 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08003247 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003248 this.cellX = cellX;
3249 this.cellY = cellY;
3250 this.cellHSpan = cellHSpan;
3251 this.cellVSpan = cellVSpan;
3252 }
3253
Adam Cohen2374abf2013-04-16 14:56:57 -07003254 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
3255 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08003256 if (isLockedToGrid) {
3257 final int myCellHSpan = cellHSpan;
3258 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07003259 int myCellX = useTmpCoords ? tmpCellX : cellX;
3260 int myCellY = useTmpCoords ? tmpCellY : cellY;
3261
3262 if (invertHorizontally) {
3263 myCellX = colCount - myCellX - cellHSpan;
3264 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08003265
Adam Cohend4844c32011-02-18 19:25:06 -08003266 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
3267 leftMargin - rightMargin;
3268 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
3269 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08003270 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
3271 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08003272 }
3273 }
Winson Chungaafa03c2010-06-11 17:34:16 -07003274
Winson Chungaafa03c2010-06-11 17:34:16 -07003275 public String toString() {
3276 return "(" + this.cellX + ", " + this.cellY + ")";
3277 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07003278
3279 public void setWidth(int width) {
3280 this.width = width;
3281 }
3282
3283 public int getWidth() {
3284 return width;
3285 }
3286
3287 public void setHeight(int height) {
3288 this.height = height;
3289 }
3290
3291 public int getHeight() {
3292 return height;
3293 }
3294
3295 public void setX(int x) {
3296 this.x = x;
3297 }
3298
3299 public int getX() {
3300 return x;
3301 }
3302
3303 public void setY(int y) {
3304 this.y = y;
3305 }
3306
3307 public int getY() {
3308 return y;
3309 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003310 }
3311
Michael Jurka0280c3b2010-09-17 15:00:07 -07003312 // This class stores info for two purposes:
3313 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
3314 // its spanX, spanY, and the screen it is on
3315 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
3316 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
3317 // the CellLayout that was long clicked
Michael Jurkae5fb0f22011-04-11 13:27:46 -07003318 static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003319 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07003320 int cellX = -1;
3321 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003322 int spanX;
3323 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07003324 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07003325 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003326
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003327 @Override
3328 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07003329 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
3330 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003331 }
3332 }
Michael Jurkad771c962011-08-09 15:00:48 -07003333
3334 public boolean lastDownOnOccupiedCell() {
3335 return mLastDownOnOccupiedCell;
3336 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003337}