blob: 89473c8b1aa65ac35c50a49b3c2f35d69fd25e72 [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;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080033import android.graphics.Rect;
Adam Cohen482ed822012-03-02 14:15:13 -080034import android.graphics.drawable.ColorDrawable;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070035import android.graphics.drawable.Drawable;
Adam Cohen1462de32012-07-24 22:34:36 -070036import android.os.Parcelable;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080037import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070038import android.util.Log;
Adam Cohen1462de32012-07-24 22:34:36 -070039import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080040import android.view.MotionEvent;
41import android.view.View;
42import android.view.ViewDebug;
43import android.view.ViewGroup;
Winson Chungaafa03c2010-06-11 17:34:16 -070044import android.view.animation.Animation;
Winson Chung150fbab2010-09-29 17:14:26 -070045import android.view.animation.DecelerateInterpolator;
Winson Chungaafa03c2010-06-11 17:34:16 -070046import android.view.animation.LayoutAnimationController;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080047
Daniel Sandler325dc232013-06-05 22:57:57 -040048import com.android.launcher3.FolderIcon.FolderRingAnimator;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070049
Adam Cohen69ce2e52011-07-03 19:25:21 -070050import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070051import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080052import java.util.Collections;
53import java.util.Comparator;
Adam Cohenbfbfd262011-06-13 16:55:12 -070054import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080055import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070056
Michael Jurkabdb5c532011-02-01 15:05:06 -080057public class CellLayout extends ViewGroup {
Winson Chungaafa03c2010-06-11 17:34:16 -070058 static final String TAG = "CellLayout";
59
Adam Cohen2acce882012-03-28 19:03:19 -070060 private Launcher mLauncher;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080061 private int mCellWidth;
62 private int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070063 private int mFixedCellWidth;
64 private int mFixedCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070065
Adam Cohend22015c2010-07-26 22:02:18 -070066 private int mCountX;
67 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080068
Adam Cohen234c4cd2011-07-17 21:03:04 -070069 private int mOriginalWidthGap;
70 private int mOriginalHeightGap;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080071 private int mWidthGap;
72 private int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070073 private int mMaxGap;
Adam Cohen917e3882013-10-31 15:03:35 -070074 private boolean mDropPending = false;
Adam Cohenc50438c2014-08-19 17:43:05 -070075 private boolean mIsDragTarget = true;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080076
Patrick Dubroyde7658b2010-09-27 11:15:43 -070077 // These are temporary variables to prevent having to allocate a new object just to
78 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Winson Chung0be025d2011-05-23 17:45:09 -070079 private final int[] mTmpXY = new int[2];
Patrick Dubroyde7658b2010-09-27 11:15:43 -070080 private final int[] mTmpPoint = new int[2];
Adam Cohen69ce2e52011-07-03 19:25:21 -070081 int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070082
The Android Open Source Project31dd5032009-03-03 19:32:27 -080083 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -080084 boolean[][] mTmpOccupied;
Michael Jurkad771c962011-08-09 15:00:48 -070085 private boolean mLastDownOnOccupiedCell = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080086
Michael Jurkadee05892010-07-27 10:01:56 -070087 private OnTouchListener mInterceptTouchListener;
88
Adam Cohen69ce2e52011-07-03 19:25:21 -070089 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -070090 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -070091
Adam Cohen02dcfcc2013-10-01 12:37:33 -070092 private float FOREGROUND_ALPHA_DAMPER = 0.65f;
Adam Cohenb5ba0972011-09-07 18:02:31 -070093 private int mForegroundAlpha = 0;
Michael Jurka5f1c5092010-09-03 14:15:02 -070094 private float mBackgroundAlpha;
Adam Cohen1b0aaac2010-10-28 11:11:18 -070095 private float mBackgroundAlphaMultiplier = 1.0f;
Winson Chung59a488a2013-12-10 12:32:14 -080096 private boolean mDrawBackground = true;
Adam Cohenf34bab52010-09-30 14:11:56 -070097
Michael Jurka33945b22010-12-21 18:19:38 -080098 private Drawable mNormalBackground;
Michael Jurka33945b22010-12-21 18:19:38 -080099 private Drawable mActiveGlowBackground;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700100 private Drawable mOverScrollForegroundDrawable;
101 private Drawable mOverScrollLeft;
102 private Drawable mOverScrollRight;
Michael Jurka18014792010-10-14 09:01:34 -0700103 private Rect mBackgroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700104 private Rect mForegroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700105 private int mForegroundPadding;
Patrick Dubroy1262e362010-10-06 15:49:50 -0700106
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700107 // These values allow a fixed measurement to be set on the CellLayout.
108 private int mFixedWidth = -1;
109 private int mFixedHeight = -1;
110
Michael Jurka33945b22010-12-21 18:19:38 -0800111 // If we're actively dragging something over this screen, mIsDragOverlapping is true
112 private boolean mIsDragOverlapping = false;
Adam Cohendedbd962013-07-11 14:21:49 -0700113 boolean mUseActiveGlowBackground = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700114
Winson Chung150fbab2010-09-29 17:14:26 -0700115 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700116 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohend41fbf52012-02-16 23:53:59 -0800117 private Rect[] mDragOutlines = new Rect[4];
Chet Haase472b2812010-10-14 07:02:04 -0700118 private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700119 private InterruptibleInOutAnimator[] mDragOutlineAnims =
120 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700121
122 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700123 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700124 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700125
Patrick Dubroy96864c32011-03-10 17:17:23 -0800126 private BubbleTextView mPressedOrFocusedIcon;
127
Adam Cohen482ed822012-03-02 14:15:13 -0800128 private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
129 HashMap<CellLayout.LayoutParams, Animator>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800130 private HashMap<View, ReorderPreviewAnimation>
131 mShakeAnimators = new HashMap<View, ReorderPreviewAnimation>();
Adam Cohen19f37922012-03-21 11:59:11 -0700132
133 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700134
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700135 // When a drag operation is in progress, holds the nearest cell to the touch point
136 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800137
Joe Onorato4be866d2010-10-10 11:26:02 -0700138 private boolean mDragging = false;
139
Patrick Dubroyce34a972010-10-19 10:34:32 -0700140 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700141 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700142
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800143 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700144 private float mHotseatScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800145
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800146 public static final int MODE_SHOW_REORDER_HINT = 0;
147 public static final int MODE_DRAG_OVER = 1;
148 public static final int MODE_ON_DROP = 2;
149 public static final int MODE_ON_DROP_EXTERNAL = 3;
150 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700151 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800152 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
153
Adam Cohena897f392012-04-27 18:12:05 -0700154 static final int LANDSCAPE = 0;
155 static final int PORTRAIT = 1;
156
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800157 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700158 private static final int REORDER_ANIMATION_DURATION = 150;
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800159 private float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700160
Adam Cohen482ed822012-03-02 14:15:13 -0800161 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
162 private Rect mOccupiedRect = new Rect();
163 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700164 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700165 private static final int INVALID_DIRECTION = -100;
Adam Cohenc6cc61d2012-04-04 12:47:08 -0700166 private DropTarget.DragEnforcer mDragEnforcer;
Adam Cohen482ed822012-03-02 14:15:13 -0800167
Winson Chung3a6e7f32013-10-09 15:50:52 -0700168 private Rect mTempRect = new Rect();
169
Michael Jurkaca993832012-06-29 15:17:04 -0700170 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700171
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800172 public CellLayout(Context context) {
173 this(context, null);
174 }
175
176 public CellLayout(Context context, AttributeSet attrs) {
177 this(context, attrs, 0);
178 }
179
180 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
181 super(context, attrs, defStyle);
Michael Jurka8b805b12012-04-18 14:23:14 -0700182 mDragEnforcer = new DropTarget.DragEnforcer(context);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700183
184 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
185 // the user where a dragged item will land when dropped.
186 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800187 setClipToPadding(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700188 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700189
Winson Chung892c74d2013-08-22 16:15:50 -0700190 LauncherAppState app = LauncherAppState.getInstance();
191 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800192 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
193
Winson Chung11a1a532013-09-13 11:14:45 -0700194 mCellWidth = mCellHeight = -1;
Nilesh Agrawal5f7099a2014-01-02 15:54:57 -0800195 mFixedCellWidth = mFixedCellHeight = -1;
Winson Chung5f8afe62013-08-12 16:19:28 -0700196 mWidthGap = mOriginalWidthGap = 0;
197 mHeightGap = mOriginalHeightGap = 0;
198 mMaxGap = Integer.MAX_VALUE;
Winson Chung892c74d2013-08-22 16:15:50 -0700199 mCountX = (int) grid.numColumns;
200 mCountY = (int) grid.numRows;
Michael Jurka0280c3b2010-09-17 15:00:07 -0700201 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800202 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700203 mPreviousReorderDirection[0] = INVALID_DIRECTION;
204 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800205
206 a.recycle();
207
208 setAlwaysDrawnWithCacheEnabled(false);
209
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700210 final Resources res = getResources();
Winson Chung6e1c0d32013-10-25 15:24:24 -0700211 mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700212
Adam Cohen410f3cd2013-09-22 12:09:32 -0700213 mNormalBackground = res.getDrawable(R.drawable.screenpanel);
214 mActiveGlowBackground = res.getDrawable(R.drawable.screenpanel_hover);
Michael Jurka33945b22010-12-21 18:19:38 -0800215
Adam Cohenb5ba0972011-09-07 18:02:31 -0700216 mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
217 mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
218 mForegroundPadding =
219 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
Michael Jurka33945b22010-12-21 18:19:38 -0800220
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800221 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
Winson Chung5f8afe62013-08-12 16:19:28 -0700222 grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700223
Winson Chungb26f3d62011-06-02 10:49:29 -0700224 mNormalBackground.setFilterBitmap(true);
Winson Chungb26f3d62011-06-02 10:49:29 -0700225 mActiveGlowBackground.setFilterBitmap(true);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700226
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700227 // Initialize the data structures used for the drag visualization.
Patrick Dubroyce34a972010-10-19 10:34:32 -0700228 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700229 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700230 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800231 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700232 }
233
234 // When dragging things around the home screens, we show a green outline of
235 // where the item will land. The outlines gradually fade out, leaving a trail
236 // behind the drag path.
237 // Set up all the animations that are used to implement this fading.
238 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700239 final float fromAlphaValue = 0;
240 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700241
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700242 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700243
244 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700245 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100246 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700247 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700248 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700249 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700250 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700251 final Bitmap outline = (Bitmap)anim.getTag();
252
253 // If an animation is started and then stopped very quickly, we can still
254 // get spurious updates we've cleared the tag. Guard against this.
255 if (outline == null) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700256 @SuppressWarnings("all") // suppress dead code warning
257 final boolean debug = false;
258 if (debug) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700259 Object val = animation.getAnimatedValue();
260 Log.d(TAG, "anim " + thisIndex + " update: " + val +
261 ", isStopped " + anim.isStopped());
262 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700263 // Try to prevent it from continuing to run
264 animation.cancel();
265 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700266 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800267 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700268 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700269 }
270 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700271 // The animation holds a reference to the drag outline bitmap as long is it's
272 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700273 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700274 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700275 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700276 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700277 anim.setTag(null);
278 }
279 }
280 });
281 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700282 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700283
Michael Jurka18014792010-10-14 09:01:34 -0700284 mBackgroundRect = new Rect();
Adam Cohenb5ba0972011-09-07 18:02:31 -0700285 mForegroundRect = new Rect();
Michael Jurkabea15192010-11-17 12:33:46 -0800286
Michael Jurkaa52570f2012-03-20 03:18:20 -0700287 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
Adam Cohen2374abf2013-04-16 14:56:57 -0700288 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700289 mCountX, mCountY);
Adam Cohen2374abf2013-04-16 14:56:57 -0700290
Michael Jurkaa52570f2012-03-20 03:18:20 -0700291 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700292 }
293
Chris Craik01f2d7f2013-10-01 14:41:56 -0700294 public void enableHardwareLayer(boolean hasLayer) {
295 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700296 }
297
298 public void buildHardwareLayer() {
299 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700300 }
301
Adam Cohen307fe232012-08-16 17:55:58 -0700302 public float getChildrenScale() {
303 return mIsHotseat ? mHotseatScale : 1.0f;
304 }
305
Winson Chung5f8afe62013-08-12 16:19:28 -0700306 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700307 mFixedCellWidth = mCellWidth = width;
308 mFixedCellHeight = mCellHeight = height;
Winson Chung5f8afe62013-08-12 16:19:28 -0700309 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
310 mCountX, mCountY);
311 }
312
Adam Cohen2801caf2011-05-13 20:57:39 -0700313 public void setGridSize(int x, int y) {
314 mCountX = x;
315 mCountY = y;
316 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800317 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700318 mTempRectStack.clear();
Adam Cohen2374abf2013-04-16 14:56:57 -0700319 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700320 mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700321 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700322 }
323
Adam Cohen2374abf2013-04-16 14:56:57 -0700324 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
325 public void setInvertIfRtl(boolean invert) {
326 mShortcutsAndWidgets.setInvertIfRtl(invert);
327 }
328
Adam Cohen917e3882013-10-31 15:03:35 -0700329 public void setDropPending(boolean pending) {
330 mDropPending = pending;
331 }
332
333 public boolean isDropPending() {
334 return mDropPending;
335 }
336
Patrick Dubroy96864c32011-03-10 17:17:23 -0800337 private void invalidateBubbleTextView(BubbleTextView icon) {
338 final int padding = icon.getPressedOrFocusedBackgroundPadding();
Winson Chung4b825dcd2011-06-19 12:41:22 -0700339 invalidate(icon.getLeft() + getPaddingLeft() - padding,
340 icon.getTop() + getPaddingTop() - padding,
341 icon.getRight() + getPaddingLeft() + padding,
342 icon.getBottom() + getPaddingTop() + padding);
Patrick Dubroy96864c32011-03-10 17:17:23 -0800343 }
344
Adam Cohenb5ba0972011-09-07 18:02:31 -0700345 void setOverScrollAmount(float r, boolean left) {
346 if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
347 mOverScrollForegroundDrawable = mOverScrollLeft;
348 } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
349 mOverScrollForegroundDrawable = mOverScrollRight;
350 }
351
Adam Cohen02dcfcc2013-10-01 12:37:33 -0700352 r *= FOREGROUND_ALPHA_DAMPER;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700353 mForegroundAlpha = (int) Math.round((r * 255));
354 mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
355 invalidate();
356 }
357
Patrick Dubroy96864c32011-03-10 17:17:23 -0800358 void setPressedOrFocusedIcon(BubbleTextView icon) {
359 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
360 // requires an expanded clip rect (due to the glow's blur radius)
361 BubbleTextView oldIcon = mPressedOrFocusedIcon;
362 mPressedOrFocusedIcon = icon;
363 if (oldIcon != null) {
364 invalidateBubbleTextView(oldIcon);
365 }
366 if (mPressedOrFocusedIcon != null) {
367 invalidateBubbleTextView(mPressedOrFocusedIcon);
368 }
369 }
370
Adam Cohendedbd962013-07-11 14:21:49 -0700371 void setUseActiveGlowBackground(boolean use) {
372 mUseActiveGlowBackground = use;
373 }
374
Winson Chung59a488a2013-12-10 12:32:14 -0800375 void disableBackground() {
376 mDrawBackground = false;
377 }
378
Adam Cohenc50438c2014-08-19 17:43:05 -0700379 void disableDragTarget() {
380 mIsDragTarget = false;
381 }
382
383 boolean isDragTarget() {
384 return mIsDragTarget;
385 }
386
387 void setIsDragOverlapping(boolean isDragOverlapping) {
388 if (mIsDragOverlapping != isDragOverlapping) {
389 mIsDragOverlapping = isDragOverlapping;
390 setUseActiveGlowBackground(mIsDragOverlapping);
391 invalidate();
392 }
393 }
394
Michael Jurka33945b22010-12-21 18:19:38 -0800395 boolean getIsDragOverlapping() {
396 return mIsDragOverlapping;
397 }
398
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700399 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700400 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700401 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
402 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
403 // When we're small, we are either drawn normally or in the "accepts drops" state (during
404 // a drag). However, we also drag the mini hover background *over* one of those two
405 // backgrounds
Winson Chung59a488a2013-12-10 12:32:14 -0800406 if (mDrawBackground && mBackgroundAlpha > 0.0f) {
Adam Cohenf34bab52010-09-30 14:11:56 -0700407 Drawable bg;
Michael Jurka33945b22010-12-21 18:19:38 -0800408
Adam Cohendedbd962013-07-11 14:21:49 -0700409 if (mUseActiveGlowBackground) {
Michael Jurka33945b22010-12-21 18:19:38 -0800410 // In the mini case, we draw the active_glow bg *over* the active background
Michael Jurkabdf78552011-10-31 14:34:25 -0700411 bg = mActiveGlowBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700412 } else {
Michael Jurkabdf78552011-10-31 14:34:25 -0700413 bg = mNormalBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700414 }
Michael Jurka33945b22010-12-21 18:19:38 -0800415
416 bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
417 bg.setBounds(mBackgroundRect);
418 bg.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700419 }
Romain Guya6abce82009-11-10 02:54:41 -0800420
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700421 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700422 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700423 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700424 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800425 final Rect r = mDragOutlines[i];
Winson Chung3a6e7f32013-10-09 15:50:52 -0700426 mTempRect.set(r);
427 Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale());
Joe Onorato4be866d2010-10-10 11:26:02 -0700428 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700429 paint.setAlpha((int)(alpha + .5f));
Winson Chung3a6e7f32013-10-09 15:50:52 -0700430 canvas.drawBitmap(b, null, mTempRect, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700431 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700432 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800433
434 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
435 // requires an expanded clip rect (due to the glow's blur radius)
436 if (mPressedOrFocusedIcon != null) {
437 final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
438 final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
439 if (b != null) {
Winson Chung3a6e7f32013-10-09 15:50:52 -0700440 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
441 (mCountX * mCellWidth);
442 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
443 int top = getPaddingTop();
Patrick Dubroy96864c32011-03-10 17:17:23 -0800444 canvas.drawBitmap(b,
Winson Chung3a6e7f32013-10-09 15:50:52 -0700445 mPressedOrFocusedIcon.getLeft() + left - padding,
446 mPressedOrFocusedIcon.getTop() + top - padding,
Patrick Dubroy96864c32011-03-10 17:17:23 -0800447 null);
448 }
449 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700450
Adam Cohen482ed822012-03-02 14:15:13 -0800451 if (DEBUG_VISUALIZE_OCCUPIED) {
452 int[] pt = new int[2];
453 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700454 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800455 for (int i = 0; i < mCountX; i++) {
456 for (int j = 0; j < mCountY; j++) {
457 if (mOccupied[i][j]) {
458 cellToPoint(i, j, pt);
459 canvas.save();
460 canvas.translate(pt[0], pt[1]);
461 cd.draw(canvas);
462 canvas.restore();
463 }
464 }
465 }
466 }
467
Andrew Flynn850d2e72012-04-26 16:51:20 -0700468 int previewOffset = FolderRingAnimator.sPreviewSize;
469
Adam Cohen69ce2e52011-07-03 19:25:21 -0700470 // The folder outer / inner ring image(s)
Winson Chung5f8afe62013-08-12 16:19:28 -0700471 LauncherAppState app = LauncherAppState.getInstance();
472 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700473 for (int i = 0; i < mFolderOuterRings.size(); i++) {
474 FolderRingAnimator fra = mFolderOuterRings.get(i);
475
Adam Cohen5108bc02013-09-20 17:04:51 -0700476 Drawable d;
477 int width, height;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700478 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700479 View child = getChildAt(fra.mCellX, fra.mCellY);
Adam Cohen558f1c22013-10-09 15:15:24 -0700480
Winson Chung89f97052013-09-20 11:32:26 -0700481 if (child != null) {
Adam Cohen558f1c22013-10-09 15:15:24 -0700482 int centerX = mTempLocation[0] + mCellWidth / 2;
483 int centerY = mTempLocation[1] + previewOffset / 2 +
484 child.getPaddingTop() + grid.folderBackgroundOffset;
485
Adam Cohen5108bc02013-09-20 17:04:51 -0700486 // Draw outer ring, if it exists
487 if (FolderIcon.HAS_OUTER_RING) {
488 d = FolderRingAnimator.sSharedOuterRingDrawable;
489 width = (int) (fra.getOuterRingSize() * getChildrenScale());
490 height = width;
491 canvas.save();
492 canvas.translate(centerX - width / 2, centerY - height / 2);
493 d.setBounds(0, 0, width, height);
494 d.draw(canvas);
495 canvas.restore();
496 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700497
Winson Chung89f97052013-09-20 11:32:26 -0700498 // Draw inner ring
499 d = FolderRingAnimator.sSharedInnerRingDrawable;
500 width = (int) (fra.getInnerRingSize() * getChildrenScale());
501 height = width;
Winson Chung89f97052013-09-20 11:32:26 -0700502 canvas.save();
503 canvas.translate(centerX - width / 2, centerY - width / 2);
504 d.setBounds(0, 0, width, height);
505 d.draw(canvas);
506 canvas.restore();
507 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700508 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700509
510 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
511 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
512 int width = d.getIntrinsicWidth();
513 int height = d.getIntrinsicHeight();
514
515 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700516 View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]);
Winson Chung89f97052013-09-20 11:32:26 -0700517 if (child != null) {
518 int centerX = mTempLocation[0] + mCellWidth / 2;
519 int centerY = mTempLocation[1] + previewOffset / 2 +
520 child.getPaddingTop() + grid.folderBackgroundOffset;
Adam Cohenc51934b2011-07-26 21:07:43 -0700521
Winson Chung89f97052013-09-20 11:32:26 -0700522 canvas.save();
523 canvas.translate(centerX - width / 2, centerY - width / 2);
524 d.setBounds(0, 0, width, height);
525 d.draw(canvas);
526 canvas.restore();
527 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700528 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700529 }
530
Adam Cohenb5ba0972011-09-07 18:02:31 -0700531 @Override
532 protected void dispatchDraw(Canvas canvas) {
533 super.dispatchDraw(canvas);
534 if (mForegroundAlpha > 0) {
535 mOverScrollForegroundDrawable.setBounds(mForegroundRect);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700536 mOverScrollForegroundDrawable.draw(canvas);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700537 }
538 }
539
Adam Cohen69ce2e52011-07-03 19:25:21 -0700540 public void showFolderAccept(FolderRingAnimator fra) {
541 mFolderOuterRings.add(fra);
542 }
543
544 public void hideFolderAccept(FolderRingAnimator fra) {
545 if (mFolderOuterRings.contains(fra)) {
546 mFolderOuterRings.remove(fra);
547 }
548 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700549 }
550
Adam Cohenc51934b2011-07-26 21:07:43 -0700551 public void setFolderLeaveBehindCell(int x, int y) {
552 mFolderLeaveBehindCell[0] = x;
553 mFolderLeaveBehindCell[1] = y;
554 invalidate();
555 }
556
557 public void clearFolderLeaveBehind() {
558 mFolderLeaveBehindCell[0] = -1;
559 mFolderLeaveBehindCell[1] = -1;
560 invalidate();
561 }
562
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700563 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700564 public boolean shouldDelayChildPressedState() {
565 return false;
566 }
567
Adam Cohen1462de32012-07-24 22:34:36 -0700568 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700569 try {
570 dispatchRestoreInstanceState(states);
571 } catch (IllegalArgumentException ex) {
572 if (LauncherAppState.isDogfoodBuild()) {
573 throw ex;
574 }
575 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
576 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
577 }
Adam Cohen1462de32012-07-24 22:34:36 -0700578 }
579
Michael Jurkae6235dd2011-10-04 15:02:05 -0700580 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700581 public void cancelLongPress() {
582 super.cancelLongPress();
583
584 // Cancel long press for all children
585 final int count = getChildCount();
586 for (int i = 0; i < count; i++) {
587 final View child = getChildAt(i);
588 child.cancelLongPress();
589 }
590 }
591
Michael Jurkadee05892010-07-27 10:01:56 -0700592 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
593 mInterceptTouchListener = listener;
594 }
595
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800596 int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700597 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800598 }
599
600 int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700601 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800602 }
603
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800604 public void setIsHotseat(boolean isHotseat) {
605 mIsHotseat = isHotseat;
Winson Chung5f8afe62013-08-12 16:19:28 -0700606 mShortcutsAndWidgets.setIsHotseat(isHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800607 }
608
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800609 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Andrew Flynn850d2e72012-04-26 16:51:20 -0700610 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700611 final LayoutParams lp = params;
612
Andrew Flynnde38e422012-05-08 11:22:15 -0700613 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800614 if (child instanceof BubbleTextView) {
615 BubbleTextView bubbleChild = (BubbleTextView) child;
Winson Chung5f8afe62013-08-12 16:19:28 -0700616 bubbleChild.setTextVisibility(!mIsHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800617 }
618
Adam Cohen307fe232012-08-16 17:55:58 -0700619 child.setScaleX(getChildrenScale());
620 child.setScaleY(getChildrenScale());
621
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800622 // Generate an id for each view, this assumes we have at most 256x256 cells
623 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700624 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700625 // If the horizontal or vertical span is set to -1, it is taken to
626 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700627 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
628 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800629
Winson Chungaafa03c2010-06-11 17:34:16 -0700630 child.setId(childId);
631
Michael Jurkaa52570f2012-03-20 03:18:20 -0700632 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700633
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700634 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700635
Winson Chungaafa03c2010-06-11 17:34:16 -0700636 return true;
637 }
638 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800639 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700640
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800641 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700642 public void removeAllViews() {
643 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700644 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700645 }
646
647 @Override
648 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700649 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700650 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700651 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700652 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700653 }
654
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700655 public void removeViewWithoutMarkingCells(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700656 mShortcutsAndWidgets.removeView(view);
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700657 }
658
Michael Jurka0280c3b2010-09-17 15:00:07 -0700659 @Override
660 public void removeView(View view) {
661 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700662 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700663 }
664
665 @Override
666 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700667 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
668 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700669 }
670
671 @Override
672 public void removeViewInLayout(View view) {
673 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700674 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700675 }
676
677 @Override
678 public void removeViews(int start, int count) {
679 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700680 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700681 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700682 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700683 }
684
685 @Override
686 public void removeViewsInLayout(int start, int count) {
687 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700688 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700689 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700690 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800691 }
692
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800693 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800694 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Cohenc1997fd2011-08-15 18:26:39 -0700695 // First we clear the tag to ensure that on every touch down we start with a fresh slate,
696 // even in the case where we return early. Not clearing here was causing bugs whereby on
697 // long-press we'd end up picking up an item from a previous drag operation.
Michael Jurkadee05892010-07-27 10:01:56 -0700698 if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
699 return true;
700 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800701
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800702 return false;
703 }
704
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700705 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700706 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800707 * @param x X coordinate of the point
708 * @param y Y coordinate of the point
709 * @param result Array of 2 ints to hold the x and y coordinate of the cell
710 */
711 void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700712 final int hStartPadding = getPaddingLeft();
713 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800714
715 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
716 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
717
Adam Cohend22015c2010-07-26 22:02:18 -0700718 final int xAxis = mCountX;
719 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800720
721 if (result[0] < 0) result[0] = 0;
722 if (result[0] >= xAxis) result[0] = xAxis - 1;
723 if (result[1] < 0) result[1] = 0;
724 if (result[1] >= yAxis) result[1] = yAxis - 1;
725 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700726
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800727 /**
728 * Given a point, return the cell that most closely encloses that point
729 * @param x X coordinate of the point
730 * @param y Y coordinate of the point
731 * @param result Array of 2 ints to hold the x and y coordinate of the cell
732 */
733 void pointToCellRounded(int x, int y, int[] result) {
734 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
735 }
736
737 /**
738 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700739 *
740 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800741 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700742 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800743 * @param result Array of 2 ints to hold the x and y coordinate of the point
744 */
745 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700746 final int hStartPadding = getPaddingLeft();
747 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800748
749 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
750 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
751 }
752
Adam Cohene3e27a82011-04-15 12:07:39 -0700753 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800754 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700755 *
756 * @param cellX X coordinate of the cell
757 * @param cellY Y coordinate of the cell
758 *
759 * @param result Array of 2 ints to hold the x and y coordinate of the point
760 */
761 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700762 regionToCenterPoint(cellX, cellY, 1, 1, result);
763 }
764
765 /**
766 * Given a cell coordinate and span return the point that represents the center of the regio
767 *
768 * @param cellX X coordinate of the cell
769 * @param cellY Y coordinate of the cell
770 *
771 * @param result Array of 2 ints to hold the x and y coordinate of the point
772 */
773 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700774 final int hStartPadding = getPaddingLeft();
775 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700776 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
777 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
778 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
779 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700780 }
781
Adam Cohen19f37922012-03-21 11:59:11 -0700782 /**
783 * Given a cell coordinate and span fills out a corresponding pixel rect
784 *
785 * @param cellX X coordinate of the cell
786 * @param cellY Y coordinate of the cell
787 * @param result Rect in which to write the result
788 */
789 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
790 final int hStartPadding = getPaddingLeft();
791 final int vStartPadding = getPaddingTop();
792 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
793 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
794 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
795 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
796 }
797
Adam Cohen482ed822012-03-02 14:15:13 -0800798 public float getDistanceFromCell(float x, float y, int[] cell) {
799 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
800 float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) +
801 Math.pow(y - mTmpPoint[1], 2));
802 return distance;
803 }
804
Romain Guy84f296c2009-11-04 15:00:44 -0800805 int getCellWidth() {
806 return mCellWidth;
807 }
808
809 int getCellHeight() {
810 return mCellHeight;
811 }
812
Adam Cohend4844c32011-02-18 19:25:06 -0800813 int getWidthGap() {
814 return mWidthGap;
815 }
816
817 int getHeightGap() {
818 return mHeightGap;
819 }
820
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700821 Rect getContentRect(Rect r) {
822 if (r == null) {
823 r = new Rect();
824 }
825 int left = getPaddingLeft();
826 int top = getPaddingTop();
Michael Jurka8b805b12012-04-18 14:23:14 -0700827 int right = left + getWidth() - getPaddingLeft() - getPaddingRight();
828 int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom();
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700829 r.set(left, top, right, bottom);
830 return r;
831 }
832
Winson Chungfe411c82013-09-26 16:07:17 -0700833 /** Return a rect that has the cellWidth/cellHeight (left, top), and
834 * widthGap/heightGap (right, bottom) */
Winson Chung66700732013-08-20 16:56:15 -0700835 static void getMetrics(Rect metrics, int paddedMeasureWidth,
836 int paddedMeasureHeight, int countX, int countY) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700837 LauncherAppState app = LauncherAppState.getInstance();
838 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Winson Chung66700732013-08-20 16:56:15 -0700839 metrics.set(grid.calculateCellWidth(paddedMeasureWidth, countX),
840 grid.calculateCellHeight(paddedMeasureHeight, countY), 0, 0);
Adam Cohenf4bd5792012-04-27 11:35:29 -0700841 }
842
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700843 public void setFixedSize(int width, int height) {
844 mFixedWidth = width;
845 mFixedHeight = height;
846 }
847
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800848 @Override
849 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700850 LauncherAppState app = LauncherAppState.getInstance();
851 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
852
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800853 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800854 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700855 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
856 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700857 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
858 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700859 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700860 int cw = grid.calculateCellWidth(childWidthSize, mCountX);
861 int ch = grid.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700862 if (cw != mCellWidth || ch != mCellHeight) {
863 mCellWidth = cw;
864 mCellHeight = ch;
865 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
866 mHeightGap, mCountX, mCountY);
867 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700868 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700869
Winson Chung2d75f122013-09-23 16:53:31 -0700870 int newWidth = childWidthSize;
871 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700872 if (mFixedWidth > 0 && mFixedHeight > 0) {
873 newWidth = mFixedWidth;
874 newHeight = mFixedHeight;
875 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800876 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
877 }
878
Adam Cohend22015c2010-07-26 22:02:18 -0700879 int numWidthGaps = mCountX - 1;
880 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800881
Adam Cohen234c4cd2011-07-17 21:03:04 -0700882 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700883 int hSpace = childWidthSize;
884 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700885 int hFreeSpace = hSpace - (mCountX * mCellWidth);
886 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700887 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
888 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -0700889 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
890 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700891 } else {
892 mWidthGap = mOriginalWidthGap;
893 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700894 }
Michael Jurka8c920dd2011-01-20 14:16:56 -0800895 int count = getChildCount();
Winson Chung5f8afe62013-08-12 16:19:28 -0700896 int maxWidth = 0;
897 int maxHeight = 0;
Michael Jurka8c920dd2011-01-20 14:16:56 -0800898 for (int i = 0; i < count; i++) {
899 View child = getChildAt(i);
Winson Chung2d75f122013-09-23 16:53:31 -0700900 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth,
901 MeasureSpec.EXACTLY);
902 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight,
903 MeasureSpec.EXACTLY);
Michael Jurka8c920dd2011-01-20 14:16:56 -0800904 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700905 maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
906 maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
Michael Jurka8c920dd2011-01-20 14:16:56 -0800907 }
Winson Chung2d75f122013-09-23 16:53:31 -0700908 if (mFixedWidth > 0 && mFixedHeight > 0) {
909 setMeasuredDimension(maxWidth, maxHeight);
910 } else {
911 setMeasuredDimension(widthSize, heightSize);
912 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800913 }
914
915 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700916 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Winson Chung38848ca2013-10-08 12:03:44 -0700917 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
918 (mCountX * mCellWidth);
919 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
920 int top = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800921 int count = getChildCount();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800922 for (int i = 0; i < count; i++) {
Michael Jurka8c920dd2011-01-20 14:16:56 -0800923 View child = getChildAt(i);
Winson Chung2d75f122013-09-23 16:53:31 -0700924 child.layout(left, top,
925 left + r - l,
926 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800927 }
928 }
929
930 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700931 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
932 super.onSizeChanged(w, h, oldw, oldh);
Winson Chung82a9bd22013-10-08 16:02:34 -0700933
934 // Expand the background drawing bounds by the padding baked into the background drawable
935 Rect padding = new Rect();
936 mNormalBackground.getPadding(padding);
937 mBackgroundRect.set(-padding.left, -padding.top, w + padding.right, h + padding.bottom);
938
Adam Cohenb5ba0972011-09-07 18:02:31 -0700939 mForegroundRect.set(mForegroundPadding, mForegroundPadding,
Adam Cohen215b4162012-08-30 13:14:08 -0700940 w - mForegroundPadding, h - mForegroundPadding);
Michael Jurkadee05892010-07-27 10:01:56 -0700941 }
942
943 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800944 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700945 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800946 }
947
948 @Override
949 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700950 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800951 }
952
Michael Jurka5f1c5092010-09-03 14:15:02 -0700953 public float getBackgroundAlpha() {
954 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -0700955 }
956
Adam Cohen1b0aaac2010-10-28 11:11:18 -0700957 public void setBackgroundAlphaMultiplier(float multiplier) {
Adam Cohenc50438c2014-08-19 17:43:05 -0700958
Michael Jurkaa3d30ad2012-05-08 13:43:43 -0700959 if (mBackgroundAlphaMultiplier != multiplier) {
960 mBackgroundAlphaMultiplier = multiplier;
961 invalidate();
962 }
Adam Cohen1b0aaac2010-10-28 11:11:18 -0700963 }
964
Adam Cohenddb82192010-11-10 16:32:54 -0800965 public float getBackgroundAlphaMultiplier() {
966 return mBackgroundAlphaMultiplier;
967 }
968
Michael Jurka5f1c5092010-09-03 14:15:02 -0700969 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -0800970 if (mBackgroundAlpha != alpha) {
971 mBackgroundAlpha = alpha;
972 invalidate();
973 }
Michael Jurkadee05892010-07-27 10:01:56 -0700974 }
975
Michael Jurkaa52570f2012-03-20 03:18:20 -0700976 public void setShortcutAndWidgetAlpha(float alpha) {
Michael Jurka0142d492010-08-25 17:46:15 -0700977 final int childCount = getChildCount();
978 for (int i = 0; i < childCount; i++) {
Michael Jurkadee05892010-07-27 10:01:56 -0700979 getChildAt(i).setAlpha(alpha);
980 }
981 }
982
Michael Jurkaa52570f2012-03-20 03:18:20 -0700983 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700984 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700985 }
986
Patrick Dubroy440c3602010-07-13 17:50:32 -0700987 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700988 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700989 }
990
Adam Cohen76fc0852011-06-17 13:26:23 -0700991 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800992 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700993 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800994 boolean[][] occupied = mOccupied;
995 if (!permanent) {
996 occupied = mTmpOccupied;
997 }
998
Adam Cohen19f37922012-03-21 11:59:11 -0700999 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001000 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1001 final ItemInfo info = (ItemInfo) child.getTag();
1002
1003 // We cancel any existing animations
1004 if (mReorderAnimators.containsKey(lp)) {
1005 mReorderAnimators.get(lp).cancel();
1006 mReorderAnimators.remove(lp);
1007 }
1008
Adam Cohen482ed822012-03-02 14:15:13 -08001009 final int oldX = lp.x;
1010 final int oldY = lp.y;
1011 if (adjustOccupied) {
1012 occupied[lp.cellX][lp.cellY] = false;
1013 occupied[cellX][cellY] = true;
1014 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001015 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001016 if (permanent) {
1017 lp.cellX = info.cellX = cellX;
1018 lp.cellY = info.cellY = cellY;
1019 } else {
1020 lp.tmpCellX = cellX;
1021 lp.tmpCellY = cellY;
1022 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001023 clc.setupLp(lp);
1024 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001025 final int newX = lp.x;
1026 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001027
Adam Cohen76fc0852011-06-17 13:26:23 -07001028 lp.x = oldX;
1029 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001030
Adam Cohen482ed822012-03-02 14:15:13 -08001031 // Exit early if we're not actually moving the view
1032 if (oldX == newX && oldY == newY) {
1033 lp.isLockedToGrid = true;
1034 return true;
1035 }
1036
Michael Jurkaf1ad6082013-03-13 12:55:46 +01001037 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001038 va.setDuration(duration);
1039 mReorderAnimators.put(lp, va);
1040
1041 va.addUpdateListener(new AnimatorUpdateListener() {
1042 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001043 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001044 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001045 lp.x = (int) ((1 - r) * oldX + r * newX);
1046 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001047 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001048 }
1049 });
Adam Cohen482ed822012-03-02 14:15:13 -08001050 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001051 boolean cancelled = false;
1052 public void onAnimationEnd(Animator animation) {
1053 // If the animation was cancelled, it means that another animation
1054 // has interrupted this one, and we don't want to lock the item into
1055 // place just yet.
1056 if (!cancelled) {
1057 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001058 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001059 }
1060 if (mReorderAnimators.containsKey(lp)) {
1061 mReorderAnimators.remove(lp);
1062 }
1063 }
1064 public void onAnimationCancel(Animator animation) {
1065 cancelled = true;
1066 }
1067 });
Adam Cohen482ed822012-03-02 14:15:13 -08001068 va.setStartDelay(delay);
1069 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001070 return true;
1071 }
1072 return false;
1073 }
1074
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001075 /**
1076 * Estimate where the top left cell of the dragged item will land if it is dropped.
1077 *
1078 * @param originX The X value of the top left corner of the item
1079 * @param originY The Y value of the top left corner of the item
1080 * @param spanX The number of horizontal cells that the item spans
1081 * @param spanY The number of vertical cells that the item spans
1082 * @param result The estimated drop cell X and Y.
1083 */
1084 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
Adam Cohend22015c2010-07-26 22:02:18 -07001085 final int countX = mCountX;
1086 final int countY = mCountY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001087
Michael Jurkaa63c4522010-08-19 13:52:27 -07001088 // pointToCellRounded takes the top left of a cell but will pad that with
1089 // cellWidth/2 and cellHeight/2 when finding the matching cell
1090 pointToCellRounded(originX, originY, result);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001091
1092 // If the item isn't fully on this screen, snap to the edges
1093 int rightOverhang = result[0] + spanX - countX;
1094 if (rightOverhang > 0) {
1095 result[0] -= rightOverhang; // Snap to right
1096 }
1097 result[0] = Math.max(0, result[0]); // Snap to left
1098 int bottomOverhang = result[1] + spanY - countY;
1099 if (bottomOverhang > 0) {
1100 result[1] -= bottomOverhang; // Snap to bottom
1101 }
1102 result[1] = Math.max(0, result[1]); // Snap to top
1103 }
1104
Adam Cohen482ed822012-03-02 14:15:13 -08001105 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1106 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001107 final int oldDragCellX = mDragCell[0];
1108 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001109
Adam Cohen2801caf2011-05-13 20:57:39 -07001110 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001111 return;
1112 }
1113
Adam Cohen482ed822012-03-02 14:15:13 -08001114 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1115 mDragCell[0] = cellX;
1116 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001117 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001118 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001119 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001120
Joe Onorato4be866d2010-10-10 11:26:02 -07001121 int left = topLeft[0];
1122 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001123
Winson Chungb8c69f32011-10-19 21:36:08 -07001124 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001125 // When drawing the drag outline, it did not account for margin offsets
1126 // added by the view's parent.
1127 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1128 left += lp.leftMargin;
1129 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001130
Adam Cohen99e8b402011-03-25 19:23:43 -07001131 // Offsets due to the size difference between the View and the dragOutline.
1132 // There is a size difference to account for the outer blur, which may lie
1133 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001134 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001135 // We center about the x axis
1136 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1137 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001138 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001139 if (dragOffset != null && dragRegion != null) {
1140 // Center the drag region *horizontally* in the cell and apply a drag
1141 // outline offset
1142 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1143 - dragRegion.width()) / 2;
Winson Chung69737c32013-10-08 17:00:19 -07001144 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1145 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1146 top += dragOffset.y + cellPaddingY;
Winson Chungb8c69f32011-10-19 21:36:08 -07001147 } else {
1148 // Center the drag outline in the cell
1149 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1150 - dragOutline.getWidth()) / 2;
1151 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1152 - dragOutline.getHeight()) / 2;
1153 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001154 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001155 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001156 mDragOutlineAnims[oldIndex].animateOut();
1157 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001158 Rect r = mDragOutlines[mDragOutlineCurrent];
1159 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1160 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001161 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001162 }
Winson Chung150fbab2010-09-29 17:14:26 -07001163
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001164 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1165 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001166 }
1167 }
1168
Adam Cohene0310962011-04-18 16:15:31 -07001169 public void clearDragOutlines() {
1170 final int oldIndex = mDragOutlineCurrent;
1171 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001172 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001173 }
1174
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001175 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001176 * Find a vacant area that will fit the given bounds nearest the requested
1177 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001178 *
Romain Guy51afc022009-05-04 18:03:43 -07001179 * @param pixelX The X location at which you want to search for a vacant area.
1180 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001181 * @param spanX Horizontal span of the object.
1182 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001183 * @param result Array in which to place the result, or null (in which case a new array will
1184 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001185 * @return The X, Y cell of a vacant area that can contain this object,
1186 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001187 */
Adam Cohend41fbf52012-02-16 23:53:59 -08001188 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
1189 int[] result) {
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001190 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001191 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001192
Michael Jurka6a1435d2010-09-27 17:35:12 -07001193 /**
1194 * Find a vacant area that will fit the given bounds nearest the requested
1195 * cell location. Uses Euclidean distance to score multiple vacant areas.
1196 *
1197 * @param pixelX The X location at which you want to search for a vacant area.
1198 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001199 * @param minSpanX The minimum horizontal span required
1200 * @param minSpanY The minimum vertical span required
1201 * @param spanX Horizontal span of the object.
1202 * @param spanY Vertical span of the object.
1203 * @param result Array in which to place the result, or null (in which case a new array will
1204 * be allocated)
1205 * @return The X, Y cell of a vacant area that can contain this object,
1206 * nearest the requested location.
1207 */
1208 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1209 int spanY, int[] result, int[] resultSpan) {
1210 return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
1211 result, resultSpan);
1212 }
1213
1214 /**
1215 * Find a vacant area that will fit the given bounds nearest the requested
1216 * cell location. Uses Euclidean distance to score multiple vacant areas.
1217 *
1218 * @param pixelX The X location at which you want to search for a vacant area.
1219 * @param pixelY The Y location at which you want to search for a vacant area.
Michael Jurka6a1435d2010-09-27 17:35:12 -07001220 * @param spanX Horizontal span of the object.
1221 * @param spanY Vertical span of the object.
Adam Cohendf035382011-04-11 17:22:04 -07001222 * @param ignoreOccupied If true, the result can be an occupied cell
1223 * @param result Array in which to place the result, or null (in which case a new array will
1224 * be allocated)
Michael Jurka6a1435d2010-09-27 17:35:12 -07001225 * @return The X, Y cell of a vacant area that can contain this object,
1226 * nearest the requested location.
1227 */
Adam Cohendf035382011-04-11 17:22:04 -07001228 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
1229 boolean ignoreOccupied, int[] result) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001230 return findNearestArea(pixelX, pixelY, spanX, spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001231 spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08001232 }
1233
1234 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1235 private void lazyInitTempRectStack() {
1236 if (mTempRectStack.isEmpty()) {
1237 for (int i = 0; i < mCountX * mCountY; i++) {
1238 mTempRectStack.push(new Rect());
1239 }
1240 }
1241 }
Adam Cohen482ed822012-03-02 14:15:13 -08001242
Adam Cohend41fbf52012-02-16 23:53:59 -08001243 private void recycleTempRects(Stack<Rect> used) {
1244 while (!used.isEmpty()) {
1245 mTempRectStack.push(used.pop());
1246 }
1247 }
1248
1249 /**
1250 * Find a vacant area that will fit the given bounds nearest the requested
1251 * cell location. Uses Euclidean distance to score multiple vacant areas.
1252 *
1253 * @param pixelX The X location at which you want to search for a vacant area.
1254 * @param pixelY The Y location at which you want to search for a vacant area.
1255 * @param minSpanX The minimum horizontal span required
1256 * @param minSpanY The minimum vertical span required
1257 * @param spanX Horizontal span of the object.
1258 * @param spanY Vertical span of the object.
1259 * @param ignoreOccupied If true, the result can be an occupied cell
1260 * @param result Array in which to place the result, or null (in which case a new array will
1261 * be allocated)
1262 * @return The X, Y cell of a vacant area that can contain this object,
1263 * nearest the requested location.
1264 */
1265 int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001266 View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
1267 boolean[][] occupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001268 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001269 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08001270 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001271
Adam Cohene3e27a82011-04-15 12:07:39 -07001272 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1273 // to the center of the item, but we are searching based on the top-left cell, so
1274 // we translate the point over to correspond to the top-left.
1275 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1276 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1277
Jeff Sharkey70864282009-04-07 21:08:40 -07001278 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001279 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001280 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001281 final Rect bestRect = new Rect(-1, -1, -1, -1);
1282 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001283
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001284 final int countX = mCountX;
1285 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001286
Adam Cohend41fbf52012-02-16 23:53:59 -08001287 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1288 spanX < minSpanX || spanY < minSpanY) {
1289 return bestXY;
1290 }
1291
1292 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001293 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001294 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1295 int ySize = -1;
1296 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001297 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001298 // First, let's see if this thing fits anywhere
1299 for (int i = 0; i < minSpanX; i++) {
1300 for (int j = 0; j < minSpanY; j++) {
Adam Cohendf035382011-04-11 17:22:04 -07001301 if (occupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001302 continue inner;
1303 }
Michael Jurkac28de512010-08-13 11:27:44 -07001304 }
1305 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001306 xSize = minSpanX;
1307 ySize = minSpanY;
1308
1309 // We know that the item will fit at _some_ acceptable size, now let's see
1310 // how big we can make it. We'll alternate between incrementing x and y spans
1311 // until we hit a limit.
1312 boolean incX = true;
1313 boolean hitMaxX = xSize >= spanX;
1314 boolean hitMaxY = ySize >= spanY;
1315 while (!(hitMaxX && hitMaxY)) {
1316 if (incX && !hitMaxX) {
1317 for (int j = 0; j < ySize; j++) {
1318 if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
1319 // We can't move out horizontally
1320 hitMaxX = true;
1321 }
1322 }
1323 if (!hitMaxX) {
1324 xSize++;
1325 }
1326 } else if (!hitMaxY) {
1327 for (int i = 0; i < xSize; i++) {
1328 if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
1329 // We can't move out vertically
1330 hitMaxY = true;
1331 }
1332 }
1333 if (!hitMaxY) {
1334 ySize++;
1335 }
1336 }
1337 hitMaxX |= xSize >= spanX;
1338 hitMaxY |= ySize >= spanY;
1339 incX = !incX;
1340 }
1341 incX = true;
1342 hitMaxX = xSize >= spanX;
1343 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001344 }
Winson Chung0be025d2011-05-23 17:45:09 -07001345 final int[] cellXY = mTmpXY;
Adam Cohene3e27a82011-04-15 12:07:39 -07001346 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001347
Adam Cohend41fbf52012-02-16 23:53:59 -08001348 // We verify that the current rect is not a sub-rect of any of our previous
1349 // candidates. In this case, the current rect is disqualified in favour of the
1350 // containing rect.
1351 Rect currentRect = mTempRectStack.pop();
1352 currentRect.set(x, y, x + xSize, y + ySize);
1353 boolean contained = false;
1354 for (Rect r : validRegions) {
1355 if (r.contains(currentRect)) {
1356 contained = true;
1357 break;
1358 }
1359 }
1360 validRegions.push(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001361 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
1362 + Math.pow(cellXY[1] - pixelY, 2));
Adam Cohen482ed822012-03-02 14:15:13 -08001363
Adam Cohend41fbf52012-02-16 23:53:59 -08001364 if ((distance <= bestDistance && !contained) ||
1365 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001366 bestDistance = distance;
1367 bestXY[0] = x;
1368 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001369 if (resultSpan != null) {
1370 resultSpan[0] = xSize;
1371 resultSpan[1] = ySize;
1372 }
1373 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001374 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001375 }
1376 }
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001377 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08001378 markCellsAsOccupiedForView(ignoreView, occupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001379
Adam Cohenc0dcf592011-06-01 15:30:43 -07001380 // Return -1, -1 if no suitable location found
1381 if (bestDistance == Double.MAX_VALUE) {
1382 bestXY[0] = -1;
1383 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001384 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001385 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001386 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001387 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001388
Adam Cohen482ed822012-03-02 14:15:13 -08001389 /**
1390 * Find a vacant area that will fit the given bounds nearest the requested
1391 * cell location, and will also weigh in a suggested direction vector of the
1392 * desired location. This method computers distance based on unit grid distances,
1393 * not pixel distances.
1394 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001395 * @param cellX The X cell nearest to which you want to search for a vacant area.
1396 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001397 * @param spanX Horizontal span of the object.
1398 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001399 * @param direction The favored direction in which the views should move from x, y
1400 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1401 * matches exactly. Otherwise we find the best matching direction.
1402 * @param occoupied The array which represents which cells in the CellLayout are occupied
1403 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001404 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001405 * @param result Array in which to place the result, or null (in which case a new array will
1406 * be allocated)
1407 * @return The X, Y cell of a vacant area that can contain this object,
1408 * nearest the requested location.
1409 */
1410 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001411 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001412 // Keep track of best-scoring drop area
1413 final int[] bestXY = result != null ? result : new int[2];
1414 float bestDistance = Float.MAX_VALUE;
1415 int bestDirectionScore = Integer.MIN_VALUE;
1416
1417 final int countX = mCountX;
1418 final int countY = mCountY;
1419
1420 for (int y = 0; y < countY - (spanY - 1); y++) {
1421 inner:
1422 for (int x = 0; x < countX - (spanX - 1); x++) {
1423 // First, let's see if this thing fits anywhere
1424 for (int i = 0; i < spanX; i++) {
1425 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001426 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001427 continue inner;
1428 }
1429 }
1430 }
1431
1432 float distance = (float)
1433 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1434 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001435 computeDirectionVector(x - cellX, y - cellY, curDirection);
1436 // The direction score is just the dot product of the two candidate direction
1437 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001438 int curDirectionScore = direction[0] * curDirection[0] +
1439 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001440 boolean exactDirectionOnly = false;
1441 boolean directionMatches = direction[0] == curDirection[0] &&
1442 direction[0] == curDirection[0];
1443 if ((directionMatches || !exactDirectionOnly) &&
1444 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001445 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1446 bestDistance = distance;
1447 bestDirectionScore = curDirectionScore;
1448 bestXY[0] = x;
1449 bestXY[1] = y;
1450 }
1451 }
1452 }
1453
1454 // Return -1, -1 if no suitable location found
1455 if (bestDistance == Float.MAX_VALUE) {
1456 bestXY[0] = -1;
1457 bestXY[1] = -1;
1458 }
1459 return bestXY;
1460 }
1461
1462 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001463 int[] direction, ItemConfiguration currentState) {
1464 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001465 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001466 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001467 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1468
Adam Cohen8baab352012-03-20 17:39:21 -07001469 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001470
1471 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001472 c.x = mTempLocation[0];
1473 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001474 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001475 }
Adam Cohen8baab352012-03-20 17:39:21 -07001476 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001477 return success;
1478 }
1479
Adam Cohenf3900c22012-11-16 18:28:11 -08001480 /**
1481 * This helper class defines a cluster of views. It helps with defining complex edges
1482 * of the cluster and determining how those edges interact with other views. The edges
1483 * essentially define a fine-grained boundary around the cluster of views -- like a more
1484 * precise version of a bounding box.
1485 */
1486 private class ViewCluster {
1487 final static int LEFT = 0;
1488 final static int TOP = 1;
1489 final static int RIGHT = 2;
1490 final static int BOTTOM = 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001491
Adam Cohenf3900c22012-11-16 18:28:11 -08001492 ArrayList<View> views;
1493 ItemConfiguration config;
1494 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001495
Adam Cohenf3900c22012-11-16 18:28:11 -08001496 int[] leftEdge = new int[mCountY];
1497 int[] rightEdge = new int[mCountY];
1498 int[] topEdge = new int[mCountX];
1499 int[] bottomEdge = new int[mCountX];
1500 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1501
1502 @SuppressWarnings("unchecked")
1503 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1504 this.views = (ArrayList<View>) views.clone();
1505 this.config = config;
1506 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001507 }
1508
Adam Cohenf3900c22012-11-16 18:28:11 -08001509 void resetEdges() {
1510 for (int i = 0; i < mCountX; i++) {
1511 topEdge[i] = -1;
1512 bottomEdge[i] = -1;
1513 }
1514 for (int i = 0; i < mCountY; i++) {
1515 leftEdge[i] = -1;
1516 rightEdge[i] = -1;
1517 }
1518 leftEdgeDirty = true;
1519 rightEdgeDirty = true;
1520 bottomEdgeDirty = true;
1521 topEdgeDirty = true;
1522 boundingRectDirty = true;
1523 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001524
Adam Cohenf3900c22012-11-16 18:28:11 -08001525 void computeEdge(int which, int[] edge) {
1526 int count = views.size();
1527 for (int i = 0; i < count; i++) {
1528 CellAndSpan cs = config.map.get(views.get(i));
1529 switch (which) {
1530 case LEFT:
1531 int left = cs.x;
1532 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1533 if (left < edge[j] || edge[j] < 0) {
1534 edge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001535 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001536 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001537 break;
1538 case RIGHT:
1539 int right = cs.x + cs.spanX;
1540 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1541 if (right > edge[j]) {
1542 edge[j] = right;
1543 }
1544 }
1545 break;
1546 case TOP:
1547 int top = cs.y;
1548 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1549 if (top < edge[j] || edge[j] < 0) {
1550 edge[j] = top;
1551 }
1552 }
1553 break;
1554 case BOTTOM:
1555 int bottom = cs.y + cs.spanY;
1556 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1557 if (bottom > edge[j]) {
1558 edge[j] = bottom;
1559 }
1560 }
1561 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001562 }
1563 }
1564 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001565
1566 boolean isViewTouchingEdge(View v, int whichEdge) {
1567 CellAndSpan cs = config.map.get(v);
1568
1569 int[] edge = getEdge(whichEdge);
1570
1571 switch (whichEdge) {
1572 case LEFT:
1573 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1574 if (edge[i] == cs.x + cs.spanX) {
1575 return true;
1576 }
1577 }
1578 break;
1579 case RIGHT:
1580 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1581 if (edge[i] == cs.x) {
1582 return true;
1583 }
1584 }
1585 break;
1586 case TOP:
1587 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1588 if (edge[i] == cs.y + cs.spanY) {
1589 return true;
1590 }
1591 }
1592 break;
1593 case BOTTOM:
1594 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1595 if (edge[i] == cs.y) {
1596 return true;
1597 }
1598 }
1599 break;
1600 }
1601 return false;
1602 }
1603
1604 void shift(int whichEdge, int delta) {
1605 for (View v: views) {
1606 CellAndSpan c = config.map.get(v);
1607 switch (whichEdge) {
1608 case LEFT:
1609 c.x -= delta;
1610 break;
1611 case RIGHT:
1612 c.x += delta;
1613 break;
1614 case TOP:
1615 c.y -= delta;
1616 break;
1617 case BOTTOM:
1618 default:
1619 c.y += delta;
1620 break;
1621 }
1622 }
1623 resetEdges();
1624 }
1625
1626 public void addView(View v) {
1627 views.add(v);
1628 resetEdges();
1629 }
1630
1631 public Rect getBoundingRect() {
1632 if (boundingRectDirty) {
1633 boolean first = true;
1634 for (View v: views) {
1635 CellAndSpan c = config.map.get(v);
1636 if (first) {
1637 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1638 first = false;
1639 } else {
1640 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1641 }
1642 }
1643 }
1644 return boundingRect;
1645 }
1646
1647 public int[] getEdge(int which) {
1648 switch (which) {
1649 case LEFT:
1650 return getLeftEdge();
1651 case RIGHT:
1652 return getRightEdge();
1653 case TOP:
1654 return getTopEdge();
1655 case BOTTOM:
1656 default:
1657 return getBottomEdge();
1658 }
1659 }
1660
1661 public int[] getLeftEdge() {
1662 if (leftEdgeDirty) {
1663 computeEdge(LEFT, leftEdge);
1664 }
1665 return leftEdge;
1666 }
1667
1668 public int[] getRightEdge() {
1669 if (rightEdgeDirty) {
1670 computeEdge(RIGHT, rightEdge);
1671 }
1672 return rightEdge;
1673 }
1674
1675 public int[] getTopEdge() {
1676 if (topEdgeDirty) {
1677 computeEdge(TOP, topEdge);
1678 }
1679 return topEdge;
1680 }
1681
1682 public int[] getBottomEdge() {
1683 if (bottomEdgeDirty) {
1684 computeEdge(BOTTOM, bottomEdge);
1685 }
1686 return bottomEdge;
1687 }
1688
1689 PositionComparator comparator = new PositionComparator();
1690 class PositionComparator implements Comparator<View> {
1691 int whichEdge = 0;
1692 public int compare(View left, View right) {
1693 CellAndSpan l = config.map.get(left);
1694 CellAndSpan r = config.map.get(right);
1695 switch (whichEdge) {
1696 case LEFT:
1697 return (r.x + r.spanX) - (l.x + l.spanX);
1698 case RIGHT:
1699 return l.x - r.x;
1700 case TOP:
1701 return (r.y + r.spanY) - (l.y + l.spanY);
1702 case BOTTOM:
1703 default:
1704 return l.y - r.y;
1705 }
1706 }
1707 }
1708
1709 public void sortConfigurationForEdgePush(int edge) {
1710 comparator.whichEdge = edge;
1711 Collections.sort(config.sortedViews, comparator);
1712 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001713 }
1714
Adam Cohenf3900c22012-11-16 18:28:11 -08001715 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1716 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001717
Adam Cohenf3900c22012-11-16 18:28:11 -08001718 ViewCluster cluster = new ViewCluster(views, currentState);
1719 Rect clusterRect = cluster.getBoundingRect();
1720 int whichEdge;
1721 int pushDistance;
1722 boolean fail = false;
1723
1724 // Determine the edge of the cluster that will be leading the push and how far
1725 // the cluster must be shifted.
1726 if (direction[0] < 0) {
1727 whichEdge = ViewCluster.LEFT;
1728 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001729 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001730 whichEdge = ViewCluster.RIGHT;
1731 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1732 } else if (direction[1] < 0) {
1733 whichEdge = ViewCluster.TOP;
1734 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1735 } else {
1736 whichEdge = ViewCluster.BOTTOM;
1737 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001738 }
1739
Adam Cohenf3900c22012-11-16 18:28:11 -08001740 // Break early for invalid push distance.
1741 if (pushDistance <= 0) {
1742 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001743 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001744
1745 // Mark the occupied state as false for the group of views we want to move.
1746 for (View v: views) {
1747 CellAndSpan c = currentState.map.get(v);
1748 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1749 }
1750
1751 // We save the current configuration -- if we fail to find a solution we will revert
1752 // to the initial state. The process of finding a solution modifies the configuration
1753 // in place, hence the need for revert in the failure case.
1754 currentState.save();
1755
1756 // The pushing algorithm is simplified by considering the views in the order in which
1757 // they would be pushed by the cluster. For example, if the cluster is leading with its
1758 // left edge, we consider sort the views by their right edge, from right to left.
1759 cluster.sortConfigurationForEdgePush(whichEdge);
1760
1761 while (pushDistance > 0 && !fail) {
1762 for (View v: currentState.sortedViews) {
1763 // For each view that isn't in the cluster, we see if the leading edge of the
1764 // cluster is contacting the edge of that view. If so, we add that view to the
1765 // cluster.
1766 if (!cluster.views.contains(v) && v != dragView) {
1767 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1768 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1769 if (!lp.canReorder) {
1770 // The push solution includes the all apps button, this is not viable.
1771 fail = true;
1772 break;
1773 }
1774 cluster.addView(v);
1775 CellAndSpan c = currentState.map.get(v);
1776
1777 // Adding view to cluster, mark it as not occupied.
1778 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1779 }
1780 }
1781 }
1782 pushDistance--;
1783
1784 // The cluster has been completed, now we move the whole thing over in the appropriate
1785 // direction.
1786 cluster.shift(whichEdge, 1);
1787 }
1788
1789 boolean foundSolution = false;
1790 clusterRect = cluster.getBoundingRect();
1791
1792 // Due to the nature of the algorithm, the only check required to verify a valid solution
1793 // is to ensure that completed shifted cluster lies completely within the cell layout.
1794 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1795 clusterRect.bottom <= mCountY) {
1796 foundSolution = true;
1797 } else {
1798 currentState.restore();
1799 }
1800
1801 // In either case, we set the occupied array as marked for the location of the views
1802 for (View v: cluster.views) {
1803 CellAndSpan c = currentState.map.get(v);
1804 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1805 }
1806
1807 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001808 }
1809
Adam Cohen482ed822012-03-02 14:15:13 -08001810 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001811 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001812 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001813
Adam Cohen8baab352012-03-20 17:39:21 -07001814 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001815 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001816 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001817 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001818 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001819 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001820 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001821 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001822 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001823 }
1824 }
Adam Cohen8baab352012-03-20 17:39:21 -07001825
Adam Cohen8baab352012-03-20 17:39:21 -07001826 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001827 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001828 CellAndSpan c = currentState.map.get(v);
1829 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1830 }
1831
Adam Cohen47a876d2012-03-19 13:21:41 -07001832 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1833 int top = boundingRect.top;
1834 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001835 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001836 // for interlocking.
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 - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001840 }
1841
Adam Cohen482ed822012-03-02 14:15:13 -08001842 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1843
Adam Cohenf3900c22012-11-16 18:28:11 -08001844 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1845 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001846
Adam Cohen8baab352012-03-20 17:39:21 -07001847 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001848 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001849 int deltaX = mTempLocation[0] - boundingRect.left;
1850 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001851 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001852 CellAndSpan c = currentState.map.get(v);
1853 c.x += deltaX;
1854 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001855 }
1856 success = true;
1857 }
Adam Cohen8baab352012-03-20 17:39:21 -07001858
1859 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001860 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001861 CellAndSpan c = currentState.map.get(v);
1862 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001863 }
1864 return success;
1865 }
1866
1867 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1868 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1869 }
1870
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001871 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1872 // to push items in each of the cardinal directions, in an order based on the direction vector
1873 // passed.
1874 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1875 int[] direction, View ignoreView, ItemConfiguration solution) {
1876 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001877 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001878 // separately in each of the components.
1879 int temp = direction[1];
1880 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001881
1882 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001883 ignoreView, solution)) {
1884 return true;
1885 }
1886 direction[1] = temp;
1887 temp = direction[0];
1888 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001889
1890 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001891 ignoreView, solution)) {
1892 return true;
1893 }
1894 // Revert the direction
1895 direction[0] = temp;
1896
1897 // Now we try pushing in each component of the opposite direction
1898 direction[0] *= -1;
1899 direction[1] *= -1;
1900 temp = direction[1];
1901 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001902 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001903 ignoreView, solution)) {
1904 return true;
1905 }
1906
1907 direction[1] = temp;
1908 temp = direction[0];
1909 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001910 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001911 ignoreView, solution)) {
1912 return true;
1913 }
1914 // revert the direction
1915 direction[0] = temp;
1916 direction[0] *= -1;
1917 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001918
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001919 } else {
1920 // If the direction vector has a single non-zero component, we push first in the
1921 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08001922 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001923 ignoreView, solution)) {
1924 return true;
1925 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001926 // Then we try the opposite direction
1927 direction[0] *= -1;
1928 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001929 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001930 ignoreView, solution)) {
1931 return true;
1932 }
1933 // Switch the direction back
1934 direction[0] *= -1;
1935 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001936
1937 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001938 // to find a solution by pushing along the perpendicular axis.
1939
1940 // Swap the components
1941 int temp = direction[1];
1942 direction[1] = direction[0];
1943 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08001944 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001945 ignoreView, solution)) {
1946 return true;
1947 }
1948
1949 // Then we try the opposite direction
1950 direction[0] *= -1;
1951 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001952 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001953 ignoreView, solution)) {
1954 return true;
1955 }
1956 // Switch the direction back
1957 direction[0] *= -1;
1958 direction[1] *= -1;
1959
1960 // Swap the components back
1961 temp = direction[1];
1962 direction[1] = direction[0];
1963 direction[0] = temp;
1964 }
1965 return false;
1966 }
1967
Adam Cohen482ed822012-03-02 14:15:13 -08001968 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001969 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001970 // Return early if get invalid cell positions
1971 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001972
Adam Cohen8baab352012-03-20 17:39:21 -07001973 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001974 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001975
Adam Cohen8baab352012-03-20 17:39:21 -07001976 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001977 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001978 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001979 if (c != null) {
1980 c.x = cellX;
1981 c.y = cellY;
1982 }
Adam Cohen482ed822012-03-02 14:15:13 -08001983 }
Adam Cohen482ed822012-03-02 14:15:13 -08001984 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1985 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001986 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001987 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001988 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001989 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001990 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001991 if (Rect.intersects(r0, r1)) {
1992 if (!lp.canReorder) {
1993 return false;
1994 }
1995 mIntersectingViews.add(child);
1996 }
1997 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001998
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001999 solution.intersectingViews = new ArrayList<View>(mIntersectingViews);
2000
Winson Chung5f8afe62013-08-12 16:19:28 -07002001 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002002 // we try to find a solution such that no displaced item travels through another item
2003 // without also displacing that item.
2004 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07002005 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07002006 return true;
2007 }
Adam Cohen47a876d2012-03-19 13:21:41 -07002008
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002009 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08002010 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07002011 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002012 return true;
2013 }
Adam Cohen47a876d2012-03-19 13:21:41 -07002014
Adam Cohen482ed822012-03-02 14:15:13 -08002015 // Ok, they couldn't move as a block, let's move them individually
2016 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07002017 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002018 return false;
2019 }
2020 }
2021 return true;
2022 }
2023
2024 /*
2025 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
2026 * the provided point and the provided cell
2027 */
Adam Cohen47a876d2012-03-19 13:21:41 -07002028 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08002029 double angle = Math.atan(((float) deltaY) / deltaX);
2030
2031 result[0] = 0;
2032 result[1] = 0;
2033 if (Math.abs(Math.cos(angle)) > 0.5f) {
2034 result[0] = (int) Math.signum(deltaX);
2035 }
2036 if (Math.abs(Math.sin(angle)) > 0.5f) {
2037 result[1] = (int) Math.signum(deltaY);
2038 }
2039 }
2040
Adam Cohen8baab352012-03-20 17:39:21 -07002041 private void copyOccupiedArray(boolean[][] occupied) {
2042 for (int i = 0; i < mCountX; i++) {
2043 for (int j = 0; j < mCountY; j++) {
2044 occupied[i][j] = mOccupied[i][j];
2045 }
2046 }
2047 }
2048
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002049 ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
2050 int spanX, int spanY, int[] direction, View dragView, boolean decX,
2051 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07002052 // Copy the current state into the solution. This solution will be manipulated as necessary.
2053 copyCurrentStateToSolution(solution, false);
2054 // Copy the current occupied array into the temporary occupied array. This array will be
2055 // manipulated as necessary to find a solution.
2056 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08002057
2058 // We find the nearest cell into which we would place the dragged item, assuming there's
2059 // nothing in its way.
2060 int result[] = new int[2];
2061 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2062
2063 boolean success = false;
2064 // First we try the exact nearest position of the item being dragged,
2065 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07002066 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
2067 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002068
2069 if (!success) {
2070 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
2071 // x, then 1 in y etc.
2072 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002073 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
2074 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002075 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002076 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
2077 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002078 }
2079 solution.isSolution = false;
2080 } else {
2081 solution.isSolution = true;
2082 solution.dragViewX = result[0];
2083 solution.dragViewY = result[1];
2084 solution.dragViewSpanX = spanX;
2085 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002086 }
2087 return solution;
2088 }
2089
2090 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002091 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002092 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002093 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002094 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002095 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08002096 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07002097 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002098 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07002099 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002100 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002101 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08002102 }
2103 }
2104
2105 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
2106 for (int i = 0; i < mCountX; i++) {
2107 for (int j = 0; j < mCountY; j++) {
2108 mTmpOccupied[i][j] = false;
2109 }
2110 }
2111
Michael Jurkaa52570f2012-03-20 03:18:20 -07002112 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002113 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002114 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002115 if (child == dragView) continue;
2116 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002117 CellAndSpan c = solution.map.get(child);
2118 if (c != null) {
2119 lp.tmpCellX = c.x;
2120 lp.tmpCellY = c.y;
2121 lp.cellHSpan = c.spanX;
2122 lp.cellVSpan = c.spanY;
2123 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002124 }
2125 }
2126 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2127 solution.dragViewSpanY, mTmpOccupied, true);
2128 }
2129
2130 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2131 commitDragView) {
2132
2133 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2134 for (int i = 0; i < mCountX; i++) {
2135 for (int j = 0; j < mCountY; j++) {
2136 occupied[i][j] = false;
2137 }
2138 }
2139
Michael Jurkaa52570f2012-03-20 03:18:20 -07002140 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002141 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002142 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002143 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002144 CellAndSpan c = solution.map.get(child);
2145 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002146 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2147 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002148 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002149 }
2150 }
2151 if (commitDragView) {
2152 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2153 solution.dragViewSpanY, occupied, true);
2154 }
2155 }
2156
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002157
2158 // This method starts or changes the reorder preview animations
2159 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
2160 View dragView, int delay, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07002161 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002162 for (int i = 0; i < childCount; i++) {
2163 View child = mShortcutsAndWidgets.getChildAt(i);
2164 if (child == dragView) continue;
2165 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002166 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
2167 != null && !solution.intersectingViews.contains(child);
2168
Adam Cohen19f37922012-03-21 11:59:11 -07002169 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002170 if (c != null && !skip) {
2171 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
2172 lp.cellY, c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002173 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002174 }
2175 }
2176 }
2177
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002178 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07002179 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002180 class ReorderPreviewAnimation {
Adam Cohen19f37922012-03-21 11:59:11 -07002181 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002182 float finalDeltaX;
2183 float finalDeltaY;
2184 float initDeltaX;
2185 float initDeltaY;
2186 float finalScale;
2187 float initScale;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002188 int mode;
2189 boolean repeating = false;
2190 private static final int PREVIEW_DURATION = 300;
2191 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
2192
2193 public static final int MODE_HINT = 0;
2194 public static final int MODE_PREVIEW = 1;
2195
Adam Cohene7587d22012-05-24 18:50:02 -07002196 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002197
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002198 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
2199 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07002200 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2201 final int x0 = mTmpPoint[0];
2202 final int y0 = mTmpPoint[1];
2203 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2204 final int x1 = mTmpPoint[0];
2205 final int y1 = mTmpPoint[1];
2206 final int dX = x1 - x0;
2207 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002208 finalDeltaX = 0;
2209 finalDeltaY = 0;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002210 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07002211 if (dX == dY && dX == 0) {
2212 } else {
2213 if (dY == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002214 finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002215 } else if (dX == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002216 finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002217 } else {
2218 double angle = Math.atan( (float) (dY) / dX);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002219 finalDeltaX = (int) (- dir * Math.signum(dX) *
2220 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
2221 finalDeltaY = (int) (- dir * Math.signum(dY) *
2222 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002223 }
2224 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002225 this.mode = mode;
Adam Cohend024f982012-05-23 18:26:45 -07002226 initDeltaX = child.getTranslationX();
2227 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002228 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002229 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002230 this.child = child;
2231 }
2232
Adam Cohend024f982012-05-23 18:26:45 -07002233 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002234 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002235 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002236 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002237 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002238 if (finalDeltaX == 0 && finalDeltaY == 0) {
2239 completeAnimationImmediately();
2240 return;
2241 }
Adam Cohen19f37922012-03-21 11:59:11 -07002242 }
Adam Cohend024f982012-05-23 18:26:45 -07002243 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002244 return;
2245 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002246 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002247 a = va;
Adam Cohen19f37922012-03-21 11:59:11 -07002248 va.setRepeatMode(ValueAnimator.REVERSE);
2249 va.setRepeatCount(ValueAnimator.INFINITE);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002250 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002251 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002252 va.addUpdateListener(new AnimatorUpdateListener() {
2253 @Override
2254 public void onAnimationUpdate(ValueAnimator animation) {
2255 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002256 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2257 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2258 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002259 child.setTranslationX(x);
2260 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002261 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002262 child.setScaleX(s);
2263 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002264 }
2265 });
2266 va.addListener(new AnimatorListenerAdapter() {
2267 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002268 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002269 initDeltaX = 0;
2270 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002271 initScale = getChildrenScale();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002272 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002273 }
2274 });
Adam Cohen19f37922012-03-21 11:59:11 -07002275 mShakeAnimators.put(child, this);
2276 va.start();
2277 }
2278
Adam Cohend024f982012-05-23 18:26:45 -07002279 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002280 if (a != null) {
2281 a.cancel();
2282 }
Adam Cohen19f37922012-03-21 11:59:11 -07002283 }
Adam Cohene7587d22012-05-24 18:50:02 -07002284
Brandon Keely50e6e562012-05-08 16:28:49 -07002285 private void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002286 if (a != null) {
2287 a.cancel();
2288 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002289
Michael Jurka2ecf9952012-06-18 12:52:28 -07002290 AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002291 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002292 s.playTogether(
Adam Cohen307fe232012-08-16 17:55:58 -07002293 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
2294 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
Michael Jurka2ecf9952012-06-18 12:52:28 -07002295 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2296 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002297 );
2298 s.setDuration(REORDER_ANIMATION_DURATION);
2299 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2300 s.start();
2301 }
Adam Cohen19f37922012-03-21 11:59:11 -07002302 }
2303
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002304 private void completeAndClearReorderPreviewAnimations() {
2305 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002306 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002307 }
2308 mShakeAnimators.clear();
2309 }
2310
Adam Cohen482ed822012-03-02 14:15:13 -08002311 private void commitTempPlacement() {
2312 for (int i = 0; i < mCountX; i++) {
2313 for (int j = 0; j < mCountY; j++) {
2314 mOccupied[i][j] = mTmpOccupied[i][j];
2315 }
2316 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002317 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002318 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002319 View child = mShortcutsAndWidgets.getChildAt(i);
2320 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2321 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002322 // We do a null check here because the item info can be null in the case of the
2323 // AllApps button in the hotseat.
2324 if (info != null) {
Adam Cohen487f7dd2012-06-28 18:12:10 -07002325 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
2326 info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
2327 info.requiresDbUpdate = true;
2328 }
Adam Cohen2acce882012-03-28 19:03:19 -07002329 info.cellX = lp.cellX = lp.tmpCellX;
2330 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002331 info.spanX = lp.cellHSpan;
2332 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002333 }
Adam Cohen482ed822012-03-02 14:15:13 -08002334 }
Adam Cohen2acce882012-03-28 19:03:19 -07002335 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002336 }
2337
2338 public void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002339 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002340 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002341 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002342 lp.useTmpCoords = useTempCoords;
2343 }
2344 }
2345
Adam Cohen482ed822012-03-02 14:15:13 -08002346 ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
2347 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2348 int[] result = new int[2];
2349 int[] resultSpan = new int[2];
2350 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
2351 resultSpan);
2352 if (result[0] >= 0 && result[1] >= 0) {
2353 copyCurrentStateToSolution(solution, false);
2354 solution.dragViewX = result[0];
2355 solution.dragViewY = result[1];
2356 solution.dragViewSpanX = resultSpan[0];
2357 solution.dragViewSpanY = resultSpan[1];
2358 solution.isSolution = true;
2359 } else {
2360 solution.isSolution = false;
2361 }
2362 return solution;
2363 }
2364
2365 public void prepareChildForDrag(View child) {
2366 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002367 }
2368
Adam Cohen19f37922012-03-21 11:59:11 -07002369 /* This seems like it should be obvious and straight-forward, but when the direction vector
2370 needs to match with the notion of the dragView pushing other views, we have to employ
2371 a slightly more subtle notion of the direction vector. The question is what two points is
2372 the vector between? The center of the dragView and its desired destination? Not quite, as
2373 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2374 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2375 or right, which helps make pushing feel right.
2376 */
2377 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2378 int spanY, View dragView, int[] resultDirection) {
2379 int[] targetDestination = new int[2];
2380
2381 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2382 Rect dragRect = new Rect();
2383 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2384 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2385
2386 Rect dropRegionRect = new Rect();
2387 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2388 dragView, dropRegionRect, mIntersectingViews);
2389
2390 int dropRegionSpanX = dropRegionRect.width();
2391 int dropRegionSpanY = dropRegionRect.height();
2392
2393 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2394 dropRegionRect.height(), dropRegionRect);
2395
2396 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2397 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2398
2399 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2400 deltaX = 0;
2401 }
2402 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2403 deltaY = 0;
2404 }
2405
2406 if (deltaX == 0 && deltaY == 0) {
2407 // No idea what to do, give a random direction.
2408 resultDirection[0] = 1;
2409 resultDirection[1] = 0;
2410 } else {
2411 computeDirectionVector(deltaX, deltaY, resultDirection);
2412 }
2413 }
2414
2415 // For a given cell and span, fetch the set of views intersecting the region.
2416 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2417 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2418 if (boundingRect != null) {
2419 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2420 }
2421 intersectingViews.clear();
2422 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2423 Rect r1 = new Rect();
2424 final int count = mShortcutsAndWidgets.getChildCount();
2425 for (int i = 0; i < count; i++) {
2426 View child = mShortcutsAndWidgets.getChildAt(i);
2427 if (child == dragView) continue;
2428 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2429 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2430 if (Rect.intersects(r0, r1)) {
2431 mIntersectingViews.add(child);
2432 if (boundingRect != null) {
2433 boundingRect.union(r1);
2434 }
2435 }
2436 }
2437 }
2438
2439 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2440 View dragView, int[] result) {
2441 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2442 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2443 mIntersectingViews);
2444 return !mIntersectingViews.isEmpty();
2445 }
2446
2447 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002448 completeAndClearReorderPreviewAnimations();
2449 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2450 final int count = mShortcutsAndWidgets.getChildCount();
2451 for (int i = 0; i < count; i++) {
2452 View child = mShortcutsAndWidgets.getChildAt(i);
2453 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2454 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2455 lp.tmpCellX = lp.cellX;
2456 lp.tmpCellY = lp.cellY;
2457 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2458 0, false, false);
2459 }
Adam Cohen19f37922012-03-21 11:59:11 -07002460 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002461 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002462 }
Adam Cohen19f37922012-03-21 11:59:11 -07002463 }
2464
Adam Cohenbebf0422012-04-11 18:06:28 -07002465 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2466 View dragView, int[] direction, boolean commit) {
2467 int[] pixelXY = new int[2];
2468 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2469
2470 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002471 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002472 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2473
2474 setUseTempCoords(true);
2475 if (swapSolution != null && swapSolution.isSolution) {
2476 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2477 // committing anything or animating anything as we just want to determine if a solution
2478 // exists
2479 copySolutionToTempState(swapSolution, dragView);
2480 setItemPlacementDirty(true);
2481 animateItemsToSolution(swapSolution, dragView, commit);
2482
2483 if (commit) {
2484 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002485 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002486 setItemPlacementDirty(false);
2487 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002488 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2489 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002490 }
2491 mShortcutsAndWidgets.requestLayout();
2492 }
2493 return swapSolution.isSolution;
2494 }
2495
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002496 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002497 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002498 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002499 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002500
2501 if (resultSpan == null) {
2502 resultSpan = new int[2];
2503 }
2504
Adam Cohen19f37922012-03-21 11:59:11 -07002505 // When we are checking drop validity or actually dropping, we don't recompute the
2506 // direction vector, since we want the solution to match the preview, and it's possible
2507 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002508 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2509 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002510 mDirectionVector[0] = mPreviousReorderDirection[0];
2511 mDirectionVector[1] = mPreviousReorderDirection[1];
2512 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002513 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2514 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2515 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002516 }
2517 } else {
2518 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2519 mPreviousReorderDirection[0] = mDirectionVector[0];
2520 mPreviousReorderDirection[1] = mDirectionVector[1];
2521 }
2522
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002523 // Find a solution involving pushing / displacing any items in the way
2524 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002525 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2526
2527 // We attempt the approach which doesn't shuffle views at all
2528 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2529 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2530
2531 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002532
2533 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2534 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002535 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2536 finalSolution = swapSolution;
2537 } else if (noShuffleSolution.isSolution) {
2538 finalSolution = noShuffleSolution;
2539 }
2540
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002541 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002542 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002543 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2544 ReorderPreviewAnimation.MODE_HINT);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002545 result[0] = finalSolution.dragViewX;
2546 result[1] = finalSolution.dragViewY;
2547 resultSpan[0] = finalSolution.dragViewSpanX;
2548 resultSpan[1] = finalSolution.dragViewSpanY;
2549 } else {
2550 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2551 }
2552 return result;
2553 }
2554
Adam Cohen482ed822012-03-02 14:15:13 -08002555 boolean foundSolution = true;
2556 if (!DESTRUCTIVE_REORDER) {
2557 setUseTempCoords(true);
2558 }
2559
2560 if (finalSolution != null) {
2561 result[0] = finalSolution.dragViewX;
2562 result[1] = finalSolution.dragViewY;
2563 resultSpan[0] = finalSolution.dragViewSpanX;
2564 resultSpan[1] = finalSolution.dragViewSpanY;
2565
2566 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2567 // committing anything or animating anything as we just want to determine if a solution
2568 // exists
2569 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2570 if (!DESTRUCTIVE_REORDER) {
2571 copySolutionToTempState(finalSolution, dragView);
2572 }
2573 setItemPlacementDirty(true);
2574 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2575
Adam Cohen19f37922012-03-21 11:59:11 -07002576 if (!DESTRUCTIVE_REORDER &&
2577 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002578 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002579 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002580 setItemPlacementDirty(false);
2581 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002582 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2583 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002584 }
2585 }
2586 } else {
2587 foundSolution = false;
2588 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2589 }
2590
2591 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2592 setUseTempCoords(false);
2593 }
Adam Cohen482ed822012-03-02 14:15:13 -08002594
Michael Jurkaa52570f2012-03-20 03:18:20 -07002595 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002596 return result;
2597 }
2598
Adam Cohen19f37922012-03-21 11:59:11 -07002599 void setItemPlacementDirty(boolean dirty) {
2600 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002601 }
Adam Cohen19f37922012-03-21 11:59:11 -07002602 boolean isItemPlacementDirty() {
2603 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002604 }
2605
2606 private class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002607 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002608 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2609 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002610 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002611 boolean isSolution = false;
2612 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2613
Adam Cohenf3900c22012-11-16 18:28:11 -08002614 void save() {
2615 // Copy current state into savedMap
2616 for (View v: map.keySet()) {
2617 map.get(v).copy(savedMap.get(v));
2618 }
2619 }
2620
2621 void restore() {
2622 // Restore current state from savedMap
2623 for (View v: savedMap.keySet()) {
2624 savedMap.get(v).copy(map.get(v));
2625 }
2626 }
2627
2628 void add(View v, CellAndSpan cs) {
2629 map.put(v, cs);
2630 savedMap.put(v, new CellAndSpan());
2631 sortedViews.add(v);
2632 }
2633
Adam Cohen482ed822012-03-02 14:15:13 -08002634 int area() {
2635 return dragViewSpanX * dragViewSpanY;
2636 }
Adam Cohen8baab352012-03-20 17:39:21 -07002637 }
2638
2639 private class CellAndSpan {
2640 int x, y;
2641 int spanX, spanY;
2642
Adam Cohenf3900c22012-11-16 18:28:11 -08002643 public CellAndSpan() {
2644 }
2645
2646 public void copy(CellAndSpan copy) {
2647 copy.x = x;
2648 copy.y = y;
2649 copy.spanX = spanX;
2650 copy.spanY = spanY;
2651 }
2652
Adam Cohen8baab352012-03-20 17:39:21 -07002653 public CellAndSpan(int x, int y, int spanX, int spanY) {
2654 this.x = x;
2655 this.y = y;
2656 this.spanX = spanX;
2657 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002658 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002659
2660 public String toString() {
2661 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2662 }
2663
Adam Cohen482ed822012-03-02 14:15:13 -08002664 }
2665
Adam Cohendf035382011-04-11 17:22:04 -07002666 /**
2667 * Find a vacant area that will fit the given bounds nearest the requested
2668 * cell location. Uses Euclidean distance to score multiple vacant areas.
2669 *
2670 * @param pixelX The X location at which you want to search for a vacant area.
2671 * @param pixelY The Y location at which you want to search for a vacant area.
2672 * @param spanX Horizontal span of the object.
2673 * @param spanY Vertical span of the object.
2674 * @param ignoreView Considers space occupied by this view as unoccupied
2675 * @param result Previously returned value to possibly recycle.
2676 * @return The X, Y cell of a vacant area that can contain this object,
2677 * nearest the requested location.
2678 */
2679 int[] findNearestVacantArea(
2680 int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
2681 return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
2682 }
2683
2684 /**
Adam Cohend41fbf52012-02-16 23:53:59 -08002685 * Find a vacant area that will fit the given bounds nearest the requested
2686 * cell location. Uses Euclidean distance to score multiple vacant areas.
2687 *
2688 * @param pixelX The X location at which you want to search for a vacant area.
2689 * @param pixelY The Y location at which you want to search for a vacant area.
2690 * @param minSpanX The minimum horizontal span required
2691 * @param minSpanY The minimum vertical span required
2692 * @param spanX Horizontal span of the object.
2693 * @param spanY Vertical span of the object.
2694 * @param ignoreView Considers space occupied by this view as unoccupied
2695 * @param result Previously returned value to possibly recycle.
2696 * @return The X, Y cell of a vacant area that can contain this object,
2697 * nearest the requested location.
2698 */
2699 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
2700 int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
Adam Cohen482ed822012-03-02 14:15:13 -08002701 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
2702 result, resultSpan, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08002703 }
2704
2705 /**
Adam Cohendf035382011-04-11 17:22:04 -07002706 * Find a starting cell position that will fit the given bounds nearest the requested
2707 * cell location. Uses Euclidean distance to score multiple vacant areas.
2708 *
2709 * @param pixelX The X location at which you want to search for a vacant area.
2710 * @param pixelY The Y location at which you want to search for a vacant area.
2711 * @param spanX Horizontal span of the object.
2712 * @param spanY Vertical span of the object.
2713 * @param ignoreView Considers space occupied by this view as unoccupied
2714 * @param result Previously returned value to possibly recycle.
2715 * @return The X, Y cell of a vacant area that can contain this object,
2716 * nearest the requested location.
2717 */
2718 int[] findNearestArea(
2719 int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2720 return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
2721 }
2722
Michael Jurka0280c3b2010-09-17 15:00:07 -07002723 boolean existsEmptyCell() {
2724 return findCellForSpan(null, 1, 1);
2725 }
2726
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002727 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002728 * Finds the upper-left coordinate of the first rectangle in the grid that can
2729 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2730 * then this method will only return coordinates for rectangles that contain the cell
2731 * (intersectX, intersectY)
2732 *
2733 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2734 * can be found.
2735 * @param spanX The horizontal span of the cell we want to find.
2736 * @param spanY The vertical span of the cell we want to find.
2737 *
2738 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002739 */
Michael Jurka0280c3b2010-09-17 15:00:07 -07002740 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Adam Cohen482ed822012-03-02 14:15:13 -08002741 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002742 }
2743
2744 /**
2745 * Like above, but ignores any cells occupied by the item "ignoreView"
2746 *
2747 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2748 * can be found.
2749 * @param spanX The horizontal span of the cell we want to find.
2750 * @param spanY The vertical span of the cell we want to find.
2751 * @param ignoreView The home screen item we should treat as not occupying any space
2752 * @return
2753 */
2754 boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
Adam Cohen482ed822012-03-02 14:15:13 -08002755 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
2756 ignoreView, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002757 }
2758
2759 /**
2760 * Like above, but if intersectX and intersectY are not -1, then this method will try to
2761 * return coordinates for rectangles that contain the cell [intersectX, intersectY]
2762 *
2763 * @param spanX The horizontal span of the cell we want to find.
2764 * @param spanY The vertical span of the cell we want to find.
2765 * @param ignoreView The home screen item we should treat as not occupying any space
2766 * @param intersectX The X coordinate of the cell that we should try to overlap
2767 * @param intersectX The Y coordinate of the cell that we should try to overlap
2768 *
2769 * @return True if a vacant cell of the specified dimension was found, false otherwise.
2770 */
2771 boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
2772 int intersectX, int intersectY) {
2773 return findCellForSpanThatIntersectsIgnoring(
Adam Cohen482ed822012-03-02 14:15:13 -08002774 cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002775 }
2776
2777 /**
2778 * The superset of the above two methods
2779 */
2780 boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002781 int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002782 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08002783 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002784
Michael Jurka28750fb2010-09-24 17:43:49 -07002785 boolean foundCell = false;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002786 while (true) {
2787 int startX = 0;
2788 if (intersectX >= 0) {
2789 startX = Math.max(startX, intersectX - (spanX - 1));
2790 }
2791 int endX = mCountX - (spanX - 1);
2792 if (intersectX >= 0) {
2793 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
2794 }
2795 int startY = 0;
2796 if (intersectY >= 0) {
2797 startY = Math.max(startY, intersectY - (spanY - 1));
2798 }
2799 int endY = mCountY - (spanY - 1);
2800 if (intersectY >= 0) {
2801 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
2802 }
2803
Winson Chungbbc60d82010-11-11 16:34:41 -08002804 for (int y = startY; y < endY && !foundCell; y++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002805 inner:
Winson Chungbbc60d82010-11-11 16:34:41 -08002806 for (int x = startX; x < endX; x++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002807 for (int i = 0; i < spanX; i++) {
2808 for (int j = 0; j < spanY; j++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002809 if (occupied[x + i][y + j]) {
Winson Chungbbc60d82010-11-11 16:34:41 -08002810 // small optimization: we can skip to after the column we just found
Michael Jurka0280c3b2010-09-17 15:00:07 -07002811 // an occupied cell
Winson Chungbbc60d82010-11-11 16:34:41 -08002812 x += i;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002813 continue inner;
2814 }
2815 }
2816 }
2817 if (cellXY != null) {
2818 cellXY[0] = x;
2819 cellXY[1] = y;
2820 }
Michael Jurka28750fb2010-09-24 17:43:49 -07002821 foundCell = true;
2822 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002823 }
2824 }
2825 if (intersectX == -1 && intersectY == -1) {
2826 break;
2827 } else {
2828 // if we failed to find anything, try again but without any requirements of
2829 // intersecting
2830 intersectX = -1;
2831 intersectY = -1;
2832 continue;
2833 }
2834 }
2835
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002836 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08002837 markCellsAsOccupiedForView(ignoreView, occupied);
Michael Jurka28750fb2010-09-24 17:43:49 -07002838 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002839 }
2840
2841 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002842 * A drag event has begun over this layout.
2843 * It may have begun over this layout (in which case onDragChild is called first),
2844 * or it may have begun on another layout.
2845 */
2846 void onDragEnter() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002847 mDragEnforcer.onDragEnter();
Winson Chungc07918d2011-07-01 15:35:26 -07002848 mDragging = true;
2849 }
2850
2851 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002852 * Called when drag has left this CellLayout or has been completed (successfully or not)
2853 */
2854 void onDragExit() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002855 mDragEnforcer.onDragExit();
Joe Onorato4be866d2010-10-10 11:26:02 -07002856 // This can actually be called when we aren't in a drag, e.g. when adding a new
2857 // item to this layout via the customize drawer.
2858 // Guard against that case.
2859 if (mDragging) {
2860 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002861 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002862
2863 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002864 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002865 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2866 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002867 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002868 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002869 }
2870
2871 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002872 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002873 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002874 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002875 *
2876 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002877 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002878 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002879 if (child != null) {
2880 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002881 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002882 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002883 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002884 }
2885
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002886 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002887 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002888 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002889 * @param cellX X coordinate of upper left corner expressed as a cell position
2890 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002891 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002892 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002893 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002894 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002895 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002896 final int cellWidth = mCellWidth;
2897 final int cellHeight = mCellHeight;
2898 final int widthGap = mWidthGap;
2899 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002900
Winson Chung4b825dcd2011-06-19 12:41:22 -07002901 final int hStartPadding = getPaddingLeft();
2902 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002903
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002904 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2905 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2906
2907 int x = hStartPadding + cellX * (cellWidth + widthGap);
2908 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002909
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002910 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002911 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002912
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002913 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002914 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002915 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002916 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002917 * @param width Width in pixels
2918 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002919 * @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 -08002920 */
Winson Chung66700732013-08-20 16:56:15 -07002921 public static int[] rectToCell(int width, int height, int[] result) {
Winson Chung5f8afe62013-08-12 16:19:28 -07002922 LauncherAppState app = LauncherAppState.getInstance();
2923 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Winson Chung66700732013-08-20 16:56:15 -07002924 Rect padding = grid.getWorkspacePadding(grid.isLandscape ?
2925 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
Winson Chung5f8afe62013-08-12 16:19:28 -07002926
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002927 // Always assume we're working with the smallest span to make sure we
2928 // reserve enough space in both orientations.
Winson Chung66700732013-08-20 16:56:15 -07002929 int parentWidth = grid.calculateCellWidth(grid.widthPx
2930 - padding.left - padding.right, (int) grid.numColumns);
2931 int parentHeight = grid.calculateCellHeight(grid.heightPx
2932 - padding.top - padding.bottom, (int) grid.numRows);
2933 int smallerSize = Math.min(parentWidth, parentHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002934
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002935 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002936 int spanX = (int) Math.ceil(width / (float) smallerSize);
2937 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002938
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002939 if (result == null) {
2940 return new int[] { spanX, spanY };
2941 }
2942 result[0] = spanX;
2943 result[1] = spanY;
2944 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002945 }
2946
Michael Jurkaf12c75c2011-01-25 22:41:40 -08002947 public int[] cellSpansToSize(int hSpans, int vSpans) {
2948 int[] size = new int[2];
2949 size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
2950 size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
2951 return size;
2952 }
2953
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002954 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08002955 * Calculate the grid spans needed to fit given item
2956 */
2957 public void calculateSpans(ItemInfo info) {
2958 final int minWidth;
2959 final int minHeight;
2960
2961 if (info instanceof LauncherAppWidgetInfo) {
2962 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
2963 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
2964 } else if (info instanceof PendingAddWidgetInfo) {
2965 minWidth = ((PendingAddWidgetInfo) info).minWidth;
2966 minHeight = ((PendingAddWidgetInfo) info).minHeight;
2967 } else {
2968 // It's not a widget, so it must be 1x1
2969 info.spanX = info.spanY = 1;
2970 return;
2971 }
2972 int[] spans = rectToCell(minWidth, minHeight, null);
2973 info.spanX = spans[0];
2974 info.spanY = spans[1];
2975 }
2976
2977 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002978 * Find the first vacant cell, if there is one.
2979 *
2980 * @param vacant Holds the x and y coordinate of the vacant cell
2981 * @param spanX Horizontal cell span.
2982 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -07002983 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002984 * @return True if a vacant cell was found
2985 */
2986 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002987
Michael Jurka0280c3b2010-09-17 15:00:07 -07002988 return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002989 }
2990
2991 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
2992 int xCount, int yCount, boolean[][] occupied) {
2993
Adam Cohen2801caf2011-05-13 20:57:39 -07002994 for (int y = 0; y < yCount; y++) {
2995 for (int x = 0; x < xCount; x++) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002996 boolean available = !occupied[x][y];
2997out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
2998 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
2999 available = available && !occupied[i][j];
3000 if (!available) break out;
3001 }
3002 }
3003
3004 if (available) {
3005 vacant[0] = x;
3006 vacant[1] = y;
3007 return true;
3008 }
3009 }
3010 }
3011
3012 return false;
3013 }
3014
Michael Jurka0280c3b2010-09-17 15:00:07 -07003015 private void clearOccupiedCells() {
3016 for (int x = 0; x < mCountX; x++) {
3017 for (int y = 0; y < mCountY; y++) {
3018 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003019 }
3020 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07003021 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003022
Adam Cohend41fbf52012-02-16 23:53:59 -08003023 public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07003024 markCellsAsUnoccupiedForView(view);
Adam Cohen482ed822012-03-02 14:15:13 -08003025 markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003026 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003027
Adam Cohend4844c32011-02-18 19:25:06 -08003028 public void markCellsAsOccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08003029 markCellsAsOccupiedForView(view, mOccupied);
3030 }
3031 public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07003032 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003033 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08003034 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003035 }
3036
Adam Cohend4844c32011-02-18 19:25:06 -08003037 public void markCellsAsUnoccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08003038 markCellsAsUnoccupiedForView(view, mOccupied);
3039 }
3040 public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07003041 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003042 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08003043 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003044 }
3045
Adam Cohen482ed822012-03-02 14:15:13 -08003046 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
3047 boolean value) {
3048 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003049 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
3050 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08003051 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003052 }
3053 }
3054 }
3055
Adam Cohen2801caf2011-05-13 20:57:39 -07003056 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07003057 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07003058 (Math.max((mCountX - 1), 0) * mWidthGap);
3059 }
3060
3061 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07003062 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07003063 (Math.max((mCountY - 1), 0) * mHeightGap);
3064 }
3065
Michael Jurka66d72172011-04-12 16:29:25 -07003066 public boolean isOccupied(int x, int y) {
3067 if (x < mCountX && y < mCountY) {
3068 return mOccupied[x][y];
3069 } else {
3070 throw new RuntimeException("Position exceeds the bound of this CellLayout");
3071 }
3072 }
3073
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003074 @Override
3075 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
3076 return new CellLayout.LayoutParams(getContext(), attrs);
3077 }
3078
3079 @Override
3080 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
3081 return p instanceof CellLayout.LayoutParams;
3082 }
3083
3084 @Override
3085 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
3086 return new CellLayout.LayoutParams(p);
3087 }
3088
Winson Chungaafa03c2010-06-11 17:34:16 -07003089 public static class CellLayoutAnimationController extends LayoutAnimationController {
3090 public CellLayoutAnimationController(Animation animation, float delay) {
3091 super(animation, delay);
3092 }
3093
3094 @Override
3095 protected long getDelayForView(View view) {
3096 return (int) (Math.random() * 150);
3097 }
3098 }
3099
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003100 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
3101 /**
3102 * Horizontal location of the item in the grid.
3103 */
3104 @ViewDebug.ExportedProperty
3105 public int cellX;
3106
3107 /**
3108 * Vertical location of the item in the grid.
3109 */
3110 @ViewDebug.ExportedProperty
3111 public int cellY;
3112
3113 /**
Adam Cohen482ed822012-03-02 14:15:13 -08003114 * Temporary horizontal location of the item in the grid during reorder
3115 */
3116 public int tmpCellX;
3117
3118 /**
3119 * Temporary vertical location of the item in the grid during reorder
3120 */
3121 public int tmpCellY;
3122
3123 /**
3124 * Indicates that the temporary coordinates should be used to layout the items
3125 */
3126 public boolean useTmpCoords;
3127
3128 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003129 * Number of cells spanned horizontally by the item.
3130 */
3131 @ViewDebug.ExportedProperty
3132 public int cellHSpan;
3133
3134 /**
3135 * Number of cells spanned vertically by the item.
3136 */
3137 @ViewDebug.ExportedProperty
3138 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07003139
Adam Cohen1b607ed2011-03-03 17:26:50 -08003140 /**
3141 * Indicates whether the item will set its x, y, width and height parameters freely,
3142 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
3143 */
Adam Cohend4844c32011-02-18 19:25:06 -08003144 public boolean isLockedToGrid = true;
3145
Adam Cohen482ed822012-03-02 14:15:13 -08003146 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07003147 * Indicates that this item should use the full extents of its parent.
3148 */
3149 public boolean isFullscreen = false;
3150
3151 /**
Adam Cohen482ed822012-03-02 14:15:13 -08003152 * Indicates whether this item can be reordered. Always true except in the case of the
3153 * the AllApps button.
3154 */
3155 public boolean canReorder = true;
3156
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003157 // X coordinate of the view in the layout.
3158 @ViewDebug.ExportedProperty
3159 int x;
3160 // Y coordinate of the view in the layout.
3161 @ViewDebug.ExportedProperty
3162 int y;
3163
Romain Guy84f296c2009-11-04 15:00:44 -08003164 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07003165
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003166 public LayoutParams(Context c, AttributeSet attrs) {
3167 super(c, attrs);
3168 cellHSpan = 1;
3169 cellVSpan = 1;
3170 }
3171
3172 public LayoutParams(ViewGroup.LayoutParams source) {
3173 super(source);
3174 cellHSpan = 1;
3175 cellVSpan = 1;
3176 }
Winson Chungaafa03c2010-06-11 17:34:16 -07003177
3178 public LayoutParams(LayoutParams source) {
3179 super(source);
3180 this.cellX = source.cellX;
3181 this.cellY = source.cellY;
3182 this.cellHSpan = source.cellHSpan;
3183 this.cellVSpan = source.cellVSpan;
3184 }
3185
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003186 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08003187 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003188 this.cellX = cellX;
3189 this.cellY = cellY;
3190 this.cellHSpan = cellHSpan;
3191 this.cellVSpan = cellVSpan;
3192 }
3193
Adam Cohen2374abf2013-04-16 14:56:57 -07003194 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
3195 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08003196 if (isLockedToGrid) {
3197 final int myCellHSpan = cellHSpan;
3198 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07003199 int myCellX = useTmpCoords ? tmpCellX : cellX;
3200 int myCellY = useTmpCoords ? tmpCellY : cellY;
3201
3202 if (invertHorizontally) {
3203 myCellX = colCount - myCellX - cellHSpan;
3204 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08003205
Adam Cohend4844c32011-02-18 19:25:06 -08003206 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
3207 leftMargin - rightMargin;
3208 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
3209 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08003210 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
3211 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08003212 }
3213 }
Winson Chungaafa03c2010-06-11 17:34:16 -07003214
Winson Chungaafa03c2010-06-11 17:34:16 -07003215 public String toString() {
3216 return "(" + this.cellX + ", " + this.cellY + ")";
3217 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07003218
3219 public void setWidth(int width) {
3220 this.width = width;
3221 }
3222
3223 public int getWidth() {
3224 return width;
3225 }
3226
3227 public void setHeight(int height) {
3228 this.height = height;
3229 }
3230
3231 public int getHeight() {
3232 return height;
3233 }
3234
3235 public void setX(int x) {
3236 this.x = x;
3237 }
3238
3239 public int getX() {
3240 return x;
3241 }
3242
3243 public void setY(int y) {
3244 this.y = y;
3245 }
3246
3247 public int getY() {
3248 return y;
3249 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003250 }
3251
Michael Jurka0280c3b2010-09-17 15:00:07 -07003252 // This class stores info for two purposes:
3253 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
3254 // its spanX, spanY, and the screen it is on
3255 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
3256 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
3257 // the CellLayout that was long clicked
Michael Jurkae5fb0f22011-04-11 13:27:46 -07003258 static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003259 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07003260 int cellX = -1;
3261 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003262 int spanX;
3263 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07003264 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07003265 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003266
Adam Cohene0aaa0d2014-05-12 12:44:22 -07003267 CellInfo(View v, ItemInfo info) {
3268 cell = v;
3269 cellX = info.cellX;
3270 cellY = info.cellY;
3271 spanX = info.spanX;
3272 spanY = info.spanY;
3273 screenId = info.screenId;
3274 container = info.container;
3275 }
3276
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003277 @Override
3278 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07003279 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
3280 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003281 }
3282 }
Michael Jurkad771c962011-08-09 15:00:48 -07003283
3284 public boolean lastDownOnOccupiedCell() {
3285 return mLastDownOnOccupiedCell;
3286 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003287}