blob: 57db805d85e6dce2f68b58f126dc47cf12661942 [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
Sunny Goyal4b6eb262015-05-14 19:24:40 -070050import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
Daniel Sandler325dc232013-06-05 22:57:57 -040051import com.android.launcher3.FolderIcon.FolderRingAnimator;
Sunny Goyale9b651e2015-04-24 11:44:51 -070052import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
53import com.android.launcher3.accessibility.FolderAccessibilityHelper;
54import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
Adam Cohen091440a2015-03-18 14:16:05 -070055import com.android.launcher3.util.Thunk;
Hyunyoung Song3f471442015-04-08 19:01:34 -070056import com.android.launcher3.widget.PendingAddWidgetInfo;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070057
Adam Cohen69ce2e52011-07-03 19:25:21 -070058import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070059import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080060import java.util.Collections;
61import java.util.Comparator;
Adam Cohenbfbfd262011-06-13 16:55:12 -070062import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080063import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070064
Sunny Goyal4b6eb262015-05-14 19:24:40 -070065public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
Sunny Goyale9b651e2015-04-24 11:44:51 -070066 public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
67 public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
68
Winson Chungaafa03c2010-06-11 17:34:16 -070069 static final String TAG = "CellLayout";
70
Adam Cohen2acce882012-03-28 19:03:19 -070071 private Launcher mLauncher;
Adam Cohen091440a2015-03-18 14:16:05 -070072 @Thunk int mCellWidth;
73 @Thunk int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070074 private int mFixedCellWidth;
75 private int mFixedCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070076
Adam Cohen091440a2015-03-18 14:16:05 -070077 @Thunk int mCountX;
78 @Thunk int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080079
Adam Cohen234c4cd2011-07-17 21:03:04 -070080 private int mOriginalWidthGap;
81 private int mOriginalHeightGap;
Adam Cohen091440a2015-03-18 14:16:05 -070082 @Thunk int mWidthGap;
83 @Thunk int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070084 private int mMaxGap;
Adam Cohen917e3882013-10-31 15:03:35 -070085 private boolean mDropPending = false;
Adam Cohenc50438c2014-08-19 17:43:05 -070086 private boolean mIsDragTarget = true;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080087
Patrick Dubroyde7658b2010-09-27 11:15:43 -070088 // These are temporary variables to prevent having to allocate a new object just to
89 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Winson Chung0be025d2011-05-23 17:45:09 -070090 private final int[] mTmpXY = new int[2];
Adam Cohen091440a2015-03-18 14:16:05 -070091 @Thunk final int[] mTmpPoint = new int[2];
Adam Cohen69ce2e52011-07-03 19:25:21 -070092 int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070093
The Android Open Source Project31dd5032009-03-03 19:32:27 -080094 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -080095 boolean[][] mTmpOccupied;
Michael Jurkad771c962011-08-09 15:00:48 -070096 private boolean mLastDownOnOccupiedCell = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080097
Michael Jurkadee05892010-07-27 10:01:56 -070098 private OnTouchListener mInterceptTouchListener;
99
Adam Cohen69ce2e52011-07-03 19:25:21 -0700100 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -0700101 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -0700102
Adam Cohen02dcfcc2013-10-01 12:37:33 -0700103 private float FOREGROUND_ALPHA_DAMPER = 0.65f;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700104 private int mForegroundAlpha = 0;
Michael Jurka5f1c5092010-09-03 14:15:02 -0700105 private float mBackgroundAlpha;
Adam Cohen1b0aaac2010-10-28 11:11:18 -0700106 private float mBackgroundAlphaMultiplier = 1.0f;
Winson Chung59a488a2013-12-10 12:32:14 -0800107 private boolean mDrawBackground = true;
Adam Cohenf34bab52010-09-30 14:11:56 -0700108
Michael Jurka33945b22010-12-21 18:19:38 -0800109 private Drawable mNormalBackground;
Michael Jurka33945b22010-12-21 18:19:38 -0800110 private Drawable mActiveGlowBackground;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700111 private Drawable mOverScrollForegroundDrawable;
112 private Drawable mOverScrollLeft;
113 private Drawable mOverScrollRight;
Michael Jurka18014792010-10-14 09:01:34 -0700114 private Rect mBackgroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700115 private Rect mForegroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700116 private int mForegroundPadding;
Patrick Dubroy1262e362010-10-06 15:49:50 -0700117
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700118 // These values allow a fixed measurement to be set on the CellLayout.
119 private int mFixedWidth = -1;
120 private int mFixedHeight = -1;
121
Michael Jurka33945b22010-12-21 18:19:38 -0800122 // If we're actively dragging something over this screen, mIsDragOverlapping is true
123 private boolean mIsDragOverlapping = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700124
Winson Chung150fbab2010-09-29 17:14:26 -0700125 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700126 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohen091440a2015-03-18 14:16:05 -0700127 @Thunk Rect[] mDragOutlines = new Rect[4];
128 @Thunk float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700129 private InterruptibleInOutAnimator[] mDragOutlineAnims =
130 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700131
132 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700133 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700134 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700135
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700136 private final ClickShadowView mTouchFeedbackView;
Patrick Dubroy96864c32011-03-10 17:17:23 -0800137
Adam Cohen091440a2015-03-18 14:16:05 -0700138 @Thunk HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
Adam Cohen482ed822012-03-02 14:15:13 -0800139 HashMap<CellLayout.LayoutParams, Animator>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800140 private HashMap<View, ReorderPreviewAnimation>
141 mShakeAnimators = new HashMap<View, ReorderPreviewAnimation>();
Adam Cohen19f37922012-03-21 11:59:11 -0700142
143 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700144
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700145 // When a drag operation is in progress, holds the nearest cell to the touch point
146 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800147
Joe Onorato4be866d2010-10-10 11:26:02 -0700148 private boolean mDragging = false;
149
Patrick Dubroyce34a972010-10-19 10:34:32 -0700150 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700151 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700152
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800153 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700154 private float mHotseatScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800155
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800156 public static final int MODE_SHOW_REORDER_HINT = 0;
157 public static final int MODE_DRAG_OVER = 1;
158 public static final int MODE_ON_DROP = 2;
159 public static final int MODE_ON_DROP_EXTERNAL = 3;
160 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700161 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800162 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
163
Adam Cohena897f392012-04-27 18:12:05 -0700164 static final int LANDSCAPE = 0;
165 static final int PORTRAIT = 1;
166
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800167 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700168 private static final int REORDER_ANIMATION_DURATION = 150;
Adam Cohen091440a2015-03-18 14:16:05 -0700169 @Thunk float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700170
Adam Cohen482ed822012-03-02 14:15:13 -0800171 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
172 private Rect mOccupiedRect = new Rect();
173 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700174 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700175 private static final int INVALID_DIRECTION = -100;
Adam Cohenc6cc61d2012-04-04 12:47:08 -0700176 private DropTarget.DragEnforcer mDragEnforcer;
Adam Cohen482ed822012-03-02 14:15:13 -0800177
Winson Chung3a6e7f32013-10-09 15:50:52 -0700178 private Rect mTempRect = new Rect();
179
Michael Jurkaca993832012-06-29 15:17:04 -0700180 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700181
Adam Cohenc9735cf2015-01-23 16:11:55 -0800182 // Related to accessible drag and drop
Sunny Goyale9b651e2015-04-24 11:44:51 -0700183 private DragAndDropAccessibilityDelegate mTouchHelper;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800184 private boolean mUseTouchHelper = false;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800185
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800186 public CellLayout(Context context) {
187 this(context, null);
188 }
189
190 public CellLayout(Context context, AttributeSet attrs) {
191 this(context, attrs, 0);
192 }
193
194 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
195 super(context, attrs, defStyle);
Michael Jurka8b805b12012-04-18 14:23:14 -0700196 mDragEnforcer = new DropTarget.DragEnforcer(context);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700197
198 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
199 // the user where a dragged item will land when dropped.
200 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800201 setClipToPadding(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700202 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700203
Winson Chung892c74d2013-08-22 16:15:50 -0700204 LauncherAppState app = LauncherAppState.getInstance();
205 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800206 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
207
Winson Chung11a1a532013-09-13 11:14:45 -0700208 mCellWidth = mCellHeight = -1;
Nilesh Agrawal5f7099a2014-01-02 15:54:57 -0800209 mFixedCellWidth = mFixedCellHeight = -1;
Winson Chung5f8afe62013-08-12 16:19:28 -0700210 mWidthGap = mOriginalWidthGap = 0;
211 mHeightGap = mOriginalHeightGap = 0;
212 mMaxGap = Integer.MAX_VALUE;
Winson Chung892c74d2013-08-22 16:15:50 -0700213 mCountX = (int) grid.numColumns;
214 mCountY = (int) grid.numRows;
Michael Jurka0280c3b2010-09-17 15:00:07 -0700215 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800216 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700217 mPreviousReorderDirection[0] = INVALID_DIRECTION;
218 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800219
220 a.recycle();
221
222 setAlwaysDrawnWithCacheEnabled(false);
223
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700224 final Resources res = getResources();
Winson Chung6e1c0d32013-10-25 15:24:24 -0700225 mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700226
Adam Cohen410f3cd2013-09-22 12:09:32 -0700227 mNormalBackground = res.getDrawable(R.drawable.screenpanel);
228 mActiveGlowBackground = res.getDrawable(R.drawable.screenpanel_hover);
Michael Jurka33945b22010-12-21 18:19:38 -0800229
Adam Cohenb5ba0972011-09-07 18:02:31 -0700230 mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
231 mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
232 mForegroundPadding =
233 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
Michael Jurka33945b22010-12-21 18:19:38 -0800234
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800235 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
Winson Chung5f8afe62013-08-12 16:19:28 -0700236 grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700237
Winson Chungb26f3d62011-06-02 10:49:29 -0700238 mNormalBackground.setFilterBitmap(true);
Winson Chungb26f3d62011-06-02 10:49:29 -0700239 mActiveGlowBackground.setFilterBitmap(true);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700240
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700241 // Initialize the data structures used for the drag visualization.
Patrick Dubroyce34a972010-10-19 10:34:32 -0700242 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700243 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700244 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800245 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700246 }
247
248 // When dragging things around the home screens, we show a green outline of
249 // where the item will land. The outlines gradually fade out, leaving a trail
250 // behind the drag path.
251 // Set up all the animations that are used to implement this fading.
252 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700253 final float fromAlphaValue = 0;
254 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700255
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700256 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700257
258 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700259 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100260 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700261 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700262 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700263 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700264 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700265 final Bitmap outline = (Bitmap)anim.getTag();
266
267 // If an animation is started and then stopped very quickly, we can still
268 // get spurious updates we've cleared the tag. Guard against this.
269 if (outline == null) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700270 @SuppressWarnings("all") // suppress dead code warning
271 final boolean debug = false;
272 if (debug) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700273 Object val = animation.getAnimatedValue();
274 Log.d(TAG, "anim " + thisIndex + " update: " + val +
275 ", isStopped " + anim.isStopped());
276 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700277 // Try to prevent it from continuing to run
278 animation.cancel();
279 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700280 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800281 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700282 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700283 }
284 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700285 // The animation holds a reference to the drag outline bitmap as long is it's
286 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700287 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700288 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700289 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700290 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700291 anim.setTag(null);
292 }
293 }
294 });
295 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700296 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700297
Michael Jurka18014792010-10-14 09:01:34 -0700298 mBackgroundRect = new Rect();
Adam Cohenb5ba0972011-09-07 18:02:31 -0700299 mForegroundRect = new Rect();
Michael Jurkabea15192010-11-17 12:33:46 -0800300
Michael Jurkaa52570f2012-03-20 03:18:20 -0700301 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
Adam Cohen2374abf2013-04-16 14:56:57 -0700302 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700303 mCountX, mCountY);
Adam Cohen2374abf2013-04-16 14:56:57 -0700304
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700305 mTouchFeedbackView = new ClickShadowView(context);
306 addView(mTouchFeedbackView);
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 Goyal4b6eb262015-05-14 19:24:40 -0700413 @Override
414 public void setPressedIcon(BubbleTextView icon, Bitmap background) {
Sunny Goyal508da152014-08-14 10:53:27 -0700415 if (icon == null || background == null) {
416 mTouchFeedbackView.setBitmap(null);
417 mTouchFeedbackView.animate().cancel();
418 } else {
Sunny Goyal508da152014-08-14 10:53:27 -0700419 if (mTouchFeedbackView.setBitmap(background)) {
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700420 mTouchFeedbackView.alignWithIconView(icon, mShortcutsAndWidgets);
421 mTouchFeedbackView.animateShadow();
Sunny Goyal508da152014-08-14 10:53:27 -0700422 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800423 }
424 }
425
Winson Chung59a488a2013-12-10 12:32:14 -0800426 void disableBackground() {
427 mDrawBackground = false;
428 }
429
Adam Cohenc50438c2014-08-19 17:43:05 -0700430 void disableDragTarget() {
431 mIsDragTarget = false;
432 }
433
434 boolean isDragTarget() {
435 return mIsDragTarget;
436 }
437
438 void setIsDragOverlapping(boolean isDragOverlapping) {
439 if (mIsDragOverlapping != isDragOverlapping) {
440 mIsDragOverlapping = isDragOverlapping;
Adam Cohenc50438c2014-08-19 17:43:05 -0700441 invalidate();
442 }
443 }
444
Michael Jurka33945b22010-12-21 18:19:38 -0800445 boolean getIsDragOverlapping() {
446 return mIsDragOverlapping;
447 }
448
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700449 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700450 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700451 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
452 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
453 // When we're small, we are either drawn normally or in the "accepts drops" state (during
454 // a drag). However, we also drag the mini hover background *over* one of those two
455 // backgrounds
Winson Chung59a488a2013-12-10 12:32:14 -0800456 if (mDrawBackground && mBackgroundAlpha > 0.0f) {
Adam Cohenf34bab52010-09-30 14:11:56 -0700457 Drawable bg;
Michael Jurka33945b22010-12-21 18:19:38 -0800458
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700459 if (mIsDragOverlapping) {
Michael Jurka33945b22010-12-21 18:19:38 -0800460 // In the mini case, we draw the active_glow bg *over* the active background
Michael Jurkabdf78552011-10-31 14:34:25 -0700461 bg = mActiveGlowBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700462 } else {
Michael Jurkabdf78552011-10-31 14:34:25 -0700463 bg = mNormalBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700464 }
Michael Jurka33945b22010-12-21 18:19:38 -0800465
466 bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
467 bg.setBounds(mBackgroundRect);
468 bg.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700469 }
Romain Guya6abce82009-11-10 02:54:41 -0800470
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700471 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700472 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700473 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700474 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800475 final Rect r = mDragOutlines[i];
Winson Chung3a6e7f32013-10-09 15:50:52 -0700476 mTempRect.set(r);
477 Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale());
Joe Onorato4be866d2010-10-10 11:26:02 -0700478 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700479 paint.setAlpha((int)(alpha + .5f));
Winson Chung3a6e7f32013-10-09 15:50:52 -0700480 canvas.drawBitmap(b, null, mTempRect, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700481 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700482 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800483
Adam Cohen482ed822012-03-02 14:15:13 -0800484 if (DEBUG_VISUALIZE_OCCUPIED) {
485 int[] pt = new int[2];
486 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700487 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800488 for (int i = 0; i < mCountX; i++) {
489 for (int j = 0; j < mCountY; j++) {
490 if (mOccupied[i][j]) {
491 cellToPoint(i, j, pt);
492 canvas.save();
493 canvas.translate(pt[0], pt[1]);
494 cd.draw(canvas);
495 canvas.restore();
496 }
497 }
498 }
499 }
500
Andrew Flynn850d2e72012-04-26 16:51:20 -0700501 int previewOffset = FolderRingAnimator.sPreviewSize;
502
Adam Cohen69ce2e52011-07-03 19:25:21 -0700503 // The folder outer / inner ring image(s)
Winson Chung5f8afe62013-08-12 16:19:28 -0700504 LauncherAppState app = LauncherAppState.getInstance();
505 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700506 for (int i = 0; i < mFolderOuterRings.size(); i++) {
507 FolderRingAnimator fra = mFolderOuterRings.get(i);
508
Adam Cohen5108bc02013-09-20 17:04:51 -0700509 Drawable d;
510 int width, height;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700511 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700512 View child = getChildAt(fra.mCellX, fra.mCellY);
Adam Cohen558f1c22013-10-09 15:15:24 -0700513
Winson Chung89f97052013-09-20 11:32:26 -0700514 if (child != null) {
Adam Cohen558f1c22013-10-09 15:15:24 -0700515 int centerX = mTempLocation[0] + mCellWidth / 2;
516 int centerY = mTempLocation[1] + previewOffset / 2 +
517 child.getPaddingTop() + grid.folderBackgroundOffset;
518
Adam Cohen5108bc02013-09-20 17:04:51 -0700519 // Draw outer ring, if it exists
520 if (FolderIcon.HAS_OUTER_RING) {
521 d = FolderRingAnimator.sSharedOuterRingDrawable;
522 width = (int) (fra.getOuterRingSize() * getChildrenScale());
523 height = width;
524 canvas.save();
525 canvas.translate(centerX - width / 2, centerY - height / 2);
526 d.setBounds(0, 0, width, height);
527 d.draw(canvas);
528 canvas.restore();
529 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700530
Winson Chung89f97052013-09-20 11:32:26 -0700531 // Draw inner ring
532 d = FolderRingAnimator.sSharedInnerRingDrawable;
533 width = (int) (fra.getInnerRingSize() * getChildrenScale());
534 height = width;
Winson Chung89f97052013-09-20 11:32:26 -0700535 canvas.save();
536 canvas.translate(centerX - width / 2, centerY - width / 2);
537 d.setBounds(0, 0, width, height);
538 d.draw(canvas);
539 canvas.restore();
540 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700541 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700542
543 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
544 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
545 int width = d.getIntrinsicWidth();
546 int height = d.getIntrinsicHeight();
547
548 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700549 View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]);
Winson Chung89f97052013-09-20 11:32:26 -0700550 if (child != null) {
551 int centerX = mTempLocation[0] + mCellWidth / 2;
552 int centerY = mTempLocation[1] + previewOffset / 2 +
553 child.getPaddingTop() + grid.folderBackgroundOffset;
Adam Cohenc51934b2011-07-26 21:07:43 -0700554
Winson Chung89f97052013-09-20 11:32:26 -0700555 canvas.save();
556 canvas.translate(centerX - width / 2, centerY - width / 2);
557 d.setBounds(0, 0, width, height);
558 d.draw(canvas);
559 canvas.restore();
560 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700561 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700562 }
563
Adam Cohenb5ba0972011-09-07 18:02:31 -0700564 @Override
565 protected void dispatchDraw(Canvas canvas) {
566 super.dispatchDraw(canvas);
567 if (mForegroundAlpha > 0) {
568 mOverScrollForegroundDrawable.setBounds(mForegroundRect);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700569 mOverScrollForegroundDrawable.draw(canvas);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700570 }
571 }
572
Adam Cohen69ce2e52011-07-03 19:25:21 -0700573 public void showFolderAccept(FolderRingAnimator fra) {
574 mFolderOuterRings.add(fra);
575 }
576
577 public void hideFolderAccept(FolderRingAnimator fra) {
578 if (mFolderOuterRings.contains(fra)) {
579 mFolderOuterRings.remove(fra);
580 }
581 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700582 }
583
Adam Cohenc51934b2011-07-26 21:07:43 -0700584 public void setFolderLeaveBehindCell(int x, int y) {
585 mFolderLeaveBehindCell[0] = x;
586 mFolderLeaveBehindCell[1] = y;
587 invalidate();
588 }
589
590 public void clearFolderLeaveBehind() {
591 mFolderLeaveBehindCell[0] = -1;
592 mFolderLeaveBehindCell[1] = -1;
593 invalidate();
594 }
595
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700596 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700597 public boolean shouldDelayChildPressedState() {
598 return false;
599 }
600
Adam Cohen1462de32012-07-24 22:34:36 -0700601 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700602 try {
603 dispatchRestoreInstanceState(states);
604 } catch (IllegalArgumentException ex) {
605 if (LauncherAppState.isDogfoodBuild()) {
606 throw ex;
607 }
608 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
609 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
610 }
Adam Cohen1462de32012-07-24 22:34:36 -0700611 }
612
Michael Jurkae6235dd2011-10-04 15:02:05 -0700613 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700614 public void cancelLongPress() {
615 super.cancelLongPress();
616
617 // Cancel long press for all children
618 final int count = getChildCount();
619 for (int i = 0; i < count; i++) {
620 final View child = getChildAt(i);
621 child.cancelLongPress();
622 }
623 }
624
Michael Jurkadee05892010-07-27 10:01:56 -0700625 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
626 mInterceptTouchListener = listener;
627 }
628
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800629 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700630 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800631 }
632
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800633 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700634 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800635 }
636
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800637 public void setIsHotseat(boolean isHotseat) {
638 mIsHotseat = isHotseat;
Winson Chung5f8afe62013-08-12 16:19:28 -0700639 mShortcutsAndWidgets.setIsHotseat(isHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800640 }
641
Sunny Goyale9b651e2015-04-24 11:44:51 -0700642 public boolean isHotseat() {
643 return mIsHotseat;
644 }
645
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800646 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700647 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700648 final LayoutParams lp = params;
649
Andrew Flynnde38e422012-05-08 11:22:15 -0700650 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800651 if (child instanceof BubbleTextView) {
652 BubbleTextView bubbleChild = (BubbleTextView) child;
Winson Chung5f8afe62013-08-12 16:19:28 -0700653 bubbleChild.setTextVisibility(!mIsHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800654 }
655
Adam Cohen307fe232012-08-16 17:55:58 -0700656 child.setScaleX(getChildrenScale());
657 child.setScaleY(getChildrenScale());
658
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800659 // Generate an id for each view, this assumes we have at most 256x256 cells
660 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700661 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700662 // If the horizontal or vertical span is set to -1, it is taken to
663 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700664 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
665 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800666
Winson Chungaafa03c2010-06-11 17:34:16 -0700667 child.setId(childId);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700668 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700669
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700670 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700671
Winson Chungaafa03c2010-06-11 17:34:16 -0700672 return true;
673 }
674 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800675 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700676
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800677 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700678 public void removeAllViews() {
679 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700680 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700681 }
682
683 @Override
684 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700685 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700686 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700687 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700688 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700689 }
690
691 @Override
692 public void removeView(View view) {
693 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700694 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700695 }
696
697 @Override
698 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700699 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
700 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700701 }
702
703 @Override
704 public void removeViewInLayout(View view) {
705 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700706 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700707 }
708
709 @Override
710 public void removeViews(int start, int count) {
711 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700712 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700713 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700714 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700715 }
716
717 @Override
718 public void removeViewsInLayout(int start, int count) {
719 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700720 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700721 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700722 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800723 }
724
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700725 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700726 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800727 * @param x X coordinate of the point
728 * @param y Y coordinate of the point
729 * @param result Array of 2 ints to hold the x and y coordinate of the cell
730 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700731 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700732 final int hStartPadding = getPaddingLeft();
733 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800734
735 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
736 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
737
Adam Cohend22015c2010-07-26 22:02:18 -0700738 final int xAxis = mCountX;
739 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800740
741 if (result[0] < 0) result[0] = 0;
742 if (result[0] >= xAxis) result[0] = xAxis - 1;
743 if (result[1] < 0) result[1] = 0;
744 if (result[1] >= yAxis) result[1] = yAxis - 1;
745 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700746
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800747 /**
748 * Given a point, return the cell that most closely encloses that point
749 * @param x X coordinate of the point
750 * @param y Y coordinate of the point
751 * @param result Array of 2 ints to hold the x and y coordinate of the cell
752 */
753 void pointToCellRounded(int x, int y, int[] result) {
754 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
755 }
756
757 /**
758 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700759 *
760 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800761 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700762 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800763 * @param result Array of 2 ints to hold the x and y coordinate of the point
764 */
765 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700766 final int hStartPadding = getPaddingLeft();
767 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800768
769 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
770 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
771 }
772
Adam Cohene3e27a82011-04-15 12:07:39 -0700773 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800774 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700775 *
776 * @param cellX X coordinate of the cell
777 * @param cellY Y coordinate of the cell
778 *
779 * @param result Array of 2 ints to hold the x and y coordinate of the point
780 */
781 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700782 regionToCenterPoint(cellX, cellY, 1, 1, result);
783 }
784
785 /**
786 * Given a cell coordinate and span return the point that represents the center of the regio
787 *
788 * @param cellX X coordinate of the cell
789 * @param cellY Y coordinate of the cell
790 *
791 * @param result Array of 2 ints to hold the x and y coordinate of the point
792 */
793 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700794 final int hStartPadding = getPaddingLeft();
795 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700796 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
797 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
798 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
799 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700800 }
801
Adam Cohen19f37922012-03-21 11:59:11 -0700802 /**
803 * Given a cell coordinate and span fills out a corresponding pixel rect
804 *
805 * @param cellX X coordinate of the cell
806 * @param cellY Y coordinate of the cell
807 * @param result Rect in which to write the result
808 */
809 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
810 final int hStartPadding = getPaddingLeft();
811 final int vStartPadding = getPaddingTop();
812 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
813 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
814 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
815 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
816 }
817
Adam Cohen482ed822012-03-02 14:15:13 -0800818 public float getDistanceFromCell(float x, float y, int[] cell) {
819 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700820 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800821 }
822
Romain Guy84f296c2009-11-04 15:00:44 -0800823 int getCellWidth() {
824 return mCellWidth;
825 }
826
827 int getCellHeight() {
828 return mCellHeight;
829 }
830
Adam Cohend4844c32011-02-18 19:25:06 -0800831 int getWidthGap() {
832 return mWidthGap;
833 }
834
835 int getHeightGap() {
836 return mHeightGap;
837 }
838
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700839 public void setFixedSize(int width, int height) {
840 mFixedWidth = width;
841 mFixedHeight = height;
842 }
843
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800844 @Override
845 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700846 LauncherAppState app = LauncherAppState.getInstance();
847 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
848
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800849 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800850 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700851 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
852 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700853 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
854 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700855 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700856 int cw = grid.calculateCellWidth(childWidthSize, mCountX);
857 int ch = grid.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700858 if (cw != mCellWidth || ch != mCellHeight) {
859 mCellWidth = cw;
860 mCellHeight = ch;
861 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
862 mHeightGap, mCountX, mCountY);
863 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700864 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700865
Winson Chung2d75f122013-09-23 16:53:31 -0700866 int newWidth = childWidthSize;
867 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700868 if (mFixedWidth > 0 && mFixedHeight > 0) {
869 newWidth = mFixedWidth;
870 newHeight = mFixedHeight;
871 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800872 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
873 }
874
Adam Cohend22015c2010-07-26 22:02:18 -0700875 int numWidthGaps = mCountX - 1;
876 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800877
Adam Cohen234c4cd2011-07-17 21:03:04 -0700878 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700879 int hSpace = childWidthSize;
880 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700881 int hFreeSpace = hSpace - (mCountX * mCellWidth);
882 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700883 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
884 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -0700885 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
886 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700887 } else {
888 mWidthGap = mOriginalWidthGap;
889 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700890 }
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700891
892 // Make the feedback view large enough to hold the blur bitmap.
893 mTouchFeedbackView.measure(
894 MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
895 MeasureSpec.EXACTLY),
896 MeasureSpec.makeMeasureSpec(mCellHeight + mTouchFeedbackView.getExtraSize(),
897 MeasureSpec.EXACTLY));
898
899 mShortcutsAndWidgets.measure(
900 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
901 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
902
903 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
904 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -0700905 if (mFixedWidth > 0 && mFixedHeight > 0) {
906 setMeasuredDimension(maxWidth, maxHeight);
907 } else {
908 setMeasuredDimension(widthSize, heightSize);
909 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800910 }
911
912 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700913 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Winson Chung38848ca2013-10-08 12:03:44 -0700914 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
915 (mCountX * mCellWidth);
916 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
917 int top = getPaddingTop();
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700918
919 mTouchFeedbackView.layout(left, top,
920 left + mTouchFeedbackView.getMeasuredWidth(),
921 top + mTouchFeedbackView.getMeasuredHeight());
922 mShortcutsAndWidgets.layout(left, top,
923 left + r - l,
924 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800925 }
926
927 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700928 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
929 super.onSizeChanged(w, h, oldw, oldh);
Winson Chung82a9bd22013-10-08 16:02:34 -0700930
931 // Expand the background drawing bounds by the padding baked into the background drawable
932 Rect padding = new Rect();
933 mNormalBackground.getPadding(padding);
934 mBackgroundRect.set(-padding.left, -padding.top, w + padding.right, h + padding.bottom);
935
Adam Cohenb5ba0972011-09-07 18:02:31 -0700936 mForegroundRect.set(mForegroundPadding, mForegroundPadding,
Adam Cohen215b4162012-08-30 13:14:08 -0700937 w - mForegroundPadding, h - mForegroundPadding);
Michael Jurkadee05892010-07-27 10:01:56 -0700938 }
939
940 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800941 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700942 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800943 }
944
945 @Override
946 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700947 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800948 }
949
Michael Jurka5f1c5092010-09-03 14:15:02 -0700950 public float getBackgroundAlpha() {
951 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -0700952 }
953
Adam Cohen1b0aaac2010-10-28 11:11:18 -0700954 public void setBackgroundAlphaMultiplier(float multiplier) {
Michael Jurkaa3d30ad2012-05-08 13:43:43 -0700955 if (mBackgroundAlphaMultiplier != multiplier) {
956 mBackgroundAlphaMultiplier = multiplier;
957 invalidate();
958 }
Adam Cohen1b0aaac2010-10-28 11:11:18 -0700959 }
960
Adam Cohenddb82192010-11-10 16:32:54 -0800961 public float getBackgroundAlphaMultiplier() {
962 return mBackgroundAlphaMultiplier;
963 }
964
Michael Jurka5f1c5092010-09-03 14:15:02 -0700965 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -0800966 if (mBackgroundAlpha != alpha) {
967 mBackgroundAlpha = alpha;
968 invalidate();
969 }
Michael Jurkadee05892010-07-27 10:01:56 -0700970 }
971
Michael Jurkaa52570f2012-03-20 03:18:20 -0700972 public void setShortcutAndWidgetAlpha(float alpha) {
Sunny Goyal02b50812014-09-10 15:44:42 -0700973 mShortcutsAndWidgets.setAlpha(alpha);
Michael Jurkadee05892010-07-27 10:01:56 -0700974 }
975
Michael Jurkaa52570f2012-03-20 03:18:20 -0700976 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700977 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700978 }
979
Patrick Dubroy440c3602010-07-13 17:50:32 -0700980 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700981 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700982 }
983
Adam Cohen76fc0852011-06-17 13:26:23 -0700984 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800985 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700986 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800987 boolean[][] occupied = mOccupied;
988 if (!permanent) {
989 occupied = mTmpOccupied;
990 }
991
Adam Cohen19f37922012-03-21 11:59:11 -0700992 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700993 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
994 final ItemInfo info = (ItemInfo) child.getTag();
995
996 // We cancel any existing animations
997 if (mReorderAnimators.containsKey(lp)) {
998 mReorderAnimators.get(lp).cancel();
999 mReorderAnimators.remove(lp);
1000 }
1001
Adam Cohen482ed822012-03-02 14:15:13 -08001002 final int oldX = lp.x;
1003 final int oldY = lp.y;
1004 if (adjustOccupied) {
1005 occupied[lp.cellX][lp.cellY] = false;
1006 occupied[cellX][cellY] = true;
1007 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001008 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001009 if (permanent) {
1010 lp.cellX = info.cellX = cellX;
1011 lp.cellY = info.cellY = cellY;
1012 } else {
1013 lp.tmpCellX = cellX;
1014 lp.tmpCellY = cellY;
1015 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001016 clc.setupLp(lp);
1017 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001018 final int newX = lp.x;
1019 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001020
Adam Cohen76fc0852011-06-17 13:26:23 -07001021 lp.x = oldX;
1022 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001023
Adam Cohen482ed822012-03-02 14:15:13 -08001024 // Exit early if we're not actually moving the view
1025 if (oldX == newX && oldY == newY) {
1026 lp.isLockedToGrid = true;
1027 return true;
1028 }
1029
Michael Jurkaf1ad6082013-03-13 12:55:46 +01001030 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001031 va.setDuration(duration);
1032 mReorderAnimators.put(lp, va);
1033
1034 va.addUpdateListener(new AnimatorUpdateListener() {
1035 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001036 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001037 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001038 lp.x = (int) ((1 - r) * oldX + r * newX);
1039 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001040 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001041 }
1042 });
Adam Cohen482ed822012-03-02 14:15:13 -08001043 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001044 boolean cancelled = false;
1045 public void onAnimationEnd(Animator animation) {
1046 // If the animation was cancelled, it means that another animation
1047 // has interrupted this one, and we don't want to lock the item into
1048 // place just yet.
1049 if (!cancelled) {
1050 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001051 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001052 }
1053 if (mReorderAnimators.containsKey(lp)) {
1054 mReorderAnimators.remove(lp);
1055 }
1056 }
1057 public void onAnimationCancel(Animator animation) {
1058 cancelled = true;
1059 }
1060 });
Adam Cohen482ed822012-03-02 14:15:13 -08001061 va.setStartDelay(delay);
1062 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001063 return true;
1064 }
1065 return false;
1066 }
1067
Adam Cohen482ed822012-03-02 14:15:13 -08001068 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1069 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001070 final int oldDragCellX = mDragCell[0];
1071 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001072
Adam Cohen2801caf2011-05-13 20:57:39 -07001073 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001074 return;
1075 }
1076
Adam Cohen482ed822012-03-02 14:15:13 -08001077 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1078 mDragCell[0] = cellX;
1079 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001080 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001081 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001082 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001083
Joe Onorato4be866d2010-10-10 11:26:02 -07001084 int left = topLeft[0];
1085 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001086
Winson Chungb8c69f32011-10-19 21:36:08 -07001087 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001088 // When drawing the drag outline, it did not account for margin offsets
1089 // added by the view's parent.
1090 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1091 left += lp.leftMargin;
1092 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001093
Adam Cohen99e8b402011-03-25 19:23:43 -07001094 // Offsets due to the size difference between the View and the dragOutline.
1095 // There is a size difference to account for the outer blur, which may lie
1096 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001097 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001098 // We center about the x axis
1099 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1100 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001101 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001102 if (dragOffset != null && dragRegion != null) {
1103 // Center the drag region *horizontally* in the cell and apply a drag
1104 // outline offset
1105 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1106 - dragRegion.width()) / 2;
Winson Chung69737c32013-10-08 17:00:19 -07001107 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1108 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1109 top += dragOffset.y + cellPaddingY;
Winson Chungb8c69f32011-10-19 21:36:08 -07001110 } else {
1111 // Center the drag outline in the cell
1112 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1113 - dragOutline.getWidth()) / 2;
1114 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1115 - dragOutline.getHeight()) / 2;
1116 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001117 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001118 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001119 mDragOutlineAnims[oldIndex].animateOut();
1120 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001121 Rect r = mDragOutlines[mDragOutlineCurrent];
1122 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1123 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001124 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001125 }
Winson Chung150fbab2010-09-29 17:14:26 -07001126
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001127 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1128 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001129 }
1130 }
1131
Adam Cohene0310962011-04-18 16:15:31 -07001132 public void clearDragOutlines() {
1133 final int oldIndex = mDragOutlineCurrent;
1134 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001135 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001136 }
1137
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001138 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001139 * Find a vacant area that will fit the given bounds nearest the requested
1140 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001141 *
Romain Guy51afc022009-05-04 18:03:43 -07001142 * @param pixelX The X location at which you want to search for a vacant area.
1143 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001144 * @param spanX Horizontal span of the object.
1145 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001146 * @param result Array in which to place the result, or null (in which case a new array will
1147 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001148 * @return The X, Y cell of a vacant area that can contain this object,
1149 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001150 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001151 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
1152 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, spanX, spanY, result, null);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001153 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001154
Michael Jurka6a1435d2010-09-27 17:35:12 -07001155 /**
1156 * Find a vacant area that will fit the given bounds nearest the requested
1157 * cell location. Uses Euclidean distance to score multiple vacant areas.
1158 *
1159 * @param pixelX The X location at which you want to search for a vacant area.
1160 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001161 * @param minSpanX The minimum horizontal span required
1162 * @param minSpanY The minimum vertical span required
1163 * @param spanX Horizontal span of the object.
1164 * @param spanY Vertical span of the object.
1165 * @param result Array in which to place the result, or null (in which case a new array will
1166 * be allocated)
1167 * @return The X, Y cell of a vacant area that can contain this object,
1168 * nearest the requested location.
1169 */
1170 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1171 int spanY, int[] result, int[] resultSpan) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001172 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true,
Adam Cohend41fbf52012-02-16 23:53:59 -08001173 result, resultSpan);
1174 }
1175
Adam Cohend41fbf52012-02-16 23:53:59 -08001176 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1177 private void lazyInitTempRectStack() {
1178 if (mTempRectStack.isEmpty()) {
1179 for (int i = 0; i < mCountX * mCountY; i++) {
1180 mTempRectStack.push(new Rect());
1181 }
1182 }
1183 }
Adam Cohen482ed822012-03-02 14:15:13 -08001184
Adam Cohend41fbf52012-02-16 23:53:59 -08001185 private void recycleTempRects(Stack<Rect> used) {
1186 while (!used.isEmpty()) {
1187 mTempRectStack.push(used.pop());
1188 }
1189 }
1190
1191 /**
1192 * Find a vacant area that will fit the given bounds nearest the requested
1193 * cell location. Uses Euclidean distance to score multiple vacant areas.
1194 *
1195 * @param pixelX The X location at which you want to search for a vacant area.
1196 * @param pixelY The Y location at which you want to search for a vacant area.
1197 * @param minSpanX The minimum horizontal span required
1198 * @param minSpanY The minimum vertical span required
1199 * @param spanX Horizontal span of the object.
1200 * @param spanY Vertical span of the object.
1201 * @param ignoreOccupied If true, the result can be an occupied cell
1202 * @param result Array in which to place the result, or null (in which case a new array will
1203 * be allocated)
1204 * @return The X, Y cell of a vacant area that can contain this object,
1205 * nearest the requested location.
1206 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001207 private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1208 int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001209 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001210
Adam Cohene3e27a82011-04-15 12:07:39 -07001211 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1212 // to the center of the item, but we are searching based on the top-left cell, so
1213 // we translate the point over to correspond to the top-left.
1214 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1215 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1216
Jeff Sharkey70864282009-04-07 21:08:40 -07001217 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001218 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001219 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001220 final Rect bestRect = new Rect(-1, -1, -1, -1);
1221 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001222
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001223 final int countX = mCountX;
1224 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001225
Adam Cohend41fbf52012-02-16 23:53:59 -08001226 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1227 spanX < minSpanX || spanY < minSpanY) {
1228 return bestXY;
1229 }
1230
1231 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001232 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001233 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1234 int ySize = -1;
1235 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001236 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001237 // First, let's see if this thing fits anywhere
1238 for (int i = 0; i < minSpanX; i++) {
1239 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001240 if (mOccupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001241 continue inner;
1242 }
Michael Jurkac28de512010-08-13 11:27:44 -07001243 }
1244 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001245 xSize = minSpanX;
1246 ySize = minSpanY;
1247
1248 // We know that the item will fit at _some_ acceptable size, now let's see
1249 // how big we can make it. We'll alternate between incrementing x and y spans
1250 // until we hit a limit.
1251 boolean incX = true;
1252 boolean hitMaxX = xSize >= spanX;
1253 boolean hitMaxY = ySize >= spanY;
1254 while (!(hitMaxX && hitMaxY)) {
1255 if (incX && !hitMaxX) {
1256 for (int j = 0; j < ySize; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001257 if (x + xSize > countX -1 || mOccupied[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001258 // We can't move out horizontally
1259 hitMaxX = true;
1260 }
1261 }
1262 if (!hitMaxX) {
1263 xSize++;
1264 }
1265 } else if (!hitMaxY) {
1266 for (int i = 0; i < xSize; i++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001267 if (y + ySize > countY - 1 || mOccupied[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001268 // We can't move out vertically
1269 hitMaxY = true;
1270 }
1271 }
1272 if (!hitMaxY) {
1273 ySize++;
1274 }
1275 }
1276 hitMaxX |= xSize >= spanX;
1277 hitMaxY |= ySize >= spanY;
1278 incX = !incX;
1279 }
1280 incX = true;
1281 hitMaxX = xSize >= spanX;
1282 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001283 }
Winson Chung0be025d2011-05-23 17:45:09 -07001284 final int[] cellXY = mTmpXY;
Adam Cohene3e27a82011-04-15 12:07:39 -07001285 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001286
Adam Cohend41fbf52012-02-16 23:53:59 -08001287 // We verify that the current rect is not a sub-rect of any of our previous
1288 // candidates. In this case, the current rect is disqualified in favour of the
1289 // containing rect.
1290 Rect currentRect = mTempRectStack.pop();
1291 currentRect.set(x, y, x + xSize, y + ySize);
1292 boolean contained = false;
1293 for (Rect r : validRegions) {
1294 if (r.contains(currentRect)) {
1295 contained = true;
1296 break;
1297 }
1298 }
1299 validRegions.push(currentRect);
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001300 double distance = Math.hypot(cellXY[0] - pixelX, cellXY[1] - pixelY);
Adam Cohen482ed822012-03-02 14:15:13 -08001301
Adam Cohend41fbf52012-02-16 23:53:59 -08001302 if ((distance <= bestDistance && !contained) ||
1303 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001304 bestDistance = distance;
1305 bestXY[0] = x;
1306 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001307 if (resultSpan != null) {
1308 resultSpan[0] = xSize;
1309 resultSpan[1] = ySize;
1310 }
1311 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001312 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001313 }
1314 }
1315
Adam Cohenc0dcf592011-06-01 15:30:43 -07001316 // Return -1, -1 if no suitable location found
1317 if (bestDistance == Double.MAX_VALUE) {
1318 bestXY[0] = -1;
1319 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001320 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001321 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001322 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001323 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001324
Adam Cohen482ed822012-03-02 14:15:13 -08001325 /**
1326 * Find a vacant area that will fit the given bounds nearest the requested
1327 * cell location, and will also weigh in a suggested direction vector of the
1328 * desired location. This method computers distance based on unit grid distances,
1329 * not pixel distances.
1330 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001331 * @param cellX The X cell nearest to which you want to search for a vacant area.
1332 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001333 * @param spanX Horizontal span of the object.
1334 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001335 * @param direction The favored direction in which the views should move from x, y
1336 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1337 * matches exactly. Otherwise we find the best matching direction.
1338 * @param occoupied The array which represents which cells in the CellLayout are occupied
1339 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001340 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001341 * @param result Array in which to place the result, or null (in which case a new array will
1342 * be allocated)
1343 * @return The X, Y cell of a vacant area that can contain this object,
1344 * nearest the requested location.
1345 */
1346 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001347 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001348 // Keep track of best-scoring drop area
1349 final int[] bestXY = result != null ? result : new int[2];
1350 float bestDistance = Float.MAX_VALUE;
1351 int bestDirectionScore = Integer.MIN_VALUE;
1352
1353 final int countX = mCountX;
1354 final int countY = mCountY;
1355
1356 for (int y = 0; y < countY - (spanY - 1); y++) {
1357 inner:
1358 for (int x = 0; x < countX - (spanX - 1); x++) {
1359 // First, let's see if this thing fits anywhere
1360 for (int i = 0; i < spanX; i++) {
1361 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001362 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001363 continue inner;
1364 }
1365 }
1366 }
1367
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001368 float distance = (float) Math.hypot(x - cellX, y - cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001369 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001370 computeDirectionVector(x - cellX, y - cellY, curDirection);
1371 // The direction score is just the dot product of the two candidate direction
1372 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001373 int curDirectionScore = direction[0] * curDirection[0] +
1374 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001375 boolean exactDirectionOnly = false;
1376 boolean directionMatches = direction[0] == curDirection[0] &&
1377 direction[0] == curDirection[0];
1378 if ((directionMatches || !exactDirectionOnly) &&
1379 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001380 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1381 bestDistance = distance;
1382 bestDirectionScore = curDirectionScore;
1383 bestXY[0] = x;
1384 bestXY[1] = y;
1385 }
1386 }
1387 }
1388
1389 // Return -1, -1 if no suitable location found
1390 if (bestDistance == Float.MAX_VALUE) {
1391 bestXY[0] = -1;
1392 bestXY[1] = -1;
1393 }
1394 return bestXY;
1395 }
1396
1397 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001398 int[] direction, ItemConfiguration currentState) {
1399 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001400 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001401 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001402 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1403
Adam Cohen8baab352012-03-20 17:39:21 -07001404 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001405
1406 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001407 c.x = mTempLocation[0];
1408 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001409 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001410 }
Adam Cohen8baab352012-03-20 17:39:21 -07001411 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001412 return success;
1413 }
1414
Adam Cohenf3900c22012-11-16 18:28:11 -08001415 /**
1416 * This helper class defines a cluster of views. It helps with defining complex edges
1417 * of the cluster and determining how those edges interact with other views. The edges
1418 * essentially define a fine-grained boundary around the cluster of views -- like a more
1419 * precise version of a bounding box.
1420 */
1421 private class ViewCluster {
1422 final static int LEFT = 0;
1423 final static int TOP = 1;
1424 final static int RIGHT = 2;
1425 final static int BOTTOM = 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001426
Adam Cohenf3900c22012-11-16 18:28:11 -08001427 ArrayList<View> views;
1428 ItemConfiguration config;
1429 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001430
Adam Cohenf3900c22012-11-16 18:28:11 -08001431 int[] leftEdge = new int[mCountY];
1432 int[] rightEdge = new int[mCountY];
1433 int[] topEdge = new int[mCountX];
1434 int[] bottomEdge = new int[mCountX];
1435 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1436
1437 @SuppressWarnings("unchecked")
1438 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1439 this.views = (ArrayList<View>) views.clone();
1440 this.config = config;
1441 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001442 }
1443
Adam Cohenf3900c22012-11-16 18:28:11 -08001444 void resetEdges() {
1445 for (int i = 0; i < mCountX; i++) {
1446 topEdge[i] = -1;
1447 bottomEdge[i] = -1;
1448 }
1449 for (int i = 0; i < mCountY; i++) {
1450 leftEdge[i] = -1;
1451 rightEdge[i] = -1;
1452 }
1453 leftEdgeDirty = true;
1454 rightEdgeDirty = true;
1455 bottomEdgeDirty = true;
1456 topEdgeDirty = true;
1457 boundingRectDirty = true;
1458 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001459
Adam Cohenf3900c22012-11-16 18:28:11 -08001460 void computeEdge(int which, int[] edge) {
1461 int count = views.size();
1462 for (int i = 0; i < count; i++) {
1463 CellAndSpan cs = config.map.get(views.get(i));
1464 switch (which) {
1465 case LEFT:
1466 int left = cs.x;
1467 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1468 if (left < edge[j] || edge[j] < 0) {
1469 edge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001470 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001471 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001472 break;
1473 case RIGHT:
1474 int right = cs.x + cs.spanX;
1475 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1476 if (right > edge[j]) {
1477 edge[j] = right;
1478 }
1479 }
1480 break;
1481 case TOP:
1482 int top = cs.y;
1483 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1484 if (top < edge[j] || edge[j] < 0) {
1485 edge[j] = top;
1486 }
1487 }
1488 break;
1489 case BOTTOM:
1490 int bottom = cs.y + cs.spanY;
1491 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1492 if (bottom > edge[j]) {
1493 edge[j] = bottom;
1494 }
1495 }
1496 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001497 }
1498 }
1499 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001500
1501 boolean isViewTouchingEdge(View v, int whichEdge) {
1502 CellAndSpan cs = config.map.get(v);
1503
1504 int[] edge = getEdge(whichEdge);
1505
1506 switch (whichEdge) {
1507 case LEFT:
1508 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1509 if (edge[i] == cs.x + cs.spanX) {
1510 return true;
1511 }
1512 }
1513 break;
1514 case RIGHT:
1515 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1516 if (edge[i] == cs.x) {
1517 return true;
1518 }
1519 }
1520 break;
1521 case TOP:
1522 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1523 if (edge[i] == cs.y + cs.spanY) {
1524 return true;
1525 }
1526 }
1527 break;
1528 case BOTTOM:
1529 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1530 if (edge[i] == cs.y) {
1531 return true;
1532 }
1533 }
1534 break;
1535 }
1536 return false;
1537 }
1538
1539 void shift(int whichEdge, int delta) {
1540 for (View v: views) {
1541 CellAndSpan c = config.map.get(v);
1542 switch (whichEdge) {
1543 case LEFT:
1544 c.x -= delta;
1545 break;
1546 case RIGHT:
1547 c.x += delta;
1548 break;
1549 case TOP:
1550 c.y -= delta;
1551 break;
1552 case BOTTOM:
1553 default:
1554 c.y += delta;
1555 break;
1556 }
1557 }
1558 resetEdges();
1559 }
1560
1561 public void addView(View v) {
1562 views.add(v);
1563 resetEdges();
1564 }
1565
1566 public Rect getBoundingRect() {
1567 if (boundingRectDirty) {
1568 boolean first = true;
1569 for (View v: views) {
1570 CellAndSpan c = config.map.get(v);
1571 if (first) {
1572 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1573 first = false;
1574 } else {
1575 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1576 }
1577 }
1578 }
1579 return boundingRect;
1580 }
1581
1582 public int[] getEdge(int which) {
1583 switch (which) {
1584 case LEFT:
1585 return getLeftEdge();
1586 case RIGHT:
1587 return getRightEdge();
1588 case TOP:
1589 return getTopEdge();
1590 case BOTTOM:
1591 default:
1592 return getBottomEdge();
1593 }
1594 }
1595
1596 public int[] getLeftEdge() {
1597 if (leftEdgeDirty) {
1598 computeEdge(LEFT, leftEdge);
1599 }
1600 return leftEdge;
1601 }
1602
1603 public int[] getRightEdge() {
1604 if (rightEdgeDirty) {
1605 computeEdge(RIGHT, rightEdge);
1606 }
1607 return rightEdge;
1608 }
1609
1610 public int[] getTopEdge() {
1611 if (topEdgeDirty) {
1612 computeEdge(TOP, topEdge);
1613 }
1614 return topEdge;
1615 }
1616
1617 public int[] getBottomEdge() {
1618 if (bottomEdgeDirty) {
1619 computeEdge(BOTTOM, bottomEdge);
1620 }
1621 return bottomEdge;
1622 }
1623
1624 PositionComparator comparator = new PositionComparator();
1625 class PositionComparator implements Comparator<View> {
1626 int whichEdge = 0;
1627 public int compare(View left, View right) {
1628 CellAndSpan l = config.map.get(left);
1629 CellAndSpan r = config.map.get(right);
1630 switch (whichEdge) {
1631 case LEFT:
1632 return (r.x + r.spanX) - (l.x + l.spanX);
1633 case RIGHT:
1634 return l.x - r.x;
1635 case TOP:
1636 return (r.y + r.spanY) - (l.y + l.spanY);
1637 case BOTTOM:
1638 default:
1639 return l.y - r.y;
1640 }
1641 }
1642 }
1643
1644 public void sortConfigurationForEdgePush(int edge) {
1645 comparator.whichEdge = edge;
1646 Collections.sort(config.sortedViews, comparator);
1647 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001648 }
1649
Adam Cohenf3900c22012-11-16 18:28:11 -08001650 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1651 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001652
Adam Cohenf3900c22012-11-16 18:28:11 -08001653 ViewCluster cluster = new ViewCluster(views, currentState);
1654 Rect clusterRect = cluster.getBoundingRect();
1655 int whichEdge;
1656 int pushDistance;
1657 boolean fail = false;
1658
1659 // Determine the edge of the cluster that will be leading the push and how far
1660 // the cluster must be shifted.
1661 if (direction[0] < 0) {
1662 whichEdge = ViewCluster.LEFT;
1663 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001664 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001665 whichEdge = ViewCluster.RIGHT;
1666 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1667 } else if (direction[1] < 0) {
1668 whichEdge = ViewCluster.TOP;
1669 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1670 } else {
1671 whichEdge = ViewCluster.BOTTOM;
1672 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001673 }
1674
Adam Cohenf3900c22012-11-16 18:28:11 -08001675 // Break early for invalid push distance.
1676 if (pushDistance <= 0) {
1677 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001678 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001679
1680 // Mark the occupied state as false for the group of views we want to move.
1681 for (View v: views) {
1682 CellAndSpan c = currentState.map.get(v);
1683 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1684 }
1685
1686 // We save the current configuration -- if we fail to find a solution we will revert
1687 // to the initial state. The process of finding a solution modifies the configuration
1688 // in place, hence the need for revert in the failure case.
1689 currentState.save();
1690
1691 // The pushing algorithm is simplified by considering the views in the order in which
1692 // they would be pushed by the cluster. For example, if the cluster is leading with its
1693 // left edge, we consider sort the views by their right edge, from right to left.
1694 cluster.sortConfigurationForEdgePush(whichEdge);
1695
1696 while (pushDistance > 0 && !fail) {
1697 for (View v: currentState.sortedViews) {
1698 // For each view that isn't in the cluster, we see if the leading edge of the
1699 // cluster is contacting the edge of that view. If so, we add that view to the
1700 // cluster.
1701 if (!cluster.views.contains(v) && v != dragView) {
1702 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1703 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1704 if (!lp.canReorder) {
1705 // The push solution includes the all apps button, this is not viable.
1706 fail = true;
1707 break;
1708 }
1709 cluster.addView(v);
1710 CellAndSpan c = currentState.map.get(v);
1711
1712 // Adding view to cluster, mark it as not occupied.
1713 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1714 }
1715 }
1716 }
1717 pushDistance--;
1718
1719 // The cluster has been completed, now we move the whole thing over in the appropriate
1720 // direction.
1721 cluster.shift(whichEdge, 1);
1722 }
1723
1724 boolean foundSolution = false;
1725 clusterRect = cluster.getBoundingRect();
1726
1727 // Due to the nature of the algorithm, the only check required to verify a valid solution
1728 // is to ensure that completed shifted cluster lies completely within the cell layout.
1729 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1730 clusterRect.bottom <= mCountY) {
1731 foundSolution = true;
1732 } else {
1733 currentState.restore();
1734 }
1735
1736 // In either case, we set the occupied array as marked for the location of the views
1737 for (View v: cluster.views) {
1738 CellAndSpan c = currentState.map.get(v);
1739 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1740 }
1741
1742 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001743 }
1744
Adam Cohen482ed822012-03-02 14:15:13 -08001745 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001746 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001747 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001748
Adam Cohen8baab352012-03-20 17:39:21 -07001749 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001750 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001751 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001752 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001753 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001754 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001755 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001756 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001757 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001758 }
1759 }
Adam Cohen8baab352012-03-20 17:39:21 -07001760
Adam Cohen8baab352012-03-20 17:39:21 -07001761 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001762 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001763 CellAndSpan c = currentState.map.get(v);
1764 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1765 }
1766
Adam Cohen47a876d2012-03-19 13:21:41 -07001767 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1768 int top = boundingRect.top;
1769 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001770 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001771 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001772 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001773 CellAndSpan c = currentState.map.get(v);
1774 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001775 }
1776
Adam Cohen482ed822012-03-02 14:15:13 -08001777 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1778
Adam Cohenf3900c22012-11-16 18:28:11 -08001779 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1780 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001781
Adam Cohen8baab352012-03-20 17:39:21 -07001782 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001783 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001784 int deltaX = mTempLocation[0] - boundingRect.left;
1785 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001786 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001787 CellAndSpan c = currentState.map.get(v);
1788 c.x += deltaX;
1789 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001790 }
1791 success = true;
1792 }
Adam Cohen8baab352012-03-20 17:39:21 -07001793
1794 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001795 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001796 CellAndSpan c = currentState.map.get(v);
1797 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001798 }
1799 return success;
1800 }
1801
1802 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1803 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1804 }
1805
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001806 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1807 // to push items in each of the cardinal directions, in an order based on the direction vector
1808 // passed.
1809 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1810 int[] direction, View ignoreView, ItemConfiguration solution) {
1811 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001812 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001813 // separately in each of the components.
1814 int temp = direction[1];
1815 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001816
1817 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001818 ignoreView, solution)) {
1819 return true;
1820 }
1821 direction[1] = temp;
1822 temp = direction[0];
1823 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001824
1825 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001826 ignoreView, solution)) {
1827 return true;
1828 }
1829 // Revert the direction
1830 direction[0] = temp;
1831
1832 // Now we try pushing in each component of the opposite direction
1833 direction[0] *= -1;
1834 direction[1] *= -1;
1835 temp = direction[1];
1836 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001837 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001838 ignoreView, solution)) {
1839 return true;
1840 }
1841
1842 direction[1] = temp;
1843 temp = direction[0];
1844 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001845 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001846 ignoreView, solution)) {
1847 return true;
1848 }
1849 // revert the direction
1850 direction[0] = temp;
1851 direction[0] *= -1;
1852 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001853
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001854 } else {
1855 // If the direction vector has a single non-zero component, we push first in the
1856 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08001857 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001858 ignoreView, solution)) {
1859 return true;
1860 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001861 // Then we try the opposite direction
1862 direction[0] *= -1;
1863 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001864 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001865 ignoreView, solution)) {
1866 return true;
1867 }
1868 // Switch the direction back
1869 direction[0] *= -1;
1870 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001871
1872 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001873 // to find a solution by pushing along the perpendicular axis.
1874
1875 // Swap the components
1876 int temp = direction[1];
1877 direction[1] = direction[0];
1878 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08001879 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001880 ignoreView, solution)) {
1881 return true;
1882 }
1883
1884 // Then we try the opposite direction
1885 direction[0] *= -1;
1886 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001887 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001888 ignoreView, solution)) {
1889 return true;
1890 }
1891 // Switch the direction back
1892 direction[0] *= -1;
1893 direction[1] *= -1;
1894
1895 // Swap the components back
1896 temp = direction[1];
1897 direction[1] = direction[0];
1898 direction[0] = temp;
1899 }
1900 return false;
1901 }
1902
Adam Cohen482ed822012-03-02 14:15:13 -08001903 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001904 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001905 // Return early if get invalid cell positions
1906 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001907
Adam Cohen8baab352012-03-20 17:39:21 -07001908 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001909 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001910
Adam Cohen8baab352012-03-20 17:39:21 -07001911 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001912 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001913 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001914 if (c != null) {
1915 c.x = cellX;
1916 c.y = cellY;
1917 }
Adam Cohen482ed822012-03-02 14:15:13 -08001918 }
Adam Cohen482ed822012-03-02 14:15:13 -08001919 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1920 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001921 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001922 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001923 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001924 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001925 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001926 if (Rect.intersects(r0, r1)) {
1927 if (!lp.canReorder) {
1928 return false;
1929 }
1930 mIntersectingViews.add(child);
1931 }
1932 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001933
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001934 solution.intersectingViews = new ArrayList<View>(mIntersectingViews);
1935
Winson Chung5f8afe62013-08-12 16:19:28 -07001936 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001937 // we try to find a solution such that no displaced item travels through another item
1938 // without also displacing that item.
1939 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001940 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001941 return true;
1942 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001943
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001944 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08001945 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001946 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001947 return true;
1948 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001949
Adam Cohen482ed822012-03-02 14:15:13 -08001950 // Ok, they couldn't move as a block, let's move them individually
1951 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001952 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001953 return false;
1954 }
1955 }
1956 return true;
1957 }
1958
1959 /*
1960 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1961 * the provided point and the provided cell
1962 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001963 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001964 double angle = Math.atan(((float) deltaY) / deltaX);
1965
1966 result[0] = 0;
1967 result[1] = 0;
1968 if (Math.abs(Math.cos(angle)) > 0.5f) {
1969 result[0] = (int) Math.signum(deltaX);
1970 }
1971 if (Math.abs(Math.sin(angle)) > 0.5f) {
1972 result[1] = (int) Math.signum(deltaY);
1973 }
1974 }
1975
Adam Cohen8baab352012-03-20 17:39:21 -07001976 private void copyOccupiedArray(boolean[][] occupied) {
1977 for (int i = 0; i < mCountX; i++) {
1978 for (int j = 0; j < mCountY; j++) {
1979 occupied[i][j] = mOccupied[i][j];
1980 }
1981 }
1982 }
1983
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001984 private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001985 int spanX, int spanY, int[] direction, View dragView, boolean decX,
1986 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001987 // Copy the current state into the solution. This solution will be manipulated as necessary.
1988 copyCurrentStateToSolution(solution, false);
1989 // Copy the current occupied array into the temporary occupied array. This array will be
1990 // manipulated as necessary to find a solution.
1991 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001992
1993 // We find the nearest cell into which we would place the dragged item, assuming there's
1994 // nothing in its way.
1995 int result[] = new int[2];
1996 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1997
1998 boolean success = false;
1999 // First we try the exact nearest position of the item being dragged,
2000 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07002001 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
2002 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002003
2004 if (!success) {
2005 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
2006 // x, then 1 in y etc.
2007 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002008 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
2009 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002010 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002011 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
2012 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002013 }
2014 solution.isSolution = false;
2015 } else {
2016 solution.isSolution = true;
2017 solution.dragViewX = result[0];
2018 solution.dragViewY = result[1];
2019 solution.dragViewSpanX = spanX;
2020 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002021 }
2022 return solution;
2023 }
2024
2025 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002026 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002027 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002028 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002029 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002030 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08002031 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07002032 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002033 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07002034 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002035 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002036 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08002037 }
2038 }
2039
2040 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
2041 for (int i = 0; i < mCountX; i++) {
2042 for (int j = 0; j < mCountY; j++) {
2043 mTmpOccupied[i][j] = false;
2044 }
2045 }
2046
Michael Jurkaa52570f2012-03-20 03:18:20 -07002047 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002048 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002049 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002050 if (child == dragView) continue;
2051 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002052 CellAndSpan c = solution.map.get(child);
2053 if (c != null) {
2054 lp.tmpCellX = c.x;
2055 lp.tmpCellY = c.y;
2056 lp.cellHSpan = c.spanX;
2057 lp.cellVSpan = c.spanY;
2058 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002059 }
2060 }
2061 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2062 solution.dragViewSpanY, mTmpOccupied, true);
2063 }
2064
2065 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2066 commitDragView) {
2067
2068 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2069 for (int i = 0; i < mCountX; i++) {
2070 for (int j = 0; j < mCountY; j++) {
2071 occupied[i][j] = false;
2072 }
2073 }
2074
Michael Jurkaa52570f2012-03-20 03:18:20 -07002075 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002076 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002077 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002078 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002079 CellAndSpan c = solution.map.get(child);
2080 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002081 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2082 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002083 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002084 }
2085 }
2086 if (commitDragView) {
2087 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2088 solution.dragViewSpanY, occupied, true);
2089 }
2090 }
2091
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002092
2093 // This method starts or changes the reorder preview animations
2094 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
2095 View dragView, int delay, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07002096 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002097 for (int i = 0; i < childCount; i++) {
2098 View child = mShortcutsAndWidgets.getChildAt(i);
2099 if (child == dragView) continue;
2100 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002101 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
2102 != null && !solution.intersectingViews.contains(child);
2103
Adam Cohen19f37922012-03-21 11:59:11 -07002104 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002105 if (c != null && !skip) {
2106 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
2107 lp.cellY, c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002108 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002109 }
2110 }
2111 }
2112
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002113 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07002114 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002115 class ReorderPreviewAnimation {
Adam Cohen19f37922012-03-21 11:59:11 -07002116 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002117 float finalDeltaX;
2118 float finalDeltaY;
2119 float initDeltaX;
2120 float initDeltaY;
2121 float finalScale;
2122 float initScale;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002123 int mode;
2124 boolean repeating = false;
2125 private static final int PREVIEW_DURATION = 300;
2126 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
2127
2128 public static final int MODE_HINT = 0;
2129 public static final int MODE_PREVIEW = 1;
2130
Adam Cohene7587d22012-05-24 18:50:02 -07002131 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002132
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002133 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
2134 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07002135 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2136 final int x0 = mTmpPoint[0];
2137 final int y0 = mTmpPoint[1];
2138 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2139 final int x1 = mTmpPoint[0];
2140 final int y1 = mTmpPoint[1];
2141 final int dX = x1 - x0;
2142 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002143 finalDeltaX = 0;
2144 finalDeltaY = 0;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002145 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07002146 if (dX == dY && dX == 0) {
2147 } else {
2148 if (dY == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002149 finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002150 } else if (dX == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002151 finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002152 } else {
2153 double angle = Math.atan( (float) (dY) / dX);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002154 finalDeltaX = (int) (- dir * Math.signum(dX) *
2155 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
2156 finalDeltaY = (int) (- dir * Math.signum(dY) *
2157 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002158 }
2159 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002160 this.mode = mode;
Adam Cohend024f982012-05-23 18:26:45 -07002161 initDeltaX = child.getTranslationX();
2162 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002163 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002164 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002165 this.child = child;
2166 }
2167
Adam Cohend024f982012-05-23 18:26:45 -07002168 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002169 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002170 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002171 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002172 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002173 if (finalDeltaX == 0 && finalDeltaY == 0) {
2174 completeAnimationImmediately();
2175 return;
2176 }
Adam Cohen19f37922012-03-21 11:59:11 -07002177 }
Adam Cohend024f982012-05-23 18:26:45 -07002178 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002179 return;
2180 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002181 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002182 a = va;
Adam Cohen19f37922012-03-21 11:59:11 -07002183 va.setRepeatMode(ValueAnimator.REVERSE);
2184 va.setRepeatCount(ValueAnimator.INFINITE);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002185 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002186 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002187 va.addUpdateListener(new AnimatorUpdateListener() {
2188 @Override
2189 public void onAnimationUpdate(ValueAnimator animation) {
2190 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002191 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2192 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2193 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002194 child.setTranslationX(x);
2195 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002196 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002197 child.setScaleX(s);
2198 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002199 }
2200 });
2201 va.addListener(new AnimatorListenerAdapter() {
2202 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002203 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002204 initDeltaX = 0;
2205 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002206 initScale = getChildrenScale();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002207 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002208 }
2209 });
Adam Cohen19f37922012-03-21 11:59:11 -07002210 mShakeAnimators.put(child, this);
2211 va.start();
2212 }
2213
Adam Cohend024f982012-05-23 18:26:45 -07002214 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002215 if (a != null) {
2216 a.cancel();
2217 }
Adam Cohen19f37922012-03-21 11:59:11 -07002218 }
Adam Cohene7587d22012-05-24 18:50:02 -07002219
Adam Cohen091440a2015-03-18 14:16:05 -07002220 @Thunk void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002221 if (a != null) {
2222 a.cancel();
2223 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002224
Michael Jurka2ecf9952012-06-18 12:52:28 -07002225 AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002226 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002227 s.playTogether(
Adam Cohen307fe232012-08-16 17:55:58 -07002228 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
2229 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
Michael Jurka2ecf9952012-06-18 12:52:28 -07002230 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2231 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002232 );
2233 s.setDuration(REORDER_ANIMATION_DURATION);
2234 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2235 s.start();
2236 }
Adam Cohen19f37922012-03-21 11:59:11 -07002237 }
2238
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002239 private void completeAndClearReorderPreviewAnimations() {
2240 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002241 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002242 }
2243 mShakeAnimators.clear();
2244 }
2245
Adam Cohen482ed822012-03-02 14:15:13 -08002246 private void commitTempPlacement() {
2247 for (int i = 0; i < mCountX; i++) {
2248 for (int j = 0; j < mCountY; j++) {
2249 mOccupied[i][j] = mTmpOccupied[i][j];
2250 }
2251 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002252 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002253 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002254 View child = mShortcutsAndWidgets.getChildAt(i);
2255 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2256 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002257 // We do a null check here because the item info can be null in the case of the
2258 // AllApps button in the hotseat.
2259 if (info != null) {
Adam Cohen487f7dd2012-06-28 18:12:10 -07002260 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
2261 info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
2262 info.requiresDbUpdate = true;
2263 }
Adam Cohen2acce882012-03-28 19:03:19 -07002264 info.cellX = lp.cellX = lp.tmpCellX;
2265 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002266 info.spanX = lp.cellHSpan;
2267 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002268 }
Adam Cohen482ed822012-03-02 14:15:13 -08002269 }
Adam Cohen2acce882012-03-28 19:03:19 -07002270 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002271 }
2272
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002273 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002274 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002275 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002276 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002277 lp.useTmpCoords = useTempCoords;
2278 }
2279 }
2280
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002281 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002282 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2283 int[] result = new int[2];
2284 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002285 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08002286 resultSpan);
2287 if (result[0] >= 0 && result[1] >= 0) {
2288 copyCurrentStateToSolution(solution, false);
2289 solution.dragViewX = result[0];
2290 solution.dragViewY = result[1];
2291 solution.dragViewSpanX = resultSpan[0];
2292 solution.dragViewSpanY = resultSpan[1];
2293 solution.isSolution = true;
2294 } else {
2295 solution.isSolution = false;
2296 }
2297 return solution;
2298 }
2299
2300 public void prepareChildForDrag(View child) {
2301 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002302 }
2303
Adam Cohen19f37922012-03-21 11:59:11 -07002304 /* This seems like it should be obvious and straight-forward, but when the direction vector
2305 needs to match with the notion of the dragView pushing other views, we have to employ
2306 a slightly more subtle notion of the direction vector. The question is what two points is
2307 the vector between? The center of the dragView and its desired destination? Not quite, as
2308 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2309 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2310 or right, which helps make pushing feel right.
2311 */
2312 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2313 int spanY, View dragView, int[] resultDirection) {
2314 int[] targetDestination = new int[2];
2315
2316 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2317 Rect dragRect = new Rect();
2318 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2319 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2320
2321 Rect dropRegionRect = new Rect();
2322 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2323 dragView, dropRegionRect, mIntersectingViews);
2324
2325 int dropRegionSpanX = dropRegionRect.width();
2326 int dropRegionSpanY = dropRegionRect.height();
2327
2328 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2329 dropRegionRect.height(), dropRegionRect);
2330
2331 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2332 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2333
2334 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2335 deltaX = 0;
2336 }
2337 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2338 deltaY = 0;
2339 }
2340
2341 if (deltaX == 0 && deltaY == 0) {
2342 // No idea what to do, give a random direction.
2343 resultDirection[0] = 1;
2344 resultDirection[1] = 0;
2345 } else {
2346 computeDirectionVector(deltaX, deltaY, resultDirection);
2347 }
2348 }
2349
2350 // For a given cell and span, fetch the set of views intersecting the region.
2351 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2352 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2353 if (boundingRect != null) {
2354 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2355 }
2356 intersectingViews.clear();
2357 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2358 Rect r1 = new Rect();
2359 final int count = mShortcutsAndWidgets.getChildCount();
2360 for (int i = 0; i < count; i++) {
2361 View child = mShortcutsAndWidgets.getChildAt(i);
2362 if (child == dragView) continue;
2363 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2364 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2365 if (Rect.intersects(r0, r1)) {
2366 mIntersectingViews.add(child);
2367 if (boundingRect != null) {
2368 boundingRect.union(r1);
2369 }
2370 }
2371 }
2372 }
2373
2374 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2375 View dragView, int[] result) {
2376 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2377 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2378 mIntersectingViews);
2379 return !mIntersectingViews.isEmpty();
2380 }
2381
2382 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002383 completeAndClearReorderPreviewAnimations();
2384 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2385 final int count = mShortcutsAndWidgets.getChildCount();
2386 for (int i = 0; i < count; i++) {
2387 View child = mShortcutsAndWidgets.getChildAt(i);
2388 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2389 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2390 lp.tmpCellX = lp.cellX;
2391 lp.tmpCellY = lp.cellY;
2392 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2393 0, false, false);
2394 }
Adam Cohen19f37922012-03-21 11:59:11 -07002395 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002396 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002397 }
Adam Cohen19f37922012-03-21 11:59:11 -07002398 }
2399
Adam Cohenbebf0422012-04-11 18:06:28 -07002400 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2401 View dragView, int[] direction, boolean commit) {
2402 int[] pixelXY = new int[2];
2403 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2404
2405 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002406 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002407 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2408
2409 setUseTempCoords(true);
2410 if (swapSolution != null && swapSolution.isSolution) {
2411 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2412 // committing anything or animating anything as we just want to determine if a solution
2413 // exists
2414 copySolutionToTempState(swapSolution, dragView);
2415 setItemPlacementDirty(true);
2416 animateItemsToSolution(swapSolution, dragView, commit);
2417
2418 if (commit) {
2419 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002420 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002421 setItemPlacementDirty(false);
2422 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002423 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2424 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002425 }
2426 mShortcutsAndWidgets.requestLayout();
2427 }
2428 return swapSolution.isSolution;
2429 }
2430
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002431 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002432 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002433 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002434 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002435
2436 if (resultSpan == null) {
2437 resultSpan = new int[2];
2438 }
2439
Adam Cohen19f37922012-03-21 11:59:11 -07002440 // When we are checking drop validity or actually dropping, we don't recompute the
2441 // direction vector, since we want the solution to match the preview, and it's possible
2442 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002443 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2444 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002445 mDirectionVector[0] = mPreviousReorderDirection[0];
2446 mDirectionVector[1] = mPreviousReorderDirection[1];
2447 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002448 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2449 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2450 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002451 }
2452 } else {
2453 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2454 mPreviousReorderDirection[0] = mDirectionVector[0];
2455 mPreviousReorderDirection[1] = mDirectionVector[1];
2456 }
2457
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002458 // Find a solution involving pushing / displacing any items in the way
2459 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002460 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2461
2462 // We attempt the approach which doesn't shuffle views at all
2463 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2464 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2465
2466 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002467
2468 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2469 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002470 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2471 finalSolution = swapSolution;
2472 } else if (noShuffleSolution.isSolution) {
2473 finalSolution = noShuffleSolution;
2474 }
2475
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002476 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002477 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002478 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2479 ReorderPreviewAnimation.MODE_HINT);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002480 result[0] = finalSolution.dragViewX;
2481 result[1] = finalSolution.dragViewY;
2482 resultSpan[0] = finalSolution.dragViewSpanX;
2483 resultSpan[1] = finalSolution.dragViewSpanY;
2484 } else {
2485 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2486 }
2487 return result;
2488 }
2489
Adam Cohen482ed822012-03-02 14:15:13 -08002490 boolean foundSolution = true;
2491 if (!DESTRUCTIVE_REORDER) {
2492 setUseTempCoords(true);
2493 }
2494
2495 if (finalSolution != null) {
2496 result[0] = finalSolution.dragViewX;
2497 result[1] = finalSolution.dragViewY;
2498 resultSpan[0] = finalSolution.dragViewSpanX;
2499 resultSpan[1] = finalSolution.dragViewSpanY;
2500
2501 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2502 // committing anything or animating anything as we just want to determine if a solution
2503 // exists
2504 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2505 if (!DESTRUCTIVE_REORDER) {
2506 copySolutionToTempState(finalSolution, dragView);
2507 }
2508 setItemPlacementDirty(true);
2509 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2510
Adam Cohen19f37922012-03-21 11:59:11 -07002511 if (!DESTRUCTIVE_REORDER &&
2512 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002513 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002514 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002515 setItemPlacementDirty(false);
2516 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002517 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2518 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002519 }
2520 }
2521 } else {
2522 foundSolution = false;
2523 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2524 }
2525
2526 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2527 setUseTempCoords(false);
2528 }
Adam Cohen482ed822012-03-02 14:15:13 -08002529
Michael Jurkaa52570f2012-03-20 03:18:20 -07002530 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002531 return result;
2532 }
2533
Adam Cohen19f37922012-03-21 11:59:11 -07002534 void setItemPlacementDirty(boolean dirty) {
2535 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002536 }
Adam Cohen19f37922012-03-21 11:59:11 -07002537 boolean isItemPlacementDirty() {
2538 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002539 }
2540
Adam Cohen091440a2015-03-18 14:16:05 -07002541 @Thunk class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002542 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002543 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2544 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002545 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002546 boolean isSolution = false;
2547 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2548
Adam Cohenf3900c22012-11-16 18:28:11 -08002549 void save() {
2550 // Copy current state into savedMap
2551 for (View v: map.keySet()) {
2552 map.get(v).copy(savedMap.get(v));
2553 }
2554 }
2555
2556 void restore() {
2557 // Restore current state from savedMap
2558 for (View v: savedMap.keySet()) {
2559 savedMap.get(v).copy(map.get(v));
2560 }
2561 }
2562
2563 void add(View v, CellAndSpan cs) {
2564 map.put(v, cs);
2565 savedMap.put(v, new CellAndSpan());
2566 sortedViews.add(v);
2567 }
2568
Adam Cohen482ed822012-03-02 14:15:13 -08002569 int area() {
2570 return dragViewSpanX * dragViewSpanY;
2571 }
Adam Cohen8baab352012-03-20 17:39:21 -07002572 }
2573
2574 private class CellAndSpan {
2575 int x, y;
2576 int spanX, spanY;
2577
Adam Cohenf3900c22012-11-16 18:28:11 -08002578 public CellAndSpan() {
2579 }
2580
2581 public void copy(CellAndSpan copy) {
2582 copy.x = x;
2583 copy.y = y;
2584 copy.spanX = spanX;
2585 copy.spanY = spanY;
2586 }
2587
Adam Cohen8baab352012-03-20 17:39:21 -07002588 public CellAndSpan(int x, int y, int spanX, int spanY) {
2589 this.x = x;
2590 this.y = y;
2591 this.spanX = spanX;
2592 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002593 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002594
2595 public String toString() {
2596 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2597 }
2598
Adam Cohen482ed822012-03-02 14:15:13 -08002599 }
2600
Adam Cohendf035382011-04-11 17:22:04 -07002601 /**
Adam Cohendf035382011-04-11 17:22:04 -07002602 * Find a starting cell position that will fit the given bounds nearest the requested
2603 * cell location. Uses Euclidean distance to score multiple vacant areas.
2604 *
2605 * @param pixelX The X location at which you want to search for a vacant area.
2606 * @param pixelY The Y location at which you want to search for a vacant area.
2607 * @param spanX Horizontal span of the object.
2608 * @param spanY Vertical span of the object.
2609 * @param ignoreView Considers space occupied by this view as unoccupied
2610 * @param result Previously returned value to possibly recycle.
2611 * @return The X, Y cell of a vacant area that can contain this object,
2612 * nearest the requested location.
2613 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002614 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2615 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002616 }
2617
Michael Jurka0280c3b2010-09-17 15:00:07 -07002618 boolean existsEmptyCell() {
2619 return findCellForSpan(null, 1, 1);
2620 }
2621
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002622 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002623 * Finds the upper-left coordinate of the first rectangle in the grid that can
2624 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2625 * then this method will only return coordinates for rectangles that contain the cell
2626 * (intersectX, intersectY)
2627 *
2628 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2629 * can be found.
2630 * @param spanX The horizontal span of the cell we want to find.
2631 * @param spanY The vertical span of the cell we want to find.
2632 *
2633 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002634 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002635 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Michael Jurka28750fb2010-09-24 17:43:49 -07002636 boolean foundCell = false;
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002637 final int endX = mCountX - (spanX - 1);
2638 final int endY = mCountY - (spanY - 1);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002639
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002640 for (int y = 0; y < endY && !foundCell; y++) {
2641 inner:
2642 for (int x = 0; x < endX; x++) {
2643 for (int i = 0; i < spanX; i++) {
2644 for (int j = 0; j < spanY; j++) {
2645 if (mOccupied[x + i][y + j]) {
2646 // small optimization: we can skip to after the column we just found
2647 // an occupied cell
2648 x += i;
2649 continue inner;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002650 }
2651 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002652 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002653 if (cellXY != null) {
2654 cellXY[0] = x;
2655 cellXY[1] = y;
2656 }
2657 foundCell = true;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002658 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002659 }
2660 }
2661
Michael Jurka28750fb2010-09-24 17:43:49 -07002662 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002663 }
2664
2665 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002666 * A drag event has begun over this layout.
2667 * It may have begun over this layout (in which case onDragChild is called first),
2668 * or it may have begun on another layout.
2669 */
2670 void onDragEnter() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002671 mDragEnforcer.onDragEnter();
Winson Chungc07918d2011-07-01 15:35:26 -07002672 mDragging = true;
2673 }
2674
2675 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002676 * Called when drag has left this CellLayout or has been completed (successfully or not)
2677 */
2678 void onDragExit() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002679 mDragEnforcer.onDragExit();
Joe Onorato4be866d2010-10-10 11:26:02 -07002680 // This can actually be called when we aren't in a drag, e.g. when adding a new
2681 // item to this layout via the customize drawer.
2682 // Guard against that case.
2683 if (mDragging) {
2684 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002685 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002686
2687 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002688 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002689 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2690 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002691 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002692 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002693 }
2694
2695 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002696 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002697 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002698 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002699 *
2700 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002701 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002702 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002703 if (child != null) {
2704 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002705 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002706 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002707 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002708 }
2709
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002710 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002711 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002712 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002713 * @param cellX X coordinate of upper left corner expressed as a cell position
2714 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002715 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002716 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002717 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002718 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002719 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002720 final int cellWidth = mCellWidth;
2721 final int cellHeight = mCellHeight;
2722 final int widthGap = mWidthGap;
2723 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002724
Winson Chung4b825dcd2011-06-19 12:41:22 -07002725 final int hStartPadding = getPaddingLeft();
2726 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002727
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002728 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2729 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2730
2731 int x = hStartPadding + cellX * (cellWidth + widthGap);
2732 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002733
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002734 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002735 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002736
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002737 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002738 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002739 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002740 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002741 * @param width Width in pixels
2742 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002743 * @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 -08002744 */
Winson Chung66700732013-08-20 16:56:15 -07002745 public static int[] rectToCell(int width, int height, int[] result) {
Winson Chung5f8afe62013-08-12 16:19:28 -07002746 LauncherAppState app = LauncherAppState.getInstance();
2747 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Winson Chung66700732013-08-20 16:56:15 -07002748 Rect padding = grid.getWorkspacePadding(grid.isLandscape ?
2749 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
Winson Chung5f8afe62013-08-12 16:19:28 -07002750
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002751 // Always assume we're working with the smallest span to make sure we
2752 // reserve enough space in both orientations.
Winson Chung66700732013-08-20 16:56:15 -07002753 int parentWidth = grid.calculateCellWidth(grid.widthPx
2754 - padding.left - padding.right, (int) grid.numColumns);
2755 int parentHeight = grid.calculateCellHeight(grid.heightPx
2756 - padding.top - padding.bottom, (int) grid.numRows);
2757 int smallerSize = Math.min(parentWidth, parentHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002758
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002759 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002760 int spanX = (int) Math.ceil(width / (float) smallerSize);
2761 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002762
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002763 if (result == null) {
2764 return new int[] { spanX, spanY };
2765 }
2766 result[0] = spanX;
2767 result[1] = spanY;
2768 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002769 }
2770
2771 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08002772 * Calculate the grid spans needed to fit given item
2773 */
2774 public void calculateSpans(ItemInfo info) {
2775 final int minWidth;
2776 final int minHeight;
2777
2778 if (info instanceof LauncherAppWidgetInfo) {
2779 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
2780 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
2781 } else if (info instanceof PendingAddWidgetInfo) {
2782 minWidth = ((PendingAddWidgetInfo) info).minWidth;
2783 minHeight = ((PendingAddWidgetInfo) info).minHeight;
2784 } else {
2785 // It's not a widget, so it must be 1x1
2786 info.spanX = info.spanY = 1;
2787 return;
2788 }
2789 int[] spans = rectToCell(minWidth, minHeight, null);
2790 info.spanX = spans[0];
2791 info.spanY = spans[1];
2792 }
2793
Michael Jurka0280c3b2010-09-17 15:00:07 -07002794 private void clearOccupiedCells() {
2795 for (int x = 0; x < mCountX; x++) {
2796 for (int y = 0; y < mCountY; y++) {
2797 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002798 }
2799 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002800 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002801
Adam Cohend4844c32011-02-18 19:25:06 -08002802 public void markCellsAsOccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002803 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002804 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002805 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002806 }
2807
Adam Cohend4844c32011-02-18 19:25:06 -08002808 public void markCellsAsUnoccupiedForView(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, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002812 }
2813
Adam Cohen482ed822012-03-02 14:15:13 -08002814 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2815 boolean value) {
2816 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002817 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2818 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002819 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002820 }
2821 }
2822 }
2823
Adam Cohen2801caf2011-05-13 20:57:39 -07002824 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002825 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002826 (Math.max((mCountX - 1), 0) * mWidthGap);
2827 }
2828
2829 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002830 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002831 (Math.max((mCountY - 1), 0) * mHeightGap);
2832 }
2833
Michael Jurka66d72172011-04-12 16:29:25 -07002834 public boolean isOccupied(int x, int y) {
2835 if (x < mCountX && y < mCountY) {
2836 return mOccupied[x][y];
2837 } else {
2838 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2839 }
2840 }
2841
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002842 @Override
2843 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2844 return new CellLayout.LayoutParams(getContext(), attrs);
2845 }
2846
2847 @Override
2848 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2849 return p instanceof CellLayout.LayoutParams;
2850 }
2851
2852 @Override
2853 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2854 return new CellLayout.LayoutParams(p);
2855 }
2856
2857 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2858 /**
2859 * Horizontal location of the item in the grid.
2860 */
2861 @ViewDebug.ExportedProperty
2862 public int cellX;
2863
2864 /**
2865 * Vertical location of the item in the grid.
2866 */
2867 @ViewDebug.ExportedProperty
2868 public int cellY;
2869
2870 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002871 * Temporary horizontal location of the item in the grid during reorder
2872 */
2873 public int tmpCellX;
2874
2875 /**
2876 * Temporary vertical location of the item in the grid during reorder
2877 */
2878 public int tmpCellY;
2879
2880 /**
2881 * Indicates that the temporary coordinates should be used to layout the items
2882 */
2883 public boolean useTmpCoords;
2884
2885 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002886 * Number of cells spanned horizontally by the item.
2887 */
2888 @ViewDebug.ExportedProperty
2889 public int cellHSpan;
2890
2891 /**
2892 * Number of cells spanned vertically by the item.
2893 */
2894 @ViewDebug.ExportedProperty
2895 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002896
Adam Cohen1b607ed2011-03-03 17:26:50 -08002897 /**
2898 * Indicates whether the item will set its x, y, width and height parameters freely,
2899 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2900 */
Adam Cohend4844c32011-02-18 19:25:06 -08002901 public boolean isLockedToGrid = true;
2902
Adam Cohen482ed822012-03-02 14:15:13 -08002903 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07002904 * Indicates that this item should use the full extents of its parent.
2905 */
2906 public boolean isFullscreen = false;
2907
2908 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002909 * Indicates whether this item can be reordered. Always true except in the case of the
2910 * the AllApps button.
2911 */
2912 public boolean canReorder = true;
2913
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002914 // X coordinate of the view in the layout.
2915 @ViewDebug.ExportedProperty
2916 int x;
2917 // Y coordinate of the view in the layout.
2918 @ViewDebug.ExportedProperty
2919 int y;
2920
Romain Guy84f296c2009-11-04 15:00:44 -08002921 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002922
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002923 public LayoutParams(Context c, AttributeSet attrs) {
2924 super(c, attrs);
2925 cellHSpan = 1;
2926 cellVSpan = 1;
2927 }
2928
2929 public LayoutParams(ViewGroup.LayoutParams source) {
2930 super(source);
2931 cellHSpan = 1;
2932 cellVSpan = 1;
2933 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002934
2935 public LayoutParams(LayoutParams source) {
2936 super(source);
2937 this.cellX = source.cellX;
2938 this.cellY = source.cellY;
2939 this.cellHSpan = source.cellHSpan;
2940 this.cellVSpan = source.cellVSpan;
2941 }
2942
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002943 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002944 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002945 this.cellX = cellX;
2946 this.cellY = cellY;
2947 this.cellHSpan = cellHSpan;
2948 this.cellVSpan = cellVSpan;
2949 }
2950
Adam Cohen2374abf2013-04-16 14:56:57 -07002951 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
2952 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08002953 if (isLockedToGrid) {
2954 final int myCellHSpan = cellHSpan;
2955 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07002956 int myCellX = useTmpCoords ? tmpCellX : cellX;
2957 int myCellY = useTmpCoords ? tmpCellY : cellY;
2958
2959 if (invertHorizontally) {
2960 myCellX = colCount - myCellX - cellHSpan;
2961 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08002962
Adam Cohend4844c32011-02-18 19:25:06 -08002963 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2964 leftMargin - rightMargin;
2965 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2966 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002967 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2968 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002969 }
2970 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002971
Winson Chungaafa03c2010-06-11 17:34:16 -07002972 public String toString() {
2973 return "(" + this.cellX + ", " + this.cellY + ")";
2974 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002975
2976 public void setWidth(int width) {
2977 this.width = width;
2978 }
2979
2980 public int getWidth() {
2981 return width;
2982 }
2983
2984 public void setHeight(int height) {
2985 this.height = height;
2986 }
2987
2988 public int getHeight() {
2989 return height;
2990 }
2991
2992 public void setX(int x) {
2993 this.x = x;
2994 }
2995
2996 public int getX() {
2997 return x;
2998 }
2999
3000 public void setY(int y) {
3001 this.y = y;
3002 }
3003
3004 public int getY() {
3005 return y;
3006 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003007 }
3008
Michael Jurka0280c3b2010-09-17 15:00:07 -07003009 // This class stores info for two purposes:
3010 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
3011 // its spanX, spanY, and the screen it is on
3012 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
3013 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
3014 // the CellLayout that was long clicked
Michael Jurkae5fb0f22011-04-11 13:27:46 -07003015 static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003016 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07003017 int cellX = -1;
3018 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003019 int spanX;
3020 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07003021 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07003022 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003023
Adam Cohene0aaa0d2014-05-12 12:44:22 -07003024 CellInfo(View v, ItemInfo info) {
3025 cell = v;
3026 cellX = info.cellX;
3027 cellY = info.cellY;
3028 spanX = info.spanX;
3029 spanY = info.spanY;
3030 screenId = info.screenId;
3031 container = info.container;
3032 }
3033
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003034 @Override
3035 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07003036 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
3037 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003038 }
3039 }
Michael Jurkad771c962011-08-09 15:00:48 -07003040
3041 public boolean lastDownOnOccupiedCell() {
3042 return mLastDownOnOccupiedCell;
3043 }
Sunny Goyala9116722015-04-29 13:55:58 -07003044
3045 public boolean findVacantCell(int spanX, int spanY, int[] outXY) {
3046 return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied);
3047 }
Sunny Goyal9ca9c132015-04-29 14:57:22 -07003048
3049 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
3050 int x2 = x + spanX - 1;
3051 int y2 = y + spanY - 1;
3052 if (x < 0 || y < 0 || x2 >= mCountX || y2 >= mCountY) {
3053 return false;
3054 }
3055 for (int i = x; i <= x2; i++) {
3056 for (int j = y; j <= y2; j++) {
3057 if (mOccupied[i][j]) {
3058 return false;
3059 }
3060 }
3061 }
3062
3063 return true;
3064 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003065}