blob: 72eabf1778a7a15142a0c0e538a51517cb525ebc [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;
Adam Cohenc9735cf2015-01-23 16:11:55 -080025import android.annotation.TargetApi;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080026import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040027import android.content.res.Resources;
Winson Chungaafa03c2010-06-11 17:34:16 -070028import android.content.res.TypedArray;
Joe Onorato4be866d2010-10-10 11:26:02 -070029import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070030import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080031import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070032import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070033import android.graphics.Point;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080034import android.graphics.Rect;
Adam Cohen482ed822012-03-02 14:15:13 -080035import android.graphics.drawable.ColorDrawable;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070036import android.graphics.drawable.Drawable;
Adam Cohenc9735cf2015-01-23 16:11:55 -080037import android.os.Build;
Adam Cohen1462de32012-07-24 22:34:36 -070038import android.os.Parcelable;
Adam Cohenc9735cf2015-01-23 16:11:55 -080039import android.support.v4.view.ViewCompat;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080040import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070041import android.util.Log;
Adam Cohen1462de32012-07-24 22:34:36 -070042import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080043import android.view.MotionEvent;
44import android.view.View;
45import android.view.ViewDebug;
46import android.view.ViewGroup;
Adam Cohenc9735cf2015-01-23 16:11:55 -080047import android.view.accessibility.AccessibilityEvent;
Winson Chung150fbab2010-09-29 17:14:26 -070048import android.view.animation.DecelerateInterpolator;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080049
Daniel Sandler325dc232013-06-05 22:57:57 -040050import com.android.launcher3.FolderIcon.FolderRingAnimator;
Sunny Goyale9b651e2015-04-24 11:44:51 -070051import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
52import com.android.launcher3.accessibility.FolderAccessibilityHelper;
53import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
Adam Cohen091440a2015-03-18 14:16:05 -070054import com.android.launcher3.util.Thunk;
Hyunyoung Song3f471442015-04-08 19:01:34 -070055import com.android.launcher3.widget.PendingAddWidgetInfo;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070056
Adam Cohen69ce2e52011-07-03 19:25:21 -070057import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070058import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080059import java.util.Collections;
60import java.util.Comparator;
Adam Cohenbfbfd262011-06-13 16:55:12 -070061import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080062import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070063
Michael Jurkabdb5c532011-02-01 15:05:06 -080064public class CellLayout extends ViewGroup {
Sunny Goyale9b651e2015-04-24 11:44:51 -070065 public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
66 public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
67
Winson Chungaafa03c2010-06-11 17:34:16 -070068 static final String TAG = "CellLayout";
69
Adam Cohen2acce882012-03-28 19:03:19 -070070 private Launcher mLauncher;
Adam Cohen091440a2015-03-18 14:16:05 -070071 @Thunk int mCellWidth;
72 @Thunk int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070073 private int mFixedCellWidth;
74 private int mFixedCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070075
Adam Cohen091440a2015-03-18 14:16:05 -070076 @Thunk int mCountX;
77 @Thunk int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080078
Adam Cohen234c4cd2011-07-17 21:03:04 -070079 private int mOriginalWidthGap;
80 private int mOriginalHeightGap;
Adam Cohen091440a2015-03-18 14:16:05 -070081 @Thunk int mWidthGap;
82 @Thunk int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070083 private int mMaxGap;
Adam Cohen917e3882013-10-31 15:03:35 -070084 private boolean mDropPending = false;
Adam Cohenc50438c2014-08-19 17:43:05 -070085 private boolean mIsDragTarget = true;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080086
Patrick Dubroyde7658b2010-09-27 11:15:43 -070087 // These are temporary variables to prevent having to allocate a new object just to
88 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Winson Chung0be025d2011-05-23 17:45:09 -070089 private final int[] mTmpXY = new int[2];
Adam Cohen091440a2015-03-18 14:16:05 -070090 @Thunk final int[] mTmpPoint = new int[2];
Adam Cohen69ce2e52011-07-03 19:25:21 -070091 int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070092
The Android Open Source Project31dd5032009-03-03 19:32:27 -080093 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -080094 boolean[][] mTmpOccupied;
Michael Jurkad771c962011-08-09 15:00:48 -070095 private boolean mLastDownOnOccupiedCell = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080096
Michael Jurkadee05892010-07-27 10:01:56 -070097 private OnTouchListener mInterceptTouchListener;
98
Adam Cohen69ce2e52011-07-03 19:25:21 -070099 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -0700100 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -0700101
Adam Cohen02dcfcc2013-10-01 12:37:33 -0700102 private float FOREGROUND_ALPHA_DAMPER = 0.65f;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700103 private int mForegroundAlpha = 0;
Michael Jurka5f1c5092010-09-03 14:15:02 -0700104 private float mBackgroundAlpha;
Adam Cohen1b0aaac2010-10-28 11:11:18 -0700105 private float mBackgroundAlphaMultiplier = 1.0f;
Winson Chung59a488a2013-12-10 12:32:14 -0800106 private boolean mDrawBackground = true;
Adam Cohenf34bab52010-09-30 14:11:56 -0700107
Michael Jurka33945b22010-12-21 18:19:38 -0800108 private Drawable mNormalBackground;
Michael Jurka33945b22010-12-21 18:19:38 -0800109 private Drawable mActiveGlowBackground;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700110 private Drawable mOverScrollForegroundDrawable;
111 private Drawable mOverScrollLeft;
112 private Drawable mOverScrollRight;
Michael Jurka18014792010-10-14 09:01:34 -0700113 private Rect mBackgroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700114 private Rect mForegroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700115 private int mForegroundPadding;
Patrick Dubroy1262e362010-10-06 15:49:50 -0700116
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700117 // These values allow a fixed measurement to be set on the CellLayout.
118 private int mFixedWidth = -1;
119 private int mFixedHeight = -1;
120
Michael Jurka33945b22010-12-21 18:19:38 -0800121 // If we're actively dragging something over this screen, mIsDragOverlapping is true
122 private boolean mIsDragOverlapping = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700123
Winson Chung150fbab2010-09-29 17:14:26 -0700124 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700125 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohen091440a2015-03-18 14:16:05 -0700126 @Thunk Rect[] mDragOutlines = new Rect[4];
127 @Thunk float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700128 private InterruptibleInOutAnimator[] mDragOutlineAnims =
129 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700130
131 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700132 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700133 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700134
Sunny Goyal508da152014-08-14 10:53:27 -0700135 private final FastBitmapView mTouchFeedbackView;
Patrick Dubroy96864c32011-03-10 17:17:23 -0800136
Adam Cohen091440a2015-03-18 14:16:05 -0700137 @Thunk HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
Adam Cohen482ed822012-03-02 14:15:13 -0800138 HashMap<CellLayout.LayoutParams, Animator>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800139 private HashMap<View, ReorderPreviewAnimation>
140 mShakeAnimators = new HashMap<View, ReorderPreviewAnimation>();
Adam Cohen19f37922012-03-21 11:59:11 -0700141
142 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700143
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700144 // When a drag operation is in progress, holds the nearest cell to the touch point
145 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800146
Joe Onorato4be866d2010-10-10 11:26:02 -0700147 private boolean mDragging = false;
148
Patrick Dubroyce34a972010-10-19 10:34:32 -0700149 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700150 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700151
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800152 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700153 private float mHotseatScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800154
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800155 public static final int MODE_SHOW_REORDER_HINT = 0;
156 public static final int MODE_DRAG_OVER = 1;
157 public static final int MODE_ON_DROP = 2;
158 public static final int MODE_ON_DROP_EXTERNAL = 3;
159 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700160 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800161 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
162
Adam Cohena897f392012-04-27 18:12:05 -0700163 static final int LANDSCAPE = 0;
164 static final int PORTRAIT = 1;
165
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800166 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700167 private static final int REORDER_ANIMATION_DURATION = 150;
Adam Cohen091440a2015-03-18 14:16:05 -0700168 @Thunk float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700169
Adam Cohen482ed822012-03-02 14:15:13 -0800170 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
171 private Rect mOccupiedRect = new Rect();
172 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700173 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700174 private static final int INVALID_DIRECTION = -100;
Adam Cohenc6cc61d2012-04-04 12:47:08 -0700175 private DropTarget.DragEnforcer mDragEnforcer;
Adam Cohen482ed822012-03-02 14:15:13 -0800176
Winson Chung3a6e7f32013-10-09 15:50:52 -0700177 private Rect mTempRect = new Rect();
178
Michael Jurkaca993832012-06-29 15:17:04 -0700179 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700180
Adam Cohenc9735cf2015-01-23 16:11:55 -0800181 // Related to accessible drag and drop
Sunny Goyale9b651e2015-04-24 11:44:51 -0700182 private DragAndDropAccessibilityDelegate mTouchHelper;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800183 private boolean mUseTouchHelper = false;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800184
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800185 public CellLayout(Context context) {
186 this(context, null);
187 }
188
189 public CellLayout(Context context, AttributeSet attrs) {
190 this(context, attrs, 0);
191 }
192
193 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
194 super(context, attrs, defStyle);
Michael Jurka8b805b12012-04-18 14:23:14 -0700195 mDragEnforcer = new DropTarget.DragEnforcer(context);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700196
197 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
198 // the user where a dragged item will land when dropped.
199 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800200 setClipToPadding(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700201 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700202
Winson Chung892c74d2013-08-22 16:15:50 -0700203 LauncherAppState app = LauncherAppState.getInstance();
204 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800205 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
206
Winson Chung11a1a532013-09-13 11:14:45 -0700207 mCellWidth = mCellHeight = -1;
Nilesh Agrawal5f7099a2014-01-02 15:54:57 -0800208 mFixedCellWidth = mFixedCellHeight = -1;
Winson Chung5f8afe62013-08-12 16:19:28 -0700209 mWidthGap = mOriginalWidthGap = 0;
210 mHeightGap = mOriginalHeightGap = 0;
211 mMaxGap = Integer.MAX_VALUE;
Winson Chung892c74d2013-08-22 16:15:50 -0700212 mCountX = (int) grid.numColumns;
213 mCountY = (int) grid.numRows;
Michael Jurka0280c3b2010-09-17 15:00:07 -0700214 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800215 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700216 mPreviousReorderDirection[0] = INVALID_DIRECTION;
217 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800218
219 a.recycle();
220
221 setAlwaysDrawnWithCacheEnabled(false);
222
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700223 final Resources res = getResources();
Winson Chung6e1c0d32013-10-25 15:24:24 -0700224 mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700225
Adam Cohen410f3cd2013-09-22 12:09:32 -0700226 mNormalBackground = res.getDrawable(R.drawable.screenpanel);
227 mActiveGlowBackground = res.getDrawable(R.drawable.screenpanel_hover);
Michael Jurka33945b22010-12-21 18:19:38 -0800228
Adam Cohenb5ba0972011-09-07 18:02:31 -0700229 mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
230 mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
231 mForegroundPadding =
232 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
Michael Jurka33945b22010-12-21 18:19:38 -0800233
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800234 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
Winson Chung5f8afe62013-08-12 16:19:28 -0700235 grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700236
Winson Chungb26f3d62011-06-02 10:49:29 -0700237 mNormalBackground.setFilterBitmap(true);
Winson Chungb26f3d62011-06-02 10:49:29 -0700238 mActiveGlowBackground.setFilterBitmap(true);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700239
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700240 // Initialize the data structures used for the drag visualization.
Patrick Dubroyce34a972010-10-19 10:34:32 -0700241 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700242 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700243 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800244 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700245 }
246
247 // When dragging things around the home screens, we show a green outline of
248 // where the item will land. The outlines gradually fade out, leaving a trail
249 // behind the drag path.
250 // Set up all the animations that are used to implement this fading.
251 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700252 final float fromAlphaValue = 0;
253 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700254
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700255 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700256
257 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700258 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100259 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700260 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700261 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700262 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700263 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700264 final Bitmap outline = (Bitmap)anim.getTag();
265
266 // If an animation is started and then stopped very quickly, we can still
267 // get spurious updates we've cleared the tag. Guard against this.
268 if (outline == null) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700269 @SuppressWarnings("all") // suppress dead code warning
270 final boolean debug = false;
271 if (debug) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700272 Object val = animation.getAnimatedValue();
273 Log.d(TAG, "anim " + thisIndex + " update: " + val +
274 ", isStopped " + anim.isStopped());
275 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700276 // Try to prevent it from continuing to run
277 animation.cancel();
278 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700279 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800280 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700281 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700282 }
283 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700284 // The animation holds a reference to the drag outline bitmap as long is it's
285 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700286 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700287 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700288 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700289 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700290 anim.setTag(null);
291 }
292 }
293 });
294 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700295 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700296
Michael Jurka18014792010-10-14 09:01:34 -0700297 mBackgroundRect = new Rect();
Adam Cohenb5ba0972011-09-07 18:02:31 -0700298 mForegroundRect = new Rect();
Michael Jurkabea15192010-11-17 12:33:46 -0800299
Michael Jurkaa52570f2012-03-20 03:18:20 -0700300 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
Adam Cohen2374abf2013-04-16 14:56:57 -0700301 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700302 mCountX, mCountY);
Adam Cohen2374abf2013-04-16 14:56:57 -0700303
Sunny Goyal508da152014-08-14 10:53:27 -0700304 mTouchFeedbackView = new FastBitmapView(context);
305 // Make the feedback view large enough to hold the blur bitmap.
306 addView(mTouchFeedbackView, (int) (grid.cellWidthPx * 1.5), (int) (grid.cellHeightPx * 1.5));
Michael Jurkaa52570f2012-03-20 03:18:20 -0700307 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700308 }
309
Adam Cohenc9735cf2015-01-23 16:11:55 -0800310 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
Sunny Goyale9b651e2015-04-24 11:44:51 -0700311 public void enableAccessibleDrag(boolean enable, int dragType) {
Adam Cohenc9735cf2015-01-23 16:11:55 -0800312 mUseTouchHelper = enable;
313 if (!enable) {
314 ViewCompat.setAccessibilityDelegate(this, null);
315 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
316 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
317 setOnClickListener(mLauncher);
318 } else {
Sunny Goyale9b651e2015-04-24 11:44:51 -0700319 if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
320 !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
321 mTouchHelper = new WorkspaceAccessibilityHelper(this);
322 } else if (dragType == FOLDER_ACCESSIBILITY_DRAG &&
323 !(mTouchHelper instanceof FolderAccessibilityHelper)) {
324 mTouchHelper = new FolderAccessibilityHelper(this);
325 }
Adam Cohenc9735cf2015-01-23 16:11:55 -0800326 ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
327 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
328 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
329 setOnClickListener(mTouchHelper);
330 }
331
332 // Invalidate the accessibility hierarchy
333 if (getParent() != null) {
334 getParent().notifySubtreeAccessibilityStateChanged(
335 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
336 }
337 }
338
339 @Override
340 public boolean dispatchHoverEvent(MotionEvent event) {
341 // Always attempt to dispatch hover events to accessibility first.
342 if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
343 return true;
344 }
345 return super.dispatchHoverEvent(event);
346 }
347
348 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800349 public boolean onInterceptTouchEvent(MotionEvent ev) {
350 if (mUseTouchHelper ||
351 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
352 return true;
353 }
354 return false;
355 }
356
Chris Craik01f2d7f2013-10-01 14:41:56 -0700357 public void enableHardwareLayer(boolean hasLayer) {
358 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700359 }
360
361 public void buildHardwareLayer() {
362 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700363 }
364
Adam Cohen307fe232012-08-16 17:55:58 -0700365 public float getChildrenScale() {
366 return mIsHotseat ? mHotseatScale : 1.0f;
367 }
368
Winson Chung5f8afe62013-08-12 16:19:28 -0700369 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700370 mFixedCellWidth = mCellWidth = width;
371 mFixedCellHeight = mCellHeight = height;
Winson Chung5f8afe62013-08-12 16:19:28 -0700372 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
373 mCountX, mCountY);
374 }
375
Adam Cohen2801caf2011-05-13 20:57:39 -0700376 public void setGridSize(int x, int y) {
377 mCountX = x;
378 mCountY = y;
379 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800380 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700381 mTempRectStack.clear();
Adam Cohen2374abf2013-04-16 14:56:57 -0700382 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700383 mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700384 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700385 }
386
Adam Cohen2374abf2013-04-16 14:56:57 -0700387 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
388 public void setInvertIfRtl(boolean invert) {
389 mShortcutsAndWidgets.setInvertIfRtl(invert);
390 }
391
Adam Cohen917e3882013-10-31 15:03:35 -0700392 public void setDropPending(boolean pending) {
393 mDropPending = pending;
394 }
395
396 public boolean isDropPending() {
397 return mDropPending;
398 }
399
Adam Cohenb5ba0972011-09-07 18:02:31 -0700400 void setOverScrollAmount(float r, boolean left) {
401 if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
402 mOverScrollForegroundDrawable = mOverScrollLeft;
403 } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
404 mOverScrollForegroundDrawable = mOverScrollRight;
405 }
406
Adam Cohen02dcfcc2013-10-01 12:37:33 -0700407 r *= FOREGROUND_ALPHA_DAMPER;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700408 mForegroundAlpha = (int) Math.round((r * 255));
409 mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
410 invalidate();
411 }
412
Sunny Goyal508da152014-08-14 10:53:27 -0700413 void setPressedIcon(BubbleTextView icon, Bitmap background, int padding) {
414 if (icon == null || background == null) {
415 mTouchFeedbackView.setBitmap(null);
416 mTouchFeedbackView.animate().cancel();
417 } else {
418 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight()
419 - (mCountX * mCellWidth);
420 mTouchFeedbackView.setTranslationX(icon.getLeft() + (int) Math.ceil(offset / 2f)
421 - padding);
422 mTouchFeedbackView.setTranslationY(icon.getTop() - padding);
423 if (mTouchFeedbackView.setBitmap(background)) {
424 mTouchFeedbackView.setAlpha(0);
425 mTouchFeedbackView.animate().alpha(1)
426 .setDuration(FastBitmapDrawable.CLICK_FEEDBACK_DURATION)
427 .setInterpolator(FastBitmapDrawable.CLICK_FEEDBACK_INTERPOLATOR)
428 .start();
429 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800430 }
431 }
432
Winson Chung59a488a2013-12-10 12:32:14 -0800433 void disableBackground() {
434 mDrawBackground = false;
435 }
436
Adam Cohenc50438c2014-08-19 17:43:05 -0700437 void disableDragTarget() {
438 mIsDragTarget = false;
439 }
440
441 boolean isDragTarget() {
442 return mIsDragTarget;
443 }
444
445 void setIsDragOverlapping(boolean isDragOverlapping) {
446 if (mIsDragOverlapping != isDragOverlapping) {
447 mIsDragOverlapping = isDragOverlapping;
Adam Cohenc50438c2014-08-19 17:43:05 -0700448 invalidate();
449 }
450 }
451
Michael Jurka33945b22010-12-21 18:19:38 -0800452 boolean getIsDragOverlapping() {
453 return mIsDragOverlapping;
454 }
455
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700456 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700457 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700458 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
459 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
460 // When we're small, we are either drawn normally or in the "accepts drops" state (during
461 // a drag). However, we also drag the mini hover background *over* one of those two
462 // backgrounds
Winson Chung59a488a2013-12-10 12:32:14 -0800463 if (mDrawBackground && mBackgroundAlpha > 0.0f) {
Adam Cohenf34bab52010-09-30 14:11:56 -0700464 Drawable bg;
Michael Jurka33945b22010-12-21 18:19:38 -0800465
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700466 if (mIsDragOverlapping) {
Michael Jurka33945b22010-12-21 18:19:38 -0800467 // In the mini case, we draw the active_glow bg *over* the active background
Michael Jurkabdf78552011-10-31 14:34:25 -0700468 bg = mActiveGlowBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700469 } else {
Michael Jurkabdf78552011-10-31 14:34:25 -0700470 bg = mNormalBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700471 }
Michael Jurka33945b22010-12-21 18:19:38 -0800472
473 bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
474 bg.setBounds(mBackgroundRect);
475 bg.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700476 }
Romain Guya6abce82009-11-10 02:54:41 -0800477
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700478 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700479 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700480 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700481 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800482 final Rect r = mDragOutlines[i];
Winson Chung3a6e7f32013-10-09 15:50:52 -0700483 mTempRect.set(r);
484 Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale());
Joe Onorato4be866d2010-10-10 11:26:02 -0700485 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700486 paint.setAlpha((int)(alpha + .5f));
Winson Chung3a6e7f32013-10-09 15:50:52 -0700487 canvas.drawBitmap(b, null, mTempRect, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700488 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700489 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800490
Adam Cohen482ed822012-03-02 14:15:13 -0800491 if (DEBUG_VISUALIZE_OCCUPIED) {
492 int[] pt = new int[2];
493 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700494 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800495 for (int i = 0; i < mCountX; i++) {
496 for (int j = 0; j < mCountY; j++) {
497 if (mOccupied[i][j]) {
498 cellToPoint(i, j, pt);
499 canvas.save();
500 canvas.translate(pt[0], pt[1]);
501 cd.draw(canvas);
502 canvas.restore();
503 }
504 }
505 }
506 }
507
Andrew Flynn850d2e72012-04-26 16:51:20 -0700508 int previewOffset = FolderRingAnimator.sPreviewSize;
509
Adam Cohen69ce2e52011-07-03 19:25:21 -0700510 // The folder outer / inner ring image(s)
Winson Chung5f8afe62013-08-12 16:19:28 -0700511 LauncherAppState app = LauncherAppState.getInstance();
512 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700513 for (int i = 0; i < mFolderOuterRings.size(); i++) {
514 FolderRingAnimator fra = mFolderOuterRings.get(i);
515
Adam Cohen5108bc02013-09-20 17:04:51 -0700516 Drawable d;
517 int width, height;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700518 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700519 View child = getChildAt(fra.mCellX, fra.mCellY);
Adam Cohen558f1c22013-10-09 15:15:24 -0700520
Winson Chung89f97052013-09-20 11:32:26 -0700521 if (child != null) {
Adam Cohen558f1c22013-10-09 15:15:24 -0700522 int centerX = mTempLocation[0] + mCellWidth / 2;
523 int centerY = mTempLocation[1] + previewOffset / 2 +
524 child.getPaddingTop() + grid.folderBackgroundOffset;
525
Adam Cohen5108bc02013-09-20 17:04:51 -0700526 // Draw outer ring, if it exists
527 if (FolderIcon.HAS_OUTER_RING) {
528 d = FolderRingAnimator.sSharedOuterRingDrawable;
529 width = (int) (fra.getOuterRingSize() * getChildrenScale());
530 height = width;
531 canvas.save();
532 canvas.translate(centerX - width / 2, centerY - height / 2);
533 d.setBounds(0, 0, width, height);
534 d.draw(canvas);
535 canvas.restore();
536 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700537
Winson Chung89f97052013-09-20 11:32:26 -0700538 // Draw inner ring
539 d = FolderRingAnimator.sSharedInnerRingDrawable;
540 width = (int) (fra.getInnerRingSize() * getChildrenScale());
541 height = width;
Winson Chung89f97052013-09-20 11:32:26 -0700542 canvas.save();
543 canvas.translate(centerX - width / 2, centerY - width / 2);
544 d.setBounds(0, 0, width, height);
545 d.draw(canvas);
546 canvas.restore();
547 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700548 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700549
550 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
551 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
552 int width = d.getIntrinsicWidth();
553 int height = d.getIntrinsicHeight();
554
555 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700556 View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]);
Winson Chung89f97052013-09-20 11:32:26 -0700557 if (child != null) {
558 int centerX = mTempLocation[0] + mCellWidth / 2;
559 int centerY = mTempLocation[1] + previewOffset / 2 +
560 child.getPaddingTop() + grid.folderBackgroundOffset;
Adam Cohenc51934b2011-07-26 21:07:43 -0700561
Winson Chung89f97052013-09-20 11:32:26 -0700562 canvas.save();
563 canvas.translate(centerX - width / 2, centerY - width / 2);
564 d.setBounds(0, 0, width, height);
565 d.draw(canvas);
566 canvas.restore();
567 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700568 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700569 }
570
Adam Cohenb5ba0972011-09-07 18:02:31 -0700571 @Override
572 protected void dispatchDraw(Canvas canvas) {
573 super.dispatchDraw(canvas);
574 if (mForegroundAlpha > 0) {
575 mOverScrollForegroundDrawable.setBounds(mForegroundRect);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700576 mOverScrollForegroundDrawable.draw(canvas);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700577 }
578 }
579
Adam Cohen69ce2e52011-07-03 19:25:21 -0700580 public void showFolderAccept(FolderRingAnimator fra) {
581 mFolderOuterRings.add(fra);
582 }
583
584 public void hideFolderAccept(FolderRingAnimator fra) {
585 if (mFolderOuterRings.contains(fra)) {
586 mFolderOuterRings.remove(fra);
587 }
588 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700589 }
590
Adam Cohenc51934b2011-07-26 21:07:43 -0700591 public void setFolderLeaveBehindCell(int x, int y) {
592 mFolderLeaveBehindCell[0] = x;
593 mFolderLeaveBehindCell[1] = y;
594 invalidate();
595 }
596
597 public void clearFolderLeaveBehind() {
598 mFolderLeaveBehindCell[0] = -1;
599 mFolderLeaveBehindCell[1] = -1;
600 invalidate();
601 }
602
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700603 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700604 public boolean shouldDelayChildPressedState() {
605 return false;
606 }
607
Adam Cohen1462de32012-07-24 22:34:36 -0700608 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700609 try {
610 dispatchRestoreInstanceState(states);
611 } catch (IllegalArgumentException ex) {
612 if (LauncherAppState.isDogfoodBuild()) {
613 throw ex;
614 }
615 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
616 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
617 }
Adam Cohen1462de32012-07-24 22:34:36 -0700618 }
619
Michael Jurkae6235dd2011-10-04 15:02:05 -0700620 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700621 public void cancelLongPress() {
622 super.cancelLongPress();
623
624 // Cancel long press for all children
625 final int count = getChildCount();
626 for (int i = 0; i < count; i++) {
627 final View child = getChildAt(i);
628 child.cancelLongPress();
629 }
630 }
631
Michael Jurkadee05892010-07-27 10:01:56 -0700632 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
633 mInterceptTouchListener = listener;
634 }
635
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800636 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700637 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800638 }
639
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800640 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700641 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800642 }
643
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800644 public void setIsHotseat(boolean isHotseat) {
645 mIsHotseat = isHotseat;
Winson Chung5f8afe62013-08-12 16:19:28 -0700646 mShortcutsAndWidgets.setIsHotseat(isHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800647 }
648
Sunny Goyale9b651e2015-04-24 11:44:51 -0700649 public boolean isHotseat() {
650 return mIsHotseat;
651 }
652
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800653 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700654 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700655 final LayoutParams lp = params;
656
Andrew Flynnde38e422012-05-08 11:22:15 -0700657 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800658 if (child instanceof BubbleTextView) {
659 BubbleTextView bubbleChild = (BubbleTextView) child;
Winson Chung5f8afe62013-08-12 16:19:28 -0700660 bubbleChild.setTextVisibility(!mIsHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800661 }
662
Adam Cohen307fe232012-08-16 17:55:58 -0700663 child.setScaleX(getChildrenScale());
664 child.setScaleY(getChildrenScale());
665
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800666 // Generate an id for each view, this assumes we have at most 256x256 cells
667 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700668 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700669 // If the horizontal or vertical span is set to -1, it is taken to
670 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700671 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
672 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800673
Winson Chungaafa03c2010-06-11 17:34:16 -0700674 child.setId(childId);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700675 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700676
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700677 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700678
Winson Chungaafa03c2010-06-11 17:34:16 -0700679 return true;
680 }
681 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800682 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700683
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800684 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700685 public void removeAllViews() {
686 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700687 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700688 }
689
690 @Override
691 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700692 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700693 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700694 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700695 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700696 }
697
698 @Override
699 public void removeView(View view) {
700 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700701 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700702 }
703
704 @Override
705 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700706 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
707 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700708 }
709
710 @Override
711 public void removeViewInLayout(View view) {
712 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700713 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700714 }
715
716 @Override
717 public void removeViews(int start, int count) {
718 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700719 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700720 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700721 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700722 }
723
724 @Override
725 public void removeViewsInLayout(int start, int count) {
726 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700727 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700728 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700729 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800730 }
731
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700732 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700733 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800734 * @param x X coordinate of the point
735 * @param y Y coordinate of the point
736 * @param result Array of 2 ints to hold the x and y coordinate of the cell
737 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700738 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700739 final int hStartPadding = getPaddingLeft();
740 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800741
742 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
743 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
744
Adam Cohend22015c2010-07-26 22:02:18 -0700745 final int xAxis = mCountX;
746 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800747
748 if (result[0] < 0) result[0] = 0;
749 if (result[0] >= xAxis) result[0] = xAxis - 1;
750 if (result[1] < 0) result[1] = 0;
751 if (result[1] >= yAxis) result[1] = yAxis - 1;
752 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700753
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800754 /**
755 * Given a point, return the cell that most closely encloses that point
756 * @param x X coordinate of the point
757 * @param y Y coordinate of the point
758 * @param result Array of 2 ints to hold the x and y coordinate of the cell
759 */
760 void pointToCellRounded(int x, int y, int[] result) {
761 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
762 }
763
764 /**
765 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700766 *
767 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800768 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700769 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800770 * @param result Array of 2 ints to hold the x and y coordinate of the point
771 */
772 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700773 final int hStartPadding = getPaddingLeft();
774 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800775
776 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
777 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
778 }
779
Adam Cohene3e27a82011-04-15 12:07:39 -0700780 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800781 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700782 *
783 * @param cellX X coordinate of the cell
784 * @param cellY Y coordinate of the cell
785 *
786 * @param result Array of 2 ints to hold the x and y coordinate of the point
787 */
788 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700789 regionToCenterPoint(cellX, cellY, 1, 1, result);
790 }
791
792 /**
793 * Given a cell coordinate and span return the point that represents the center of the regio
794 *
795 * @param cellX X coordinate of the cell
796 * @param cellY Y coordinate of the cell
797 *
798 * @param result Array of 2 ints to hold the x and y coordinate of the point
799 */
800 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700801 final int hStartPadding = getPaddingLeft();
802 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700803 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
804 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
805 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
806 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700807 }
808
Adam Cohen19f37922012-03-21 11:59:11 -0700809 /**
810 * Given a cell coordinate and span fills out a corresponding pixel rect
811 *
812 * @param cellX X coordinate of the cell
813 * @param cellY Y coordinate of the cell
814 * @param result Rect in which to write the result
815 */
816 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
817 final int hStartPadding = getPaddingLeft();
818 final int vStartPadding = getPaddingTop();
819 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
820 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
821 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
822 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
823 }
824
Adam Cohen482ed822012-03-02 14:15:13 -0800825 public float getDistanceFromCell(float x, float y, int[] cell) {
826 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700827 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800828 }
829
Romain Guy84f296c2009-11-04 15:00:44 -0800830 int getCellWidth() {
831 return mCellWidth;
832 }
833
834 int getCellHeight() {
835 return mCellHeight;
836 }
837
Adam Cohend4844c32011-02-18 19:25:06 -0800838 int getWidthGap() {
839 return mWidthGap;
840 }
841
842 int getHeightGap() {
843 return mHeightGap;
844 }
845
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700846 public void setFixedSize(int width, int height) {
847 mFixedWidth = width;
848 mFixedHeight = height;
849 }
850
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800851 @Override
852 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700853 LauncherAppState app = LauncherAppState.getInstance();
854 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
855
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800856 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800857 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700858 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
859 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700860 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
861 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700862 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700863 int cw = grid.calculateCellWidth(childWidthSize, mCountX);
864 int ch = grid.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700865 if (cw != mCellWidth || ch != mCellHeight) {
866 mCellWidth = cw;
867 mCellHeight = ch;
868 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
869 mHeightGap, mCountX, mCountY);
870 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700871 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700872
Winson Chung2d75f122013-09-23 16:53:31 -0700873 int newWidth = childWidthSize;
874 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700875 if (mFixedWidth > 0 && mFixedHeight > 0) {
876 newWidth = mFixedWidth;
877 newHeight = mFixedHeight;
878 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800879 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
880 }
881
Adam Cohend22015c2010-07-26 22:02:18 -0700882 int numWidthGaps = mCountX - 1;
883 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800884
Adam Cohen234c4cd2011-07-17 21:03:04 -0700885 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700886 int hSpace = childWidthSize;
887 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700888 int hFreeSpace = hSpace - (mCountX * mCellWidth);
889 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700890 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
891 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -0700892 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
893 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700894 } else {
895 mWidthGap = mOriginalWidthGap;
896 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700897 }
Michael Jurka8c920dd2011-01-20 14:16:56 -0800898 int count = getChildCount();
Winson Chung5f8afe62013-08-12 16:19:28 -0700899 int maxWidth = 0;
900 int maxHeight = 0;
Michael Jurka8c920dd2011-01-20 14:16:56 -0800901 for (int i = 0; i < count; i++) {
902 View child = getChildAt(i);
Winson Chung2d75f122013-09-23 16:53:31 -0700903 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth,
904 MeasureSpec.EXACTLY);
905 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight,
906 MeasureSpec.EXACTLY);
Michael Jurka8c920dd2011-01-20 14:16:56 -0800907 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700908 maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
909 maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
Michael Jurka8c920dd2011-01-20 14:16:56 -0800910 }
Winson Chung2d75f122013-09-23 16:53:31 -0700911 if (mFixedWidth > 0 && mFixedHeight > 0) {
912 setMeasuredDimension(maxWidth, maxHeight);
913 } else {
914 setMeasuredDimension(widthSize, heightSize);
915 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800916 }
917
918 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700919 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Winson Chung38848ca2013-10-08 12:03:44 -0700920 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
921 (mCountX * mCellWidth);
922 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
923 int top = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800924 int count = getChildCount();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800925 for (int i = 0; i < count; i++) {
Michael Jurka8c920dd2011-01-20 14:16:56 -0800926 View child = getChildAt(i);
Winson Chung2d75f122013-09-23 16:53:31 -0700927 child.layout(left, top,
928 left + r - l,
929 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800930 }
931 }
932
933 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700934 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
935 super.onSizeChanged(w, h, oldw, oldh);
Winson Chung82a9bd22013-10-08 16:02:34 -0700936
937 // Expand the background drawing bounds by the padding baked into the background drawable
938 Rect padding = new Rect();
939 mNormalBackground.getPadding(padding);
940 mBackgroundRect.set(-padding.left, -padding.top, w + padding.right, h + padding.bottom);
941
Adam Cohenb5ba0972011-09-07 18:02:31 -0700942 mForegroundRect.set(mForegroundPadding, mForegroundPadding,
Adam Cohen215b4162012-08-30 13:14:08 -0700943 w - mForegroundPadding, h - mForegroundPadding);
Michael Jurkadee05892010-07-27 10:01:56 -0700944 }
945
946 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800947 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700948 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800949 }
950
951 @Override
952 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700953 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800954 }
955
Michael Jurka5f1c5092010-09-03 14:15:02 -0700956 public float getBackgroundAlpha() {
957 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -0700958 }
959
Adam Cohen1b0aaac2010-10-28 11:11:18 -0700960 public void setBackgroundAlphaMultiplier(float multiplier) {
Michael Jurkaa3d30ad2012-05-08 13:43:43 -0700961 if (mBackgroundAlphaMultiplier != multiplier) {
962 mBackgroundAlphaMultiplier = multiplier;
963 invalidate();
964 }
Adam Cohen1b0aaac2010-10-28 11:11:18 -0700965 }
966
Adam Cohenddb82192010-11-10 16:32:54 -0800967 public float getBackgroundAlphaMultiplier() {
968 return mBackgroundAlphaMultiplier;
969 }
970
Michael Jurka5f1c5092010-09-03 14:15:02 -0700971 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -0800972 if (mBackgroundAlpha != alpha) {
973 mBackgroundAlpha = alpha;
974 invalidate();
975 }
Michael Jurkadee05892010-07-27 10:01:56 -0700976 }
977
Michael Jurkaa52570f2012-03-20 03:18:20 -0700978 public void setShortcutAndWidgetAlpha(float alpha) {
Sunny Goyal02b50812014-09-10 15:44:42 -0700979 mShortcutsAndWidgets.setAlpha(alpha);
Michael Jurkadee05892010-07-27 10:01:56 -0700980 }
981
Michael Jurkaa52570f2012-03-20 03:18:20 -0700982 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700983 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700984 }
985
Patrick Dubroy440c3602010-07-13 17:50:32 -0700986 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700987 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700988 }
989
Adam Cohen76fc0852011-06-17 13:26:23 -0700990 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800991 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700992 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800993 boolean[][] occupied = mOccupied;
994 if (!permanent) {
995 occupied = mTmpOccupied;
996 }
997
Adam Cohen19f37922012-03-21 11:59:11 -0700998 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700999 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1000 final ItemInfo info = (ItemInfo) child.getTag();
1001
1002 // We cancel any existing animations
1003 if (mReorderAnimators.containsKey(lp)) {
1004 mReorderAnimators.get(lp).cancel();
1005 mReorderAnimators.remove(lp);
1006 }
1007
Adam Cohen482ed822012-03-02 14:15:13 -08001008 final int oldX = lp.x;
1009 final int oldY = lp.y;
1010 if (adjustOccupied) {
1011 occupied[lp.cellX][lp.cellY] = false;
1012 occupied[cellX][cellY] = true;
1013 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001014 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001015 if (permanent) {
1016 lp.cellX = info.cellX = cellX;
1017 lp.cellY = info.cellY = cellY;
1018 } else {
1019 lp.tmpCellX = cellX;
1020 lp.tmpCellY = cellY;
1021 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001022 clc.setupLp(lp);
1023 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001024 final int newX = lp.x;
1025 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001026
Adam Cohen76fc0852011-06-17 13:26:23 -07001027 lp.x = oldX;
1028 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001029
Adam Cohen482ed822012-03-02 14:15:13 -08001030 // Exit early if we're not actually moving the view
1031 if (oldX == newX && oldY == newY) {
1032 lp.isLockedToGrid = true;
1033 return true;
1034 }
1035
Michael Jurkaf1ad6082013-03-13 12:55:46 +01001036 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001037 va.setDuration(duration);
1038 mReorderAnimators.put(lp, va);
1039
1040 va.addUpdateListener(new AnimatorUpdateListener() {
1041 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001042 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001043 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001044 lp.x = (int) ((1 - r) * oldX + r * newX);
1045 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001046 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001047 }
1048 });
Adam Cohen482ed822012-03-02 14:15:13 -08001049 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001050 boolean cancelled = false;
1051 public void onAnimationEnd(Animator animation) {
1052 // If the animation was cancelled, it means that another animation
1053 // has interrupted this one, and we don't want to lock the item into
1054 // place just yet.
1055 if (!cancelled) {
1056 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001057 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001058 }
1059 if (mReorderAnimators.containsKey(lp)) {
1060 mReorderAnimators.remove(lp);
1061 }
1062 }
1063 public void onAnimationCancel(Animator animation) {
1064 cancelled = true;
1065 }
1066 });
Adam Cohen482ed822012-03-02 14:15:13 -08001067 va.setStartDelay(delay);
1068 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001069 return true;
1070 }
1071 return false;
1072 }
1073
Adam Cohen482ed822012-03-02 14:15:13 -08001074 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1075 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001076 final int oldDragCellX = mDragCell[0];
1077 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001078
Adam Cohen2801caf2011-05-13 20:57:39 -07001079 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001080 return;
1081 }
1082
Adam Cohen482ed822012-03-02 14:15:13 -08001083 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1084 mDragCell[0] = cellX;
1085 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001086 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001087 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001088 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001089
Joe Onorato4be866d2010-10-10 11:26:02 -07001090 int left = topLeft[0];
1091 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001092
Winson Chungb8c69f32011-10-19 21:36:08 -07001093 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001094 // When drawing the drag outline, it did not account for margin offsets
1095 // added by the view's parent.
1096 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1097 left += lp.leftMargin;
1098 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001099
Adam Cohen99e8b402011-03-25 19:23:43 -07001100 // Offsets due to the size difference between the View and the dragOutline.
1101 // There is a size difference to account for the outer blur, which may lie
1102 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001103 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001104 // We center about the x axis
1105 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1106 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001107 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001108 if (dragOffset != null && dragRegion != null) {
1109 // Center the drag region *horizontally* in the cell and apply a drag
1110 // outline offset
1111 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1112 - dragRegion.width()) / 2;
Winson Chung69737c32013-10-08 17:00:19 -07001113 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1114 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1115 top += dragOffset.y + cellPaddingY;
Winson Chungb8c69f32011-10-19 21:36:08 -07001116 } else {
1117 // Center the drag outline in the cell
1118 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1119 - dragOutline.getWidth()) / 2;
1120 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1121 - dragOutline.getHeight()) / 2;
1122 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001123 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001124 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001125 mDragOutlineAnims[oldIndex].animateOut();
1126 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001127 Rect r = mDragOutlines[mDragOutlineCurrent];
1128 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1129 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001130 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001131 }
Winson Chung150fbab2010-09-29 17:14:26 -07001132
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001133 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1134 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001135 }
1136 }
1137
Adam Cohene0310962011-04-18 16:15:31 -07001138 public void clearDragOutlines() {
1139 final int oldIndex = mDragOutlineCurrent;
1140 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001141 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001142 }
1143
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001144 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001145 * Find a vacant area that will fit the given bounds nearest the requested
1146 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001147 *
Romain Guy51afc022009-05-04 18:03:43 -07001148 * @param pixelX The X location at which you want to search for a vacant area.
1149 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001150 * @param spanX Horizontal span of the object.
1151 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001152 * @param result Array in which to place the result, or null (in which case a new array will
1153 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001154 * @return The X, Y cell of a vacant area that can contain this object,
1155 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001156 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001157 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
1158 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, spanX, spanY, result, null);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001159 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001160
Michael Jurka6a1435d2010-09-27 17:35:12 -07001161 /**
1162 * Find a vacant area that will fit the given bounds nearest the requested
1163 * cell location. Uses Euclidean distance to score multiple vacant areas.
1164 *
1165 * @param pixelX The X location at which you want to search for a vacant area.
1166 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001167 * @param minSpanX The minimum horizontal span required
1168 * @param minSpanY The minimum vertical span required
1169 * @param spanX Horizontal span of the object.
1170 * @param spanY Vertical span of the object.
1171 * @param result Array in which to place the result, or null (in which case a new array will
1172 * be allocated)
1173 * @return The X, Y cell of a vacant area that can contain this object,
1174 * nearest the requested location.
1175 */
1176 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1177 int spanY, int[] result, int[] resultSpan) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001178 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true,
Adam Cohend41fbf52012-02-16 23:53:59 -08001179 result, resultSpan);
1180 }
1181
Adam Cohend41fbf52012-02-16 23:53:59 -08001182 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1183 private void lazyInitTempRectStack() {
1184 if (mTempRectStack.isEmpty()) {
1185 for (int i = 0; i < mCountX * mCountY; i++) {
1186 mTempRectStack.push(new Rect());
1187 }
1188 }
1189 }
Adam Cohen482ed822012-03-02 14:15:13 -08001190
Adam Cohend41fbf52012-02-16 23:53:59 -08001191 private void recycleTempRects(Stack<Rect> used) {
1192 while (!used.isEmpty()) {
1193 mTempRectStack.push(used.pop());
1194 }
1195 }
1196
1197 /**
1198 * Find a vacant area that will fit the given bounds nearest the requested
1199 * cell location. Uses Euclidean distance to score multiple vacant areas.
1200 *
1201 * @param pixelX The X location at which you want to search for a vacant area.
1202 * @param pixelY The Y location at which you want to search for a vacant area.
1203 * @param minSpanX The minimum horizontal span required
1204 * @param minSpanY The minimum vertical span required
1205 * @param spanX Horizontal span of the object.
1206 * @param spanY Vertical span of the object.
1207 * @param ignoreOccupied If true, the result can be an occupied cell
1208 * @param result Array in which to place the result, or null (in which case a new array will
1209 * be allocated)
1210 * @return The X, Y cell of a vacant area that can contain this object,
1211 * nearest the requested location.
1212 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001213 private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1214 int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001215 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001216
Adam Cohene3e27a82011-04-15 12:07:39 -07001217 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1218 // to the center of the item, but we are searching based on the top-left cell, so
1219 // we translate the point over to correspond to the top-left.
1220 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1221 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1222
Jeff Sharkey70864282009-04-07 21:08:40 -07001223 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001224 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001225 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001226 final Rect bestRect = new Rect(-1, -1, -1, -1);
1227 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001228
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001229 final int countX = mCountX;
1230 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001231
Adam Cohend41fbf52012-02-16 23:53:59 -08001232 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1233 spanX < minSpanX || spanY < minSpanY) {
1234 return bestXY;
1235 }
1236
1237 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001238 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001239 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1240 int ySize = -1;
1241 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001242 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001243 // First, let's see if this thing fits anywhere
1244 for (int i = 0; i < minSpanX; i++) {
1245 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001246 if (mOccupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001247 continue inner;
1248 }
Michael Jurkac28de512010-08-13 11:27:44 -07001249 }
1250 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001251 xSize = minSpanX;
1252 ySize = minSpanY;
1253
1254 // We know that the item will fit at _some_ acceptable size, now let's see
1255 // how big we can make it. We'll alternate between incrementing x and y spans
1256 // until we hit a limit.
1257 boolean incX = true;
1258 boolean hitMaxX = xSize >= spanX;
1259 boolean hitMaxY = ySize >= spanY;
1260 while (!(hitMaxX && hitMaxY)) {
1261 if (incX && !hitMaxX) {
1262 for (int j = 0; j < ySize; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001263 if (x + xSize > countX -1 || mOccupied[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001264 // We can't move out horizontally
1265 hitMaxX = true;
1266 }
1267 }
1268 if (!hitMaxX) {
1269 xSize++;
1270 }
1271 } else if (!hitMaxY) {
1272 for (int i = 0; i < xSize; i++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001273 if (y + ySize > countY - 1 || mOccupied[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001274 // We can't move out vertically
1275 hitMaxY = true;
1276 }
1277 }
1278 if (!hitMaxY) {
1279 ySize++;
1280 }
1281 }
1282 hitMaxX |= xSize >= spanX;
1283 hitMaxY |= ySize >= spanY;
1284 incX = !incX;
1285 }
1286 incX = true;
1287 hitMaxX = xSize >= spanX;
1288 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001289 }
Winson Chung0be025d2011-05-23 17:45:09 -07001290 final int[] cellXY = mTmpXY;
Adam Cohene3e27a82011-04-15 12:07:39 -07001291 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001292
Adam Cohend41fbf52012-02-16 23:53:59 -08001293 // We verify that the current rect is not a sub-rect of any of our previous
1294 // candidates. In this case, the current rect is disqualified in favour of the
1295 // containing rect.
1296 Rect currentRect = mTempRectStack.pop();
1297 currentRect.set(x, y, x + xSize, y + ySize);
1298 boolean contained = false;
1299 for (Rect r : validRegions) {
1300 if (r.contains(currentRect)) {
1301 contained = true;
1302 break;
1303 }
1304 }
1305 validRegions.push(currentRect);
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001306 double distance = Math.hypot(cellXY[0] - pixelX, cellXY[1] - pixelY);
Adam Cohen482ed822012-03-02 14:15:13 -08001307
Adam Cohend41fbf52012-02-16 23:53:59 -08001308 if ((distance <= bestDistance && !contained) ||
1309 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001310 bestDistance = distance;
1311 bestXY[0] = x;
1312 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001313 if (resultSpan != null) {
1314 resultSpan[0] = xSize;
1315 resultSpan[1] = ySize;
1316 }
1317 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001318 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001319 }
1320 }
1321
Adam Cohenc0dcf592011-06-01 15:30:43 -07001322 // Return -1, -1 if no suitable location found
1323 if (bestDistance == Double.MAX_VALUE) {
1324 bestXY[0] = -1;
1325 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001326 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001327 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001328 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001329 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001330
Adam Cohen482ed822012-03-02 14:15:13 -08001331 /**
1332 * Find a vacant area that will fit the given bounds nearest the requested
1333 * cell location, and will also weigh in a suggested direction vector of the
1334 * desired location. This method computers distance based on unit grid distances,
1335 * not pixel distances.
1336 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001337 * @param cellX The X cell nearest to which you want to search for a vacant area.
1338 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001339 * @param spanX Horizontal span of the object.
1340 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001341 * @param direction The favored direction in which the views should move from x, y
1342 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1343 * matches exactly. Otherwise we find the best matching direction.
1344 * @param occoupied The array which represents which cells in the CellLayout are occupied
1345 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001346 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001347 * @param result Array in which to place the result, or null (in which case a new array will
1348 * be allocated)
1349 * @return The X, Y cell of a vacant area that can contain this object,
1350 * nearest the requested location.
1351 */
1352 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001353 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001354 // Keep track of best-scoring drop area
1355 final int[] bestXY = result != null ? result : new int[2];
1356 float bestDistance = Float.MAX_VALUE;
1357 int bestDirectionScore = Integer.MIN_VALUE;
1358
1359 final int countX = mCountX;
1360 final int countY = mCountY;
1361
1362 for (int y = 0; y < countY - (spanY - 1); y++) {
1363 inner:
1364 for (int x = 0; x < countX - (spanX - 1); x++) {
1365 // First, let's see if this thing fits anywhere
1366 for (int i = 0; i < spanX; i++) {
1367 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001368 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001369 continue inner;
1370 }
1371 }
1372 }
1373
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001374 float distance = (float) Math.hypot(x - cellX, y - cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001375 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001376 computeDirectionVector(x - cellX, y - cellY, curDirection);
1377 // The direction score is just the dot product of the two candidate direction
1378 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001379 int curDirectionScore = direction[0] * curDirection[0] +
1380 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001381 boolean exactDirectionOnly = false;
1382 boolean directionMatches = direction[0] == curDirection[0] &&
1383 direction[0] == curDirection[0];
1384 if ((directionMatches || !exactDirectionOnly) &&
1385 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001386 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1387 bestDistance = distance;
1388 bestDirectionScore = curDirectionScore;
1389 bestXY[0] = x;
1390 bestXY[1] = y;
1391 }
1392 }
1393 }
1394
1395 // Return -1, -1 if no suitable location found
1396 if (bestDistance == Float.MAX_VALUE) {
1397 bestXY[0] = -1;
1398 bestXY[1] = -1;
1399 }
1400 return bestXY;
1401 }
1402
1403 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001404 int[] direction, ItemConfiguration currentState) {
1405 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001406 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001407 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001408 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1409
Adam Cohen8baab352012-03-20 17:39:21 -07001410 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001411
1412 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001413 c.x = mTempLocation[0];
1414 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001415 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001416 }
Adam Cohen8baab352012-03-20 17:39:21 -07001417 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001418 return success;
1419 }
1420
Adam Cohenf3900c22012-11-16 18:28:11 -08001421 /**
1422 * This helper class defines a cluster of views. It helps with defining complex edges
1423 * of the cluster and determining how those edges interact with other views. The edges
1424 * essentially define a fine-grained boundary around the cluster of views -- like a more
1425 * precise version of a bounding box.
1426 */
1427 private class ViewCluster {
1428 final static int LEFT = 0;
1429 final static int TOP = 1;
1430 final static int RIGHT = 2;
1431 final static int BOTTOM = 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001432
Adam Cohenf3900c22012-11-16 18:28:11 -08001433 ArrayList<View> views;
1434 ItemConfiguration config;
1435 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001436
Adam Cohenf3900c22012-11-16 18:28:11 -08001437 int[] leftEdge = new int[mCountY];
1438 int[] rightEdge = new int[mCountY];
1439 int[] topEdge = new int[mCountX];
1440 int[] bottomEdge = new int[mCountX];
1441 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1442
1443 @SuppressWarnings("unchecked")
1444 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1445 this.views = (ArrayList<View>) views.clone();
1446 this.config = config;
1447 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001448 }
1449
Adam Cohenf3900c22012-11-16 18:28:11 -08001450 void resetEdges() {
1451 for (int i = 0; i < mCountX; i++) {
1452 topEdge[i] = -1;
1453 bottomEdge[i] = -1;
1454 }
1455 for (int i = 0; i < mCountY; i++) {
1456 leftEdge[i] = -1;
1457 rightEdge[i] = -1;
1458 }
1459 leftEdgeDirty = true;
1460 rightEdgeDirty = true;
1461 bottomEdgeDirty = true;
1462 topEdgeDirty = true;
1463 boundingRectDirty = true;
1464 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001465
Adam Cohenf3900c22012-11-16 18:28:11 -08001466 void computeEdge(int which, int[] edge) {
1467 int count = views.size();
1468 for (int i = 0; i < count; i++) {
1469 CellAndSpan cs = config.map.get(views.get(i));
1470 switch (which) {
1471 case LEFT:
1472 int left = cs.x;
1473 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1474 if (left < edge[j] || edge[j] < 0) {
1475 edge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001476 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001477 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001478 break;
1479 case RIGHT:
1480 int right = cs.x + cs.spanX;
1481 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1482 if (right > edge[j]) {
1483 edge[j] = right;
1484 }
1485 }
1486 break;
1487 case TOP:
1488 int top = cs.y;
1489 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1490 if (top < edge[j] || edge[j] < 0) {
1491 edge[j] = top;
1492 }
1493 }
1494 break;
1495 case BOTTOM:
1496 int bottom = cs.y + cs.spanY;
1497 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1498 if (bottom > edge[j]) {
1499 edge[j] = bottom;
1500 }
1501 }
1502 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001503 }
1504 }
1505 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001506
1507 boolean isViewTouchingEdge(View v, int whichEdge) {
1508 CellAndSpan cs = config.map.get(v);
1509
1510 int[] edge = getEdge(whichEdge);
1511
1512 switch (whichEdge) {
1513 case LEFT:
1514 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1515 if (edge[i] == cs.x + cs.spanX) {
1516 return true;
1517 }
1518 }
1519 break;
1520 case RIGHT:
1521 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1522 if (edge[i] == cs.x) {
1523 return true;
1524 }
1525 }
1526 break;
1527 case TOP:
1528 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1529 if (edge[i] == cs.y + cs.spanY) {
1530 return true;
1531 }
1532 }
1533 break;
1534 case BOTTOM:
1535 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1536 if (edge[i] == cs.y) {
1537 return true;
1538 }
1539 }
1540 break;
1541 }
1542 return false;
1543 }
1544
1545 void shift(int whichEdge, int delta) {
1546 for (View v: views) {
1547 CellAndSpan c = config.map.get(v);
1548 switch (whichEdge) {
1549 case LEFT:
1550 c.x -= delta;
1551 break;
1552 case RIGHT:
1553 c.x += delta;
1554 break;
1555 case TOP:
1556 c.y -= delta;
1557 break;
1558 case BOTTOM:
1559 default:
1560 c.y += delta;
1561 break;
1562 }
1563 }
1564 resetEdges();
1565 }
1566
1567 public void addView(View v) {
1568 views.add(v);
1569 resetEdges();
1570 }
1571
1572 public Rect getBoundingRect() {
1573 if (boundingRectDirty) {
1574 boolean first = true;
1575 for (View v: views) {
1576 CellAndSpan c = config.map.get(v);
1577 if (first) {
1578 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1579 first = false;
1580 } else {
1581 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1582 }
1583 }
1584 }
1585 return boundingRect;
1586 }
1587
1588 public int[] getEdge(int which) {
1589 switch (which) {
1590 case LEFT:
1591 return getLeftEdge();
1592 case RIGHT:
1593 return getRightEdge();
1594 case TOP:
1595 return getTopEdge();
1596 case BOTTOM:
1597 default:
1598 return getBottomEdge();
1599 }
1600 }
1601
1602 public int[] getLeftEdge() {
1603 if (leftEdgeDirty) {
1604 computeEdge(LEFT, leftEdge);
1605 }
1606 return leftEdge;
1607 }
1608
1609 public int[] getRightEdge() {
1610 if (rightEdgeDirty) {
1611 computeEdge(RIGHT, rightEdge);
1612 }
1613 return rightEdge;
1614 }
1615
1616 public int[] getTopEdge() {
1617 if (topEdgeDirty) {
1618 computeEdge(TOP, topEdge);
1619 }
1620 return topEdge;
1621 }
1622
1623 public int[] getBottomEdge() {
1624 if (bottomEdgeDirty) {
1625 computeEdge(BOTTOM, bottomEdge);
1626 }
1627 return bottomEdge;
1628 }
1629
1630 PositionComparator comparator = new PositionComparator();
1631 class PositionComparator implements Comparator<View> {
1632 int whichEdge = 0;
1633 public int compare(View left, View right) {
1634 CellAndSpan l = config.map.get(left);
1635 CellAndSpan r = config.map.get(right);
1636 switch (whichEdge) {
1637 case LEFT:
1638 return (r.x + r.spanX) - (l.x + l.spanX);
1639 case RIGHT:
1640 return l.x - r.x;
1641 case TOP:
1642 return (r.y + r.spanY) - (l.y + l.spanY);
1643 case BOTTOM:
1644 default:
1645 return l.y - r.y;
1646 }
1647 }
1648 }
1649
1650 public void sortConfigurationForEdgePush(int edge) {
1651 comparator.whichEdge = edge;
1652 Collections.sort(config.sortedViews, comparator);
1653 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001654 }
1655
Adam Cohenf3900c22012-11-16 18:28:11 -08001656 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1657 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001658
Adam Cohenf3900c22012-11-16 18:28:11 -08001659 ViewCluster cluster = new ViewCluster(views, currentState);
1660 Rect clusterRect = cluster.getBoundingRect();
1661 int whichEdge;
1662 int pushDistance;
1663 boolean fail = false;
1664
1665 // Determine the edge of the cluster that will be leading the push and how far
1666 // the cluster must be shifted.
1667 if (direction[0] < 0) {
1668 whichEdge = ViewCluster.LEFT;
1669 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001670 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001671 whichEdge = ViewCluster.RIGHT;
1672 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1673 } else if (direction[1] < 0) {
1674 whichEdge = ViewCluster.TOP;
1675 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1676 } else {
1677 whichEdge = ViewCluster.BOTTOM;
1678 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001679 }
1680
Adam Cohenf3900c22012-11-16 18:28:11 -08001681 // Break early for invalid push distance.
1682 if (pushDistance <= 0) {
1683 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001684 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001685
1686 // Mark the occupied state as false for the group of views we want to move.
1687 for (View v: views) {
1688 CellAndSpan c = currentState.map.get(v);
1689 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1690 }
1691
1692 // We save the current configuration -- if we fail to find a solution we will revert
1693 // to the initial state. The process of finding a solution modifies the configuration
1694 // in place, hence the need for revert in the failure case.
1695 currentState.save();
1696
1697 // The pushing algorithm is simplified by considering the views in the order in which
1698 // they would be pushed by the cluster. For example, if the cluster is leading with its
1699 // left edge, we consider sort the views by their right edge, from right to left.
1700 cluster.sortConfigurationForEdgePush(whichEdge);
1701
1702 while (pushDistance > 0 && !fail) {
1703 for (View v: currentState.sortedViews) {
1704 // For each view that isn't in the cluster, we see if the leading edge of the
1705 // cluster is contacting the edge of that view. If so, we add that view to the
1706 // cluster.
1707 if (!cluster.views.contains(v) && v != dragView) {
1708 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1709 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1710 if (!lp.canReorder) {
1711 // The push solution includes the all apps button, this is not viable.
1712 fail = true;
1713 break;
1714 }
1715 cluster.addView(v);
1716 CellAndSpan c = currentState.map.get(v);
1717
1718 // Adding view to cluster, mark it as not occupied.
1719 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1720 }
1721 }
1722 }
1723 pushDistance--;
1724
1725 // The cluster has been completed, now we move the whole thing over in the appropriate
1726 // direction.
1727 cluster.shift(whichEdge, 1);
1728 }
1729
1730 boolean foundSolution = false;
1731 clusterRect = cluster.getBoundingRect();
1732
1733 // Due to the nature of the algorithm, the only check required to verify a valid solution
1734 // is to ensure that completed shifted cluster lies completely within the cell layout.
1735 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1736 clusterRect.bottom <= mCountY) {
1737 foundSolution = true;
1738 } else {
1739 currentState.restore();
1740 }
1741
1742 // In either case, we set the occupied array as marked for the location of the views
1743 for (View v: cluster.views) {
1744 CellAndSpan c = currentState.map.get(v);
1745 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1746 }
1747
1748 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001749 }
1750
Adam Cohen482ed822012-03-02 14:15:13 -08001751 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001752 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001753 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001754
Adam Cohen8baab352012-03-20 17:39:21 -07001755 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001756 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001757 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001758 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001759 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001760 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001761 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001762 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001763 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001764 }
1765 }
Adam Cohen8baab352012-03-20 17:39:21 -07001766
Adam Cohen8baab352012-03-20 17:39:21 -07001767 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001768 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001769 CellAndSpan c = currentState.map.get(v);
1770 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1771 }
1772
Adam Cohen47a876d2012-03-19 13:21:41 -07001773 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1774 int top = boundingRect.top;
1775 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001776 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001777 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001778 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001779 CellAndSpan c = currentState.map.get(v);
1780 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001781 }
1782
Adam Cohen482ed822012-03-02 14:15:13 -08001783 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1784
Adam Cohenf3900c22012-11-16 18:28:11 -08001785 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1786 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001787
Adam Cohen8baab352012-03-20 17:39:21 -07001788 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001789 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001790 int deltaX = mTempLocation[0] - boundingRect.left;
1791 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001792 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001793 CellAndSpan c = currentState.map.get(v);
1794 c.x += deltaX;
1795 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001796 }
1797 success = true;
1798 }
Adam Cohen8baab352012-03-20 17:39:21 -07001799
1800 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001801 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001802 CellAndSpan c = currentState.map.get(v);
1803 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001804 }
1805 return success;
1806 }
1807
1808 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1809 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1810 }
1811
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001812 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1813 // to push items in each of the cardinal directions, in an order based on the direction vector
1814 // passed.
1815 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1816 int[] direction, View ignoreView, ItemConfiguration solution) {
1817 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001818 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001819 // separately in each of the components.
1820 int temp = direction[1];
1821 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001822
1823 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001824 ignoreView, solution)) {
1825 return true;
1826 }
1827 direction[1] = temp;
1828 temp = direction[0];
1829 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001830
1831 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001832 ignoreView, solution)) {
1833 return true;
1834 }
1835 // Revert the direction
1836 direction[0] = temp;
1837
1838 // Now we try pushing in each component of the opposite direction
1839 direction[0] *= -1;
1840 direction[1] *= -1;
1841 temp = direction[1];
1842 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001843 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001844 ignoreView, solution)) {
1845 return true;
1846 }
1847
1848 direction[1] = temp;
1849 temp = direction[0];
1850 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001851 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001852 ignoreView, solution)) {
1853 return true;
1854 }
1855 // revert the direction
1856 direction[0] = temp;
1857 direction[0] *= -1;
1858 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001859
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001860 } else {
1861 // If the direction vector has a single non-zero component, we push first in the
1862 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08001863 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001864 ignoreView, solution)) {
1865 return true;
1866 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001867 // Then we try the opposite direction
1868 direction[0] *= -1;
1869 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001870 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001871 ignoreView, solution)) {
1872 return true;
1873 }
1874 // Switch the direction back
1875 direction[0] *= -1;
1876 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001877
1878 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001879 // to find a solution by pushing along the perpendicular axis.
1880
1881 // Swap the components
1882 int temp = direction[1];
1883 direction[1] = direction[0];
1884 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08001885 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001886 ignoreView, solution)) {
1887 return true;
1888 }
1889
1890 // Then we try the opposite direction
1891 direction[0] *= -1;
1892 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001893 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001894 ignoreView, solution)) {
1895 return true;
1896 }
1897 // Switch the direction back
1898 direction[0] *= -1;
1899 direction[1] *= -1;
1900
1901 // Swap the components back
1902 temp = direction[1];
1903 direction[1] = direction[0];
1904 direction[0] = temp;
1905 }
1906 return false;
1907 }
1908
Adam Cohen482ed822012-03-02 14:15:13 -08001909 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001910 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001911 // Return early if get invalid cell positions
1912 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001913
Adam Cohen8baab352012-03-20 17:39:21 -07001914 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001915 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001916
Adam Cohen8baab352012-03-20 17:39:21 -07001917 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001918 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001919 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001920 if (c != null) {
1921 c.x = cellX;
1922 c.y = cellY;
1923 }
Adam Cohen482ed822012-03-02 14:15:13 -08001924 }
Adam Cohen482ed822012-03-02 14:15:13 -08001925 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1926 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001927 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001928 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001929 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001930 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001931 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001932 if (Rect.intersects(r0, r1)) {
1933 if (!lp.canReorder) {
1934 return false;
1935 }
1936 mIntersectingViews.add(child);
1937 }
1938 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001939
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001940 solution.intersectingViews = new ArrayList<View>(mIntersectingViews);
1941
Winson Chung5f8afe62013-08-12 16:19:28 -07001942 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001943 // we try to find a solution such that no displaced item travels through another item
1944 // without also displacing that item.
1945 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001946 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001947 return true;
1948 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001949
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001950 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08001951 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001952 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001953 return true;
1954 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001955
Adam Cohen482ed822012-03-02 14:15:13 -08001956 // Ok, they couldn't move as a block, let's move them individually
1957 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001958 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001959 return false;
1960 }
1961 }
1962 return true;
1963 }
1964
1965 /*
1966 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1967 * the provided point and the provided cell
1968 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001969 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001970 double angle = Math.atan(((float) deltaY) / deltaX);
1971
1972 result[0] = 0;
1973 result[1] = 0;
1974 if (Math.abs(Math.cos(angle)) > 0.5f) {
1975 result[0] = (int) Math.signum(deltaX);
1976 }
1977 if (Math.abs(Math.sin(angle)) > 0.5f) {
1978 result[1] = (int) Math.signum(deltaY);
1979 }
1980 }
1981
Adam Cohen8baab352012-03-20 17:39:21 -07001982 private void copyOccupiedArray(boolean[][] occupied) {
1983 for (int i = 0; i < mCountX; i++) {
1984 for (int j = 0; j < mCountY; j++) {
1985 occupied[i][j] = mOccupied[i][j];
1986 }
1987 }
1988 }
1989
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001990 private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001991 int spanX, int spanY, int[] direction, View dragView, boolean decX,
1992 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001993 // Copy the current state into the solution. This solution will be manipulated as necessary.
1994 copyCurrentStateToSolution(solution, false);
1995 // Copy the current occupied array into the temporary occupied array. This array will be
1996 // manipulated as necessary to find a solution.
1997 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001998
1999 // We find the nearest cell into which we would place the dragged item, assuming there's
2000 // nothing in its way.
2001 int result[] = new int[2];
2002 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2003
2004 boolean success = false;
2005 // First we try the exact nearest position of the item being dragged,
2006 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07002007 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
2008 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002009
2010 if (!success) {
2011 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
2012 // x, then 1 in y etc.
2013 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002014 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
2015 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002016 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002017 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
2018 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002019 }
2020 solution.isSolution = false;
2021 } else {
2022 solution.isSolution = true;
2023 solution.dragViewX = result[0];
2024 solution.dragViewY = result[1];
2025 solution.dragViewSpanX = spanX;
2026 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002027 }
2028 return solution;
2029 }
2030
2031 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002032 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002033 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002034 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002035 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002036 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08002037 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07002038 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002039 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07002040 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002041 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002042 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08002043 }
2044 }
2045
2046 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
2047 for (int i = 0; i < mCountX; i++) {
2048 for (int j = 0; j < mCountY; j++) {
2049 mTmpOccupied[i][j] = false;
2050 }
2051 }
2052
Michael Jurkaa52570f2012-03-20 03:18:20 -07002053 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002054 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002055 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002056 if (child == dragView) continue;
2057 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002058 CellAndSpan c = solution.map.get(child);
2059 if (c != null) {
2060 lp.tmpCellX = c.x;
2061 lp.tmpCellY = c.y;
2062 lp.cellHSpan = c.spanX;
2063 lp.cellVSpan = c.spanY;
2064 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002065 }
2066 }
2067 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2068 solution.dragViewSpanY, mTmpOccupied, true);
2069 }
2070
2071 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2072 commitDragView) {
2073
2074 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2075 for (int i = 0; i < mCountX; i++) {
2076 for (int j = 0; j < mCountY; j++) {
2077 occupied[i][j] = false;
2078 }
2079 }
2080
Michael Jurkaa52570f2012-03-20 03:18:20 -07002081 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002082 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002083 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002084 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002085 CellAndSpan c = solution.map.get(child);
2086 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002087 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2088 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002089 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002090 }
2091 }
2092 if (commitDragView) {
2093 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2094 solution.dragViewSpanY, occupied, true);
2095 }
2096 }
2097
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002098
2099 // This method starts or changes the reorder preview animations
2100 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
2101 View dragView, int delay, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07002102 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002103 for (int i = 0; i < childCount; i++) {
2104 View child = mShortcutsAndWidgets.getChildAt(i);
2105 if (child == dragView) continue;
2106 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002107 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
2108 != null && !solution.intersectingViews.contains(child);
2109
Adam Cohen19f37922012-03-21 11:59:11 -07002110 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002111 if (c != null && !skip) {
2112 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
2113 lp.cellY, c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002114 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002115 }
2116 }
2117 }
2118
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002119 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07002120 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002121 class ReorderPreviewAnimation {
Adam Cohen19f37922012-03-21 11:59:11 -07002122 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002123 float finalDeltaX;
2124 float finalDeltaY;
2125 float initDeltaX;
2126 float initDeltaY;
2127 float finalScale;
2128 float initScale;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002129 int mode;
2130 boolean repeating = false;
2131 private static final int PREVIEW_DURATION = 300;
2132 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
2133
2134 public static final int MODE_HINT = 0;
2135 public static final int MODE_PREVIEW = 1;
2136
Adam Cohene7587d22012-05-24 18:50:02 -07002137 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002138
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002139 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
2140 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07002141 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2142 final int x0 = mTmpPoint[0];
2143 final int y0 = mTmpPoint[1];
2144 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2145 final int x1 = mTmpPoint[0];
2146 final int y1 = mTmpPoint[1];
2147 final int dX = x1 - x0;
2148 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002149 finalDeltaX = 0;
2150 finalDeltaY = 0;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002151 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07002152 if (dX == dY && dX == 0) {
2153 } else {
2154 if (dY == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002155 finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002156 } else if (dX == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002157 finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002158 } else {
2159 double angle = Math.atan( (float) (dY) / dX);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002160 finalDeltaX = (int) (- dir * Math.signum(dX) *
2161 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
2162 finalDeltaY = (int) (- dir * Math.signum(dY) *
2163 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002164 }
2165 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002166 this.mode = mode;
Adam Cohend024f982012-05-23 18:26:45 -07002167 initDeltaX = child.getTranslationX();
2168 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002169 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002170 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002171 this.child = child;
2172 }
2173
Adam Cohend024f982012-05-23 18:26:45 -07002174 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002175 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002176 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002177 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002178 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002179 if (finalDeltaX == 0 && finalDeltaY == 0) {
2180 completeAnimationImmediately();
2181 return;
2182 }
Adam Cohen19f37922012-03-21 11:59:11 -07002183 }
Adam Cohend024f982012-05-23 18:26:45 -07002184 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002185 return;
2186 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002187 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002188 a = va;
Adam Cohen19f37922012-03-21 11:59:11 -07002189 va.setRepeatMode(ValueAnimator.REVERSE);
2190 va.setRepeatCount(ValueAnimator.INFINITE);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002191 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002192 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002193 va.addUpdateListener(new AnimatorUpdateListener() {
2194 @Override
2195 public void onAnimationUpdate(ValueAnimator animation) {
2196 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002197 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2198 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2199 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002200 child.setTranslationX(x);
2201 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002202 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002203 child.setScaleX(s);
2204 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002205 }
2206 });
2207 va.addListener(new AnimatorListenerAdapter() {
2208 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002209 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002210 initDeltaX = 0;
2211 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002212 initScale = getChildrenScale();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002213 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002214 }
2215 });
Adam Cohen19f37922012-03-21 11:59:11 -07002216 mShakeAnimators.put(child, this);
2217 va.start();
2218 }
2219
Adam Cohend024f982012-05-23 18:26:45 -07002220 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002221 if (a != null) {
2222 a.cancel();
2223 }
Adam Cohen19f37922012-03-21 11:59:11 -07002224 }
Adam Cohene7587d22012-05-24 18:50:02 -07002225
Adam Cohen091440a2015-03-18 14:16:05 -07002226 @Thunk void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002227 if (a != null) {
2228 a.cancel();
2229 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002230
Michael Jurka2ecf9952012-06-18 12:52:28 -07002231 AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002232 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002233 s.playTogether(
Adam Cohen307fe232012-08-16 17:55:58 -07002234 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
2235 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
Michael Jurka2ecf9952012-06-18 12:52:28 -07002236 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2237 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002238 );
2239 s.setDuration(REORDER_ANIMATION_DURATION);
2240 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2241 s.start();
2242 }
Adam Cohen19f37922012-03-21 11:59:11 -07002243 }
2244
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002245 private void completeAndClearReorderPreviewAnimations() {
2246 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002247 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002248 }
2249 mShakeAnimators.clear();
2250 }
2251
Adam Cohen482ed822012-03-02 14:15:13 -08002252 private void commitTempPlacement() {
2253 for (int i = 0; i < mCountX; i++) {
2254 for (int j = 0; j < mCountY; j++) {
2255 mOccupied[i][j] = mTmpOccupied[i][j];
2256 }
2257 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002258 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002259 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002260 View child = mShortcutsAndWidgets.getChildAt(i);
2261 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2262 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002263 // We do a null check here because the item info can be null in the case of the
2264 // AllApps button in the hotseat.
2265 if (info != null) {
Adam Cohen487f7dd2012-06-28 18:12:10 -07002266 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
2267 info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
2268 info.requiresDbUpdate = true;
2269 }
Adam Cohen2acce882012-03-28 19:03:19 -07002270 info.cellX = lp.cellX = lp.tmpCellX;
2271 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002272 info.spanX = lp.cellHSpan;
2273 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002274 }
Adam Cohen482ed822012-03-02 14:15:13 -08002275 }
Adam Cohen2acce882012-03-28 19:03:19 -07002276 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002277 }
2278
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002279 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002280 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002281 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002282 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002283 lp.useTmpCoords = useTempCoords;
2284 }
2285 }
2286
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002287 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002288 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2289 int[] result = new int[2];
2290 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002291 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08002292 resultSpan);
2293 if (result[0] >= 0 && result[1] >= 0) {
2294 copyCurrentStateToSolution(solution, false);
2295 solution.dragViewX = result[0];
2296 solution.dragViewY = result[1];
2297 solution.dragViewSpanX = resultSpan[0];
2298 solution.dragViewSpanY = resultSpan[1];
2299 solution.isSolution = true;
2300 } else {
2301 solution.isSolution = false;
2302 }
2303 return solution;
2304 }
2305
2306 public void prepareChildForDrag(View child) {
2307 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002308 }
2309
Adam Cohen19f37922012-03-21 11:59:11 -07002310 /* This seems like it should be obvious and straight-forward, but when the direction vector
2311 needs to match with the notion of the dragView pushing other views, we have to employ
2312 a slightly more subtle notion of the direction vector. The question is what two points is
2313 the vector between? The center of the dragView and its desired destination? Not quite, as
2314 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2315 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2316 or right, which helps make pushing feel right.
2317 */
2318 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2319 int spanY, View dragView, int[] resultDirection) {
2320 int[] targetDestination = new int[2];
2321
2322 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2323 Rect dragRect = new Rect();
2324 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2325 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2326
2327 Rect dropRegionRect = new Rect();
2328 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2329 dragView, dropRegionRect, mIntersectingViews);
2330
2331 int dropRegionSpanX = dropRegionRect.width();
2332 int dropRegionSpanY = dropRegionRect.height();
2333
2334 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2335 dropRegionRect.height(), dropRegionRect);
2336
2337 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2338 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2339
2340 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2341 deltaX = 0;
2342 }
2343 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2344 deltaY = 0;
2345 }
2346
2347 if (deltaX == 0 && deltaY == 0) {
2348 // No idea what to do, give a random direction.
2349 resultDirection[0] = 1;
2350 resultDirection[1] = 0;
2351 } else {
2352 computeDirectionVector(deltaX, deltaY, resultDirection);
2353 }
2354 }
2355
2356 // For a given cell and span, fetch the set of views intersecting the region.
2357 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2358 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2359 if (boundingRect != null) {
2360 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2361 }
2362 intersectingViews.clear();
2363 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2364 Rect r1 = new Rect();
2365 final int count = mShortcutsAndWidgets.getChildCount();
2366 for (int i = 0; i < count; i++) {
2367 View child = mShortcutsAndWidgets.getChildAt(i);
2368 if (child == dragView) continue;
2369 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2370 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2371 if (Rect.intersects(r0, r1)) {
2372 mIntersectingViews.add(child);
2373 if (boundingRect != null) {
2374 boundingRect.union(r1);
2375 }
2376 }
2377 }
2378 }
2379
2380 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2381 View dragView, int[] result) {
2382 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2383 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2384 mIntersectingViews);
2385 return !mIntersectingViews.isEmpty();
2386 }
2387
2388 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002389 completeAndClearReorderPreviewAnimations();
2390 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2391 final int count = mShortcutsAndWidgets.getChildCount();
2392 for (int i = 0; i < count; i++) {
2393 View child = mShortcutsAndWidgets.getChildAt(i);
2394 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2395 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2396 lp.tmpCellX = lp.cellX;
2397 lp.tmpCellY = lp.cellY;
2398 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2399 0, false, false);
2400 }
Adam Cohen19f37922012-03-21 11:59:11 -07002401 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002402 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002403 }
Adam Cohen19f37922012-03-21 11:59:11 -07002404 }
2405
Adam Cohenbebf0422012-04-11 18:06:28 -07002406 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2407 View dragView, int[] direction, boolean commit) {
2408 int[] pixelXY = new int[2];
2409 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2410
2411 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002412 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002413 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2414
2415 setUseTempCoords(true);
2416 if (swapSolution != null && swapSolution.isSolution) {
2417 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2418 // committing anything or animating anything as we just want to determine if a solution
2419 // exists
2420 copySolutionToTempState(swapSolution, dragView);
2421 setItemPlacementDirty(true);
2422 animateItemsToSolution(swapSolution, dragView, commit);
2423
2424 if (commit) {
2425 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002426 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002427 setItemPlacementDirty(false);
2428 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002429 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2430 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002431 }
2432 mShortcutsAndWidgets.requestLayout();
2433 }
2434 return swapSolution.isSolution;
2435 }
2436
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002437 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002438 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002439 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002440 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002441
2442 if (resultSpan == null) {
2443 resultSpan = new int[2];
2444 }
2445
Adam Cohen19f37922012-03-21 11:59:11 -07002446 // When we are checking drop validity or actually dropping, we don't recompute the
2447 // direction vector, since we want the solution to match the preview, and it's possible
2448 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002449 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2450 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002451 mDirectionVector[0] = mPreviousReorderDirection[0];
2452 mDirectionVector[1] = mPreviousReorderDirection[1];
2453 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002454 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2455 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2456 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002457 }
2458 } else {
2459 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2460 mPreviousReorderDirection[0] = mDirectionVector[0];
2461 mPreviousReorderDirection[1] = mDirectionVector[1];
2462 }
2463
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002464 // Find a solution involving pushing / displacing any items in the way
2465 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002466 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2467
2468 // We attempt the approach which doesn't shuffle views at all
2469 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2470 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2471
2472 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002473
2474 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2475 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002476 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2477 finalSolution = swapSolution;
2478 } else if (noShuffleSolution.isSolution) {
2479 finalSolution = noShuffleSolution;
2480 }
2481
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002482 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002483 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002484 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2485 ReorderPreviewAnimation.MODE_HINT);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002486 result[0] = finalSolution.dragViewX;
2487 result[1] = finalSolution.dragViewY;
2488 resultSpan[0] = finalSolution.dragViewSpanX;
2489 resultSpan[1] = finalSolution.dragViewSpanY;
2490 } else {
2491 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2492 }
2493 return result;
2494 }
2495
Adam Cohen482ed822012-03-02 14:15:13 -08002496 boolean foundSolution = true;
2497 if (!DESTRUCTIVE_REORDER) {
2498 setUseTempCoords(true);
2499 }
2500
2501 if (finalSolution != null) {
2502 result[0] = finalSolution.dragViewX;
2503 result[1] = finalSolution.dragViewY;
2504 resultSpan[0] = finalSolution.dragViewSpanX;
2505 resultSpan[1] = finalSolution.dragViewSpanY;
2506
2507 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2508 // committing anything or animating anything as we just want to determine if a solution
2509 // exists
2510 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2511 if (!DESTRUCTIVE_REORDER) {
2512 copySolutionToTempState(finalSolution, dragView);
2513 }
2514 setItemPlacementDirty(true);
2515 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2516
Adam Cohen19f37922012-03-21 11:59:11 -07002517 if (!DESTRUCTIVE_REORDER &&
2518 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002519 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002520 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002521 setItemPlacementDirty(false);
2522 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002523 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2524 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002525 }
2526 }
2527 } else {
2528 foundSolution = false;
2529 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2530 }
2531
2532 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2533 setUseTempCoords(false);
2534 }
Adam Cohen482ed822012-03-02 14:15:13 -08002535
Michael Jurkaa52570f2012-03-20 03:18:20 -07002536 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002537 return result;
2538 }
2539
Adam Cohen19f37922012-03-21 11:59:11 -07002540 void setItemPlacementDirty(boolean dirty) {
2541 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002542 }
Adam Cohen19f37922012-03-21 11:59:11 -07002543 boolean isItemPlacementDirty() {
2544 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002545 }
2546
Adam Cohen091440a2015-03-18 14:16:05 -07002547 @Thunk class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002548 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002549 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2550 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002551 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002552 boolean isSolution = false;
2553 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2554
Adam Cohenf3900c22012-11-16 18:28:11 -08002555 void save() {
2556 // Copy current state into savedMap
2557 for (View v: map.keySet()) {
2558 map.get(v).copy(savedMap.get(v));
2559 }
2560 }
2561
2562 void restore() {
2563 // Restore current state from savedMap
2564 for (View v: savedMap.keySet()) {
2565 savedMap.get(v).copy(map.get(v));
2566 }
2567 }
2568
2569 void add(View v, CellAndSpan cs) {
2570 map.put(v, cs);
2571 savedMap.put(v, new CellAndSpan());
2572 sortedViews.add(v);
2573 }
2574
Adam Cohen482ed822012-03-02 14:15:13 -08002575 int area() {
2576 return dragViewSpanX * dragViewSpanY;
2577 }
Adam Cohen8baab352012-03-20 17:39:21 -07002578 }
2579
2580 private class CellAndSpan {
2581 int x, y;
2582 int spanX, spanY;
2583
Adam Cohenf3900c22012-11-16 18:28:11 -08002584 public CellAndSpan() {
2585 }
2586
2587 public void copy(CellAndSpan copy) {
2588 copy.x = x;
2589 copy.y = y;
2590 copy.spanX = spanX;
2591 copy.spanY = spanY;
2592 }
2593
Adam Cohen8baab352012-03-20 17:39:21 -07002594 public CellAndSpan(int x, int y, int spanX, int spanY) {
2595 this.x = x;
2596 this.y = y;
2597 this.spanX = spanX;
2598 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002599 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002600
2601 public String toString() {
2602 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2603 }
2604
Adam Cohen482ed822012-03-02 14:15:13 -08002605 }
2606
Adam Cohendf035382011-04-11 17:22:04 -07002607 /**
Adam Cohendf035382011-04-11 17:22:04 -07002608 * Find a starting cell position that will fit the given bounds nearest the requested
2609 * cell location. Uses Euclidean distance to score multiple vacant areas.
2610 *
2611 * @param pixelX The X location at which you want to search for a vacant area.
2612 * @param pixelY The Y location at which you want to search for a vacant area.
2613 * @param spanX Horizontal span of the object.
2614 * @param spanY Vertical span of the object.
2615 * @param ignoreView Considers space occupied by this view as unoccupied
2616 * @param result Previously returned value to possibly recycle.
2617 * @return The X, Y cell of a vacant area that can contain this object,
2618 * nearest the requested location.
2619 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002620 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2621 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002622 }
2623
Michael Jurka0280c3b2010-09-17 15:00:07 -07002624 boolean existsEmptyCell() {
2625 return findCellForSpan(null, 1, 1);
2626 }
2627
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002628 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002629 * Finds the upper-left coordinate of the first rectangle in the grid that can
2630 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2631 * then this method will only return coordinates for rectangles that contain the cell
2632 * (intersectX, intersectY)
2633 *
2634 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2635 * can be found.
2636 * @param spanX The horizontal span of the cell we want to find.
2637 * @param spanY The vertical span of the cell we want to find.
2638 *
2639 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002640 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002641 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Michael Jurka28750fb2010-09-24 17:43:49 -07002642 boolean foundCell = false;
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002643 final int endX = mCountX - (spanX - 1);
2644 final int endY = mCountY - (spanY - 1);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002645
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002646 for (int y = 0; y < endY && !foundCell; y++) {
2647 inner:
2648 for (int x = 0; x < endX; x++) {
2649 for (int i = 0; i < spanX; i++) {
2650 for (int j = 0; j < spanY; j++) {
2651 if (mOccupied[x + i][y + j]) {
2652 // small optimization: we can skip to after the column we just found
2653 // an occupied cell
2654 x += i;
2655 continue inner;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002656 }
2657 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002658 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002659 if (cellXY != null) {
2660 cellXY[0] = x;
2661 cellXY[1] = y;
2662 }
2663 foundCell = true;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002664 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002665 }
2666 }
2667
Michael Jurka28750fb2010-09-24 17:43:49 -07002668 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002669 }
2670
2671 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002672 * A drag event has begun over this layout.
2673 * It may have begun over this layout (in which case onDragChild is called first),
2674 * or it may have begun on another layout.
2675 */
2676 void onDragEnter() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002677 mDragEnforcer.onDragEnter();
Winson Chungc07918d2011-07-01 15:35:26 -07002678 mDragging = true;
2679 }
2680
2681 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002682 * Called when drag has left this CellLayout or has been completed (successfully or not)
2683 */
2684 void onDragExit() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002685 mDragEnforcer.onDragExit();
Joe Onorato4be866d2010-10-10 11:26:02 -07002686 // This can actually be called when we aren't in a drag, e.g. when adding a new
2687 // item to this layout via the customize drawer.
2688 // Guard against that case.
2689 if (mDragging) {
2690 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002691 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002692
2693 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002694 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002695 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2696 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002697 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002698 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002699 }
2700
2701 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002702 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002703 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002704 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002705 *
2706 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002707 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002708 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002709 if (child != null) {
2710 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002711 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002712 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002713 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002714 }
2715
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002716 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002717 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002718 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002719 * @param cellX X coordinate of upper left corner expressed as a cell position
2720 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002721 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002722 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002723 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002724 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002725 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002726 final int cellWidth = mCellWidth;
2727 final int cellHeight = mCellHeight;
2728 final int widthGap = mWidthGap;
2729 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002730
Winson Chung4b825dcd2011-06-19 12:41:22 -07002731 final int hStartPadding = getPaddingLeft();
2732 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002733
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002734 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2735 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2736
2737 int x = hStartPadding + cellX * (cellWidth + widthGap);
2738 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002739
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002740 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002741 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002742
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002743 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002744 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002745 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002746 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002747 * @param width Width in pixels
2748 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002749 * @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 -08002750 */
Winson Chung66700732013-08-20 16:56:15 -07002751 public static int[] rectToCell(int width, int height, int[] result) {
Winson Chung5f8afe62013-08-12 16:19:28 -07002752 LauncherAppState app = LauncherAppState.getInstance();
2753 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Winson Chung66700732013-08-20 16:56:15 -07002754 Rect padding = grid.getWorkspacePadding(grid.isLandscape ?
2755 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
Winson Chung5f8afe62013-08-12 16:19:28 -07002756
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002757 // Always assume we're working with the smallest span to make sure we
2758 // reserve enough space in both orientations.
Winson Chung66700732013-08-20 16:56:15 -07002759 int parentWidth = grid.calculateCellWidth(grid.widthPx
2760 - padding.left - padding.right, (int) grid.numColumns);
2761 int parentHeight = grid.calculateCellHeight(grid.heightPx
2762 - padding.top - padding.bottom, (int) grid.numRows);
2763 int smallerSize = Math.min(parentWidth, parentHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002764
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002765 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002766 int spanX = (int) Math.ceil(width / (float) smallerSize);
2767 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002768
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002769 if (result == null) {
2770 return new int[] { spanX, spanY };
2771 }
2772 result[0] = spanX;
2773 result[1] = spanY;
2774 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002775 }
2776
2777 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08002778 * Calculate the grid spans needed to fit given item
2779 */
2780 public void calculateSpans(ItemInfo info) {
2781 final int minWidth;
2782 final int minHeight;
2783
2784 if (info instanceof LauncherAppWidgetInfo) {
2785 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
2786 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
2787 } else if (info instanceof PendingAddWidgetInfo) {
2788 minWidth = ((PendingAddWidgetInfo) info).minWidth;
2789 minHeight = ((PendingAddWidgetInfo) info).minHeight;
2790 } else {
2791 // It's not a widget, so it must be 1x1
2792 info.spanX = info.spanY = 1;
2793 return;
2794 }
2795 int[] spans = rectToCell(minWidth, minHeight, null);
2796 info.spanX = spans[0];
2797 info.spanY = spans[1];
2798 }
2799
Michael Jurka0280c3b2010-09-17 15:00:07 -07002800 private void clearOccupiedCells() {
2801 for (int x = 0; x < mCountX; x++) {
2802 for (int y = 0; y < mCountY; y++) {
2803 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002804 }
2805 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002806 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002807
Adam Cohend4844c32011-02-18 19:25:06 -08002808 public void markCellsAsOccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002809 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002810 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002811 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002812 }
2813
Adam Cohend4844c32011-02-18 19:25:06 -08002814 public void markCellsAsUnoccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002815 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002816 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002817 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002818 }
2819
Adam Cohen482ed822012-03-02 14:15:13 -08002820 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2821 boolean value) {
2822 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002823 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2824 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002825 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002826 }
2827 }
2828 }
2829
Adam Cohen2801caf2011-05-13 20:57:39 -07002830 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002831 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002832 (Math.max((mCountX - 1), 0) * mWidthGap);
2833 }
2834
2835 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002836 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002837 (Math.max((mCountY - 1), 0) * mHeightGap);
2838 }
2839
Michael Jurka66d72172011-04-12 16:29:25 -07002840 public boolean isOccupied(int x, int y) {
2841 if (x < mCountX && y < mCountY) {
2842 return mOccupied[x][y];
2843 } else {
2844 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2845 }
2846 }
2847
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002848 @Override
2849 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2850 return new CellLayout.LayoutParams(getContext(), attrs);
2851 }
2852
2853 @Override
2854 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2855 return p instanceof CellLayout.LayoutParams;
2856 }
2857
2858 @Override
2859 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2860 return new CellLayout.LayoutParams(p);
2861 }
2862
2863 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2864 /**
2865 * Horizontal location of the item in the grid.
2866 */
2867 @ViewDebug.ExportedProperty
2868 public int cellX;
2869
2870 /**
2871 * Vertical location of the item in the grid.
2872 */
2873 @ViewDebug.ExportedProperty
2874 public int cellY;
2875
2876 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002877 * Temporary horizontal location of the item in the grid during reorder
2878 */
2879 public int tmpCellX;
2880
2881 /**
2882 * Temporary vertical location of the item in the grid during reorder
2883 */
2884 public int tmpCellY;
2885
2886 /**
2887 * Indicates that the temporary coordinates should be used to layout the items
2888 */
2889 public boolean useTmpCoords;
2890
2891 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002892 * Number of cells spanned horizontally by the item.
2893 */
2894 @ViewDebug.ExportedProperty
2895 public int cellHSpan;
2896
2897 /**
2898 * Number of cells spanned vertically by the item.
2899 */
2900 @ViewDebug.ExportedProperty
2901 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002902
Adam Cohen1b607ed2011-03-03 17:26:50 -08002903 /**
2904 * Indicates whether the item will set its x, y, width and height parameters freely,
2905 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2906 */
Adam Cohend4844c32011-02-18 19:25:06 -08002907 public boolean isLockedToGrid = true;
2908
Adam Cohen482ed822012-03-02 14:15:13 -08002909 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07002910 * Indicates that this item should use the full extents of its parent.
2911 */
2912 public boolean isFullscreen = false;
2913
2914 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002915 * Indicates whether this item can be reordered. Always true except in the case of the
2916 * the AllApps button.
2917 */
2918 public boolean canReorder = true;
2919
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002920 // X coordinate of the view in the layout.
2921 @ViewDebug.ExportedProperty
2922 int x;
2923 // Y coordinate of the view in the layout.
2924 @ViewDebug.ExportedProperty
2925 int y;
2926
Romain Guy84f296c2009-11-04 15:00:44 -08002927 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002928
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002929 public LayoutParams(Context c, AttributeSet attrs) {
2930 super(c, attrs);
2931 cellHSpan = 1;
2932 cellVSpan = 1;
2933 }
2934
2935 public LayoutParams(ViewGroup.LayoutParams source) {
2936 super(source);
2937 cellHSpan = 1;
2938 cellVSpan = 1;
2939 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002940
2941 public LayoutParams(LayoutParams source) {
2942 super(source);
2943 this.cellX = source.cellX;
2944 this.cellY = source.cellY;
2945 this.cellHSpan = source.cellHSpan;
2946 this.cellVSpan = source.cellVSpan;
2947 }
2948
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002949 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002950 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002951 this.cellX = cellX;
2952 this.cellY = cellY;
2953 this.cellHSpan = cellHSpan;
2954 this.cellVSpan = cellVSpan;
2955 }
2956
Adam Cohen2374abf2013-04-16 14:56:57 -07002957 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
2958 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08002959 if (isLockedToGrid) {
2960 final int myCellHSpan = cellHSpan;
2961 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07002962 int myCellX = useTmpCoords ? tmpCellX : cellX;
2963 int myCellY = useTmpCoords ? tmpCellY : cellY;
2964
2965 if (invertHorizontally) {
2966 myCellX = colCount - myCellX - cellHSpan;
2967 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08002968
Adam Cohend4844c32011-02-18 19:25:06 -08002969 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2970 leftMargin - rightMargin;
2971 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2972 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002973 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2974 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002975 }
2976 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002977
Winson Chungaafa03c2010-06-11 17:34:16 -07002978 public String toString() {
2979 return "(" + this.cellX + ", " + this.cellY + ")";
2980 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002981
2982 public void setWidth(int width) {
2983 this.width = width;
2984 }
2985
2986 public int getWidth() {
2987 return width;
2988 }
2989
2990 public void setHeight(int height) {
2991 this.height = height;
2992 }
2993
2994 public int getHeight() {
2995 return height;
2996 }
2997
2998 public void setX(int x) {
2999 this.x = x;
3000 }
3001
3002 public int getX() {
3003 return x;
3004 }
3005
3006 public void setY(int y) {
3007 this.y = y;
3008 }
3009
3010 public int getY() {
3011 return y;
3012 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003013 }
3014
Michael Jurka0280c3b2010-09-17 15:00:07 -07003015 // This class stores info for two purposes:
3016 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
3017 // its spanX, spanY, and the screen it is on
3018 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
3019 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
3020 // the CellLayout that was long clicked
Michael Jurkae5fb0f22011-04-11 13:27:46 -07003021 static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003022 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07003023 int cellX = -1;
3024 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003025 int spanX;
3026 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07003027 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07003028 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003029
Adam Cohene0aaa0d2014-05-12 12:44:22 -07003030 CellInfo(View v, ItemInfo info) {
3031 cell = v;
3032 cellX = info.cellX;
3033 cellY = info.cellY;
3034 spanX = info.spanX;
3035 spanY = info.spanY;
3036 screenId = info.screenId;
3037 container = info.container;
3038 }
3039
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003040 @Override
3041 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07003042 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
3043 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003044 }
3045 }
Michael Jurkad771c962011-08-09 15:00:48 -07003046
3047 public boolean lastDownOnOccupiedCell() {
3048 return mLastDownOnOccupiedCell;
3049 }
Sunny Goyala9116722015-04-29 13:55:58 -07003050
3051 public boolean findVacantCell(int spanX, int spanY, int[] outXY) {
3052 return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied);
3053 }
Sunny Goyal9ca9c132015-04-29 14:57:22 -07003054
3055 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
3056 int x2 = x + spanX - 1;
3057 int y2 = y + spanY - 1;
3058 if (x < 0 || y < 0 || x2 >= mCountX || y2 >= mCountY) {
3059 return false;
3060 }
3061 for (int i = x; i <= x2; i++) {
3062 for (int j = y; j <= y2; j++) {
3063 if (mOccupied[i][j]) {
3064 return false;
3065 }
3066 }
3067 }
3068
3069 return true;
3070 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003071}