blob: 8ca8d91059ac716986a2cc0ce7508ac6a5fce0b5 [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 Cohen1462de32012-07-24 22:34:36 -070038import android.os.Parcelable;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080039import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070040import android.util.Log;
Adam Cohen1462de32012-07-24 22:34:36 -070041import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080042import android.view.MotionEvent;
43import android.view.View;
44import android.view.ViewDebug;
45import android.view.ViewGroup;
Winson Chungaafa03c2010-06-11 17:34:16 -070046import android.view.animation.Animation;
Winson Chung150fbab2010-09-29 17:14:26 -070047import android.view.animation.DecelerateInterpolator;
Winson Chungaafa03c2010-06-11 17:34:16 -070048import android.view.animation.LayoutAnimationController;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080049
Daniel Sandler325dc232013-06-05 22:57:57 -040050import com.android.launcher3.FolderIcon.FolderRingAnimator;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070051
Adam Cohen69ce2e52011-07-03 19:25:21 -070052import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070053import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080054import java.util.Collections;
55import java.util.Comparator;
Adam Cohenbfbfd262011-06-13 16:55:12 -070056import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080057import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070058
Michael Jurkabdb5c532011-02-01 15:05:06 -080059public class CellLayout extends ViewGroup {
Winson Chungaafa03c2010-06-11 17:34:16 -070060 static final String TAG = "CellLayout";
61
Adam Cohen2acce882012-03-28 19:03:19 -070062 private Launcher mLauncher;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080063 private int mCellWidth;
64 private int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070065 private int mFixedCellWidth;
66 private int mFixedCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070067
Adam Cohend22015c2010-07-26 22:02:18 -070068 private int mCountX;
69 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080070
Adam Cohen234c4cd2011-07-17 21:03:04 -070071 private int mOriginalWidthGap;
72 private int mOriginalHeightGap;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080073 private int mWidthGap;
74 private int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070075 private int mMaxGap;
Adam Cohenebea84d2011-11-09 17:20:41 -080076 private boolean mScrollingTransformsDirty = false;
Adam Cohen917e3882013-10-31 15:03:35 -070077 private boolean mDropPending = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080078
Patrick Dubroyde7658b2010-09-27 11:15:43 -070079 // These are temporary variables to prevent having to allocate a new object just to
80 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Winson Chung0be025d2011-05-23 17:45:09 -070081 private final int[] mTmpXY = new int[2];
Patrick Dubroyde7658b2010-09-27 11:15:43 -070082 private final int[] mTmpPoint = new int[2];
Adam Cohen69ce2e52011-07-03 19:25:21 -070083 int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070084
The Android Open Source Project31dd5032009-03-03 19:32:27 -080085 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -080086 boolean[][] mTmpOccupied;
Michael Jurkad771c962011-08-09 15:00:48 -070087 private boolean mLastDownOnOccupiedCell = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080088
Michael Jurkadee05892010-07-27 10:01:56 -070089 private OnTouchListener mInterceptTouchListener;
90
Adam Cohen69ce2e52011-07-03 19:25:21 -070091 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -070092 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -070093
Adam Cohen02dcfcc2013-10-01 12:37:33 -070094 private float FOREGROUND_ALPHA_DAMPER = 0.65f;
Adam Cohenb5ba0972011-09-07 18:02:31 -070095 private int mForegroundAlpha = 0;
Michael Jurka5f1c5092010-09-03 14:15:02 -070096 private float mBackgroundAlpha;
Adam Cohen1b0aaac2010-10-28 11:11:18 -070097 private float mBackgroundAlphaMultiplier = 1.0f;
Winson Chung59a488a2013-12-10 12:32:14 -080098 private boolean mDrawBackground = true;
Adam Cohenf34bab52010-09-30 14:11:56 -070099
Michael Jurka33945b22010-12-21 18:19:38 -0800100 private Drawable mNormalBackground;
Michael Jurka33945b22010-12-21 18:19:38 -0800101 private Drawable mActiveGlowBackground;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700102 private Drawable mOverScrollForegroundDrawable;
103 private Drawable mOverScrollLeft;
104 private Drawable mOverScrollRight;
Michael Jurka18014792010-10-14 09:01:34 -0700105 private Rect mBackgroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700106 private Rect mForegroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700107 private int mForegroundPadding;
Patrick Dubroy1262e362010-10-06 15:49:50 -0700108
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700109 // These values allow a fixed measurement to be set on the CellLayout.
110 private int mFixedWidth = -1;
111 private int mFixedHeight = -1;
112
Michael Jurka33945b22010-12-21 18:19:38 -0800113 // If we're actively dragging something over this screen, mIsDragOverlapping is true
114 private boolean mIsDragOverlapping = false;
Adam Cohendedbd962013-07-11 14:21:49 -0700115 boolean mUseActiveGlowBackground = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700116
Winson Chung150fbab2010-09-29 17:14:26 -0700117 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700118 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohend41fbf52012-02-16 23:53:59 -0800119 private Rect[] mDragOutlines = new Rect[4];
Chet Haase472b2812010-10-14 07:02:04 -0700120 private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700121 private InterruptibleInOutAnimator[] mDragOutlineAnims =
122 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700123
124 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700125 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700126 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700127
Patrick Dubroy96864c32011-03-10 17:17:23 -0800128 private BubbleTextView mPressedOrFocusedIcon;
129
Adam Cohen482ed822012-03-02 14:15:13 -0800130 private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
131 HashMap<CellLayout.LayoutParams, Animator>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800132 private HashMap<View, ReorderPreviewAnimation>
133 mShakeAnimators = new HashMap<View, ReorderPreviewAnimation>();
Adam Cohen19f37922012-03-21 11:59:11 -0700134
135 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700136
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700137 // When a drag operation is in progress, holds the nearest cell to the touch point
138 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800139
Joe Onorato4be866d2010-10-10 11:26:02 -0700140 private boolean mDragging = false;
141
Patrick Dubroyce34a972010-10-19 10:34:32 -0700142 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700143 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700144
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800145 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700146 private float mHotseatScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800147
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800148 public static final int MODE_SHOW_REORDER_HINT = 0;
149 public static final int MODE_DRAG_OVER = 1;
150 public static final int MODE_ON_DROP = 2;
151 public static final int MODE_ON_DROP_EXTERNAL = 3;
152 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700153 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800154 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
155
Adam Cohena897f392012-04-27 18:12:05 -0700156 static final int LANDSCAPE = 0;
157 static final int PORTRAIT = 1;
158
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800159 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700160 private static final int REORDER_ANIMATION_DURATION = 150;
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800161 private float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700162
Adam Cohen482ed822012-03-02 14:15:13 -0800163 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
164 private Rect mOccupiedRect = new Rect();
165 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700166 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700167 private static final int INVALID_DIRECTION = -100;
Adam Cohenc6cc61d2012-04-04 12:47:08 -0700168 private DropTarget.DragEnforcer mDragEnforcer;
Adam Cohen482ed822012-03-02 14:15:13 -0800169
Winson Chung3a6e7f32013-10-09 15:50:52 -0700170 private Rect mTempRect = new Rect();
171
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;
Nilesh Agrawal5f7099a2014-01-02 15:54:57 -0800199 mFixedCellWidth = 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 Chung6e1c0d32013-10-25 15:24:24 -0700215 mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
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 Cohenfa3c58f2013-12-06 16:10:55 -0800225 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_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
Chris Craik01f2d7f2013-10-01 14:41:56 -0700298 public void enableHardwareLayer(boolean hasLayer) {
299 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700300 }
301
302 public void buildHardwareLayer() {
303 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700304 }
305
Adam Cohen307fe232012-08-16 17:55:58 -0700306 public float getChildrenScale() {
307 return mIsHotseat ? mHotseatScale : 1.0f;
308 }
309
Winson Chung5f8afe62013-08-12 16:19:28 -0700310 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700311 mFixedCellWidth = mCellWidth = width;
312 mFixedCellHeight = mCellHeight = height;
Winson Chung5f8afe62013-08-12 16:19:28 -0700313 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
314 mCountX, mCountY);
315 }
316
Adam Cohen2801caf2011-05-13 20:57:39 -0700317 public void setGridSize(int x, int y) {
318 mCountX = x;
319 mCountY = y;
320 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800321 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700322 mTempRectStack.clear();
Adam Cohen2374abf2013-04-16 14:56:57 -0700323 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700324 mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700325 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700326 }
327
Adam Cohen2374abf2013-04-16 14:56:57 -0700328 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
329 public void setInvertIfRtl(boolean invert) {
330 mShortcutsAndWidgets.setInvertIfRtl(invert);
331 }
332
Adam Cohen917e3882013-10-31 15:03:35 -0700333 public void setDropPending(boolean pending) {
334 mDropPending = pending;
335 }
336
337 public boolean isDropPending() {
338 return mDropPending;
339 }
340
Patrick Dubroy96864c32011-03-10 17:17:23 -0800341 private void invalidateBubbleTextView(BubbleTextView icon) {
342 final int padding = icon.getPressedOrFocusedBackgroundPadding();
Winson Chung4b825dcd2011-06-19 12:41:22 -0700343 invalidate(icon.getLeft() + getPaddingLeft() - padding,
344 icon.getTop() + getPaddingTop() - padding,
345 icon.getRight() + getPaddingLeft() + padding,
346 icon.getBottom() + getPaddingTop() + padding);
Patrick Dubroy96864c32011-03-10 17:17:23 -0800347 }
348
Adam Cohenb5ba0972011-09-07 18:02:31 -0700349 void setOverScrollAmount(float r, boolean left) {
350 if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
351 mOverScrollForegroundDrawable = mOverScrollLeft;
352 } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
353 mOverScrollForegroundDrawable = mOverScrollRight;
354 }
355
Adam Cohen02dcfcc2013-10-01 12:37:33 -0700356 r *= FOREGROUND_ALPHA_DAMPER;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700357 mForegroundAlpha = (int) Math.round((r * 255));
358 mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
359 invalidate();
360 }
361
Patrick Dubroy96864c32011-03-10 17:17:23 -0800362 void setPressedOrFocusedIcon(BubbleTextView icon) {
363 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
364 // requires an expanded clip rect (due to the glow's blur radius)
365 BubbleTextView oldIcon = mPressedOrFocusedIcon;
366 mPressedOrFocusedIcon = icon;
367 if (oldIcon != null) {
368 invalidateBubbleTextView(oldIcon);
369 }
370 if (mPressedOrFocusedIcon != null) {
371 invalidateBubbleTextView(mPressedOrFocusedIcon);
372 }
373 }
374
Michael Jurka33945b22010-12-21 18:19:38 -0800375 void setIsDragOverlapping(boolean isDragOverlapping) {
376 if (mIsDragOverlapping != isDragOverlapping) {
377 mIsDragOverlapping = isDragOverlapping;
Adam Cohendedbd962013-07-11 14:21:49 -0700378 setUseActiveGlowBackground(mIsDragOverlapping);
Michael Jurka33945b22010-12-21 18:19:38 -0800379 invalidate();
380 }
381 }
382
Adam Cohendedbd962013-07-11 14:21:49 -0700383 void setUseActiveGlowBackground(boolean use) {
384 mUseActiveGlowBackground = use;
385 }
386
Winson Chung59a488a2013-12-10 12:32:14 -0800387 void disableBackground() {
388 mDrawBackground = false;
389 }
390
Michael Jurka33945b22010-12-21 18:19:38 -0800391 boolean getIsDragOverlapping() {
392 return mIsDragOverlapping;
393 }
394
Adam Cohenebea84d2011-11-09 17:20:41 -0800395 protected void setOverscrollTransformsDirty(boolean dirty) {
396 mScrollingTransformsDirty = dirty;
397 }
398
399 protected void resetOverscrollTransforms() {
400 if (mScrollingTransformsDirty) {
401 setOverscrollTransformsDirty(false);
402 setTranslationX(0);
403 setRotationY(0);
404 // It doesn't matter if we pass true or false here, the important thing is that we
405 // pass 0, which results in the overscroll drawable not being drawn any more.
406 setOverScrollAmount(0, false);
407 setPivotX(getMeasuredWidth() / 2);
408 setPivotY(getMeasuredHeight() / 2);
409 }
410 }
411
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700412 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700413 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700414 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
415 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
416 // When we're small, we are either drawn normally or in the "accepts drops" state (during
417 // a drag). However, we also drag the mini hover background *over* one of those two
418 // backgrounds
Winson Chung59a488a2013-12-10 12:32:14 -0800419 if (mDrawBackground && mBackgroundAlpha > 0.0f) {
Adam Cohenf34bab52010-09-30 14:11:56 -0700420 Drawable bg;
Michael Jurka33945b22010-12-21 18:19:38 -0800421
Adam Cohendedbd962013-07-11 14:21:49 -0700422 if (mUseActiveGlowBackground) {
Michael Jurka33945b22010-12-21 18:19:38 -0800423 // In the mini case, we draw the active_glow bg *over* the active background
Michael Jurkabdf78552011-10-31 14:34:25 -0700424 bg = mActiveGlowBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700425 } else {
Michael Jurkabdf78552011-10-31 14:34:25 -0700426 bg = mNormalBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700427 }
Michael Jurka33945b22010-12-21 18:19:38 -0800428
429 bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
430 bg.setBounds(mBackgroundRect);
431 bg.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700432 }
Romain Guya6abce82009-11-10 02:54:41 -0800433
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700434 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700435 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700436 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700437 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800438 final Rect r = mDragOutlines[i];
Winson Chung3a6e7f32013-10-09 15:50:52 -0700439 mTempRect.set(r);
440 Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale());
Joe Onorato4be866d2010-10-10 11:26:02 -0700441 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700442 paint.setAlpha((int)(alpha + .5f));
Winson Chung3a6e7f32013-10-09 15:50:52 -0700443 canvas.drawBitmap(b, null, mTempRect, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700444 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700445 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800446
447 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
448 // requires an expanded clip rect (due to the glow's blur radius)
449 if (mPressedOrFocusedIcon != null) {
450 final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
451 final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
452 if (b != null) {
Winson Chung3a6e7f32013-10-09 15:50:52 -0700453 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
454 (mCountX * mCellWidth);
455 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
456 int top = getPaddingTop();
Patrick Dubroy96864c32011-03-10 17:17:23 -0800457 canvas.drawBitmap(b,
Winson Chung3a6e7f32013-10-09 15:50:52 -0700458 mPressedOrFocusedIcon.getLeft() + left - padding,
459 mPressedOrFocusedIcon.getTop() + top - padding,
Patrick Dubroy96864c32011-03-10 17:17:23 -0800460 null);
461 }
462 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700463
Adam Cohen482ed822012-03-02 14:15:13 -0800464 if (DEBUG_VISUALIZE_OCCUPIED) {
465 int[] pt = new int[2];
466 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700467 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800468 for (int i = 0; i < mCountX; i++) {
469 for (int j = 0; j < mCountY; j++) {
470 if (mOccupied[i][j]) {
471 cellToPoint(i, j, pt);
472 canvas.save();
473 canvas.translate(pt[0], pt[1]);
474 cd.draw(canvas);
475 canvas.restore();
476 }
477 }
478 }
479 }
480
Andrew Flynn850d2e72012-04-26 16:51:20 -0700481 int previewOffset = FolderRingAnimator.sPreviewSize;
482
Adam Cohen69ce2e52011-07-03 19:25:21 -0700483 // The folder outer / inner ring image(s)
Winson Chung5f8afe62013-08-12 16:19:28 -0700484 LauncherAppState app = LauncherAppState.getInstance();
485 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700486 for (int i = 0; i < mFolderOuterRings.size(); i++) {
487 FolderRingAnimator fra = mFolderOuterRings.get(i);
488
Adam Cohen5108bc02013-09-20 17:04:51 -0700489 Drawable d;
490 int width, height;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700491 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700492 View child = getChildAt(fra.mCellX, fra.mCellY);
Adam Cohen558f1c22013-10-09 15:15:24 -0700493
Winson Chung89f97052013-09-20 11:32:26 -0700494 if (child != null) {
Adam Cohen558f1c22013-10-09 15:15:24 -0700495 int centerX = mTempLocation[0] + mCellWidth / 2;
496 int centerY = mTempLocation[1] + previewOffset / 2 +
497 child.getPaddingTop() + grid.folderBackgroundOffset;
498
Adam Cohen5108bc02013-09-20 17:04:51 -0700499 // Draw outer ring, if it exists
500 if (FolderIcon.HAS_OUTER_RING) {
501 d = FolderRingAnimator.sSharedOuterRingDrawable;
502 width = (int) (fra.getOuterRingSize() * getChildrenScale());
503 height = width;
504 canvas.save();
505 canvas.translate(centerX - width / 2, centerY - height / 2);
506 d.setBounds(0, 0, width, height);
507 d.draw(canvas);
508 canvas.restore();
509 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700510
Winson Chung89f97052013-09-20 11:32:26 -0700511 // Draw inner ring
512 d = FolderRingAnimator.sSharedInnerRingDrawable;
513 width = (int) (fra.getInnerRingSize() * getChildrenScale());
514 height = width;
Winson Chung89f97052013-09-20 11:32:26 -0700515 canvas.save();
516 canvas.translate(centerX - width / 2, centerY - width / 2);
517 d.setBounds(0, 0, width, height);
518 d.draw(canvas);
519 canvas.restore();
520 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700521 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700522
523 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
524 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
525 int width = d.getIntrinsicWidth();
526 int height = d.getIntrinsicHeight();
527
528 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700529 View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]);
Winson Chung89f97052013-09-20 11:32:26 -0700530 if (child != null) {
531 int centerX = mTempLocation[0] + mCellWidth / 2;
532 int centerY = mTempLocation[1] + previewOffset / 2 +
533 child.getPaddingTop() + grid.folderBackgroundOffset;
Adam Cohenc51934b2011-07-26 21:07:43 -0700534
Winson Chung89f97052013-09-20 11:32:26 -0700535 canvas.save();
536 canvas.translate(centerX - width / 2, centerY - width / 2);
537 d.setBounds(0, 0, width, height);
538 d.draw(canvas);
539 canvas.restore();
540 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700541 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700542 }
543
Adam Cohenb5ba0972011-09-07 18:02:31 -0700544 @Override
545 protected void dispatchDraw(Canvas canvas) {
546 super.dispatchDraw(canvas);
547 if (mForegroundAlpha > 0) {
548 mOverScrollForegroundDrawable.setBounds(mForegroundRect);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700549 mOverScrollForegroundDrawable.draw(canvas);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700550 }
551 }
552
Adam Cohen69ce2e52011-07-03 19:25:21 -0700553 public void showFolderAccept(FolderRingAnimator fra) {
554 mFolderOuterRings.add(fra);
555 }
556
557 public void hideFolderAccept(FolderRingAnimator fra) {
558 if (mFolderOuterRings.contains(fra)) {
559 mFolderOuterRings.remove(fra);
560 }
561 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700562 }
563
Adam Cohenc51934b2011-07-26 21:07:43 -0700564 public void setFolderLeaveBehindCell(int x, int y) {
565 mFolderLeaveBehindCell[0] = x;
566 mFolderLeaveBehindCell[1] = y;
567 invalidate();
568 }
569
570 public void clearFolderLeaveBehind() {
571 mFolderLeaveBehindCell[0] = -1;
572 mFolderLeaveBehindCell[1] = -1;
573 invalidate();
574 }
575
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700576 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700577 public boolean shouldDelayChildPressedState() {
578 return false;
579 }
580
Adam Cohen1462de32012-07-24 22:34:36 -0700581 public void restoreInstanceState(SparseArray<Parcelable> states) {
582 dispatchRestoreInstanceState(states);
583 }
584
Michael Jurkae6235dd2011-10-04 15:02:05 -0700585 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700586 public void cancelLongPress() {
587 super.cancelLongPress();
588
589 // Cancel long press for all children
590 final int count = getChildCount();
591 for (int i = 0; i < count; i++) {
592 final View child = getChildAt(i);
593 child.cancelLongPress();
594 }
595 }
596
Michael Jurkadee05892010-07-27 10:01:56 -0700597 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
598 mInterceptTouchListener = listener;
599 }
600
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800601 int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700602 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800603 }
604
605 int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700606 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800607 }
608
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800609 public void setIsHotseat(boolean isHotseat) {
610 mIsHotseat = isHotseat;
Winson Chung5f8afe62013-08-12 16:19:28 -0700611 mShortcutsAndWidgets.setIsHotseat(isHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800612 }
613
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800614 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Andrew Flynn850d2e72012-04-26 16:51:20 -0700615 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700616 final LayoutParams lp = params;
617
Andrew Flynnde38e422012-05-08 11:22:15 -0700618 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800619 if (child instanceof BubbleTextView) {
620 BubbleTextView bubbleChild = (BubbleTextView) child;
Winson Chung5f8afe62013-08-12 16:19:28 -0700621 bubbleChild.setTextVisibility(!mIsHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800622 }
623
Adam Cohen307fe232012-08-16 17:55:58 -0700624 child.setScaleX(getChildrenScale());
625 child.setScaleY(getChildrenScale());
626
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800627 // Generate an id for each view, this assumes we have at most 256x256 cells
628 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700629 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700630 // If the horizontal or vertical span is set to -1, it is taken to
631 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700632 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
633 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800634
Winson Chungaafa03c2010-06-11 17:34:16 -0700635 child.setId(childId);
636
Michael Jurkaa52570f2012-03-20 03:18:20 -0700637 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700638
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700639 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700640
Winson Chungaafa03c2010-06-11 17:34:16 -0700641 return true;
642 }
643 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800644 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700645
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800646 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700647 public void removeAllViews() {
648 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700649 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700650 }
651
652 @Override
653 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700654 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700655 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700656 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700657 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700658 }
659
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700660 public void removeViewWithoutMarkingCells(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700661 mShortcutsAndWidgets.removeView(view);
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700662 }
663
Michael Jurka0280c3b2010-09-17 15:00:07 -0700664 @Override
665 public void removeView(View view) {
666 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700667 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700668 }
669
670 @Override
671 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700672 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
673 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700674 }
675
676 @Override
677 public void removeViewInLayout(View view) {
678 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700679 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700680 }
681
682 @Override
683 public void removeViews(int start, int count) {
684 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700685 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700686 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700687 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700688 }
689
690 @Override
691 public void removeViewsInLayout(int start, int count) {
692 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700693 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700694 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700695 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800696 }
697
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800698 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800699 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Cohenc1997fd2011-08-15 18:26:39 -0700700 // First we clear the tag to ensure that on every touch down we start with a fresh slate,
701 // even in the case where we return early. Not clearing here was causing bugs whereby on
702 // long-press we'd end up picking up an item from a previous drag operation.
703 final int action = ev.getAction();
704
Adam Cohenc1997fd2011-08-15 18:26:39 -0700705
Michael Jurkadee05892010-07-27 10:01:56 -0700706 if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
707 return true;
708 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800709
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800710 return false;
711 }
712
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700713 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700714 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800715 * @param x X coordinate of the point
716 * @param y Y coordinate of the point
717 * @param result Array of 2 ints to hold the x and y coordinate of the cell
718 */
719 void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700720 final int hStartPadding = getPaddingLeft();
721 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800722
723 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
724 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
725
Adam Cohend22015c2010-07-26 22:02:18 -0700726 final int xAxis = mCountX;
727 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800728
729 if (result[0] < 0) result[0] = 0;
730 if (result[0] >= xAxis) result[0] = xAxis - 1;
731 if (result[1] < 0) result[1] = 0;
732 if (result[1] >= yAxis) result[1] = yAxis - 1;
733 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700734
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800735 /**
736 * Given a point, return the cell that most closely encloses that point
737 * @param x X coordinate of the point
738 * @param y Y coordinate of the point
739 * @param result Array of 2 ints to hold the x and y coordinate of the cell
740 */
741 void pointToCellRounded(int x, int y, int[] result) {
742 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
743 }
744
745 /**
746 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700747 *
748 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800749 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700750 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800751 * @param result Array of 2 ints to hold the x and y coordinate of the point
752 */
753 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700754 final int hStartPadding = getPaddingLeft();
755 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800756
757 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
758 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
759 }
760
Adam Cohene3e27a82011-04-15 12:07:39 -0700761 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800762 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700763 *
764 * @param cellX X coordinate of the cell
765 * @param cellY Y coordinate of the cell
766 *
767 * @param result Array of 2 ints to hold the x and y coordinate of the point
768 */
769 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700770 regionToCenterPoint(cellX, cellY, 1, 1, result);
771 }
772
773 /**
774 * Given a cell coordinate and span return the point that represents the center of the regio
775 *
776 * @param cellX X coordinate of the cell
777 * @param cellY Y coordinate of the cell
778 *
779 * @param result Array of 2 ints to hold the x and y coordinate of the point
780 */
781 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700782 final int hStartPadding = getPaddingLeft();
783 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700784 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
785 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
786 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
787 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700788 }
789
Adam Cohen19f37922012-03-21 11:59:11 -0700790 /**
791 * Given a cell coordinate and span fills out a corresponding pixel rect
792 *
793 * @param cellX X coordinate of the cell
794 * @param cellY Y coordinate of the cell
795 * @param result Rect in which to write the result
796 */
797 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
798 final int hStartPadding = getPaddingLeft();
799 final int vStartPadding = getPaddingTop();
800 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
801 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
802 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
803 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
804 }
805
Adam Cohen482ed822012-03-02 14:15:13 -0800806 public float getDistanceFromCell(float x, float y, int[] cell) {
807 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
808 float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) +
809 Math.pow(y - mTmpPoint[1], 2));
810 return distance;
811 }
812
Romain Guy84f296c2009-11-04 15:00:44 -0800813 int getCellWidth() {
814 return mCellWidth;
815 }
816
817 int getCellHeight() {
818 return mCellHeight;
819 }
820
Adam Cohend4844c32011-02-18 19:25:06 -0800821 int getWidthGap() {
822 return mWidthGap;
823 }
824
825 int getHeightGap() {
826 return mHeightGap;
827 }
828
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700829 Rect getContentRect(Rect r) {
830 if (r == null) {
831 r = new Rect();
832 }
833 int left = getPaddingLeft();
834 int top = getPaddingTop();
Michael Jurka8b805b12012-04-18 14:23:14 -0700835 int right = left + getWidth() - getPaddingLeft() - getPaddingRight();
836 int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom();
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700837 r.set(left, top, right, bottom);
838 return r;
839 }
840
Winson Chungfe411c82013-09-26 16:07:17 -0700841 /** Return a rect that has the cellWidth/cellHeight (left, top), and
842 * widthGap/heightGap (right, bottom) */
Winson Chung66700732013-08-20 16:56:15 -0700843 static void getMetrics(Rect metrics, int paddedMeasureWidth,
844 int paddedMeasureHeight, int countX, int countY) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700845 LauncherAppState app = LauncherAppState.getInstance();
846 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Winson Chung66700732013-08-20 16:56:15 -0700847 metrics.set(grid.calculateCellWidth(paddedMeasureWidth, countX),
848 grid.calculateCellHeight(paddedMeasureHeight, countY), 0, 0);
Adam Cohenf4bd5792012-04-27 11:35:29 -0700849 }
850
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700851 public void setFixedSize(int width, int height) {
852 mFixedWidth = width;
853 mFixedHeight = height;
854 }
855
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800856 @Override
857 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700858 LauncherAppState app = LauncherAppState.getInstance();
859 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
860
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800861 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800862 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700863 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
864 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700865 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
866 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700867 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700868 int cw = grid.calculateCellWidth(childWidthSize, mCountX);
869 int ch = grid.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700870 if (cw != mCellWidth || ch != mCellHeight) {
871 mCellWidth = cw;
872 mCellHeight = ch;
873 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
874 mHeightGap, mCountX, mCountY);
875 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700876 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700877
Winson Chung2d75f122013-09-23 16:53:31 -0700878 int newWidth = childWidthSize;
879 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700880 if (mFixedWidth > 0 && mFixedHeight > 0) {
881 newWidth = mFixedWidth;
882 newHeight = mFixedHeight;
883 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800884 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
885 }
886
Adam Cohend22015c2010-07-26 22:02:18 -0700887 int numWidthGaps = mCountX - 1;
888 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800889
Adam Cohen234c4cd2011-07-17 21:03:04 -0700890 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700891 int hSpace = childWidthSize;
892 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700893 int hFreeSpace = hSpace - (mCountX * mCellWidth);
894 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700895 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
896 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -0700897 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
898 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700899 } else {
900 mWidthGap = mOriginalWidthGap;
901 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700902 }
Michael Jurka8c920dd2011-01-20 14:16:56 -0800903 int count = getChildCount();
Winson Chung5f8afe62013-08-12 16:19:28 -0700904 int maxWidth = 0;
905 int maxHeight = 0;
Michael Jurka8c920dd2011-01-20 14:16:56 -0800906 for (int i = 0; i < count; i++) {
907 View child = getChildAt(i);
Winson Chung2d75f122013-09-23 16:53:31 -0700908 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth,
909 MeasureSpec.EXACTLY);
910 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight,
911 MeasureSpec.EXACTLY);
Michael Jurka8c920dd2011-01-20 14:16:56 -0800912 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700913 maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
914 maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
Michael Jurka8c920dd2011-01-20 14:16:56 -0800915 }
Winson Chung2d75f122013-09-23 16:53:31 -0700916 if (mFixedWidth > 0 && mFixedHeight > 0) {
917 setMeasuredDimension(maxWidth, maxHeight);
918 } else {
919 setMeasuredDimension(widthSize, heightSize);
920 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800921 }
922
923 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700924 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Winson Chung38848ca2013-10-08 12:03:44 -0700925 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
926 (mCountX * mCellWidth);
927 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
928 int top = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800929 int count = getChildCount();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800930 for (int i = 0; i < count; i++) {
Michael Jurka8c920dd2011-01-20 14:16:56 -0800931 View child = getChildAt(i);
Winson Chung2d75f122013-09-23 16:53:31 -0700932 child.layout(left, top,
933 left + r - l,
934 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800935 }
936 }
937
938 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700939 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
940 super.onSizeChanged(w, h, oldw, oldh);
Winson Chung82a9bd22013-10-08 16:02:34 -0700941
942 // Expand the background drawing bounds by the padding baked into the background drawable
943 Rect padding = new Rect();
944 mNormalBackground.getPadding(padding);
945 mBackgroundRect.set(-padding.left, -padding.top, w + padding.right, h + padding.bottom);
946
Adam Cohenb5ba0972011-09-07 18:02:31 -0700947 mForegroundRect.set(mForegroundPadding, mForegroundPadding,
Adam Cohen215b4162012-08-30 13:14:08 -0700948 w - mForegroundPadding, h - mForegroundPadding);
Michael Jurkadee05892010-07-27 10:01:56 -0700949 }
950
951 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800952 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700953 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800954 }
955
956 @Override
957 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700958 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800959 }
960
Michael Jurka5f1c5092010-09-03 14:15:02 -0700961 public float getBackgroundAlpha() {
962 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -0700963 }
964
Adam Cohen1b0aaac2010-10-28 11:11:18 -0700965 public void setBackgroundAlphaMultiplier(float multiplier) {
Michael Jurkaa3d30ad2012-05-08 13:43:43 -0700966 if (mBackgroundAlphaMultiplier != multiplier) {
967 mBackgroundAlphaMultiplier = multiplier;
968 invalidate();
969 }
Adam Cohen1b0aaac2010-10-28 11:11:18 -0700970 }
971
Adam Cohenddb82192010-11-10 16:32:54 -0800972 public float getBackgroundAlphaMultiplier() {
973 return mBackgroundAlphaMultiplier;
974 }
975
Michael Jurka5f1c5092010-09-03 14:15:02 -0700976 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -0800977 if (mBackgroundAlpha != alpha) {
978 mBackgroundAlpha = alpha;
979 invalidate();
980 }
Michael Jurkadee05892010-07-27 10:01:56 -0700981 }
982
Michael Jurkaa52570f2012-03-20 03:18:20 -0700983 public void setShortcutAndWidgetAlpha(float alpha) {
Michael Jurka0142d492010-08-25 17:46:15 -0700984 final int childCount = getChildCount();
985 for (int i = 0; i < childCount; i++) {
Michael Jurkadee05892010-07-27 10:01:56 -0700986 getChildAt(i).setAlpha(alpha);
987 }
988 }
989
Michael Jurkaa52570f2012-03-20 03:18:20 -0700990 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
991 if (getChildCount() > 0) {
992 return (ShortcutAndWidgetContainer) getChildAt(0);
993 }
994 return null;
995 }
996
Patrick Dubroy440c3602010-07-13 17:50:32 -0700997 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700998 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700999 }
1000
Adam Cohen76fc0852011-06-17 13:26:23 -07001001 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -08001002 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001003 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -08001004 boolean[][] occupied = mOccupied;
1005 if (!permanent) {
1006 occupied = mTmpOccupied;
1007 }
1008
Adam Cohen19f37922012-03-21 11:59:11 -07001009 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001010 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1011 final ItemInfo info = (ItemInfo) child.getTag();
1012
1013 // We cancel any existing animations
1014 if (mReorderAnimators.containsKey(lp)) {
1015 mReorderAnimators.get(lp).cancel();
1016 mReorderAnimators.remove(lp);
1017 }
1018
Adam Cohen482ed822012-03-02 14:15:13 -08001019 final int oldX = lp.x;
1020 final int oldY = lp.y;
1021 if (adjustOccupied) {
1022 occupied[lp.cellX][lp.cellY] = false;
1023 occupied[cellX][cellY] = true;
1024 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001025 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001026 if (permanent) {
1027 lp.cellX = info.cellX = cellX;
1028 lp.cellY = info.cellY = cellY;
1029 } else {
1030 lp.tmpCellX = cellX;
1031 lp.tmpCellY = cellY;
1032 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001033 clc.setupLp(lp);
1034 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001035 final int newX = lp.x;
1036 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001037
Adam Cohen76fc0852011-06-17 13:26:23 -07001038 lp.x = oldX;
1039 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001040
Adam Cohen482ed822012-03-02 14:15:13 -08001041 // Exit early if we're not actually moving the view
1042 if (oldX == newX && oldY == newY) {
1043 lp.isLockedToGrid = true;
1044 return true;
1045 }
1046
Michael Jurkaf1ad6082013-03-13 12:55:46 +01001047 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001048 va.setDuration(duration);
1049 mReorderAnimators.put(lp, va);
1050
1051 va.addUpdateListener(new AnimatorUpdateListener() {
1052 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001053 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001054 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001055 lp.x = (int) ((1 - r) * oldX + r * newX);
1056 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001057 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001058 }
1059 });
Adam Cohen482ed822012-03-02 14:15:13 -08001060 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001061 boolean cancelled = false;
1062 public void onAnimationEnd(Animator animation) {
1063 // If the animation was cancelled, it means that another animation
1064 // has interrupted this one, and we don't want to lock the item into
1065 // place just yet.
1066 if (!cancelled) {
1067 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001068 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001069 }
1070 if (mReorderAnimators.containsKey(lp)) {
1071 mReorderAnimators.remove(lp);
1072 }
1073 }
1074 public void onAnimationCancel(Animator animation) {
1075 cancelled = true;
1076 }
1077 });
Adam Cohen482ed822012-03-02 14:15:13 -08001078 va.setStartDelay(delay);
1079 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001080 return true;
1081 }
1082 return false;
1083 }
1084
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001085 /**
1086 * Estimate where the top left cell of the dragged item will land if it is dropped.
1087 *
1088 * @param originX The X value of the top left corner of the item
1089 * @param originY The Y value of the top left corner of the item
1090 * @param spanX The number of horizontal cells that the item spans
1091 * @param spanY The number of vertical cells that the item spans
1092 * @param result The estimated drop cell X and Y.
1093 */
1094 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
Adam Cohend22015c2010-07-26 22:02:18 -07001095 final int countX = mCountX;
1096 final int countY = mCountY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001097
Michael Jurkaa63c4522010-08-19 13:52:27 -07001098 // pointToCellRounded takes the top left of a cell but will pad that with
1099 // cellWidth/2 and cellHeight/2 when finding the matching cell
1100 pointToCellRounded(originX, originY, result);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001101
1102 // If the item isn't fully on this screen, snap to the edges
1103 int rightOverhang = result[0] + spanX - countX;
1104 if (rightOverhang > 0) {
1105 result[0] -= rightOverhang; // Snap to right
1106 }
1107 result[0] = Math.max(0, result[0]); // Snap to left
1108 int bottomOverhang = result[1] + spanY - countY;
1109 if (bottomOverhang > 0) {
1110 result[1] -= bottomOverhang; // Snap to bottom
1111 }
1112 result[1] = Math.max(0, result[1]); // Snap to top
1113 }
1114
Adam Cohen482ed822012-03-02 14:15:13 -08001115 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1116 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001117 final int oldDragCellX = mDragCell[0];
1118 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001119
Adam Cohen2801caf2011-05-13 20:57:39 -07001120 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001121 return;
1122 }
1123
Adam Cohen482ed822012-03-02 14:15:13 -08001124 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1125 mDragCell[0] = cellX;
1126 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001127 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001128 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001129 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001130
Joe Onorato4be866d2010-10-10 11:26:02 -07001131 int left = topLeft[0];
1132 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001133
Winson Chungb8c69f32011-10-19 21:36:08 -07001134 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001135 // When drawing the drag outline, it did not account for margin offsets
1136 // added by the view's parent.
1137 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1138 left += lp.leftMargin;
1139 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001140
Adam Cohen99e8b402011-03-25 19:23:43 -07001141 // Offsets due to the size difference between the View and the dragOutline.
1142 // There is a size difference to account for the outer blur, which may lie
1143 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001144 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001145 // We center about the x axis
1146 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1147 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001148 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001149 if (dragOffset != null && dragRegion != null) {
1150 // Center the drag region *horizontally* in the cell and apply a drag
1151 // outline offset
1152 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1153 - dragRegion.width()) / 2;
Winson Chung69737c32013-10-08 17:00:19 -07001154 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1155 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1156 top += dragOffset.y + cellPaddingY;
Winson Chungb8c69f32011-10-19 21:36:08 -07001157 } else {
1158 // Center the drag outline in the cell
1159 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1160 - dragOutline.getWidth()) / 2;
1161 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1162 - dragOutline.getHeight()) / 2;
1163 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001164 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001165 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001166 mDragOutlineAnims[oldIndex].animateOut();
1167 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001168 Rect r = mDragOutlines[mDragOutlineCurrent];
1169 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1170 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001171 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001172 }
Winson Chung150fbab2010-09-29 17:14:26 -07001173
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001174 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1175 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001176 }
1177 }
1178
Adam Cohene0310962011-04-18 16:15:31 -07001179 public void clearDragOutlines() {
1180 final int oldIndex = mDragOutlineCurrent;
1181 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001182 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001183 }
1184
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001185 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001186 * Find a vacant area that will fit the given bounds nearest the requested
1187 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001188 *
Romain Guy51afc022009-05-04 18:03:43 -07001189 * @param pixelX The X location at which you want to search for a vacant area.
1190 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001191 * @param spanX Horizontal span of the object.
1192 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001193 * @param result Array in which to place the result, or null (in which case a new array will
1194 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001195 * @return The X, Y cell of a vacant area that can contain this object,
1196 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001197 */
Adam Cohend41fbf52012-02-16 23:53:59 -08001198 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
1199 int[] result) {
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001200 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001201 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001202
Michael Jurka6a1435d2010-09-27 17:35:12 -07001203 /**
1204 * Find a vacant area that will fit the given bounds nearest the requested
1205 * cell location. Uses Euclidean distance to score multiple vacant areas.
1206 *
1207 * @param pixelX The X location at which you want to search for a vacant area.
1208 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001209 * @param minSpanX The minimum horizontal span required
1210 * @param minSpanY The minimum vertical span required
1211 * @param spanX Horizontal span of the object.
1212 * @param spanY Vertical span of the object.
1213 * @param result Array in which to place the result, or null (in which case a new array will
1214 * be allocated)
1215 * @return The X, Y cell of a vacant area that can contain this object,
1216 * nearest the requested location.
1217 */
1218 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1219 int spanY, int[] result, int[] resultSpan) {
1220 return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
1221 result, resultSpan);
1222 }
1223
1224 /**
1225 * Find a vacant area that will fit the given bounds nearest the requested
1226 * cell location. Uses Euclidean distance to score multiple vacant areas.
1227 *
1228 * @param pixelX The X location at which you want to search for a vacant area.
1229 * @param pixelY The Y location at which you want to search for a vacant area.
Michael Jurka6a1435d2010-09-27 17:35:12 -07001230 * @param spanX Horizontal span of the object.
1231 * @param spanY Vertical span of the object.
Adam Cohendf035382011-04-11 17:22:04 -07001232 * @param ignoreOccupied If true, the result can be an occupied cell
1233 * @param result Array in which to place the result, or null (in which case a new array will
1234 * be allocated)
Michael Jurka6a1435d2010-09-27 17:35:12 -07001235 * @return The X, Y cell of a vacant area that can contain this object,
1236 * nearest the requested location.
1237 */
Adam Cohendf035382011-04-11 17:22:04 -07001238 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
1239 boolean ignoreOccupied, int[] result) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001240 return findNearestArea(pixelX, pixelY, spanX, spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001241 spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08001242 }
1243
1244 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1245 private void lazyInitTempRectStack() {
1246 if (mTempRectStack.isEmpty()) {
1247 for (int i = 0; i < mCountX * mCountY; i++) {
1248 mTempRectStack.push(new Rect());
1249 }
1250 }
1251 }
Adam Cohen482ed822012-03-02 14:15:13 -08001252
Adam Cohend41fbf52012-02-16 23:53:59 -08001253 private void recycleTempRects(Stack<Rect> used) {
1254 while (!used.isEmpty()) {
1255 mTempRectStack.push(used.pop());
1256 }
1257 }
1258
1259 /**
1260 * Find a vacant area that will fit the given bounds nearest the requested
1261 * cell location. Uses Euclidean distance to score multiple vacant areas.
1262 *
1263 * @param pixelX The X location at which you want to search for a vacant area.
1264 * @param pixelY The Y location at which you want to search for a vacant area.
1265 * @param minSpanX The minimum horizontal span required
1266 * @param minSpanY The minimum vertical span required
1267 * @param spanX Horizontal span of the object.
1268 * @param spanY Vertical span of the object.
1269 * @param ignoreOccupied If true, the result can be an occupied cell
1270 * @param result Array in which to place the result, or null (in which case a new array will
1271 * be allocated)
1272 * @return The X, Y cell of a vacant area that can contain this object,
1273 * nearest the requested location.
1274 */
1275 int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001276 View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
1277 boolean[][] occupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001278 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001279 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08001280 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001281
Adam Cohene3e27a82011-04-15 12:07:39 -07001282 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1283 // to the center of the item, but we are searching based on the top-left cell, so
1284 // we translate the point over to correspond to the top-left.
1285 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1286 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1287
Jeff Sharkey70864282009-04-07 21:08:40 -07001288 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001289 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001290 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001291 final Rect bestRect = new Rect(-1, -1, -1, -1);
1292 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001293
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001294 final int countX = mCountX;
1295 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001296
Adam Cohend41fbf52012-02-16 23:53:59 -08001297 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1298 spanX < minSpanX || spanY < minSpanY) {
1299 return bestXY;
1300 }
1301
1302 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001303 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001304 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1305 int ySize = -1;
1306 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001307 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001308 // First, let's see if this thing fits anywhere
1309 for (int i = 0; i < minSpanX; i++) {
1310 for (int j = 0; j < minSpanY; j++) {
Adam Cohendf035382011-04-11 17:22:04 -07001311 if (occupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001312 continue inner;
1313 }
Michael Jurkac28de512010-08-13 11:27:44 -07001314 }
1315 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001316 xSize = minSpanX;
1317 ySize = minSpanY;
1318
1319 // We know that the item will fit at _some_ acceptable size, now let's see
1320 // how big we can make it. We'll alternate between incrementing x and y spans
1321 // until we hit a limit.
1322 boolean incX = true;
1323 boolean hitMaxX = xSize >= spanX;
1324 boolean hitMaxY = ySize >= spanY;
1325 while (!(hitMaxX && hitMaxY)) {
1326 if (incX && !hitMaxX) {
1327 for (int j = 0; j < ySize; j++) {
1328 if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
1329 // We can't move out horizontally
1330 hitMaxX = true;
1331 }
1332 }
1333 if (!hitMaxX) {
1334 xSize++;
1335 }
1336 } else if (!hitMaxY) {
1337 for (int i = 0; i < xSize; i++) {
1338 if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
1339 // We can't move out vertically
1340 hitMaxY = true;
1341 }
1342 }
1343 if (!hitMaxY) {
1344 ySize++;
1345 }
1346 }
1347 hitMaxX |= xSize >= spanX;
1348 hitMaxY |= ySize >= spanY;
1349 incX = !incX;
1350 }
1351 incX = true;
1352 hitMaxX = xSize >= spanX;
1353 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001354 }
Winson Chung0be025d2011-05-23 17:45:09 -07001355 final int[] cellXY = mTmpXY;
Adam Cohene3e27a82011-04-15 12:07:39 -07001356 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001357
Adam Cohend41fbf52012-02-16 23:53:59 -08001358 // We verify that the current rect is not a sub-rect of any of our previous
1359 // candidates. In this case, the current rect is disqualified in favour of the
1360 // containing rect.
1361 Rect currentRect = mTempRectStack.pop();
1362 currentRect.set(x, y, x + xSize, y + ySize);
1363 boolean contained = false;
1364 for (Rect r : validRegions) {
1365 if (r.contains(currentRect)) {
1366 contained = true;
1367 break;
1368 }
1369 }
1370 validRegions.push(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001371 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
1372 + Math.pow(cellXY[1] - pixelY, 2));
Adam Cohen482ed822012-03-02 14:15:13 -08001373
Adam Cohend41fbf52012-02-16 23:53:59 -08001374 if ((distance <= bestDistance && !contained) ||
1375 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001376 bestDistance = distance;
1377 bestXY[0] = x;
1378 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001379 if (resultSpan != null) {
1380 resultSpan[0] = xSize;
1381 resultSpan[1] = ySize;
1382 }
1383 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001384 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001385 }
1386 }
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001387 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08001388 markCellsAsOccupiedForView(ignoreView, occupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001389
Adam Cohenc0dcf592011-06-01 15:30:43 -07001390 // Return -1, -1 if no suitable location found
1391 if (bestDistance == Double.MAX_VALUE) {
1392 bestXY[0] = -1;
1393 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001394 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001395 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001396 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001397 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001398
Adam Cohen482ed822012-03-02 14:15:13 -08001399 /**
1400 * Find a vacant area that will fit the given bounds nearest the requested
1401 * cell location, and will also weigh in a suggested direction vector of the
1402 * desired location. This method computers distance based on unit grid distances,
1403 * not pixel distances.
1404 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001405 * @param cellX The X cell nearest to which you want to search for a vacant area.
1406 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001407 * @param spanX Horizontal span of the object.
1408 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001409 * @param direction The favored direction in which the views should move from x, y
1410 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1411 * matches exactly. Otherwise we find the best matching direction.
1412 * @param occoupied The array which represents which cells in the CellLayout are occupied
1413 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001414 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001415 * @param result Array in which to place the result, or null (in which case a new array will
1416 * be allocated)
1417 * @return The X, Y cell of a vacant area that can contain this object,
1418 * nearest the requested location.
1419 */
1420 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001421 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001422 // Keep track of best-scoring drop area
1423 final int[] bestXY = result != null ? result : new int[2];
1424 float bestDistance = Float.MAX_VALUE;
1425 int bestDirectionScore = Integer.MIN_VALUE;
1426
1427 final int countX = mCountX;
1428 final int countY = mCountY;
1429
1430 for (int y = 0; y < countY - (spanY - 1); y++) {
1431 inner:
1432 for (int x = 0; x < countX - (spanX - 1); x++) {
1433 // First, let's see if this thing fits anywhere
1434 for (int i = 0; i < spanX; i++) {
1435 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001436 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001437 continue inner;
1438 }
1439 }
1440 }
1441
1442 float distance = (float)
1443 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1444 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001445 computeDirectionVector(x - cellX, y - cellY, curDirection);
1446 // The direction score is just the dot product of the two candidate direction
1447 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001448 int curDirectionScore = direction[0] * curDirection[0] +
1449 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001450 boolean exactDirectionOnly = false;
1451 boolean directionMatches = direction[0] == curDirection[0] &&
1452 direction[0] == curDirection[0];
1453 if ((directionMatches || !exactDirectionOnly) &&
1454 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001455 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1456 bestDistance = distance;
1457 bestDirectionScore = curDirectionScore;
1458 bestXY[0] = x;
1459 bestXY[1] = y;
1460 }
1461 }
1462 }
1463
1464 // Return -1, -1 if no suitable location found
1465 if (bestDistance == Float.MAX_VALUE) {
1466 bestXY[0] = -1;
1467 bestXY[1] = -1;
1468 }
1469 return bestXY;
1470 }
1471
1472 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001473 int[] direction, ItemConfiguration currentState) {
1474 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001475 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001476 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001477 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1478
Adam Cohen8baab352012-03-20 17:39:21 -07001479 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001480
1481 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001482 c.x = mTempLocation[0];
1483 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001484 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001485 }
Adam Cohen8baab352012-03-20 17:39:21 -07001486 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001487 return success;
1488 }
1489
Adam Cohenf3900c22012-11-16 18:28:11 -08001490 /**
1491 * This helper class defines a cluster of views. It helps with defining complex edges
1492 * of the cluster and determining how those edges interact with other views. The edges
1493 * essentially define a fine-grained boundary around the cluster of views -- like a more
1494 * precise version of a bounding box.
1495 */
1496 private class ViewCluster {
1497 final static int LEFT = 0;
1498 final static int TOP = 1;
1499 final static int RIGHT = 2;
1500 final static int BOTTOM = 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001501
Adam Cohenf3900c22012-11-16 18:28:11 -08001502 ArrayList<View> views;
1503 ItemConfiguration config;
1504 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001505
Adam Cohenf3900c22012-11-16 18:28:11 -08001506 int[] leftEdge = new int[mCountY];
1507 int[] rightEdge = new int[mCountY];
1508 int[] topEdge = new int[mCountX];
1509 int[] bottomEdge = new int[mCountX];
1510 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1511
1512 @SuppressWarnings("unchecked")
1513 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1514 this.views = (ArrayList<View>) views.clone();
1515 this.config = config;
1516 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001517 }
1518
Adam Cohenf3900c22012-11-16 18:28:11 -08001519 void resetEdges() {
1520 for (int i = 0; i < mCountX; i++) {
1521 topEdge[i] = -1;
1522 bottomEdge[i] = -1;
1523 }
1524 for (int i = 0; i < mCountY; i++) {
1525 leftEdge[i] = -1;
1526 rightEdge[i] = -1;
1527 }
1528 leftEdgeDirty = true;
1529 rightEdgeDirty = true;
1530 bottomEdgeDirty = true;
1531 topEdgeDirty = true;
1532 boundingRectDirty = true;
1533 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001534
Adam Cohenf3900c22012-11-16 18:28:11 -08001535 void computeEdge(int which, int[] edge) {
1536 int count = views.size();
1537 for (int i = 0; i < count; i++) {
1538 CellAndSpan cs = config.map.get(views.get(i));
1539 switch (which) {
1540 case LEFT:
1541 int left = cs.x;
1542 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1543 if (left < edge[j] || edge[j] < 0) {
1544 edge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001545 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001546 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001547 break;
1548 case RIGHT:
1549 int right = cs.x + cs.spanX;
1550 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1551 if (right > edge[j]) {
1552 edge[j] = right;
1553 }
1554 }
1555 break;
1556 case TOP:
1557 int top = cs.y;
1558 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1559 if (top < edge[j] || edge[j] < 0) {
1560 edge[j] = top;
1561 }
1562 }
1563 break;
1564 case BOTTOM:
1565 int bottom = cs.y + cs.spanY;
1566 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1567 if (bottom > edge[j]) {
1568 edge[j] = bottom;
1569 }
1570 }
1571 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001572 }
1573 }
1574 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001575
1576 boolean isViewTouchingEdge(View v, int whichEdge) {
1577 CellAndSpan cs = config.map.get(v);
1578
1579 int[] edge = getEdge(whichEdge);
1580
1581 switch (whichEdge) {
1582 case LEFT:
1583 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1584 if (edge[i] == cs.x + cs.spanX) {
1585 return true;
1586 }
1587 }
1588 break;
1589 case RIGHT:
1590 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1591 if (edge[i] == cs.x) {
1592 return true;
1593 }
1594 }
1595 break;
1596 case TOP:
1597 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1598 if (edge[i] == cs.y + cs.spanY) {
1599 return true;
1600 }
1601 }
1602 break;
1603 case BOTTOM:
1604 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1605 if (edge[i] == cs.y) {
1606 return true;
1607 }
1608 }
1609 break;
1610 }
1611 return false;
1612 }
1613
1614 void shift(int whichEdge, int delta) {
1615 for (View v: views) {
1616 CellAndSpan c = config.map.get(v);
1617 switch (whichEdge) {
1618 case LEFT:
1619 c.x -= delta;
1620 break;
1621 case RIGHT:
1622 c.x += delta;
1623 break;
1624 case TOP:
1625 c.y -= delta;
1626 break;
1627 case BOTTOM:
1628 default:
1629 c.y += delta;
1630 break;
1631 }
1632 }
1633 resetEdges();
1634 }
1635
1636 public void addView(View v) {
1637 views.add(v);
1638 resetEdges();
1639 }
1640
1641 public Rect getBoundingRect() {
1642 if (boundingRectDirty) {
1643 boolean first = true;
1644 for (View v: views) {
1645 CellAndSpan c = config.map.get(v);
1646 if (first) {
1647 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1648 first = false;
1649 } else {
1650 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1651 }
1652 }
1653 }
1654 return boundingRect;
1655 }
1656
1657 public int[] getEdge(int which) {
1658 switch (which) {
1659 case LEFT:
1660 return getLeftEdge();
1661 case RIGHT:
1662 return getRightEdge();
1663 case TOP:
1664 return getTopEdge();
1665 case BOTTOM:
1666 default:
1667 return getBottomEdge();
1668 }
1669 }
1670
1671 public int[] getLeftEdge() {
1672 if (leftEdgeDirty) {
1673 computeEdge(LEFT, leftEdge);
1674 }
1675 return leftEdge;
1676 }
1677
1678 public int[] getRightEdge() {
1679 if (rightEdgeDirty) {
1680 computeEdge(RIGHT, rightEdge);
1681 }
1682 return rightEdge;
1683 }
1684
1685 public int[] getTopEdge() {
1686 if (topEdgeDirty) {
1687 computeEdge(TOP, topEdge);
1688 }
1689 return topEdge;
1690 }
1691
1692 public int[] getBottomEdge() {
1693 if (bottomEdgeDirty) {
1694 computeEdge(BOTTOM, bottomEdge);
1695 }
1696 return bottomEdge;
1697 }
1698
1699 PositionComparator comparator = new PositionComparator();
1700 class PositionComparator implements Comparator<View> {
1701 int whichEdge = 0;
1702 public int compare(View left, View right) {
1703 CellAndSpan l = config.map.get(left);
1704 CellAndSpan r = config.map.get(right);
1705 switch (whichEdge) {
1706 case LEFT:
1707 return (r.x + r.spanX) - (l.x + l.spanX);
1708 case RIGHT:
1709 return l.x - r.x;
1710 case TOP:
1711 return (r.y + r.spanY) - (l.y + l.spanY);
1712 case BOTTOM:
1713 default:
1714 return l.y - r.y;
1715 }
1716 }
1717 }
1718
1719 public void sortConfigurationForEdgePush(int edge) {
1720 comparator.whichEdge = edge;
1721 Collections.sort(config.sortedViews, comparator);
1722 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001723 }
1724
Adam Cohenf3900c22012-11-16 18:28:11 -08001725 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1726 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001727
Adam Cohenf3900c22012-11-16 18:28:11 -08001728 ViewCluster cluster = new ViewCluster(views, currentState);
1729 Rect clusterRect = cluster.getBoundingRect();
1730 int whichEdge;
1731 int pushDistance;
1732 boolean fail = false;
1733
1734 // Determine the edge of the cluster that will be leading the push and how far
1735 // the cluster must be shifted.
1736 if (direction[0] < 0) {
1737 whichEdge = ViewCluster.LEFT;
1738 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001739 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001740 whichEdge = ViewCluster.RIGHT;
1741 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1742 } else if (direction[1] < 0) {
1743 whichEdge = ViewCluster.TOP;
1744 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1745 } else {
1746 whichEdge = ViewCluster.BOTTOM;
1747 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001748 }
1749
Adam Cohenf3900c22012-11-16 18:28:11 -08001750 // Break early for invalid push distance.
1751 if (pushDistance <= 0) {
1752 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001753 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001754
1755 // Mark the occupied state as false for the group of views we want to move.
1756 for (View v: views) {
1757 CellAndSpan c = currentState.map.get(v);
1758 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1759 }
1760
1761 // We save the current configuration -- if we fail to find a solution we will revert
1762 // to the initial state. The process of finding a solution modifies the configuration
1763 // in place, hence the need for revert in the failure case.
1764 currentState.save();
1765
1766 // The pushing algorithm is simplified by considering the views in the order in which
1767 // they would be pushed by the cluster. For example, if the cluster is leading with its
1768 // left edge, we consider sort the views by their right edge, from right to left.
1769 cluster.sortConfigurationForEdgePush(whichEdge);
1770
1771 while (pushDistance > 0 && !fail) {
1772 for (View v: currentState.sortedViews) {
1773 // For each view that isn't in the cluster, we see if the leading edge of the
1774 // cluster is contacting the edge of that view. If so, we add that view to the
1775 // cluster.
1776 if (!cluster.views.contains(v) && v != dragView) {
1777 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1778 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1779 if (!lp.canReorder) {
1780 // The push solution includes the all apps button, this is not viable.
1781 fail = true;
1782 break;
1783 }
1784 cluster.addView(v);
1785 CellAndSpan c = currentState.map.get(v);
1786
1787 // Adding view to cluster, mark it as not occupied.
1788 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1789 }
1790 }
1791 }
1792 pushDistance--;
1793
1794 // The cluster has been completed, now we move the whole thing over in the appropriate
1795 // direction.
1796 cluster.shift(whichEdge, 1);
1797 }
1798
1799 boolean foundSolution = false;
1800 clusterRect = cluster.getBoundingRect();
1801
1802 // Due to the nature of the algorithm, the only check required to verify a valid solution
1803 // is to ensure that completed shifted cluster lies completely within the cell layout.
1804 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1805 clusterRect.bottom <= mCountY) {
1806 foundSolution = true;
1807 } else {
1808 currentState.restore();
1809 }
1810
1811 // In either case, we set the occupied array as marked for the location of the views
1812 for (View v: cluster.views) {
1813 CellAndSpan c = currentState.map.get(v);
1814 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1815 }
1816
1817 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001818 }
1819
Adam Cohen482ed822012-03-02 14:15:13 -08001820 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001821 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001822 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001823
Adam Cohen8baab352012-03-20 17:39:21 -07001824 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001825 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001826 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001827 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001828 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001829 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001830 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001831 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001832 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001833 }
1834 }
Adam Cohen8baab352012-03-20 17:39:21 -07001835
Adam Cohen8baab352012-03-20 17:39:21 -07001836 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001837 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001838 CellAndSpan c = currentState.map.get(v);
1839 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1840 }
1841
Adam Cohen47a876d2012-03-19 13:21:41 -07001842 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1843 int top = boundingRect.top;
1844 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001845 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001846 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001847 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001848 CellAndSpan c = currentState.map.get(v);
1849 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001850 }
1851
Adam Cohen482ed822012-03-02 14:15:13 -08001852 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1853
Adam Cohenf3900c22012-11-16 18:28:11 -08001854 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1855 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001856
Adam Cohen8baab352012-03-20 17:39:21 -07001857 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001858 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001859 int deltaX = mTempLocation[0] - boundingRect.left;
1860 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001861 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001862 CellAndSpan c = currentState.map.get(v);
1863 c.x += deltaX;
1864 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001865 }
1866 success = true;
1867 }
Adam Cohen8baab352012-03-20 17:39:21 -07001868
1869 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001870 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001871 CellAndSpan c = currentState.map.get(v);
1872 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001873 }
1874 return success;
1875 }
1876
1877 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1878 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1879 }
1880
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001881 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1882 // to push items in each of the cardinal directions, in an order based on the direction vector
1883 // passed.
1884 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1885 int[] direction, View ignoreView, ItemConfiguration solution) {
1886 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001887 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001888 // separately in each of the components.
1889 int temp = direction[1];
1890 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001891
1892 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001893 ignoreView, solution)) {
1894 return true;
1895 }
1896 direction[1] = temp;
1897 temp = direction[0];
1898 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001899
1900 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001901 ignoreView, solution)) {
1902 return true;
1903 }
1904 // Revert the direction
1905 direction[0] = temp;
1906
1907 // Now we try pushing in each component of the opposite direction
1908 direction[0] *= -1;
1909 direction[1] *= -1;
1910 temp = direction[1];
1911 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001912 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001913 ignoreView, solution)) {
1914 return true;
1915 }
1916
1917 direction[1] = temp;
1918 temp = direction[0];
1919 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001920 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001921 ignoreView, solution)) {
1922 return true;
1923 }
1924 // revert the direction
1925 direction[0] = temp;
1926 direction[0] *= -1;
1927 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001928
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001929 } else {
1930 // If the direction vector has a single non-zero component, we push first in the
1931 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08001932 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001933 ignoreView, solution)) {
1934 return true;
1935 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001936 // Then we try the opposite direction
1937 direction[0] *= -1;
1938 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001939 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001940 ignoreView, solution)) {
1941 return true;
1942 }
1943 // Switch the direction back
1944 direction[0] *= -1;
1945 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001946
1947 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001948 // to find a solution by pushing along the perpendicular axis.
1949
1950 // Swap the components
1951 int temp = direction[1];
1952 direction[1] = direction[0];
1953 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08001954 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001955 ignoreView, solution)) {
1956 return true;
1957 }
1958
1959 // Then we try the opposite direction
1960 direction[0] *= -1;
1961 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001962 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001963 ignoreView, solution)) {
1964 return true;
1965 }
1966 // Switch the direction back
1967 direction[0] *= -1;
1968 direction[1] *= -1;
1969
1970 // Swap the components back
1971 temp = direction[1];
1972 direction[1] = direction[0];
1973 direction[0] = temp;
1974 }
1975 return false;
1976 }
1977
Adam Cohen482ed822012-03-02 14:15:13 -08001978 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001979 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001980 // Return early if get invalid cell positions
1981 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001982
Adam Cohen8baab352012-03-20 17:39:21 -07001983 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001984 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001985
Adam Cohen8baab352012-03-20 17:39:21 -07001986 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001987 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001988 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001989 if (c != null) {
1990 c.x = cellX;
1991 c.y = cellY;
1992 }
Adam Cohen482ed822012-03-02 14:15:13 -08001993 }
Adam Cohen482ed822012-03-02 14:15:13 -08001994 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1995 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001996 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001997 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001998 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001999 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002000 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08002001 if (Rect.intersects(r0, r1)) {
2002 if (!lp.canReorder) {
2003 return false;
2004 }
2005 mIntersectingViews.add(child);
2006 }
2007 }
Adam Cohen47a876d2012-03-19 13:21:41 -07002008
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002009 solution.intersectingViews = new ArrayList<View>(mIntersectingViews);
2010
Winson Chung5f8afe62013-08-12 16:19:28 -07002011 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002012 // we try to find a solution such that no displaced item travels through another item
2013 // without also displacing that item.
2014 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07002015 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07002016 return true;
2017 }
Adam Cohen47a876d2012-03-19 13:21:41 -07002018
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002019 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08002020 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07002021 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002022 return true;
2023 }
Adam Cohen47a876d2012-03-19 13:21:41 -07002024
Adam Cohen482ed822012-03-02 14:15:13 -08002025 // Ok, they couldn't move as a block, let's move them individually
2026 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07002027 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002028 return false;
2029 }
2030 }
2031 return true;
2032 }
2033
2034 /*
2035 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
2036 * the provided point and the provided cell
2037 */
Adam Cohen47a876d2012-03-19 13:21:41 -07002038 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08002039 double angle = Math.atan(((float) deltaY) / deltaX);
2040
2041 result[0] = 0;
2042 result[1] = 0;
2043 if (Math.abs(Math.cos(angle)) > 0.5f) {
2044 result[0] = (int) Math.signum(deltaX);
2045 }
2046 if (Math.abs(Math.sin(angle)) > 0.5f) {
2047 result[1] = (int) Math.signum(deltaY);
2048 }
2049 }
2050
Adam Cohen8baab352012-03-20 17:39:21 -07002051 private void copyOccupiedArray(boolean[][] occupied) {
2052 for (int i = 0; i < mCountX; i++) {
2053 for (int j = 0; j < mCountY; j++) {
2054 occupied[i][j] = mOccupied[i][j];
2055 }
2056 }
2057 }
2058
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002059 ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
2060 int spanX, int spanY, int[] direction, View dragView, boolean decX,
2061 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07002062 // Copy the current state into the solution. This solution will be manipulated as necessary.
2063 copyCurrentStateToSolution(solution, false);
2064 // Copy the current occupied array into the temporary occupied array. This array will be
2065 // manipulated as necessary to find a solution.
2066 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08002067
2068 // We find the nearest cell into which we would place the dragged item, assuming there's
2069 // nothing in its way.
2070 int result[] = new int[2];
2071 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2072
2073 boolean success = false;
2074 // First we try the exact nearest position of the item being dragged,
2075 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07002076 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
2077 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002078
2079 if (!success) {
2080 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
2081 // x, then 1 in y etc.
2082 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002083 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
2084 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002085 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002086 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
2087 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002088 }
2089 solution.isSolution = false;
2090 } else {
2091 solution.isSolution = true;
2092 solution.dragViewX = result[0];
2093 solution.dragViewY = result[1];
2094 solution.dragViewSpanX = spanX;
2095 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002096 }
2097 return solution;
2098 }
2099
2100 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002101 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002102 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002103 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002104 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002105 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08002106 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07002107 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002108 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07002109 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002110 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002111 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08002112 }
2113 }
2114
2115 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
2116 for (int i = 0; i < mCountX; i++) {
2117 for (int j = 0; j < mCountY; j++) {
2118 mTmpOccupied[i][j] = false;
2119 }
2120 }
2121
Michael Jurkaa52570f2012-03-20 03:18:20 -07002122 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002123 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002124 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002125 if (child == dragView) continue;
2126 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002127 CellAndSpan c = solution.map.get(child);
2128 if (c != null) {
2129 lp.tmpCellX = c.x;
2130 lp.tmpCellY = c.y;
2131 lp.cellHSpan = c.spanX;
2132 lp.cellVSpan = c.spanY;
2133 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002134 }
2135 }
2136 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2137 solution.dragViewSpanY, mTmpOccupied, true);
2138 }
2139
2140 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2141 commitDragView) {
2142
2143 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2144 for (int i = 0; i < mCountX; i++) {
2145 for (int j = 0; j < mCountY; j++) {
2146 occupied[i][j] = false;
2147 }
2148 }
2149
Michael Jurkaa52570f2012-03-20 03:18:20 -07002150 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002151 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002152 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002153 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002154 CellAndSpan c = solution.map.get(child);
2155 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002156 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2157 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002158 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002159 }
2160 }
2161 if (commitDragView) {
2162 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2163 solution.dragViewSpanY, occupied, true);
2164 }
2165 }
2166
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002167
2168 // This method starts or changes the reorder preview animations
2169 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
2170 View dragView, int delay, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07002171 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002172 for (int i = 0; i < childCount; i++) {
2173 View child = mShortcutsAndWidgets.getChildAt(i);
2174 if (child == dragView) continue;
2175 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002176 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
2177 != null && !solution.intersectingViews.contains(child);
2178
Adam Cohen19f37922012-03-21 11:59:11 -07002179 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002180 if (c != null && !skip) {
2181 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
2182 lp.cellY, c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002183 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002184 }
2185 }
2186 }
2187
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002188 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07002189 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002190 class ReorderPreviewAnimation {
Adam Cohen19f37922012-03-21 11:59:11 -07002191 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002192 float finalDeltaX;
2193 float finalDeltaY;
2194 float initDeltaX;
2195 float initDeltaY;
2196 float finalScale;
2197 float initScale;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002198 int mode;
2199 boolean repeating = false;
2200 private static final int PREVIEW_DURATION = 300;
2201 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
2202
2203 public static final int MODE_HINT = 0;
2204 public static final int MODE_PREVIEW = 1;
2205
Adam Cohene7587d22012-05-24 18:50:02 -07002206 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002207
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002208 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
2209 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07002210 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2211 final int x0 = mTmpPoint[0];
2212 final int y0 = mTmpPoint[1];
2213 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2214 final int x1 = mTmpPoint[0];
2215 final int y1 = mTmpPoint[1];
2216 final int dX = x1 - x0;
2217 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002218 finalDeltaX = 0;
2219 finalDeltaY = 0;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002220 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07002221 if (dX == dY && dX == 0) {
2222 } else {
2223 if (dY == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002224 finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002225 } else if (dX == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002226 finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002227 } else {
2228 double angle = Math.atan( (float) (dY) / dX);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002229 finalDeltaX = (int) (- dir * Math.signum(dX) *
2230 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
2231 finalDeltaY = (int) (- dir * Math.signum(dY) *
2232 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002233 }
2234 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002235 this.mode = mode;
Adam Cohend024f982012-05-23 18:26:45 -07002236 initDeltaX = child.getTranslationX();
2237 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002238 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002239 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002240 this.child = child;
2241 }
2242
Adam Cohend024f982012-05-23 18:26:45 -07002243 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002244 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002245 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002246 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002247 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002248 if (finalDeltaX == 0 && finalDeltaY == 0) {
2249 completeAnimationImmediately();
2250 return;
2251 }
Adam Cohen19f37922012-03-21 11:59:11 -07002252 }
Adam Cohend024f982012-05-23 18:26:45 -07002253 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002254 return;
2255 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002256 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002257 a = va;
Adam Cohen19f37922012-03-21 11:59:11 -07002258 va.setRepeatMode(ValueAnimator.REVERSE);
2259 va.setRepeatCount(ValueAnimator.INFINITE);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002260 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002261 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002262 va.addUpdateListener(new AnimatorUpdateListener() {
2263 @Override
2264 public void onAnimationUpdate(ValueAnimator animation) {
2265 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002266 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2267 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2268 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002269 child.setTranslationX(x);
2270 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002271 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002272 child.setScaleX(s);
2273 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002274 }
2275 });
2276 va.addListener(new AnimatorListenerAdapter() {
2277 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002278 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002279 initDeltaX = 0;
2280 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002281 initScale = getChildrenScale();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002282 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002283 }
2284 });
Adam Cohen19f37922012-03-21 11:59:11 -07002285 mShakeAnimators.put(child, this);
2286 va.start();
2287 }
2288
Adam Cohend024f982012-05-23 18:26:45 -07002289 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002290 if (a != null) {
2291 a.cancel();
2292 }
Adam Cohen19f37922012-03-21 11:59:11 -07002293 }
Adam Cohene7587d22012-05-24 18:50:02 -07002294
Brandon Keely50e6e562012-05-08 16:28:49 -07002295 private void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002296 if (a != null) {
2297 a.cancel();
2298 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002299
Michael Jurka2ecf9952012-06-18 12:52:28 -07002300 AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002301 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002302 s.playTogether(
Adam Cohen307fe232012-08-16 17:55:58 -07002303 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
2304 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
Michael Jurka2ecf9952012-06-18 12:52:28 -07002305 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2306 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002307 );
2308 s.setDuration(REORDER_ANIMATION_DURATION);
2309 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2310 s.start();
2311 }
Adam Cohen19f37922012-03-21 11:59:11 -07002312 }
2313
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002314 private void completeAndClearReorderPreviewAnimations() {
2315 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002316 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002317 }
2318 mShakeAnimators.clear();
2319 }
2320
Adam Cohen482ed822012-03-02 14:15:13 -08002321 private void commitTempPlacement() {
2322 for (int i = 0; i < mCountX; i++) {
2323 for (int j = 0; j < mCountY; j++) {
2324 mOccupied[i][j] = mTmpOccupied[i][j];
2325 }
2326 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002327 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002328 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002329 View child = mShortcutsAndWidgets.getChildAt(i);
2330 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2331 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002332 // We do a null check here because the item info can be null in the case of the
2333 // AllApps button in the hotseat.
2334 if (info != null) {
Adam Cohen487f7dd2012-06-28 18:12:10 -07002335 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
2336 info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
2337 info.requiresDbUpdate = true;
2338 }
Adam Cohen2acce882012-03-28 19:03:19 -07002339 info.cellX = lp.cellX = lp.tmpCellX;
2340 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002341 info.spanX = lp.cellHSpan;
2342 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002343 }
Adam Cohen482ed822012-03-02 14:15:13 -08002344 }
Adam Cohen2acce882012-03-28 19:03:19 -07002345 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002346 }
2347
2348 public void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002349 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002350 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002351 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002352 lp.useTmpCoords = useTempCoords;
2353 }
2354 }
2355
Adam Cohen482ed822012-03-02 14:15:13 -08002356 ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
2357 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2358 int[] result = new int[2];
2359 int[] resultSpan = new int[2];
2360 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
2361 resultSpan);
2362 if (result[0] >= 0 && result[1] >= 0) {
2363 copyCurrentStateToSolution(solution, false);
2364 solution.dragViewX = result[0];
2365 solution.dragViewY = result[1];
2366 solution.dragViewSpanX = resultSpan[0];
2367 solution.dragViewSpanY = resultSpan[1];
2368 solution.isSolution = true;
2369 } else {
2370 solution.isSolution = false;
2371 }
2372 return solution;
2373 }
2374
2375 public void prepareChildForDrag(View child) {
2376 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002377 }
2378
Adam Cohen19f37922012-03-21 11:59:11 -07002379 /* This seems like it should be obvious and straight-forward, but when the direction vector
2380 needs to match with the notion of the dragView pushing other views, we have to employ
2381 a slightly more subtle notion of the direction vector. The question is what two points is
2382 the vector between? The center of the dragView and its desired destination? Not quite, as
2383 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2384 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2385 or right, which helps make pushing feel right.
2386 */
2387 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2388 int spanY, View dragView, int[] resultDirection) {
2389 int[] targetDestination = new int[2];
2390
2391 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2392 Rect dragRect = new Rect();
2393 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2394 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2395
2396 Rect dropRegionRect = new Rect();
2397 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2398 dragView, dropRegionRect, mIntersectingViews);
2399
2400 int dropRegionSpanX = dropRegionRect.width();
2401 int dropRegionSpanY = dropRegionRect.height();
2402
2403 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2404 dropRegionRect.height(), dropRegionRect);
2405
2406 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2407 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2408
2409 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2410 deltaX = 0;
2411 }
2412 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2413 deltaY = 0;
2414 }
2415
2416 if (deltaX == 0 && deltaY == 0) {
2417 // No idea what to do, give a random direction.
2418 resultDirection[0] = 1;
2419 resultDirection[1] = 0;
2420 } else {
2421 computeDirectionVector(deltaX, deltaY, resultDirection);
2422 }
2423 }
2424
2425 // For a given cell and span, fetch the set of views intersecting the region.
2426 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2427 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2428 if (boundingRect != null) {
2429 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2430 }
2431 intersectingViews.clear();
2432 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2433 Rect r1 = new Rect();
2434 final int count = mShortcutsAndWidgets.getChildCount();
2435 for (int i = 0; i < count; i++) {
2436 View child = mShortcutsAndWidgets.getChildAt(i);
2437 if (child == dragView) continue;
2438 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2439 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2440 if (Rect.intersects(r0, r1)) {
2441 mIntersectingViews.add(child);
2442 if (boundingRect != null) {
2443 boundingRect.union(r1);
2444 }
2445 }
2446 }
2447 }
2448
2449 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2450 View dragView, int[] result) {
2451 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2452 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2453 mIntersectingViews);
2454 return !mIntersectingViews.isEmpty();
2455 }
2456
2457 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002458 completeAndClearReorderPreviewAnimations();
2459 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2460 final int count = mShortcutsAndWidgets.getChildCount();
2461 for (int i = 0; i < count; i++) {
2462 View child = mShortcutsAndWidgets.getChildAt(i);
2463 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2464 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2465 lp.tmpCellX = lp.cellX;
2466 lp.tmpCellY = lp.cellY;
2467 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2468 0, false, false);
2469 }
Adam Cohen19f37922012-03-21 11:59:11 -07002470 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002471 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002472 }
Adam Cohen19f37922012-03-21 11:59:11 -07002473 }
2474
Adam Cohenbebf0422012-04-11 18:06:28 -07002475 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2476 View dragView, int[] direction, boolean commit) {
2477 int[] pixelXY = new int[2];
2478 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2479
2480 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002481 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002482 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2483
2484 setUseTempCoords(true);
2485 if (swapSolution != null && swapSolution.isSolution) {
2486 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2487 // committing anything or animating anything as we just want to determine if a solution
2488 // exists
2489 copySolutionToTempState(swapSolution, dragView);
2490 setItemPlacementDirty(true);
2491 animateItemsToSolution(swapSolution, dragView, commit);
2492
2493 if (commit) {
2494 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002495 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002496 setItemPlacementDirty(false);
2497 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002498 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2499 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002500 }
2501 mShortcutsAndWidgets.requestLayout();
2502 }
2503 return swapSolution.isSolution;
2504 }
2505
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002506 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002507 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002508 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002509 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002510
2511 if (resultSpan == null) {
2512 resultSpan = new int[2];
2513 }
2514
Adam Cohen19f37922012-03-21 11:59:11 -07002515 // When we are checking drop validity or actually dropping, we don't recompute the
2516 // direction vector, since we want the solution to match the preview, and it's possible
2517 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002518 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2519 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002520 mDirectionVector[0] = mPreviousReorderDirection[0];
2521 mDirectionVector[1] = mPreviousReorderDirection[1];
2522 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002523 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2524 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2525 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002526 }
2527 } else {
2528 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2529 mPreviousReorderDirection[0] = mDirectionVector[0];
2530 mPreviousReorderDirection[1] = mDirectionVector[1];
2531 }
2532
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002533 // Find a solution involving pushing / displacing any items in the way
2534 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002535 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2536
2537 // We attempt the approach which doesn't shuffle views at all
2538 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2539 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2540
2541 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002542
2543 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2544 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002545 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2546 finalSolution = swapSolution;
2547 } else if (noShuffleSolution.isSolution) {
2548 finalSolution = noShuffleSolution;
2549 }
2550
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002551 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002552 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002553 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2554 ReorderPreviewAnimation.MODE_HINT);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002555 result[0] = finalSolution.dragViewX;
2556 result[1] = finalSolution.dragViewY;
2557 resultSpan[0] = finalSolution.dragViewSpanX;
2558 resultSpan[1] = finalSolution.dragViewSpanY;
2559 } else {
2560 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2561 }
2562 return result;
2563 }
2564
Adam Cohen482ed822012-03-02 14:15:13 -08002565 boolean foundSolution = true;
2566 if (!DESTRUCTIVE_REORDER) {
2567 setUseTempCoords(true);
2568 }
2569
2570 if (finalSolution != null) {
2571 result[0] = finalSolution.dragViewX;
2572 result[1] = finalSolution.dragViewY;
2573 resultSpan[0] = finalSolution.dragViewSpanX;
2574 resultSpan[1] = finalSolution.dragViewSpanY;
2575
2576 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2577 // committing anything or animating anything as we just want to determine if a solution
2578 // exists
2579 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2580 if (!DESTRUCTIVE_REORDER) {
2581 copySolutionToTempState(finalSolution, dragView);
2582 }
2583 setItemPlacementDirty(true);
2584 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2585
Adam Cohen19f37922012-03-21 11:59:11 -07002586 if (!DESTRUCTIVE_REORDER &&
2587 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002588 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002589 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002590 setItemPlacementDirty(false);
2591 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002592 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2593 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002594 }
2595 }
2596 } else {
2597 foundSolution = false;
2598 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2599 }
2600
2601 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2602 setUseTempCoords(false);
2603 }
Adam Cohen482ed822012-03-02 14:15:13 -08002604
Michael Jurkaa52570f2012-03-20 03:18:20 -07002605 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002606 return result;
2607 }
2608
Adam Cohen19f37922012-03-21 11:59:11 -07002609 void setItemPlacementDirty(boolean dirty) {
2610 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002611 }
Adam Cohen19f37922012-03-21 11:59:11 -07002612 boolean isItemPlacementDirty() {
2613 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002614 }
2615
2616 private class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002617 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002618 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2619 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002620 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002621 boolean isSolution = false;
2622 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2623
Adam Cohenf3900c22012-11-16 18:28:11 -08002624 void save() {
2625 // Copy current state into savedMap
2626 for (View v: map.keySet()) {
2627 map.get(v).copy(savedMap.get(v));
2628 }
2629 }
2630
2631 void restore() {
2632 // Restore current state from savedMap
2633 for (View v: savedMap.keySet()) {
2634 savedMap.get(v).copy(map.get(v));
2635 }
2636 }
2637
2638 void add(View v, CellAndSpan cs) {
2639 map.put(v, cs);
2640 savedMap.put(v, new CellAndSpan());
2641 sortedViews.add(v);
2642 }
2643
Adam Cohen482ed822012-03-02 14:15:13 -08002644 int area() {
2645 return dragViewSpanX * dragViewSpanY;
2646 }
Adam Cohen8baab352012-03-20 17:39:21 -07002647 }
2648
2649 private class CellAndSpan {
2650 int x, y;
2651 int spanX, spanY;
2652
Adam Cohenf3900c22012-11-16 18:28:11 -08002653 public CellAndSpan() {
2654 }
2655
2656 public void copy(CellAndSpan copy) {
2657 copy.x = x;
2658 copy.y = y;
2659 copy.spanX = spanX;
2660 copy.spanY = spanY;
2661 }
2662
Adam Cohen8baab352012-03-20 17:39:21 -07002663 public CellAndSpan(int x, int y, int spanX, int spanY) {
2664 this.x = x;
2665 this.y = y;
2666 this.spanX = spanX;
2667 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002668 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002669
2670 public String toString() {
2671 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2672 }
2673
Adam Cohen482ed822012-03-02 14:15:13 -08002674 }
2675
Adam Cohendf035382011-04-11 17:22:04 -07002676 /**
2677 * Find a vacant area that will fit the given bounds nearest the requested
2678 * cell location. Uses Euclidean distance to score multiple vacant areas.
2679 *
2680 * @param pixelX The X location at which you want to search for a vacant area.
2681 * @param pixelY The Y location at which you want to search for a vacant area.
2682 * @param spanX Horizontal span of the object.
2683 * @param spanY Vertical span of the object.
2684 * @param ignoreView Considers space occupied by this view as unoccupied
2685 * @param result Previously returned value to possibly recycle.
2686 * @return The X, Y cell of a vacant area that can contain this object,
2687 * nearest the requested location.
2688 */
2689 int[] findNearestVacantArea(
2690 int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
2691 return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
2692 }
2693
2694 /**
Adam Cohend41fbf52012-02-16 23:53:59 -08002695 * Find a vacant area that will fit the given bounds nearest the requested
2696 * cell location. Uses Euclidean distance to score multiple vacant areas.
2697 *
2698 * @param pixelX The X location at which you want to search for a vacant area.
2699 * @param pixelY The Y location at which you want to search for a vacant area.
2700 * @param minSpanX The minimum horizontal span required
2701 * @param minSpanY The minimum vertical span required
2702 * @param spanX Horizontal span of the object.
2703 * @param spanY Vertical span of the object.
2704 * @param ignoreView Considers space occupied by this view as unoccupied
2705 * @param result Previously returned value to possibly recycle.
2706 * @return The X, Y cell of a vacant area that can contain this object,
2707 * nearest the requested location.
2708 */
2709 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
2710 int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
Adam Cohen482ed822012-03-02 14:15:13 -08002711 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
2712 result, resultSpan, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08002713 }
2714
2715 /**
Adam Cohendf035382011-04-11 17:22:04 -07002716 * Find a starting cell position that will fit the given bounds nearest the requested
2717 * cell location. Uses Euclidean distance to score multiple vacant areas.
2718 *
2719 * @param pixelX The X location at which you want to search for a vacant area.
2720 * @param pixelY The Y location at which you want to search for a vacant area.
2721 * @param spanX Horizontal span of the object.
2722 * @param spanY Vertical span of the object.
2723 * @param ignoreView Considers space occupied by this view as unoccupied
2724 * @param result Previously returned value to possibly recycle.
2725 * @return The X, Y cell of a vacant area that can contain this object,
2726 * nearest the requested location.
2727 */
2728 int[] findNearestArea(
2729 int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2730 return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
2731 }
2732
Michael Jurka0280c3b2010-09-17 15:00:07 -07002733 boolean existsEmptyCell() {
2734 return findCellForSpan(null, 1, 1);
2735 }
2736
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002737 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002738 * Finds the upper-left coordinate of the first rectangle in the grid that can
2739 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2740 * then this method will only return coordinates for rectangles that contain the cell
2741 * (intersectX, intersectY)
2742 *
2743 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2744 * can be found.
2745 * @param spanX The horizontal span of the cell we want to find.
2746 * @param spanY The vertical span of the cell we want to find.
2747 *
2748 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002749 */
Michael Jurka0280c3b2010-09-17 15:00:07 -07002750 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Adam Cohen482ed822012-03-02 14:15:13 -08002751 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002752 }
2753
2754 /**
2755 * Like above, but ignores any cells occupied by the item "ignoreView"
2756 *
2757 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2758 * can be found.
2759 * @param spanX The horizontal span of the cell we want to find.
2760 * @param spanY The vertical span of the cell we want to find.
2761 * @param ignoreView The home screen item we should treat as not occupying any space
2762 * @return
2763 */
2764 boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
Adam Cohen482ed822012-03-02 14:15:13 -08002765 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
2766 ignoreView, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002767 }
2768
2769 /**
2770 * Like above, but if intersectX and intersectY are not -1, then this method will try to
2771 * return coordinates for rectangles that contain the cell [intersectX, intersectY]
2772 *
2773 * @param spanX The horizontal span of the cell we want to find.
2774 * @param spanY The vertical span of the cell we want to find.
2775 * @param ignoreView The home screen item we should treat as not occupying any space
2776 * @param intersectX The X coordinate of the cell that we should try to overlap
2777 * @param intersectX The Y coordinate of the cell that we should try to overlap
2778 *
2779 * @return True if a vacant cell of the specified dimension was found, false otherwise.
2780 */
2781 boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
2782 int intersectX, int intersectY) {
2783 return findCellForSpanThatIntersectsIgnoring(
Adam Cohen482ed822012-03-02 14:15:13 -08002784 cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002785 }
2786
2787 /**
2788 * The superset of the above two methods
2789 */
2790 boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002791 int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002792 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08002793 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002794
Michael Jurka28750fb2010-09-24 17:43:49 -07002795 boolean foundCell = false;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002796 while (true) {
2797 int startX = 0;
2798 if (intersectX >= 0) {
2799 startX = Math.max(startX, intersectX - (spanX - 1));
2800 }
2801 int endX = mCountX - (spanX - 1);
2802 if (intersectX >= 0) {
2803 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
2804 }
2805 int startY = 0;
2806 if (intersectY >= 0) {
2807 startY = Math.max(startY, intersectY - (spanY - 1));
2808 }
2809 int endY = mCountY - (spanY - 1);
2810 if (intersectY >= 0) {
2811 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
2812 }
2813
Winson Chungbbc60d82010-11-11 16:34:41 -08002814 for (int y = startY; y < endY && !foundCell; y++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002815 inner:
Winson Chungbbc60d82010-11-11 16:34:41 -08002816 for (int x = startX; x < endX; x++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002817 for (int i = 0; i < spanX; i++) {
2818 for (int j = 0; j < spanY; j++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002819 if (occupied[x + i][y + j]) {
Winson Chungbbc60d82010-11-11 16:34:41 -08002820 // small optimization: we can skip to after the column we just found
Michael Jurka0280c3b2010-09-17 15:00:07 -07002821 // an occupied cell
Winson Chungbbc60d82010-11-11 16:34:41 -08002822 x += i;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002823 continue inner;
2824 }
2825 }
2826 }
2827 if (cellXY != null) {
2828 cellXY[0] = x;
2829 cellXY[1] = y;
2830 }
Michael Jurka28750fb2010-09-24 17:43:49 -07002831 foundCell = true;
2832 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002833 }
2834 }
2835 if (intersectX == -1 && intersectY == -1) {
2836 break;
2837 } else {
2838 // if we failed to find anything, try again but without any requirements of
2839 // intersecting
2840 intersectX = -1;
2841 intersectY = -1;
2842 continue;
2843 }
2844 }
2845
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002846 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08002847 markCellsAsOccupiedForView(ignoreView, occupied);
Michael Jurka28750fb2010-09-24 17:43:49 -07002848 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002849 }
2850
2851 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002852 * A drag event has begun over this layout.
2853 * It may have begun over this layout (in which case onDragChild is called first),
2854 * or it may have begun on another layout.
2855 */
2856 void onDragEnter() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002857 mDragEnforcer.onDragEnter();
Winson Chungc07918d2011-07-01 15:35:26 -07002858 mDragging = true;
2859 }
2860
2861 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002862 * Called when drag has left this CellLayout or has been completed (successfully or not)
2863 */
2864 void onDragExit() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002865 mDragEnforcer.onDragExit();
Joe Onorato4be866d2010-10-10 11:26:02 -07002866 // This can actually be called when we aren't in a drag, e.g. when adding a new
2867 // item to this layout via the customize drawer.
2868 // Guard against that case.
2869 if (mDragging) {
2870 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002871 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002872
2873 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002874 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002875 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2876 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002877 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002878 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002879 }
2880
2881 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002882 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002883 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002884 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002885 *
2886 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002887 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002888 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002889 if (child != null) {
2890 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002891 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002892 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002893 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002894 }
2895
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002896 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002897 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002898 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002899 * @param cellX X coordinate of upper left corner expressed as a cell position
2900 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002901 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002902 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002903 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002904 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002905 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002906 final int cellWidth = mCellWidth;
2907 final int cellHeight = mCellHeight;
2908 final int widthGap = mWidthGap;
2909 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002910
Winson Chung4b825dcd2011-06-19 12:41:22 -07002911 final int hStartPadding = getPaddingLeft();
2912 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002913
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002914 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2915 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2916
2917 int x = hStartPadding + cellX * (cellWidth + widthGap);
2918 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002919
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002920 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002921 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002922
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002923 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002924 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002925 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002926 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002927 * @param width Width in pixels
2928 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002929 * @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 -08002930 */
Winson Chung66700732013-08-20 16:56:15 -07002931 public static int[] rectToCell(int width, int height, int[] result) {
Winson Chung5f8afe62013-08-12 16:19:28 -07002932 LauncherAppState app = LauncherAppState.getInstance();
2933 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Winson Chung66700732013-08-20 16:56:15 -07002934 Rect padding = grid.getWorkspacePadding(grid.isLandscape ?
2935 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
Winson Chung5f8afe62013-08-12 16:19:28 -07002936
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002937 // Always assume we're working with the smallest span to make sure we
2938 // reserve enough space in both orientations.
Winson Chung66700732013-08-20 16:56:15 -07002939 int parentWidth = grid.calculateCellWidth(grid.widthPx
2940 - padding.left - padding.right, (int) grid.numColumns);
2941 int parentHeight = grid.calculateCellHeight(grid.heightPx
2942 - padding.top - padding.bottom, (int) grid.numRows);
2943 int smallerSize = Math.min(parentWidth, parentHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002944
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002945 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002946 int spanX = (int) Math.ceil(width / (float) smallerSize);
2947 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002948
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002949 if (result == null) {
2950 return new int[] { spanX, spanY };
2951 }
2952 result[0] = spanX;
2953 result[1] = spanY;
2954 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002955 }
2956
Michael Jurkaf12c75c2011-01-25 22:41:40 -08002957 public int[] cellSpansToSize(int hSpans, int vSpans) {
2958 int[] size = new int[2];
2959 size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
2960 size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
2961 return size;
2962 }
2963
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002964 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08002965 * Calculate the grid spans needed to fit given item
2966 */
2967 public void calculateSpans(ItemInfo info) {
2968 final int minWidth;
2969 final int minHeight;
2970
2971 if (info instanceof LauncherAppWidgetInfo) {
2972 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
2973 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
2974 } else if (info instanceof PendingAddWidgetInfo) {
2975 minWidth = ((PendingAddWidgetInfo) info).minWidth;
2976 minHeight = ((PendingAddWidgetInfo) info).minHeight;
2977 } else {
2978 // It's not a widget, so it must be 1x1
2979 info.spanX = info.spanY = 1;
2980 return;
2981 }
2982 int[] spans = rectToCell(minWidth, minHeight, null);
2983 info.spanX = spans[0];
2984 info.spanY = spans[1];
2985 }
2986
2987 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002988 * Find the first vacant cell, if there is one.
2989 *
2990 * @param vacant Holds the x and y coordinate of the vacant cell
2991 * @param spanX Horizontal cell span.
2992 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -07002993 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002994 * @return True if a vacant cell was found
2995 */
2996 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002997
Michael Jurka0280c3b2010-09-17 15:00:07 -07002998 return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002999 }
3000
3001 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
3002 int xCount, int yCount, boolean[][] occupied) {
3003
Adam Cohen2801caf2011-05-13 20:57:39 -07003004 for (int y = 0; y < yCount; y++) {
3005 for (int x = 0; x < xCount; x++) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003006 boolean available = !occupied[x][y];
3007out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
3008 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
3009 available = available && !occupied[i][j];
3010 if (!available) break out;
3011 }
3012 }
3013
3014 if (available) {
3015 vacant[0] = x;
3016 vacant[1] = y;
3017 return true;
3018 }
3019 }
3020 }
3021
3022 return false;
3023 }
3024
Michael Jurka0280c3b2010-09-17 15:00:07 -07003025 private void clearOccupiedCells() {
3026 for (int x = 0; x < mCountX; x++) {
3027 for (int y = 0; y < mCountY; y++) {
3028 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003029 }
3030 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07003031 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003032
Adam Cohend41fbf52012-02-16 23:53:59 -08003033 public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07003034 markCellsAsUnoccupiedForView(view);
Adam Cohen482ed822012-03-02 14:15:13 -08003035 markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003036 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003037
Adam Cohend4844c32011-02-18 19:25:06 -08003038 public void markCellsAsOccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08003039 markCellsAsOccupiedForView(view, mOccupied);
3040 }
3041 public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07003042 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003043 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08003044 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003045 }
3046
Adam Cohend4844c32011-02-18 19:25:06 -08003047 public void markCellsAsUnoccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08003048 markCellsAsUnoccupiedForView(view, mOccupied);
3049 }
3050 public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07003051 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003052 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08003053 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003054 }
3055
Adam Cohen482ed822012-03-02 14:15:13 -08003056 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
3057 boolean value) {
3058 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003059 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
3060 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08003061 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003062 }
3063 }
3064 }
3065
Adam Cohen2801caf2011-05-13 20:57:39 -07003066 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07003067 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07003068 (Math.max((mCountX - 1), 0) * mWidthGap);
3069 }
3070
3071 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07003072 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07003073 (Math.max((mCountY - 1), 0) * mHeightGap);
3074 }
3075
Michael Jurka66d72172011-04-12 16:29:25 -07003076 public boolean isOccupied(int x, int y) {
3077 if (x < mCountX && y < mCountY) {
3078 return mOccupied[x][y];
3079 } else {
3080 throw new RuntimeException("Position exceeds the bound of this CellLayout");
3081 }
3082 }
3083
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003084 @Override
3085 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
3086 return new CellLayout.LayoutParams(getContext(), attrs);
3087 }
3088
3089 @Override
3090 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
3091 return p instanceof CellLayout.LayoutParams;
3092 }
3093
3094 @Override
3095 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
3096 return new CellLayout.LayoutParams(p);
3097 }
3098
Winson Chungaafa03c2010-06-11 17:34:16 -07003099 public static class CellLayoutAnimationController extends LayoutAnimationController {
3100 public CellLayoutAnimationController(Animation animation, float delay) {
3101 super(animation, delay);
3102 }
3103
3104 @Override
3105 protected long getDelayForView(View view) {
3106 return (int) (Math.random() * 150);
3107 }
3108 }
3109
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003110 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
3111 /**
3112 * Horizontal location of the item in the grid.
3113 */
3114 @ViewDebug.ExportedProperty
3115 public int cellX;
3116
3117 /**
3118 * Vertical location of the item in the grid.
3119 */
3120 @ViewDebug.ExportedProperty
3121 public int cellY;
3122
3123 /**
Adam Cohen482ed822012-03-02 14:15:13 -08003124 * Temporary horizontal location of the item in the grid during reorder
3125 */
3126 public int tmpCellX;
3127
3128 /**
3129 * Temporary vertical location of the item in the grid during reorder
3130 */
3131 public int tmpCellY;
3132
3133 /**
3134 * Indicates that the temporary coordinates should be used to layout the items
3135 */
3136 public boolean useTmpCoords;
3137
3138 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003139 * Number of cells spanned horizontally by the item.
3140 */
3141 @ViewDebug.ExportedProperty
3142 public int cellHSpan;
3143
3144 /**
3145 * Number of cells spanned vertically by the item.
3146 */
3147 @ViewDebug.ExportedProperty
3148 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07003149
Adam Cohen1b607ed2011-03-03 17:26:50 -08003150 /**
3151 * Indicates whether the item will set its x, y, width and height parameters freely,
3152 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
3153 */
Adam Cohend4844c32011-02-18 19:25:06 -08003154 public boolean isLockedToGrid = true;
3155
Adam Cohen482ed822012-03-02 14:15:13 -08003156 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07003157 * Indicates that this item should use the full extents of its parent.
3158 */
3159 public boolean isFullscreen = false;
3160
3161 /**
Adam Cohen482ed822012-03-02 14:15:13 -08003162 * Indicates whether this item can be reordered. Always true except in the case of the
3163 * the AllApps button.
3164 */
3165 public boolean canReorder = true;
3166
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003167 // X coordinate of the view in the layout.
3168 @ViewDebug.ExportedProperty
3169 int x;
3170 // Y coordinate of the view in the layout.
3171 @ViewDebug.ExportedProperty
3172 int y;
3173
Romain Guy84f296c2009-11-04 15:00:44 -08003174 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07003175
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003176 public LayoutParams(Context c, AttributeSet attrs) {
3177 super(c, attrs);
3178 cellHSpan = 1;
3179 cellVSpan = 1;
3180 }
3181
3182 public LayoutParams(ViewGroup.LayoutParams source) {
3183 super(source);
3184 cellHSpan = 1;
3185 cellVSpan = 1;
3186 }
Winson Chungaafa03c2010-06-11 17:34:16 -07003187
3188 public LayoutParams(LayoutParams source) {
3189 super(source);
3190 this.cellX = source.cellX;
3191 this.cellY = source.cellY;
3192 this.cellHSpan = source.cellHSpan;
3193 this.cellVSpan = source.cellVSpan;
3194 }
3195
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003196 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08003197 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003198 this.cellX = cellX;
3199 this.cellY = cellY;
3200 this.cellHSpan = cellHSpan;
3201 this.cellVSpan = cellVSpan;
3202 }
3203
Adam Cohen2374abf2013-04-16 14:56:57 -07003204 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
3205 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08003206 if (isLockedToGrid) {
3207 final int myCellHSpan = cellHSpan;
3208 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07003209 int myCellX = useTmpCoords ? tmpCellX : cellX;
3210 int myCellY = useTmpCoords ? tmpCellY : cellY;
3211
3212 if (invertHorizontally) {
3213 myCellX = colCount - myCellX - cellHSpan;
3214 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08003215
Adam Cohend4844c32011-02-18 19:25:06 -08003216 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
3217 leftMargin - rightMargin;
3218 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
3219 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08003220 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
3221 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08003222 }
3223 }
Winson Chungaafa03c2010-06-11 17:34:16 -07003224
Winson Chungaafa03c2010-06-11 17:34:16 -07003225 public String toString() {
3226 return "(" + this.cellX + ", " + this.cellY + ")";
3227 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07003228
3229 public void setWidth(int width) {
3230 this.width = width;
3231 }
3232
3233 public int getWidth() {
3234 return width;
3235 }
3236
3237 public void setHeight(int height) {
3238 this.height = height;
3239 }
3240
3241 public int getHeight() {
3242 return height;
3243 }
3244
3245 public void setX(int x) {
3246 this.x = x;
3247 }
3248
3249 public int getX() {
3250 return x;
3251 }
3252
3253 public void setY(int y) {
3254 this.y = y;
3255 }
3256
3257 public int getY() {
3258 return y;
3259 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003260 }
3261
Michael Jurka0280c3b2010-09-17 15:00:07 -07003262 // This class stores info for two purposes:
3263 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
3264 // its spanX, spanY, and the screen it is on
3265 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
3266 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
3267 // the CellLayout that was long clicked
Michael Jurkae5fb0f22011-04-11 13:27:46 -07003268 static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003269 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07003270 int cellX = -1;
3271 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003272 int spanX;
3273 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07003274 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07003275 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003276
Adam Cohene0aaa0d2014-05-12 12:44:22 -07003277 CellInfo(View v, ItemInfo info) {
3278 cell = v;
3279 cellX = info.cellX;
3280 cellY = info.cellY;
3281 spanX = info.spanX;
3282 spanY = info.spanY;
3283 screenId = info.screenId;
3284 container = info.container;
3285 }
3286
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003287 @Override
3288 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07003289 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
3290 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003291 }
3292 }
Michael Jurkad771c962011-08-09 15:00:48 -07003293
3294 public boolean lastDownOnOccupiedCell() {
3295 return mLastDownOnOccupiedCell;
3296 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003297}