blob: 48ddfe6bf3b31d7f439f32f32f53e57ed53d66b2 [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 Cohenf34bab52010-09-30 14:11:56 -0700106
Michael Jurka33945b22010-12-21 18:19:38 -0800107 private Drawable mNormalBackground;
Michael Jurka33945b22010-12-21 18:19:38 -0800108 private Drawable mActiveGlowBackground;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700109 private Drawable mOverScrollForegroundDrawable;
110 private Drawable mOverScrollLeft;
111 private Drawable mOverScrollRight;
Michael Jurka18014792010-10-14 09:01:34 -0700112 private Rect mBackgroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700113 private Rect mForegroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700114 private int mForegroundPadding;
Patrick Dubroy1262e362010-10-06 15:49:50 -0700115
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700116 // These values allow a fixed measurement to be set on the CellLayout.
117 private int mFixedWidth = -1;
118 private int mFixedHeight = -1;
119
Michael Jurka33945b22010-12-21 18:19:38 -0800120 // If we're actively dragging something over this screen, mIsDragOverlapping is true
121 private boolean mIsDragOverlapping = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700122
Winson Chung150fbab2010-09-29 17:14:26 -0700123 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700124 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohen091440a2015-03-18 14:16:05 -0700125 @Thunk Rect[] mDragOutlines = new Rect[4];
126 @Thunk float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700127 private InterruptibleInOutAnimator[] mDragOutlineAnims =
128 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700129
130 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700131 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700132 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700133
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700134 private final ClickShadowView mTouchFeedbackView;
Patrick Dubroy96864c32011-03-10 17:17:23 -0800135
Adam Cohen091440a2015-03-18 14:16:05 -0700136 @Thunk HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
Adam Cohen482ed822012-03-02 14:15:13 -0800137 HashMap<CellLayout.LayoutParams, Animator>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800138 private HashMap<View, ReorderPreviewAnimation>
139 mShakeAnimators = new HashMap<View, ReorderPreviewAnimation>();
Adam Cohen19f37922012-03-21 11:59:11 -0700140
141 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700142
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700143 // When a drag operation is in progress, holds the nearest cell to the touch point
144 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800145
Joe Onorato4be866d2010-10-10 11:26:02 -0700146 private boolean mDragging = false;
147
Patrick Dubroyce34a972010-10-19 10:34:32 -0700148 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700149 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700150
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800151 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700152 private float mHotseatScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800153
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800154 public static final int MODE_SHOW_REORDER_HINT = 0;
155 public static final int MODE_DRAG_OVER = 1;
156 public static final int MODE_ON_DROP = 2;
157 public static final int MODE_ON_DROP_EXTERNAL = 3;
158 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700159 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800160 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
161
Adam Cohena897f392012-04-27 18:12:05 -0700162 static final int LANDSCAPE = 0;
163 static final int PORTRAIT = 1;
164
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800165 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700166 private static final int REORDER_ANIMATION_DURATION = 150;
Adam Cohen091440a2015-03-18 14:16:05 -0700167 @Thunk float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700168
Adam Cohen482ed822012-03-02 14:15:13 -0800169 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
170 private Rect mOccupiedRect = new Rect();
171 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700172 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700173 private static final int INVALID_DIRECTION = -100;
Adam Cohenc6cc61d2012-04-04 12:47:08 -0700174 private DropTarget.DragEnforcer mDragEnforcer;
Adam Cohen482ed822012-03-02 14:15:13 -0800175
Winson Chung3a6e7f32013-10-09 15:50:52 -0700176 private Rect mTempRect = new Rect();
177
Michael Jurkaca993832012-06-29 15:17:04 -0700178 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700179
Adam Cohenc9735cf2015-01-23 16:11:55 -0800180 // Related to accessible drag and drop
Sunny Goyale9b651e2015-04-24 11:44:51 -0700181 private DragAndDropAccessibilityDelegate mTouchHelper;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800182 private boolean mUseTouchHelper = false;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800183
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800184 public CellLayout(Context context) {
185 this(context, null);
186 }
187
188 public CellLayout(Context context, AttributeSet attrs) {
189 this(context, attrs, 0);
190 }
191
192 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
193 super(context, attrs, defStyle);
Michael Jurka8b805b12012-04-18 14:23:14 -0700194 mDragEnforcer = new DropTarget.DragEnforcer(context);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700195
196 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
197 // the user where a dragged item will land when dropped.
198 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800199 setClipToPadding(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700200 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700201
Winson Chung892c74d2013-08-22 16:15:50 -0700202 LauncherAppState app = LauncherAppState.getInstance();
203 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800204 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
205
Winson Chung11a1a532013-09-13 11:14:45 -0700206 mCellWidth = mCellHeight = -1;
Nilesh Agrawal5f7099a2014-01-02 15:54:57 -0800207 mFixedCellWidth = mFixedCellHeight = -1;
Winson Chung5f8afe62013-08-12 16:19:28 -0700208 mWidthGap = mOriginalWidthGap = 0;
209 mHeightGap = mOriginalHeightGap = 0;
210 mMaxGap = Integer.MAX_VALUE;
Winson Chung892c74d2013-08-22 16:15:50 -0700211 mCountX = (int) grid.numColumns;
212 mCountY = (int) grid.numRows;
Michael Jurka0280c3b2010-09-17 15:00:07 -0700213 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800214 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700215 mPreviousReorderDirection[0] = INVALID_DIRECTION;
216 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800217
218 a.recycle();
219
220 setAlwaysDrawnWithCacheEnabled(false);
221
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700222 final Resources res = getResources();
Winson Chung6e1c0d32013-10-25 15:24:24 -0700223 mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700224
Adam Cohen410f3cd2013-09-22 12:09:32 -0700225 mNormalBackground = res.getDrawable(R.drawable.screenpanel);
226 mActiveGlowBackground = res.getDrawable(R.drawable.screenpanel_hover);
Michael Jurka33945b22010-12-21 18:19:38 -0800227
Adam Cohenb5ba0972011-09-07 18:02:31 -0700228 mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
229 mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
230 mForegroundPadding =
231 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
Michael Jurka33945b22010-12-21 18:19:38 -0800232
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800233 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
Winson Chung5f8afe62013-08-12 16:19:28 -0700234 grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700235
Winson Chungb26f3d62011-06-02 10:49:29 -0700236 mNormalBackground.setFilterBitmap(true);
Winson Chungb26f3d62011-06-02 10:49:29 -0700237 mActiveGlowBackground.setFilterBitmap(true);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700238
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700239 // Initialize the data structures used for the drag visualization.
Patrick Dubroyce34a972010-10-19 10:34:32 -0700240 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700241 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700242 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800243 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700244 }
245
246 // When dragging things around the home screens, we show a green outline of
247 // where the item will land. The outlines gradually fade out, leaving a trail
248 // behind the drag path.
249 // Set up all the animations that are used to implement this fading.
250 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700251 final float fromAlphaValue = 0;
252 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700253
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700254 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700255
256 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700257 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100258 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700259 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700260 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700261 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700262 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700263 final Bitmap outline = (Bitmap)anim.getTag();
264
265 // If an animation is started and then stopped very quickly, we can still
266 // get spurious updates we've cleared the tag. Guard against this.
267 if (outline == null) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700268 @SuppressWarnings("all") // suppress dead code warning
269 final boolean debug = false;
270 if (debug) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700271 Object val = animation.getAnimatedValue();
272 Log.d(TAG, "anim " + thisIndex + " update: " + val +
273 ", isStopped " + anim.isStopped());
274 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700275 // Try to prevent it from continuing to run
276 animation.cancel();
277 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700278 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800279 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700280 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700281 }
282 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700283 // The animation holds a reference to the drag outline bitmap as long is it's
284 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700285 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700286 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700287 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700288 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700289 anim.setTag(null);
290 }
291 }
292 });
293 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700294 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700295
Michael Jurka18014792010-10-14 09:01:34 -0700296 mBackgroundRect = new Rect();
Adam Cohenb5ba0972011-09-07 18:02:31 -0700297 mForegroundRect = new Rect();
Michael Jurkabea15192010-11-17 12:33:46 -0800298
Michael Jurkaa52570f2012-03-20 03:18:20 -0700299 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
Adam Cohen2374abf2013-04-16 14:56:57 -0700300 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700301 mCountX, mCountY);
Adam Cohen2374abf2013-04-16 14:56:57 -0700302
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700303 mTouchFeedbackView = new ClickShadowView(context);
304 addView(mTouchFeedbackView);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700305 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700306 }
307
Adam Cohenc9735cf2015-01-23 16:11:55 -0800308 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
Sunny Goyale9b651e2015-04-24 11:44:51 -0700309 public void enableAccessibleDrag(boolean enable, int dragType) {
Adam Cohenc9735cf2015-01-23 16:11:55 -0800310 mUseTouchHelper = enable;
311 if (!enable) {
312 ViewCompat.setAccessibilityDelegate(this, null);
313 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
314 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
315 setOnClickListener(mLauncher);
316 } else {
Sunny Goyale9b651e2015-04-24 11:44:51 -0700317 if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
318 !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
319 mTouchHelper = new WorkspaceAccessibilityHelper(this);
320 } else if (dragType == FOLDER_ACCESSIBILITY_DRAG &&
321 !(mTouchHelper instanceof FolderAccessibilityHelper)) {
322 mTouchHelper = new FolderAccessibilityHelper(this);
323 }
Adam Cohenc9735cf2015-01-23 16:11:55 -0800324 ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
325 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
326 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
327 setOnClickListener(mTouchHelper);
328 }
329
330 // Invalidate the accessibility hierarchy
331 if (getParent() != null) {
332 getParent().notifySubtreeAccessibilityStateChanged(
333 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
334 }
335 }
336
337 @Override
338 public boolean dispatchHoverEvent(MotionEvent event) {
339 // Always attempt to dispatch hover events to accessibility first.
340 if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
341 return true;
342 }
343 return super.dispatchHoverEvent(event);
344 }
345
346 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800347 public boolean onInterceptTouchEvent(MotionEvent ev) {
348 if (mUseTouchHelper ||
349 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
350 return true;
351 }
352 return false;
353 }
354
Chris Craik01f2d7f2013-10-01 14:41:56 -0700355 public void enableHardwareLayer(boolean hasLayer) {
356 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700357 }
358
359 public void buildHardwareLayer() {
360 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700361 }
362
Adam Cohen307fe232012-08-16 17:55:58 -0700363 public float getChildrenScale() {
364 return mIsHotseat ? mHotseatScale : 1.0f;
365 }
366
Winson Chung5f8afe62013-08-12 16:19:28 -0700367 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700368 mFixedCellWidth = mCellWidth = width;
369 mFixedCellHeight = mCellHeight = height;
Winson Chung5f8afe62013-08-12 16:19:28 -0700370 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
371 mCountX, mCountY);
372 }
373
Adam Cohen2801caf2011-05-13 20:57:39 -0700374 public void setGridSize(int x, int y) {
375 mCountX = x;
376 mCountY = y;
377 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800378 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700379 mTempRectStack.clear();
Adam Cohen2374abf2013-04-16 14:56:57 -0700380 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700381 mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700382 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700383 }
384
Adam Cohen2374abf2013-04-16 14:56:57 -0700385 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
386 public void setInvertIfRtl(boolean invert) {
387 mShortcutsAndWidgets.setInvertIfRtl(invert);
388 }
389
Adam Cohen917e3882013-10-31 15:03:35 -0700390 public void setDropPending(boolean pending) {
391 mDropPending = pending;
392 }
393
394 public boolean isDropPending() {
395 return mDropPending;
396 }
397
Adam Cohenb5ba0972011-09-07 18:02:31 -0700398 void setOverScrollAmount(float r, boolean left) {
399 if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
400 mOverScrollForegroundDrawable = mOverScrollLeft;
401 } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
402 mOverScrollForegroundDrawable = mOverScrollRight;
403 }
404
Adam Cohen02dcfcc2013-10-01 12:37:33 -0700405 r *= FOREGROUND_ALPHA_DAMPER;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700406 mForegroundAlpha = (int) Math.round((r * 255));
407 mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
408 invalidate();
409 }
410
Sunny Goyal4b6eb262015-05-14 19:24:40 -0700411 @Override
412 public void setPressedIcon(BubbleTextView icon, Bitmap background) {
Sunny Goyal508da152014-08-14 10:53:27 -0700413 if (icon == null || background == null) {
414 mTouchFeedbackView.setBitmap(null);
415 mTouchFeedbackView.animate().cancel();
416 } else {
Sunny Goyal508da152014-08-14 10:53:27 -0700417 if (mTouchFeedbackView.setBitmap(background)) {
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700418 mTouchFeedbackView.alignWithIconView(icon, mShortcutsAndWidgets);
419 mTouchFeedbackView.animateShadow();
Sunny Goyal508da152014-08-14 10:53:27 -0700420 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800421 }
422 }
423
Adam Cohenc50438c2014-08-19 17:43:05 -0700424 void disableDragTarget() {
425 mIsDragTarget = false;
426 }
427
428 boolean isDragTarget() {
429 return mIsDragTarget;
430 }
431
432 void setIsDragOverlapping(boolean isDragOverlapping) {
433 if (mIsDragOverlapping != isDragOverlapping) {
434 mIsDragOverlapping = isDragOverlapping;
Adam Cohenc50438c2014-08-19 17:43:05 -0700435 invalidate();
436 }
437 }
438
Michael Jurka33945b22010-12-21 18:19:38 -0800439 boolean getIsDragOverlapping() {
440 return mIsDragOverlapping;
441 }
442
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700443 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700444 protected void onDraw(Canvas canvas) {
Sunny Goyal05739772015-05-19 19:59:09 -0700445 if (!mIsDragTarget) {
446 return;
447 }
448
Michael Jurka3e7c7632010-10-02 16:01:03 -0700449 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
450 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
451 // When we're small, we are either drawn normally or in the "accepts drops" state (during
452 // a drag). However, we also drag the mini hover background *over* one of those two
453 // backgrounds
Sunny Goyal05739772015-05-19 19:59:09 -0700454 if (mBackgroundAlpha > 0.0f) {
Adam Cohenf34bab52010-09-30 14:11:56 -0700455 Drawable bg;
Michael Jurka33945b22010-12-21 18:19:38 -0800456
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700457 if (mIsDragOverlapping) {
Michael Jurka33945b22010-12-21 18:19:38 -0800458 // In the mini case, we draw the active_glow bg *over* the active background
Michael Jurkabdf78552011-10-31 14:34:25 -0700459 bg = mActiveGlowBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700460 } else {
Michael Jurkabdf78552011-10-31 14:34:25 -0700461 bg = mNormalBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700462 }
Michael Jurka33945b22010-12-21 18:19:38 -0800463
Sunny Goyal05739772015-05-19 19:59:09 -0700464 bg.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurka33945b22010-12-21 18:19:38 -0800465 bg.setBounds(mBackgroundRect);
466 bg.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700467 }
Romain Guya6abce82009-11-10 02:54:41 -0800468
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700469 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700470 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700471 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700472 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800473 final Rect r = mDragOutlines[i];
Winson Chung3a6e7f32013-10-09 15:50:52 -0700474 mTempRect.set(r);
475 Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale());
Joe Onorato4be866d2010-10-10 11:26:02 -0700476 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700477 paint.setAlpha((int)(alpha + .5f));
Winson Chung3a6e7f32013-10-09 15:50:52 -0700478 canvas.drawBitmap(b, null, mTempRect, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700479 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700480 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800481
Adam Cohen482ed822012-03-02 14:15:13 -0800482 if (DEBUG_VISUALIZE_OCCUPIED) {
483 int[] pt = new int[2];
484 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700485 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800486 for (int i = 0; i < mCountX; i++) {
487 for (int j = 0; j < mCountY; j++) {
488 if (mOccupied[i][j]) {
489 cellToPoint(i, j, pt);
490 canvas.save();
491 canvas.translate(pt[0], pt[1]);
492 cd.draw(canvas);
493 canvas.restore();
494 }
495 }
496 }
497 }
498
Andrew Flynn850d2e72012-04-26 16:51:20 -0700499 int previewOffset = FolderRingAnimator.sPreviewSize;
500
Adam Cohen69ce2e52011-07-03 19:25:21 -0700501 // The folder outer / inner ring image(s)
Winson Chung5f8afe62013-08-12 16:19:28 -0700502 LauncherAppState app = LauncherAppState.getInstance();
503 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700504 for (int i = 0; i < mFolderOuterRings.size(); i++) {
505 FolderRingAnimator fra = mFolderOuterRings.get(i);
506
Adam Cohen5108bc02013-09-20 17:04:51 -0700507 Drawable d;
508 int width, height;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700509 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700510 View child = getChildAt(fra.mCellX, fra.mCellY);
Adam Cohen558f1c22013-10-09 15:15:24 -0700511
Winson Chung89f97052013-09-20 11:32:26 -0700512 if (child != null) {
Adam Cohen558f1c22013-10-09 15:15:24 -0700513 int centerX = mTempLocation[0] + mCellWidth / 2;
514 int centerY = mTempLocation[1] + previewOffset / 2 +
515 child.getPaddingTop() + grid.folderBackgroundOffset;
516
Adam Cohen5108bc02013-09-20 17:04:51 -0700517 // Draw outer ring, if it exists
518 if (FolderIcon.HAS_OUTER_RING) {
519 d = FolderRingAnimator.sSharedOuterRingDrawable;
520 width = (int) (fra.getOuterRingSize() * getChildrenScale());
521 height = width;
522 canvas.save();
523 canvas.translate(centerX - width / 2, centerY - height / 2);
524 d.setBounds(0, 0, width, height);
525 d.draw(canvas);
526 canvas.restore();
527 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700528
Winson Chung89f97052013-09-20 11:32:26 -0700529 // Draw inner ring
530 d = FolderRingAnimator.sSharedInnerRingDrawable;
531 width = (int) (fra.getInnerRingSize() * getChildrenScale());
532 height = width;
Winson Chung89f97052013-09-20 11:32:26 -0700533 canvas.save();
534 canvas.translate(centerX - width / 2, centerY - width / 2);
535 d.setBounds(0, 0, width, height);
536 d.draw(canvas);
537 canvas.restore();
538 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700539 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700540
541 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
542 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
543 int width = d.getIntrinsicWidth();
544 int height = d.getIntrinsicHeight();
545
546 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700547 View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]);
Winson Chung89f97052013-09-20 11:32:26 -0700548 if (child != null) {
549 int centerX = mTempLocation[0] + mCellWidth / 2;
550 int centerY = mTempLocation[1] + previewOffset / 2 +
551 child.getPaddingTop() + grid.folderBackgroundOffset;
Adam Cohenc51934b2011-07-26 21:07:43 -0700552
Winson Chung89f97052013-09-20 11:32:26 -0700553 canvas.save();
554 canvas.translate(centerX - width / 2, centerY - width / 2);
555 d.setBounds(0, 0, width, height);
556 d.draw(canvas);
557 canvas.restore();
558 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700559 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700560 }
561
Adam Cohenb5ba0972011-09-07 18:02:31 -0700562 @Override
563 protected void dispatchDraw(Canvas canvas) {
564 super.dispatchDraw(canvas);
565 if (mForegroundAlpha > 0) {
566 mOverScrollForegroundDrawable.setBounds(mForegroundRect);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700567 mOverScrollForegroundDrawable.draw(canvas);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700568 }
569 }
570
Adam Cohen69ce2e52011-07-03 19:25:21 -0700571 public void showFolderAccept(FolderRingAnimator fra) {
572 mFolderOuterRings.add(fra);
573 }
574
575 public void hideFolderAccept(FolderRingAnimator fra) {
576 if (mFolderOuterRings.contains(fra)) {
577 mFolderOuterRings.remove(fra);
578 }
579 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700580 }
581
Adam Cohenc51934b2011-07-26 21:07:43 -0700582 public void setFolderLeaveBehindCell(int x, int y) {
583 mFolderLeaveBehindCell[0] = x;
584 mFolderLeaveBehindCell[1] = y;
585 invalidate();
586 }
587
588 public void clearFolderLeaveBehind() {
589 mFolderLeaveBehindCell[0] = -1;
590 mFolderLeaveBehindCell[1] = -1;
591 invalidate();
592 }
593
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700594 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700595 public boolean shouldDelayChildPressedState() {
596 return false;
597 }
598
Adam Cohen1462de32012-07-24 22:34:36 -0700599 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700600 try {
601 dispatchRestoreInstanceState(states);
602 } catch (IllegalArgumentException ex) {
603 if (LauncherAppState.isDogfoodBuild()) {
604 throw ex;
605 }
606 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
607 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
608 }
Adam Cohen1462de32012-07-24 22:34:36 -0700609 }
610
Michael Jurkae6235dd2011-10-04 15:02:05 -0700611 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700612 public void cancelLongPress() {
613 super.cancelLongPress();
614
615 // Cancel long press for all children
616 final int count = getChildCount();
617 for (int i = 0; i < count; i++) {
618 final View child = getChildAt(i);
619 child.cancelLongPress();
620 }
621 }
622
Michael Jurkadee05892010-07-27 10:01:56 -0700623 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
624 mInterceptTouchListener = listener;
625 }
626
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800627 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700628 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800629 }
630
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800631 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700632 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800633 }
634
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800635 public void setIsHotseat(boolean isHotseat) {
636 mIsHotseat = isHotseat;
Winson Chung5f8afe62013-08-12 16:19:28 -0700637 mShortcutsAndWidgets.setIsHotseat(isHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800638 }
639
Sunny Goyale9b651e2015-04-24 11:44:51 -0700640 public boolean isHotseat() {
641 return mIsHotseat;
642 }
643
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800644 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700645 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700646 final LayoutParams lp = params;
647
Andrew Flynnde38e422012-05-08 11:22:15 -0700648 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800649 if (child instanceof BubbleTextView) {
650 BubbleTextView bubbleChild = (BubbleTextView) child;
Winson Chung5f8afe62013-08-12 16:19:28 -0700651 bubbleChild.setTextVisibility(!mIsHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800652 }
653
Adam Cohen307fe232012-08-16 17:55:58 -0700654 child.setScaleX(getChildrenScale());
655 child.setScaleY(getChildrenScale());
656
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800657 // Generate an id for each view, this assumes we have at most 256x256 cells
658 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700659 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700660 // If the horizontal or vertical span is set to -1, it is taken to
661 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700662 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
663 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800664
Winson Chungaafa03c2010-06-11 17:34:16 -0700665 child.setId(childId);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700666 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700667
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700668 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700669
Winson Chungaafa03c2010-06-11 17:34:16 -0700670 return true;
671 }
672 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800673 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700674
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800675 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700676 public void removeAllViews() {
677 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700678 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700679 }
680
681 @Override
682 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700683 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700684 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700685 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700686 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700687 }
688
689 @Override
690 public void removeView(View view) {
691 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700692 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700693 }
694
695 @Override
696 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700697 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
698 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700699 }
700
701 @Override
702 public void removeViewInLayout(View view) {
703 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700704 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700705 }
706
707 @Override
708 public void removeViews(int start, int count) {
709 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700710 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700711 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700712 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700713 }
714
715 @Override
716 public void removeViewsInLayout(int start, int count) {
717 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700718 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700719 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700720 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800721 }
722
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700723 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700724 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800725 * @param x X coordinate of the point
726 * @param y Y coordinate of the point
727 * @param result Array of 2 ints to hold the x and y coordinate of the cell
728 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700729 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700730 final int hStartPadding = getPaddingLeft();
731 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800732
733 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
734 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
735
Adam Cohend22015c2010-07-26 22:02:18 -0700736 final int xAxis = mCountX;
737 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800738
739 if (result[0] < 0) result[0] = 0;
740 if (result[0] >= xAxis) result[0] = xAxis - 1;
741 if (result[1] < 0) result[1] = 0;
742 if (result[1] >= yAxis) result[1] = yAxis - 1;
743 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700744
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800745 /**
746 * Given a point, return the cell that most closely encloses that point
747 * @param x X coordinate of the point
748 * @param y Y coordinate of the point
749 * @param result Array of 2 ints to hold the x and y coordinate of the cell
750 */
751 void pointToCellRounded(int x, int y, int[] result) {
752 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
753 }
754
755 /**
756 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700757 *
758 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800759 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700760 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800761 * @param result Array of 2 ints to hold the x and y coordinate of the point
762 */
763 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700764 final int hStartPadding = getPaddingLeft();
765 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800766
767 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
768 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
769 }
770
Adam Cohene3e27a82011-04-15 12:07:39 -0700771 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800772 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700773 *
774 * @param cellX X coordinate of the cell
775 * @param cellY Y coordinate of the cell
776 *
777 * @param result Array of 2 ints to hold the x and y coordinate of the point
778 */
779 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700780 regionToCenterPoint(cellX, cellY, 1, 1, result);
781 }
782
783 /**
784 * Given a cell coordinate and span return the point that represents the center of the regio
785 *
786 * @param cellX X coordinate of the cell
787 * @param cellY Y coordinate of the cell
788 *
789 * @param result Array of 2 ints to hold the x and y coordinate of the point
790 */
791 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700792 final int hStartPadding = getPaddingLeft();
793 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700794 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
795 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
796 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
797 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700798 }
799
Adam Cohen19f37922012-03-21 11:59:11 -0700800 /**
801 * Given a cell coordinate and span fills out a corresponding pixel rect
802 *
803 * @param cellX X coordinate of the cell
804 * @param cellY Y coordinate of the cell
805 * @param result Rect in which to write the result
806 */
807 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
808 final int hStartPadding = getPaddingLeft();
809 final int vStartPadding = getPaddingTop();
810 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
811 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
812 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
813 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
814 }
815
Adam Cohen482ed822012-03-02 14:15:13 -0800816 public float getDistanceFromCell(float x, float y, int[] cell) {
817 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700818 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800819 }
820
Romain Guy84f296c2009-11-04 15:00:44 -0800821 int getCellWidth() {
822 return mCellWidth;
823 }
824
825 int getCellHeight() {
826 return mCellHeight;
827 }
828
Adam Cohend4844c32011-02-18 19:25:06 -0800829 int getWidthGap() {
830 return mWidthGap;
831 }
832
833 int getHeightGap() {
834 return mHeightGap;
835 }
836
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700837 public void setFixedSize(int width, int height) {
838 mFixedWidth = width;
839 mFixedHeight = height;
840 }
841
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800842 @Override
843 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700844 LauncherAppState app = LauncherAppState.getInstance();
845 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
846
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800847 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800848 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700849 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
850 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700851 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
852 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700853 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700854 int cw = grid.calculateCellWidth(childWidthSize, mCountX);
855 int ch = grid.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700856 if (cw != mCellWidth || ch != mCellHeight) {
857 mCellWidth = cw;
858 mCellHeight = ch;
859 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
860 mHeightGap, mCountX, mCountY);
861 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700862 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700863
Winson Chung2d75f122013-09-23 16:53:31 -0700864 int newWidth = childWidthSize;
865 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700866 if (mFixedWidth > 0 && mFixedHeight > 0) {
867 newWidth = mFixedWidth;
868 newHeight = mFixedHeight;
869 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800870 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
871 }
872
Adam Cohend22015c2010-07-26 22:02:18 -0700873 int numWidthGaps = mCountX - 1;
874 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800875
Adam Cohen234c4cd2011-07-17 21:03:04 -0700876 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700877 int hSpace = childWidthSize;
878 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700879 int hFreeSpace = hSpace - (mCountX * mCellWidth);
880 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700881 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
882 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -0700883 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
884 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700885 } else {
886 mWidthGap = mOriginalWidthGap;
887 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700888 }
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700889
890 // Make the feedback view large enough to hold the blur bitmap.
891 mTouchFeedbackView.measure(
892 MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
893 MeasureSpec.EXACTLY),
894 MeasureSpec.makeMeasureSpec(mCellHeight + mTouchFeedbackView.getExtraSize(),
895 MeasureSpec.EXACTLY));
896
897 mShortcutsAndWidgets.measure(
898 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
899 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
900
901 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
902 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -0700903 if (mFixedWidth > 0 && mFixedHeight > 0) {
904 setMeasuredDimension(maxWidth, maxHeight);
905 } else {
906 setMeasuredDimension(widthSize, heightSize);
907 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800908 }
909
910 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700911 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Winson Chung38848ca2013-10-08 12:03:44 -0700912 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
913 (mCountX * mCellWidth);
914 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
915 int top = getPaddingTop();
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700916
917 mTouchFeedbackView.layout(left, top,
918 left + mTouchFeedbackView.getMeasuredWidth(),
919 top + mTouchFeedbackView.getMeasuredHeight());
920 mShortcutsAndWidgets.layout(left, top,
921 left + r - l,
922 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800923 }
924
925 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700926 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
927 super.onSizeChanged(w, h, oldw, oldh);
Winson Chung82a9bd22013-10-08 16:02:34 -0700928
929 // Expand the background drawing bounds by the padding baked into the background drawable
930 Rect padding = new Rect();
931 mNormalBackground.getPadding(padding);
932 mBackgroundRect.set(-padding.left, -padding.top, w + padding.right, h + padding.bottom);
933
Adam Cohenb5ba0972011-09-07 18:02:31 -0700934 mForegroundRect.set(mForegroundPadding, mForegroundPadding,
Adam Cohen215b4162012-08-30 13:14:08 -0700935 w - mForegroundPadding, h - mForegroundPadding);
Michael Jurkadee05892010-07-27 10:01:56 -0700936 }
937
938 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800939 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700940 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800941 }
942
943 @Override
944 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700945 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800946 }
947
Michael Jurka5f1c5092010-09-03 14:15:02 -0700948 public float getBackgroundAlpha() {
949 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -0700950 }
951
Michael Jurka5f1c5092010-09-03 14:15:02 -0700952 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -0800953 if (mBackgroundAlpha != alpha) {
954 mBackgroundAlpha = alpha;
955 invalidate();
956 }
Michael Jurkadee05892010-07-27 10:01:56 -0700957 }
958
Michael Jurkaa52570f2012-03-20 03:18:20 -0700959 public void setShortcutAndWidgetAlpha(float alpha) {
Sunny Goyal02b50812014-09-10 15:44:42 -0700960 mShortcutsAndWidgets.setAlpha(alpha);
Michael Jurkadee05892010-07-27 10:01:56 -0700961 }
962
Michael Jurkaa52570f2012-03-20 03:18:20 -0700963 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700964 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700965 }
966
Patrick Dubroy440c3602010-07-13 17:50:32 -0700967 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700968 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700969 }
970
Adam Cohen76fc0852011-06-17 13:26:23 -0700971 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800972 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700973 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800974 boolean[][] occupied = mOccupied;
975 if (!permanent) {
976 occupied = mTmpOccupied;
977 }
978
Adam Cohen19f37922012-03-21 11:59:11 -0700979 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700980 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
981 final ItemInfo info = (ItemInfo) child.getTag();
982
983 // We cancel any existing animations
984 if (mReorderAnimators.containsKey(lp)) {
985 mReorderAnimators.get(lp).cancel();
986 mReorderAnimators.remove(lp);
987 }
988
Adam Cohen482ed822012-03-02 14:15:13 -0800989 final int oldX = lp.x;
990 final int oldY = lp.y;
991 if (adjustOccupied) {
992 occupied[lp.cellX][lp.cellY] = false;
993 occupied[cellX][cellY] = true;
994 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700995 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -0800996 if (permanent) {
997 lp.cellX = info.cellX = cellX;
998 lp.cellY = info.cellY = cellY;
999 } else {
1000 lp.tmpCellX = cellX;
1001 lp.tmpCellY = cellY;
1002 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001003 clc.setupLp(lp);
1004 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001005 final int newX = lp.x;
1006 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001007
Adam Cohen76fc0852011-06-17 13:26:23 -07001008 lp.x = oldX;
1009 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001010
Adam Cohen482ed822012-03-02 14:15:13 -08001011 // Exit early if we're not actually moving the view
1012 if (oldX == newX && oldY == newY) {
1013 lp.isLockedToGrid = true;
1014 return true;
1015 }
1016
Michael Jurkaf1ad6082013-03-13 12:55:46 +01001017 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001018 va.setDuration(duration);
1019 mReorderAnimators.put(lp, va);
1020
1021 va.addUpdateListener(new AnimatorUpdateListener() {
1022 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001023 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001024 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001025 lp.x = (int) ((1 - r) * oldX + r * newX);
1026 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001027 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001028 }
1029 });
Adam Cohen482ed822012-03-02 14:15:13 -08001030 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001031 boolean cancelled = false;
1032 public void onAnimationEnd(Animator animation) {
1033 // If the animation was cancelled, it means that another animation
1034 // has interrupted this one, and we don't want to lock the item into
1035 // place just yet.
1036 if (!cancelled) {
1037 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001038 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001039 }
1040 if (mReorderAnimators.containsKey(lp)) {
1041 mReorderAnimators.remove(lp);
1042 }
1043 }
1044 public void onAnimationCancel(Animator animation) {
1045 cancelled = true;
1046 }
1047 });
Adam Cohen482ed822012-03-02 14:15:13 -08001048 va.setStartDelay(delay);
1049 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001050 return true;
1051 }
1052 return false;
1053 }
1054
Adam Cohen482ed822012-03-02 14:15:13 -08001055 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1056 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001057 final int oldDragCellX = mDragCell[0];
1058 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001059
Adam Cohen2801caf2011-05-13 20:57:39 -07001060 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001061 return;
1062 }
1063
Adam Cohen482ed822012-03-02 14:15:13 -08001064 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1065 mDragCell[0] = cellX;
1066 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001067 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001068 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001069 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001070
Joe Onorato4be866d2010-10-10 11:26:02 -07001071 int left = topLeft[0];
1072 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001073
Winson Chungb8c69f32011-10-19 21:36:08 -07001074 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001075 // When drawing the drag outline, it did not account for margin offsets
1076 // added by the view's parent.
1077 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1078 left += lp.leftMargin;
1079 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001080
Adam Cohen99e8b402011-03-25 19:23:43 -07001081 // Offsets due to the size difference between the View and the dragOutline.
1082 // There is a size difference to account for the outer blur, which may lie
1083 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001084 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001085 // We center about the x axis
1086 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1087 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001088 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001089 if (dragOffset != null && dragRegion != null) {
1090 // Center the drag region *horizontally* in the cell and apply a drag
1091 // outline offset
1092 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1093 - dragRegion.width()) / 2;
Winson Chung69737c32013-10-08 17:00:19 -07001094 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1095 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1096 top += dragOffset.y + cellPaddingY;
Winson Chungb8c69f32011-10-19 21:36:08 -07001097 } else {
1098 // Center the drag outline in the cell
1099 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1100 - dragOutline.getWidth()) / 2;
1101 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1102 - dragOutline.getHeight()) / 2;
1103 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001104 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001105 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001106 mDragOutlineAnims[oldIndex].animateOut();
1107 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001108 Rect r = mDragOutlines[mDragOutlineCurrent];
1109 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1110 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001111 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001112 }
Winson Chung150fbab2010-09-29 17:14:26 -07001113
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001114 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1115 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001116 }
1117 }
1118
Adam Cohene0310962011-04-18 16:15:31 -07001119 public void clearDragOutlines() {
1120 final int oldIndex = mDragOutlineCurrent;
1121 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001122 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001123 }
1124
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001125 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001126 * Find a vacant area that will fit the given bounds nearest the requested
1127 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001128 *
Romain Guy51afc022009-05-04 18:03:43 -07001129 * @param pixelX The X location at which you want to search for a vacant area.
1130 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001131 * @param spanX Horizontal span of the object.
1132 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001133 * @param result Array in which to place the result, or null (in which case a new array will
1134 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001135 * @return The X, Y cell of a vacant area that can contain this object,
1136 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001137 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001138 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
1139 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, spanX, spanY, result, null);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001140 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001141
Michael Jurka6a1435d2010-09-27 17:35:12 -07001142 /**
1143 * Find a vacant area that will fit the given bounds nearest the requested
1144 * cell location. Uses Euclidean distance to score multiple vacant areas.
1145 *
1146 * @param pixelX The X location at which you want to search for a vacant area.
1147 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001148 * @param minSpanX The minimum horizontal span required
1149 * @param minSpanY The minimum vertical span required
1150 * @param spanX Horizontal span of the object.
1151 * @param spanY Vertical span of the object.
1152 * @param result Array in which to place the result, or null (in which case a new array will
1153 * be allocated)
1154 * @return The X, Y cell of a vacant area that can contain this object,
1155 * nearest the requested location.
1156 */
1157 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1158 int spanY, int[] result, int[] resultSpan) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001159 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true,
Adam Cohend41fbf52012-02-16 23:53:59 -08001160 result, resultSpan);
1161 }
1162
Adam Cohend41fbf52012-02-16 23:53:59 -08001163 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1164 private void lazyInitTempRectStack() {
1165 if (mTempRectStack.isEmpty()) {
1166 for (int i = 0; i < mCountX * mCountY; i++) {
1167 mTempRectStack.push(new Rect());
1168 }
1169 }
1170 }
Adam Cohen482ed822012-03-02 14:15:13 -08001171
Adam Cohend41fbf52012-02-16 23:53:59 -08001172 private void recycleTempRects(Stack<Rect> used) {
1173 while (!used.isEmpty()) {
1174 mTempRectStack.push(used.pop());
1175 }
1176 }
1177
1178 /**
1179 * Find a vacant area that will fit the given bounds nearest the requested
1180 * cell location. Uses Euclidean distance to score multiple vacant areas.
1181 *
1182 * @param pixelX The X location at which you want to search for a vacant area.
1183 * @param pixelY The Y location at which you want to search for a vacant area.
1184 * @param minSpanX The minimum horizontal span required
1185 * @param minSpanY The minimum vertical span required
1186 * @param spanX Horizontal span of the object.
1187 * @param spanY Vertical span of the object.
1188 * @param ignoreOccupied If true, the result can be an occupied cell
1189 * @param result Array in which to place the result, or null (in which case a new array will
1190 * be allocated)
1191 * @return The X, Y cell of a vacant area that can contain this object,
1192 * nearest the requested location.
1193 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001194 private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1195 int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001196 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001197
Adam Cohene3e27a82011-04-15 12:07:39 -07001198 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1199 // to the center of the item, but we are searching based on the top-left cell, so
1200 // we translate the point over to correspond to the top-left.
1201 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1202 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1203
Jeff Sharkey70864282009-04-07 21:08:40 -07001204 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001205 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001206 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001207 final Rect bestRect = new Rect(-1, -1, -1, -1);
1208 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001209
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001210 final int countX = mCountX;
1211 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001212
Adam Cohend41fbf52012-02-16 23:53:59 -08001213 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1214 spanX < minSpanX || spanY < minSpanY) {
1215 return bestXY;
1216 }
1217
1218 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001219 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001220 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1221 int ySize = -1;
1222 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001223 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001224 // First, let's see if this thing fits anywhere
1225 for (int i = 0; i < minSpanX; i++) {
1226 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001227 if (mOccupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001228 continue inner;
1229 }
Michael Jurkac28de512010-08-13 11:27:44 -07001230 }
1231 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001232 xSize = minSpanX;
1233 ySize = minSpanY;
1234
1235 // We know that the item will fit at _some_ acceptable size, now let's see
1236 // how big we can make it. We'll alternate between incrementing x and y spans
1237 // until we hit a limit.
1238 boolean incX = true;
1239 boolean hitMaxX = xSize >= spanX;
1240 boolean hitMaxY = ySize >= spanY;
1241 while (!(hitMaxX && hitMaxY)) {
1242 if (incX && !hitMaxX) {
1243 for (int j = 0; j < ySize; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001244 if (x + xSize > countX -1 || mOccupied[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001245 // We can't move out horizontally
1246 hitMaxX = true;
1247 }
1248 }
1249 if (!hitMaxX) {
1250 xSize++;
1251 }
1252 } else if (!hitMaxY) {
1253 for (int i = 0; i < xSize; i++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001254 if (y + ySize > countY - 1 || mOccupied[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001255 // We can't move out vertically
1256 hitMaxY = true;
1257 }
1258 }
1259 if (!hitMaxY) {
1260 ySize++;
1261 }
1262 }
1263 hitMaxX |= xSize >= spanX;
1264 hitMaxY |= ySize >= spanY;
1265 incX = !incX;
1266 }
1267 incX = true;
1268 hitMaxX = xSize >= spanX;
1269 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001270 }
Winson Chung0be025d2011-05-23 17:45:09 -07001271 final int[] cellXY = mTmpXY;
Adam Cohene3e27a82011-04-15 12:07:39 -07001272 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001273
Adam Cohend41fbf52012-02-16 23:53:59 -08001274 // We verify that the current rect is not a sub-rect of any of our previous
1275 // candidates. In this case, the current rect is disqualified in favour of the
1276 // containing rect.
1277 Rect currentRect = mTempRectStack.pop();
1278 currentRect.set(x, y, x + xSize, y + ySize);
1279 boolean contained = false;
1280 for (Rect r : validRegions) {
1281 if (r.contains(currentRect)) {
1282 contained = true;
1283 break;
1284 }
1285 }
1286 validRegions.push(currentRect);
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001287 double distance = Math.hypot(cellXY[0] - pixelX, cellXY[1] - pixelY);
Adam Cohen482ed822012-03-02 14:15:13 -08001288
Adam Cohend41fbf52012-02-16 23:53:59 -08001289 if ((distance <= bestDistance && !contained) ||
1290 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001291 bestDistance = distance;
1292 bestXY[0] = x;
1293 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001294 if (resultSpan != null) {
1295 resultSpan[0] = xSize;
1296 resultSpan[1] = ySize;
1297 }
1298 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001299 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001300 }
1301 }
1302
Adam Cohenc0dcf592011-06-01 15:30:43 -07001303 // Return -1, -1 if no suitable location found
1304 if (bestDistance == Double.MAX_VALUE) {
1305 bestXY[0] = -1;
1306 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001307 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001308 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001309 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001310 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001311
Adam Cohen482ed822012-03-02 14:15:13 -08001312 /**
1313 * Find a vacant area that will fit the given bounds nearest the requested
1314 * cell location, and will also weigh in a suggested direction vector of the
1315 * desired location. This method computers distance based on unit grid distances,
1316 * not pixel distances.
1317 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001318 * @param cellX The X cell nearest to which you want to search for a vacant area.
1319 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001320 * @param spanX Horizontal span of the object.
1321 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001322 * @param direction The favored direction in which the views should move from x, y
1323 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1324 * matches exactly. Otherwise we find the best matching direction.
1325 * @param occoupied The array which represents which cells in the CellLayout are occupied
1326 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001327 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001328 * @param result Array in which to place the result, or null (in which case a new array will
1329 * be allocated)
1330 * @return The X, Y cell of a vacant area that can contain this object,
1331 * nearest the requested location.
1332 */
1333 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001334 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001335 // Keep track of best-scoring drop area
1336 final int[] bestXY = result != null ? result : new int[2];
1337 float bestDistance = Float.MAX_VALUE;
1338 int bestDirectionScore = Integer.MIN_VALUE;
1339
1340 final int countX = mCountX;
1341 final int countY = mCountY;
1342
1343 for (int y = 0; y < countY - (spanY - 1); y++) {
1344 inner:
1345 for (int x = 0; x < countX - (spanX - 1); x++) {
1346 // First, let's see if this thing fits anywhere
1347 for (int i = 0; i < spanX; i++) {
1348 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001349 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001350 continue inner;
1351 }
1352 }
1353 }
1354
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001355 float distance = (float) Math.hypot(x - cellX, y - cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001356 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001357 computeDirectionVector(x - cellX, y - cellY, curDirection);
1358 // The direction score is just the dot product of the two candidate direction
1359 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001360 int curDirectionScore = direction[0] * curDirection[0] +
1361 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001362 boolean exactDirectionOnly = false;
1363 boolean directionMatches = direction[0] == curDirection[0] &&
1364 direction[0] == curDirection[0];
1365 if ((directionMatches || !exactDirectionOnly) &&
1366 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001367 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1368 bestDistance = distance;
1369 bestDirectionScore = curDirectionScore;
1370 bestXY[0] = x;
1371 bestXY[1] = y;
1372 }
1373 }
1374 }
1375
1376 // Return -1, -1 if no suitable location found
1377 if (bestDistance == Float.MAX_VALUE) {
1378 bestXY[0] = -1;
1379 bestXY[1] = -1;
1380 }
1381 return bestXY;
1382 }
1383
1384 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001385 int[] direction, ItemConfiguration currentState) {
1386 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001387 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001388 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001389 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1390
Adam Cohen8baab352012-03-20 17:39:21 -07001391 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001392
1393 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001394 c.x = mTempLocation[0];
1395 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001396 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001397 }
Adam Cohen8baab352012-03-20 17:39:21 -07001398 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001399 return success;
1400 }
1401
Adam Cohenf3900c22012-11-16 18:28:11 -08001402 /**
1403 * This helper class defines a cluster of views. It helps with defining complex edges
1404 * of the cluster and determining how those edges interact with other views. The edges
1405 * essentially define a fine-grained boundary around the cluster of views -- like a more
1406 * precise version of a bounding box.
1407 */
1408 private class ViewCluster {
1409 final static int LEFT = 0;
1410 final static int TOP = 1;
1411 final static int RIGHT = 2;
1412 final static int BOTTOM = 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001413
Adam Cohenf3900c22012-11-16 18:28:11 -08001414 ArrayList<View> views;
1415 ItemConfiguration config;
1416 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001417
Adam Cohenf3900c22012-11-16 18:28:11 -08001418 int[] leftEdge = new int[mCountY];
1419 int[] rightEdge = new int[mCountY];
1420 int[] topEdge = new int[mCountX];
1421 int[] bottomEdge = new int[mCountX];
1422 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1423
1424 @SuppressWarnings("unchecked")
1425 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1426 this.views = (ArrayList<View>) views.clone();
1427 this.config = config;
1428 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001429 }
1430
Adam Cohenf3900c22012-11-16 18:28:11 -08001431 void resetEdges() {
1432 for (int i = 0; i < mCountX; i++) {
1433 topEdge[i] = -1;
1434 bottomEdge[i] = -1;
1435 }
1436 for (int i = 0; i < mCountY; i++) {
1437 leftEdge[i] = -1;
1438 rightEdge[i] = -1;
1439 }
1440 leftEdgeDirty = true;
1441 rightEdgeDirty = true;
1442 bottomEdgeDirty = true;
1443 topEdgeDirty = true;
1444 boundingRectDirty = true;
1445 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001446
Adam Cohenf3900c22012-11-16 18:28:11 -08001447 void computeEdge(int which, int[] edge) {
1448 int count = views.size();
1449 for (int i = 0; i < count; i++) {
1450 CellAndSpan cs = config.map.get(views.get(i));
1451 switch (which) {
1452 case LEFT:
1453 int left = cs.x;
1454 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1455 if (left < edge[j] || edge[j] < 0) {
1456 edge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001457 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001458 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001459 break;
1460 case RIGHT:
1461 int right = cs.x + cs.spanX;
1462 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1463 if (right > edge[j]) {
1464 edge[j] = right;
1465 }
1466 }
1467 break;
1468 case TOP:
1469 int top = cs.y;
1470 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1471 if (top < edge[j] || edge[j] < 0) {
1472 edge[j] = top;
1473 }
1474 }
1475 break;
1476 case BOTTOM:
1477 int bottom = cs.y + cs.spanY;
1478 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1479 if (bottom > edge[j]) {
1480 edge[j] = bottom;
1481 }
1482 }
1483 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001484 }
1485 }
1486 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001487
1488 boolean isViewTouchingEdge(View v, int whichEdge) {
1489 CellAndSpan cs = config.map.get(v);
1490
1491 int[] edge = getEdge(whichEdge);
1492
1493 switch (whichEdge) {
1494 case LEFT:
1495 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1496 if (edge[i] == cs.x + cs.spanX) {
1497 return true;
1498 }
1499 }
1500 break;
1501 case RIGHT:
1502 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1503 if (edge[i] == cs.x) {
1504 return true;
1505 }
1506 }
1507 break;
1508 case TOP:
1509 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1510 if (edge[i] == cs.y + cs.spanY) {
1511 return true;
1512 }
1513 }
1514 break;
1515 case BOTTOM:
1516 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1517 if (edge[i] == cs.y) {
1518 return true;
1519 }
1520 }
1521 break;
1522 }
1523 return false;
1524 }
1525
1526 void shift(int whichEdge, int delta) {
1527 for (View v: views) {
1528 CellAndSpan c = config.map.get(v);
1529 switch (whichEdge) {
1530 case LEFT:
1531 c.x -= delta;
1532 break;
1533 case RIGHT:
1534 c.x += delta;
1535 break;
1536 case TOP:
1537 c.y -= delta;
1538 break;
1539 case BOTTOM:
1540 default:
1541 c.y += delta;
1542 break;
1543 }
1544 }
1545 resetEdges();
1546 }
1547
1548 public void addView(View v) {
1549 views.add(v);
1550 resetEdges();
1551 }
1552
1553 public Rect getBoundingRect() {
1554 if (boundingRectDirty) {
1555 boolean first = true;
1556 for (View v: views) {
1557 CellAndSpan c = config.map.get(v);
1558 if (first) {
1559 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1560 first = false;
1561 } else {
1562 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1563 }
1564 }
1565 }
1566 return boundingRect;
1567 }
1568
1569 public int[] getEdge(int which) {
1570 switch (which) {
1571 case LEFT:
1572 return getLeftEdge();
1573 case RIGHT:
1574 return getRightEdge();
1575 case TOP:
1576 return getTopEdge();
1577 case BOTTOM:
1578 default:
1579 return getBottomEdge();
1580 }
1581 }
1582
1583 public int[] getLeftEdge() {
1584 if (leftEdgeDirty) {
1585 computeEdge(LEFT, leftEdge);
1586 }
1587 return leftEdge;
1588 }
1589
1590 public int[] getRightEdge() {
1591 if (rightEdgeDirty) {
1592 computeEdge(RIGHT, rightEdge);
1593 }
1594 return rightEdge;
1595 }
1596
1597 public int[] getTopEdge() {
1598 if (topEdgeDirty) {
1599 computeEdge(TOP, topEdge);
1600 }
1601 return topEdge;
1602 }
1603
1604 public int[] getBottomEdge() {
1605 if (bottomEdgeDirty) {
1606 computeEdge(BOTTOM, bottomEdge);
1607 }
1608 return bottomEdge;
1609 }
1610
1611 PositionComparator comparator = new PositionComparator();
1612 class PositionComparator implements Comparator<View> {
1613 int whichEdge = 0;
1614 public int compare(View left, View right) {
1615 CellAndSpan l = config.map.get(left);
1616 CellAndSpan r = config.map.get(right);
1617 switch (whichEdge) {
1618 case LEFT:
1619 return (r.x + r.spanX) - (l.x + l.spanX);
1620 case RIGHT:
1621 return l.x - r.x;
1622 case TOP:
1623 return (r.y + r.spanY) - (l.y + l.spanY);
1624 case BOTTOM:
1625 default:
1626 return l.y - r.y;
1627 }
1628 }
1629 }
1630
1631 public void sortConfigurationForEdgePush(int edge) {
1632 comparator.whichEdge = edge;
1633 Collections.sort(config.sortedViews, comparator);
1634 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001635 }
1636
Adam Cohenf3900c22012-11-16 18:28:11 -08001637 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1638 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001639
Adam Cohenf3900c22012-11-16 18:28:11 -08001640 ViewCluster cluster = new ViewCluster(views, currentState);
1641 Rect clusterRect = cluster.getBoundingRect();
1642 int whichEdge;
1643 int pushDistance;
1644 boolean fail = false;
1645
1646 // Determine the edge of the cluster that will be leading the push and how far
1647 // the cluster must be shifted.
1648 if (direction[0] < 0) {
1649 whichEdge = ViewCluster.LEFT;
1650 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001651 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001652 whichEdge = ViewCluster.RIGHT;
1653 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1654 } else if (direction[1] < 0) {
1655 whichEdge = ViewCluster.TOP;
1656 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1657 } else {
1658 whichEdge = ViewCluster.BOTTOM;
1659 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001660 }
1661
Adam Cohenf3900c22012-11-16 18:28:11 -08001662 // Break early for invalid push distance.
1663 if (pushDistance <= 0) {
1664 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001665 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001666
1667 // Mark the occupied state as false for the group of views we want to move.
1668 for (View v: views) {
1669 CellAndSpan c = currentState.map.get(v);
1670 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1671 }
1672
1673 // We save the current configuration -- if we fail to find a solution we will revert
1674 // to the initial state. The process of finding a solution modifies the configuration
1675 // in place, hence the need for revert in the failure case.
1676 currentState.save();
1677
1678 // The pushing algorithm is simplified by considering the views in the order in which
1679 // they would be pushed by the cluster. For example, if the cluster is leading with its
1680 // left edge, we consider sort the views by their right edge, from right to left.
1681 cluster.sortConfigurationForEdgePush(whichEdge);
1682
1683 while (pushDistance > 0 && !fail) {
1684 for (View v: currentState.sortedViews) {
1685 // For each view that isn't in the cluster, we see if the leading edge of the
1686 // cluster is contacting the edge of that view. If so, we add that view to the
1687 // cluster.
1688 if (!cluster.views.contains(v) && v != dragView) {
1689 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1690 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1691 if (!lp.canReorder) {
1692 // The push solution includes the all apps button, this is not viable.
1693 fail = true;
1694 break;
1695 }
1696 cluster.addView(v);
1697 CellAndSpan c = currentState.map.get(v);
1698
1699 // Adding view to cluster, mark it as not occupied.
1700 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1701 }
1702 }
1703 }
1704 pushDistance--;
1705
1706 // The cluster has been completed, now we move the whole thing over in the appropriate
1707 // direction.
1708 cluster.shift(whichEdge, 1);
1709 }
1710
1711 boolean foundSolution = false;
1712 clusterRect = cluster.getBoundingRect();
1713
1714 // Due to the nature of the algorithm, the only check required to verify a valid solution
1715 // is to ensure that completed shifted cluster lies completely within the cell layout.
1716 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1717 clusterRect.bottom <= mCountY) {
1718 foundSolution = true;
1719 } else {
1720 currentState.restore();
1721 }
1722
1723 // In either case, we set the occupied array as marked for the location of the views
1724 for (View v: cluster.views) {
1725 CellAndSpan c = currentState.map.get(v);
1726 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1727 }
1728
1729 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001730 }
1731
Adam Cohen482ed822012-03-02 14:15:13 -08001732 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001733 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001734 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001735
Adam Cohen8baab352012-03-20 17:39:21 -07001736 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001737 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001738 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001739 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001740 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001741 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001742 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001743 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001744 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001745 }
1746 }
Adam Cohen8baab352012-03-20 17:39:21 -07001747
Adam Cohen8baab352012-03-20 17:39:21 -07001748 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001749 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001750 CellAndSpan c = currentState.map.get(v);
1751 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1752 }
1753
Adam Cohen47a876d2012-03-19 13:21:41 -07001754 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1755 int top = boundingRect.top;
1756 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001757 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001758 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001759 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001760 CellAndSpan c = currentState.map.get(v);
1761 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001762 }
1763
Adam Cohen482ed822012-03-02 14:15:13 -08001764 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1765
Adam Cohenf3900c22012-11-16 18:28:11 -08001766 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1767 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001768
Adam Cohen8baab352012-03-20 17:39:21 -07001769 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001770 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001771 int deltaX = mTempLocation[0] - boundingRect.left;
1772 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001773 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001774 CellAndSpan c = currentState.map.get(v);
1775 c.x += deltaX;
1776 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001777 }
1778 success = true;
1779 }
Adam Cohen8baab352012-03-20 17:39:21 -07001780
1781 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001782 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001783 CellAndSpan c = currentState.map.get(v);
1784 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001785 }
1786 return success;
1787 }
1788
1789 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1790 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1791 }
1792
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001793 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1794 // to push items in each of the cardinal directions, in an order based on the direction vector
1795 // passed.
1796 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1797 int[] direction, View ignoreView, ItemConfiguration solution) {
1798 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001799 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001800 // separately in each of the components.
1801 int temp = direction[1];
1802 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001803
1804 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001805 ignoreView, solution)) {
1806 return true;
1807 }
1808 direction[1] = temp;
1809 temp = direction[0];
1810 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001811
1812 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001813 ignoreView, solution)) {
1814 return true;
1815 }
1816 // Revert the direction
1817 direction[0] = temp;
1818
1819 // Now we try pushing in each component of the opposite direction
1820 direction[0] *= -1;
1821 direction[1] *= -1;
1822 temp = direction[1];
1823 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001824 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001825 ignoreView, solution)) {
1826 return true;
1827 }
1828
1829 direction[1] = temp;
1830 temp = direction[0];
1831 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001832 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001833 ignoreView, solution)) {
1834 return true;
1835 }
1836 // revert the direction
1837 direction[0] = temp;
1838 direction[0] *= -1;
1839 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001840
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001841 } else {
1842 // If the direction vector has a single non-zero component, we push first in the
1843 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08001844 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001845 ignoreView, solution)) {
1846 return true;
1847 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001848 // Then we try the opposite direction
1849 direction[0] *= -1;
1850 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001851 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001852 ignoreView, solution)) {
1853 return true;
1854 }
1855 // Switch the direction back
1856 direction[0] *= -1;
1857 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001858
1859 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001860 // to find a solution by pushing along the perpendicular axis.
1861
1862 // Swap the components
1863 int temp = direction[1];
1864 direction[1] = direction[0];
1865 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08001866 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001867 ignoreView, solution)) {
1868 return true;
1869 }
1870
1871 // Then we try the opposite direction
1872 direction[0] *= -1;
1873 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001874 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001875 ignoreView, solution)) {
1876 return true;
1877 }
1878 // Switch the direction back
1879 direction[0] *= -1;
1880 direction[1] *= -1;
1881
1882 // Swap the components back
1883 temp = direction[1];
1884 direction[1] = direction[0];
1885 direction[0] = temp;
1886 }
1887 return false;
1888 }
1889
Adam Cohen482ed822012-03-02 14:15:13 -08001890 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001891 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001892 // Return early if get invalid cell positions
1893 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001894
Adam Cohen8baab352012-03-20 17:39:21 -07001895 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001896 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001897
Adam Cohen8baab352012-03-20 17:39:21 -07001898 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001899 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001900 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001901 if (c != null) {
1902 c.x = cellX;
1903 c.y = cellY;
1904 }
Adam Cohen482ed822012-03-02 14:15:13 -08001905 }
Adam Cohen482ed822012-03-02 14:15:13 -08001906 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1907 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001908 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001909 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001910 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001911 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001912 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001913 if (Rect.intersects(r0, r1)) {
1914 if (!lp.canReorder) {
1915 return false;
1916 }
1917 mIntersectingViews.add(child);
1918 }
1919 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001920
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001921 solution.intersectingViews = new ArrayList<View>(mIntersectingViews);
1922
Winson Chung5f8afe62013-08-12 16:19:28 -07001923 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001924 // we try to find a solution such that no displaced item travels through another item
1925 // without also displacing that item.
1926 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001927 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001928 return true;
1929 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001930
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001931 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08001932 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001933 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001934 return true;
1935 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001936
Adam Cohen482ed822012-03-02 14:15:13 -08001937 // Ok, they couldn't move as a block, let's move them individually
1938 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001939 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001940 return false;
1941 }
1942 }
1943 return true;
1944 }
1945
1946 /*
1947 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1948 * the provided point and the provided cell
1949 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001950 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001951 double angle = Math.atan(((float) deltaY) / deltaX);
1952
1953 result[0] = 0;
1954 result[1] = 0;
1955 if (Math.abs(Math.cos(angle)) > 0.5f) {
1956 result[0] = (int) Math.signum(deltaX);
1957 }
1958 if (Math.abs(Math.sin(angle)) > 0.5f) {
1959 result[1] = (int) Math.signum(deltaY);
1960 }
1961 }
1962
Adam Cohen8baab352012-03-20 17:39:21 -07001963 private void copyOccupiedArray(boolean[][] occupied) {
1964 for (int i = 0; i < mCountX; i++) {
1965 for (int j = 0; j < mCountY; j++) {
1966 occupied[i][j] = mOccupied[i][j];
1967 }
1968 }
1969 }
1970
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001971 private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001972 int spanX, int spanY, int[] direction, View dragView, boolean decX,
1973 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001974 // Copy the current state into the solution. This solution will be manipulated as necessary.
1975 copyCurrentStateToSolution(solution, false);
1976 // Copy the current occupied array into the temporary occupied array. This array will be
1977 // manipulated as necessary to find a solution.
1978 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001979
1980 // We find the nearest cell into which we would place the dragged item, assuming there's
1981 // nothing in its way.
1982 int result[] = new int[2];
1983 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1984
1985 boolean success = false;
1986 // First we try the exact nearest position of the item being dragged,
1987 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001988 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1989 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001990
1991 if (!success) {
1992 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1993 // x, then 1 in y etc.
1994 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001995 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
1996 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001997 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001998 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
1999 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002000 }
2001 solution.isSolution = false;
2002 } else {
2003 solution.isSolution = true;
2004 solution.dragViewX = result[0];
2005 solution.dragViewY = result[1];
2006 solution.dragViewSpanX = spanX;
2007 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002008 }
2009 return solution;
2010 }
2011
2012 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002013 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002014 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002015 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002016 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002017 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08002018 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07002019 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002020 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07002021 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002022 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002023 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08002024 }
2025 }
2026
2027 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
2028 for (int i = 0; i < mCountX; i++) {
2029 for (int j = 0; j < mCountY; j++) {
2030 mTmpOccupied[i][j] = false;
2031 }
2032 }
2033
Michael Jurkaa52570f2012-03-20 03:18:20 -07002034 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002035 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002036 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002037 if (child == dragView) continue;
2038 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002039 CellAndSpan c = solution.map.get(child);
2040 if (c != null) {
2041 lp.tmpCellX = c.x;
2042 lp.tmpCellY = c.y;
2043 lp.cellHSpan = c.spanX;
2044 lp.cellVSpan = c.spanY;
2045 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002046 }
2047 }
2048 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2049 solution.dragViewSpanY, mTmpOccupied, true);
2050 }
2051
2052 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2053 commitDragView) {
2054
2055 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2056 for (int i = 0; i < mCountX; i++) {
2057 for (int j = 0; j < mCountY; j++) {
2058 occupied[i][j] = false;
2059 }
2060 }
2061
Michael Jurkaa52570f2012-03-20 03:18:20 -07002062 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002063 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002064 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002065 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002066 CellAndSpan c = solution.map.get(child);
2067 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002068 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2069 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002070 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002071 }
2072 }
2073 if (commitDragView) {
2074 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2075 solution.dragViewSpanY, occupied, true);
2076 }
2077 }
2078
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002079
2080 // This method starts or changes the reorder preview animations
2081 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
2082 View dragView, int delay, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07002083 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002084 for (int i = 0; i < childCount; i++) {
2085 View child = mShortcutsAndWidgets.getChildAt(i);
2086 if (child == dragView) continue;
2087 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002088 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
2089 != null && !solution.intersectingViews.contains(child);
2090
Adam Cohen19f37922012-03-21 11:59:11 -07002091 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002092 if (c != null && !skip) {
2093 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
2094 lp.cellY, c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002095 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002096 }
2097 }
2098 }
2099
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002100 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07002101 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002102 class ReorderPreviewAnimation {
Adam Cohen19f37922012-03-21 11:59:11 -07002103 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002104 float finalDeltaX;
2105 float finalDeltaY;
2106 float initDeltaX;
2107 float initDeltaY;
2108 float finalScale;
2109 float initScale;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002110 int mode;
2111 boolean repeating = false;
2112 private static final int PREVIEW_DURATION = 300;
2113 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
2114
2115 public static final int MODE_HINT = 0;
2116 public static final int MODE_PREVIEW = 1;
2117
Adam Cohene7587d22012-05-24 18:50:02 -07002118 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002119
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002120 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
2121 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07002122 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2123 final int x0 = mTmpPoint[0];
2124 final int y0 = mTmpPoint[1];
2125 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2126 final int x1 = mTmpPoint[0];
2127 final int y1 = mTmpPoint[1];
2128 final int dX = x1 - x0;
2129 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002130 finalDeltaX = 0;
2131 finalDeltaY = 0;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002132 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07002133 if (dX == dY && dX == 0) {
2134 } else {
2135 if (dY == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002136 finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002137 } else if (dX == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002138 finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002139 } else {
2140 double angle = Math.atan( (float) (dY) / dX);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002141 finalDeltaX = (int) (- dir * Math.signum(dX) *
2142 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
2143 finalDeltaY = (int) (- dir * Math.signum(dY) *
2144 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002145 }
2146 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002147 this.mode = mode;
Adam Cohend024f982012-05-23 18:26:45 -07002148 initDeltaX = child.getTranslationX();
2149 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002150 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002151 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002152 this.child = child;
2153 }
2154
Adam Cohend024f982012-05-23 18:26:45 -07002155 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002156 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002157 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002158 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002159 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002160 if (finalDeltaX == 0 && finalDeltaY == 0) {
2161 completeAnimationImmediately();
2162 return;
2163 }
Adam Cohen19f37922012-03-21 11:59:11 -07002164 }
Adam Cohend024f982012-05-23 18:26:45 -07002165 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002166 return;
2167 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002168 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002169 a = va;
Adam Cohen19f37922012-03-21 11:59:11 -07002170 va.setRepeatMode(ValueAnimator.REVERSE);
2171 va.setRepeatCount(ValueAnimator.INFINITE);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002172 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002173 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002174 va.addUpdateListener(new AnimatorUpdateListener() {
2175 @Override
2176 public void onAnimationUpdate(ValueAnimator animation) {
2177 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002178 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2179 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2180 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002181 child.setTranslationX(x);
2182 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002183 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002184 child.setScaleX(s);
2185 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002186 }
2187 });
2188 va.addListener(new AnimatorListenerAdapter() {
2189 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002190 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002191 initDeltaX = 0;
2192 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002193 initScale = getChildrenScale();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002194 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002195 }
2196 });
Adam Cohen19f37922012-03-21 11:59:11 -07002197 mShakeAnimators.put(child, this);
2198 va.start();
2199 }
2200
Adam Cohend024f982012-05-23 18:26:45 -07002201 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002202 if (a != null) {
2203 a.cancel();
2204 }
Adam Cohen19f37922012-03-21 11:59:11 -07002205 }
Adam Cohene7587d22012-05-24 18:50:02 -07002206
Adam Cohen091440a2015-03-18 14:16:05 -07002207 @Thunk void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002208 if (a != null) {
2209 a.cancel();
2210 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002211
Michael Jurka2ecf9952012-06-18 12:52:28 -07002212 AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002213 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002214 s.playTogether(
Adam Cohen307fe232012-08-16 17:55:58 -07002215 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
2216 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
Michael Jurka2ecf9952012-06-18 12:52:28 -07002217 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2218 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002219 );
2220 s.setDuration(REORDER_ANIMATION_DURATION);
2221 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2222 s.start();
2223 }
Adam Cohen19f37922012-03-21 11:59:11 -07002224 }
2225
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002226 private void completeAndClearReorderPreviewAnimations() {
2227 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002228 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002229 }
2230 mShakeAnimators.clear();
2231 }
2232
Adam Cohen482ed822012-03-02 14:15:13 -08002233 private void commitTempPlacement() {
2234 for (int i = 0; i < mCountX; i++) {
2235 for (int j = 0; j < mCountY; j++) {
2236 mOccupied[i][j] = mTmpOccupied[i][j];
2237 }
2238 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002239 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002240 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002241 View child = mShortcutsAndWidgets.getChildAt(i);
2242 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2243 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002244 // We do a null check here because the item info can be null in the case of the
2245 // AllApps button in the hotseat.
2246 if (info != null) {
Adam Cohen487f7dd2012-06-28 18:12:10 -07002247 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
2248 info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
2249 info.requiresDbUpdate = true;
2250 }
Adam Cohen2acce882012-03-28 19:03:19 -07002251 info.cellX = lp.cellX = lp.tmpCellX;
2252 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002253 info.spanX = lp.cellHSpan;
2254 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002255 }
Adam Cohen482ed822012-03-02 14:15:13 -08002256 }
Adam Cohen2acce882012-03-28 19:03:19 -07002257 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002258 }
2259
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002260 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002261 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002262 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002263 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002264 lp.useTmpCoords = useTempCoords;
2265 }
2266 }
2267
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002268 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002269 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2270 int[] result = new int[2];
2271 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002272 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08002273 resultSpan);
2274 if (result[0] >= 0 && result[1] >= 0) {
2275 copyCurrentStateToSolution(solution, false);
2276 solution.dragViewX = result[0];
2277 solution.dragViewY = result[1];
2278 solution.dragViewSpanX = resultSpan[0];
2279 solution.dragViewSpanY = resultSpan[1];
2280 solution.isSolution = true;
2281 } else {
2282 solution.isSolution = false;
2283 }
2284 return solution;
2285 }
2286
2287 public void prepareChildForDrag(View child) {
2288 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002289 }
2290
Adam Cohen19f37922012-03-21 11:59:11 -07002291 /* This seems like it should be obvious and straight-forward, but when the direction vector
2292 needs to match with the notion of the dragView pushing other views, we have to employ
2293 a slightly more subtle notion of the direction vector. The question is what two points is
2294 the vector between? The center of the dragView and its desired destination? Not quite, as
2295 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2296 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2297 or right, which helps make pushing feel right.
2298 */
2299 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2300 int spanY, View dragView, int[] resultDirection) {
2301 int[] targetDestination = new int[2];
2302
2303 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2304 Rect dragRect = new Rect();
2305 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2306 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2307
2308 Rect dropRegionRect = new Rect();
2309 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2310 dragView, dropRegionRect, mIntersectingViews);
2311
2312 int dropRegionSpanX = dropRegionRect.width();
2313 int dropRegionSpanY = dropRegionRect.height();
2314
2315 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2316 dropRegionRect.height(), dropRegionRect);
2317
2318 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2319 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2320
2321 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2322 deltaX = 0;
2323 }
2324 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2325 deltaY = 0;
2326 }
2327
2328 if (deltaX == 0 && deltaY == 0) {
2329 // No idea what to do, give a random direction.
2330 resultDirection[0] = 1;
2331 resultDirection[1] = 0;
2332 } else {
2333 computeDirectionVector(deltaX, deltaY, resultDirection);
2334 }
2335 }
2336
2337 // For a given cell and span, fetch the set of views intersecting the region.
2338 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2339 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2340 if (boundingRect != null) {
2341 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2342 }
2343 intersectingViews.clear();
2344 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2345 Rect r1 = new Rect();
2346 final int count = mShortcutsAndWidgets.getChildCount();
2347 for (int i = 0; i < count; i++) {
2348 View child = mShortcutsAndWidgets.getChildAt(i);
2349 if (child == dragView) continue;
2350 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2351 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2352 if (Rect.intersects(r0, r1)) {
2353 mIntersectingViews.add(child);
2354 if (boundingRect != null) {
2355 boundingRect.union(r1);
2356 }
2357 }
2358 }
2359 }
2360
2361 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2362 View dragView, int[] result) {
2363 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2364 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2365 mIntersectingViews);
2366 return !mIntersectingViews.isEmpty();
2367 }
2368
2369 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002370 completeAndClearReorderPreviewAnimations();
2371 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2372 final int count = mShortcutsAndWidgets.getChildCount();
2373 for (int i = 0; i < count; i++) {
2374 View child = mShortcutsAndWidgets.getChildAt(i);
2375 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2376 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2377 lp.tmpCellX = lp.cellX;
2378 lp.tmpCellY = lp.cellY;
2379 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2380 0, false, false);
2381 }
Adam Cohen19f37922012-03-21 11:59:11 -07002382 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002383 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002384 }
Adam Cohen19f37922012-03-21 11:59:11 -07002385 }
2386
Adam Cohenbebf0422012-04-11 18:06:28 -07002387 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2388 View dragView, int[] direction, boolean commit) {
2389 int[] pixelXY = new int[2];
2390 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2391
2392 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002393 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002394 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2395
2396 setUseTempCoords(true);
2397 if (swapSolution != null && swapSolution.isSolution) {
2398 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2399 // committing anything or animating anything as we just want to determine if a solution
2400 // exists
2401 copySolutionToTempState(swapSolution, dragView);
2402 setItemPlacementDirty(true);
2403 animateItemsToSolution(swapSolution, dragView, commit);
2404
2405 if (commit) {
2406 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002407 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002408 setItemPlacementDirty(false);
2409 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002410 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2411 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002412 }
2413 mShortcutsAndWidgets.requestLayout();
2414 }
2415 return swapSolution.isSolution;
2416 }
2417
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002418 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002419 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002420 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002421 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002422
2423 if (resultSpan == null) {
2424 resultSpan = new int[2];
2425 }
2426
Adam Cohen19f37922012-03-21 11:59:11 -07002427 // When we are checking drop validity or actually dropping, we don't recompute the
2428 // direction vector, since we want the solution to match the preview, and it's possible
2429 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002430 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2431 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002432 mDirectionVector[0] = mPreviousReorderDirection[0];
2433 mDirectionVector[1] = mPreviousReorderDirection[1];
2434 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002435 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2436 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2437 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002438 }
2439 } else {
2440 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2441 mPreviousReorderDirection[0] = mDirectionVector[0];
2442 mPreviousReorderDirection[1] = mDirectionVector[1];
2443 }
2444
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002445 // Find a solution involving pushing / displacing any items in the way
2446 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002447 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2448
2449 // We attempt the approach which doesn't shuffle views at all
2450 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2451 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2452
2453 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002454
2455 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2456 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002457 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2458 finalSolution = swapSolution;
2459 } else if (noShuffleSolution.isSolution) {
2460 finalSolution = noShuffleSolution;
2461 }
2462
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002463 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002464 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002465 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2466 ReorderPreviewAnimation.MODE_HINT);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002467 result[0] = finalSolution.dragViewX;
2468 result[1] = finalSolution.dragViewY;
2469 resultSpan[0] = finalSolution.dragViewSpanX;
2470 resultSpan[1] = finalSolution.dragViewSpanY;
2471 } else {
2472 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2473 }
2474 return result;
2475 }
2476
Adam Cohen482ed822012-03-02 14:15:13 -08002477 boolean foundSolution = true;
2478 if (!DESTRUCTIVE_REORDER) {
2479 setUseTempCoords(true);
2480 }
2481
2482 if (finalSolution != null) {
2483 result[0] = finalSolution.dragViewX;
2484 result[1] = finalSolution.dragViewY;
2485 resultSpan[0] = finalSolution.dragViewSpanX;
2486 resultSpan[1] = finalSolution.dragViewSpanY;
2487
2488 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2489 // committing anything or animating anything as we just want to determine if a solution
2490 // exists
2491 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2492 if (!DESTRUCTIVE_REORDER) {
2493 copySolutionToTempState(finalSolution, dragView);
2494 }
2495 setItemPlacementDirty(true);
2496 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2497
Adam Cohen19f37922012-03-21 11:59:11 -07002498 if (!DESTRUCTIVE_REORDER &&
2499 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002500 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002501 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002502 setItemPlacementDirty(false);
2503 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002504 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2505 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002506 }
2507 }
2508 } else {
2509 foundSolution = false;
2510 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2511 }
2512
2513 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2514 setUseTempCoords(false);
2515 }
Adam Cohen482ed822012-03-02 14:15:13 -08002516
Michael Jurkaa52570f2012-03-20 03:18:20 -07002517 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002518 return result;
2519 }
2520
Adam Cohen19f37922012-03-21 11:59:11 -07002521 void setItemPlacementDirty(boolean dirty) {
2522 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002523 }
Adam Cohen19f37922012-03-21 11:59:11 -07002524 boolean isItemPlacementDirty() {
2525 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002526 }
2527
Adam Cohen091440a2015-03-18 14:16:05 -07002528 @Thunk class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002529 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002530 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2531 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002532 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002533 boolean isSolution = false;
2534 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2535
Adam Cohenf3900c22012-11-16 18:28:11 -08002536 void save() {
2537 // Copy current state into savedMap
2538 for (View v: map.keySet()) {
2539 map.get(v).copy(savedMap.get(v));
2540 }
2541 }
2542
2543 void restore() {
2544 // Restore current state from savedMap
2545 for (View v: savedMap.keySet()) {
2546 savedMap.get(v).copy(map.get(v));
2547 }
2548 }
2549
2550 void add(View v, CellAndSpan cs) {
2551 map.put(v, cs);
2552 savedMap.put(v, new CellAndSpan());
2553 sortedViews.add(v);
2554 }
2555
Adam Cohen482ed822012-03-02 14:15:13 -08002556 int area() {
2557 return dragViewSpanX * dragViewSpanY;
2558 }
Adam Cohen8baab352012-03-20 17:39:21 -07002559 }
2560
2561 private class CellAndSpan {
2562 int x, y;
2563 int spanX, spanY;
2564
Adam Cohenf3900c22012-11-16 18:28:11 -08002565 public CellAndSpan() {
2566 }
2567
2568 public void copy(CellAndSpan copy) {
2569 copy.x = x;
2570 copy.y = y;
2571 copy.spanX = spanX;
2572 copy.spanY = spanY;
2573 }
2574
Adam Cohen8baab352012-03-20 17:39:21 -07002575 public CellAndSpan(int x, int y, int spanX, int spanY) {
2576 this.x = x;
2577 this.y = y;
2578 this.spanX = spanX;
2579 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002580 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002581
2582 public String toString() {
2583 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2584 }
2585
Adam Cohen482ed822012-03-02 14:15:13 -08002586 }
2587
Adam Cohendf035382011-04-11 17:22:04 -07002588 /**
Adam Cohendf035382011-04-11 17:22:04 -07002589 * Find a starting cell position that will fit the given bounds nearest the requested
2590 * cell location. Uses Euclidean distance to score multiple vacant areas.
2591 *
2592 * @param pixelX The X location at which you want to search for a vacant area.
2593 * @param pixelY The Y location at which you want to search for a vacant area.
2594 * @param spanX Horizontal span of the object.
2595 * @param spanY Vertical span of the object.
2596 * @param ignoreView Considers space occupied by this view as unoccupied
2597 * @param result Previously returned value to possibly recycle.
2598 * @return The X, Y cell of a vacant area that can contain this object,
2599 * nearest the requested location.
2600 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002601 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2602 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002603 }
2604
Michael Jurka0280c3b2010-09-17 15:00:07 -07002605 boolean existsEmptyCell() {
2606 return findCellForSpan(null, 1, 1);
2607 }
2608
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002609 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002610 * Finds the upper-left coordinate of the first rectangle in the grid that can
2611 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2612 * then this method will only return coordinates for rectangles that contain the cell
2613 * (intersectX, intersectY)
2614 *
2615 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2616 * can be found.
2617 * @param spanX The horizontal span of the cell we want to find.
2618 * @param spanY The vertical span of the cell we want to find.
2619 *
2620 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002621 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002622 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Michael Jurka28750fb2010-09-24 17:43:49 -07002623 boolean foundCell = false;
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002624 final int endX = mCountX - (spanX - 1);
2625 final int endY = mCountY - (spanY - 1);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002626
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002627 for (int y = 0; y < endY && !foundCell; y++) {
2628 inner:
2629 for (int x = 0; x < endX; x++) {
2630 for (int i = 0; i < spanX; i++) {
2631 for (int j = 0; j < spanY; j++) {
2632 if (mOccupied[x + i][y + j]) {
2633 // small optimization: we can skip to after the column we just found
2634 // an occupied cell
2635 x += i;
2636 continue inner;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002637 }
2638 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002639 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002640 if (cellXY != null) {
2641 cellXY[0] = x;
2642 cellXY[1] = y;
2643 }
2644 foundCell = true;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002645 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002646 }
2647 }
2648
Michael Jurka28750fb2010-09-24 17:43:49 -07002649 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002650 }
2651
2652 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002653 * A drag event has begun over this layout.
2654 * It may have begun over this layout (in which case onDragChild is called first),
2655 * or it may have begun on another layout.
2656 */
2657 void onDragEnter() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002658 mDragEnforcer.onDragEnter();
Winson Chungc07918d2011-07-01 15:35:26 -07002659 mDragging = true;
2660 }
2661
2662 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002663 * Called when drag has left this CellLayout or has been completed (successfully or not)
2664 */
2665 void onDragExit() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002666 mDragEnforcer.onDragExit();
Joe Onorato4be866d2010-10-10 11:26:02 -07002667 // This can actually be called when we aren't in a drag, e.g. when adding a new
2668 // item to this layout via the customize drawer.
2669 // Guard against that case.
2670 if (mDragging) {
2671 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002672 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002673
2674 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002675 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002676 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2677 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002678 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002679 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002680 }
2681
2682 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002683 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002684 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002685 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002686 *
2687 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002688 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002689 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002690 if (child != null) {
2691 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002692 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002693 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002694 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002695 }
2696
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002697 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002698 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002699 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002700 * @param cellX X coordinate of upper left corner expressed as a cell position
2701 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002702 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002703 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002704 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002705 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002706 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002707 final int cellWidth = mCellWidth;
2708 final int cellHeight = mCellHeight;
2709 final int widthGap = mWidthGap;
2710 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002711
Winson Chung4b825dcd2011-06-19 12:41:22 -07002712 final int hStartPadding = getPaddingLeft();
2713 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002714
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002715 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2716 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2717
2718 int x = hStartPadding + cellX * (cellWidth + widthGap);
2719 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002720
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002721 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002722 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002723
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002724 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002725 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002726 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002727 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002728 * @param width Width in pixels
2729 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002730 * @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 -08002731 */
Winson Chung66700732013-08-20 16:56:15 -07002732 public static int[] rectToCell(int width, int height, int[] result) {
Winson Chung5f8afe62013-08-12 16:19:28 -07002733 LauncherAppState app = LauncherAppState.getInstance();
2734 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Winson Chung66700732013-08-20 16:56:15 -07002735 Rect padding = grid.getWorkspacePadding(grid.isLandscape ?
2736 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
Winson Chung5f8afe62013-08-12 16:19:28 -07002737
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002738 // Always assume we're working with the smallest span to make sure we
2739 // reserve enough space in both orientations.
Winson Chung66700732013-08-20 16:56:15 -07002740 int parentWidth = grid.calculateCellWidth(grid.widthPx
2741 - padding.left - padding.right, (int) grid.numColumns);
2742 int parentHeight = grid.calculateCellHeight(grid.heightPx
2743 - padding.top - padding.bottom, (int) grid.numRows);
2744 int smallerSize = Math.min(parentWidth, parentHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002745
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002746 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002747 int spanX = (int) Math.ceil(width / (float) smallerSize);
2748 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002749
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002750 if (result == null) {
2751 return new int[] { spanX, spanY };
2752 }
2753 result[0] = spanX;
2754 result[1] = spanY;
2755 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002756 }
2757
2758 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08002759 * Calculate the grid spans needed to fit given item
2760 */
2761 public void calculateSpans(ItemInfo info) {
2762 final int minWidth;
2763 final int minHeight;
2764
2765 if (info instanceof LauncherAppWidgetInfo) {
2766 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
2767 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
2768 } else if (info instanceof PendingAddWidgetInfo) {
2769 minWidth = ((PendingAddWidgetInfo) info).minWidth;
2770 minHeight = ((PendingAddWidgetInfo) info).minHeight;
2771 } else {
2772 // It's not a widget, so it must be 1x1
2773 info.spanX = info.spanY = 1;
2774 return;
2775 }
2776 int[] spans = rectToCell(minWidth, minHeight, null);
2777 info.spanX = spans[0];
2778 info.spanY = spans[1];
2779 }
2780
Michael Jurka0280c3b2010-09-17 15:00:07 -07002781 private void clearOccupiedCells() {
2782 for (int x = 0; x < mCountX; x++) {
2783 for (int y = 0; y < mCountY; y++) {
2784 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002785 }
2786 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002787 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002788
Adam Cohend4844c32011-02-18 19:25:06 -08002789 public void markCellsAsOccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002790 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002791 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002792 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002793 }
2794
Adam Cohend4844c32011-02-18 19:25:06 -08002795 public void markCellsAsUnoccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002796 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002797 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002798 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002799 }
2800
Adam Cohen482ed822012-03-02 14:15:13 -08002801 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2802 boolean value) {
2803 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002804 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2805 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002806 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002807 }
2808 }
2809 }
2810
Adam Cohen2801caf2011-05-13 20:57:39 -07002811 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002812 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002813 (Math.max((mCountX - 1), 0) * mWidthGap);
2814 }
2815
2816 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002817 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002818 (Math.max((mCountY - 1), 0) * mHeightGap);
2819 }
2820
Michael Jurka66d72172011-04-12 16:29:25 -07002821 public boolean isOccupied(int x, int y) {
2822 if (x < mCountX && y < mCountY) {
2823 return mOccupied[x][y];
2824 } else {
2825 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2826 }
2827 }
2828
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002829 @Override
2830 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2831 return new CellLayout.LayoutParams(getContext(), attrs);
2832 }
2833
2834 @Override
2835 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2836 return p instanceof CellLayout.LayoutParams;
2837 }
2838
2839 @Override
2840 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2841 return new CellLayout.LayoutParams(p);
2842 }
2843
2844 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2845 /**
2846 * Horizontal location of the item in the grid.
2847 */
2848 @ViewDebug.ExportedProperty
2849 public int cellX;
2850
2851 /**
2852 * Vertical location of the item in the grid.
2853 */
2854 @ViewDebug.ExportedProperty
2855 public int cellY;
2856
2857 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002858 * Temporary horizontal location of the item in the grid during reorder
2859 */
2860 public int tmpCellX;
2861
2862 /**
2863 * Temporary vertical location of the item in the grid during reorder
2864 */
2865 public int tmpCellY;
2866
2867 /**
2868 * Indicates that the temporary coordinates should be used to layout the items
2869 */
2870 public boolean useTmpCoords;
2871
2872 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002873 * Number of cells spanned horizontally by the item.
2874 */
2875 @ViewDebug.ExportedProperty
2876 public int cellHSpan;
2877
2878 /**
2879 * Number of cells spanned vertically by the item.
2880 */
2881 @ViewDebug.ExportedProperty
2882 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002883
Adam Cohen1b607ed2011-03-03 17:26:50 -08002884 /**
2885 * Indicates whether the item will set its x, y, width and height parameters freely,
2886 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2887 */
Adam Cohend4844c32011-02-18 19:25:06 -08002888 public boolean isLockedToGrid = true;
2889
Adam Cohen482ed822012-03-02 14:15:13 -08002890 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07002891 * Indicates that this item should use the full extents of its parent.
2892 */
2893 public boolean isFullscreen = false;
2894
2895 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002896 * Indicates whether this item can be reordered. Always true except in the case of the
2897 * the AllApps button.
2898 */
2899 public boolean canReorder = true;
2900
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002901 // X coordinate of the view in the layout.
2902 @ViewDebug.ExportedProperty
2903 int x;
2904 // Y coordinate of the view in the layout.
2905 @ViewDebug.ExportedProperty
2906 int y;
2907
Romain Guy84f296c2009-11-04 15:00:44 -08002908 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002909
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002910 public LayoutParams(Context c, AttributeSet attrs) {
2911 super(c, attrs);
2912 cellHSpan = 1;
2913 cellVSpan = 1;
2914 }
2915
2916 public LayoutParams(ViewGroup.LayoutParams source) {
2917 super(source);
2918 cellHSpan = 1;
2919 cellVSpan = 1;
2920 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002921
2922 public LayoutParams(LayoutParams source) {
2923 super(source);
2924 this.cellX = source.cellX;
2925 this.cellY = source.cellY;
2926 this.cellHSpan = source.cellHSpan;
2927 this.cellVSpan = source.cellVSpan;
2928 }
2929
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002930 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002931 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002932 this.cellX = cellX;
2933 this.cellY = cellY;
2934 this.cellHSpan = cellHSpan;
2935 this.cellVSpan = cellVSpan;
2936 }
2937
Adam Cohen2374abf2013-04-16 14:56:57 -07002938 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
2939 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08002940 if (isLockedToGrid) {
2941 final int myCellHSpan = cellHSpan;
2942 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07002943 int myCellX = useTmpCoords ? tmpCellX : cellX;
2944 int myCellY = useTmpCoords ? tmpCellY : cellY;
2945
2946 if (invertHorizontally) {
2947 myCellX = colCount - myCellX - cellHSpan;
2948 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08002949
Adam Cohend4844c32011-02-18 19:25:06 -08002950 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2951 leftMargin - rightMargin;
2952 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2953 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002954 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2955 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002956 }
2957 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002958
Winson Chungaafa03c2010-06-11 17:34:16 -07002959 public String toString() {
2960 return "(" + this.cellX + ", " + this.cellY + ")";
2961 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002962
2963 public void setWidth(int width) {
2964 this.width = width;
2965 }
2966
2967 public int getWidth() {
2968 return width;
2969 }
2970
2971 public void setHeight(int height) {
2972 this.height = height;
2973 }
2974
2975 public int getHeight() {
2976 return height;
2977 }
2978
2979 public void setX(int x) {
2980 this.x = x;
2981 }
2982
2983 public int getX() {
2984 return x;
2985 }
2986
2987 public void setY(int y) {
2988 this.y = y;
2989 }
2990
2991 public int getY() {
2992 return y;
2993 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002994 }
2995
Michael Jurka0280c3b2010-09-17 15:00:07 -07002996 // This class stores info for two purposes:
2997 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2998 // its spanX, spanY, and the screen it is on
2999 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
3000 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
3001 // the CellLayout that was long clicked
Michael Jurkae5fb0f22011-04-11 13:27:46 -07003002 static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003003 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07003004 int cellX = -1;
3005 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003006 int spanX;
3007 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07003008 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07003009 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003010
Adam Cohene0aaa0d2014-05-12 12:44:22 -07003011 CellInfo(View v, ItemInfo info) {
3012 cell = v;
3013 cellX = info.cellX;
3014 cellY = info.cellY;
3015 spanX = info.spanX;
3016 spanY = info.spanY;
3017 screenId = info.screenId;
3018 container = info.container;
3019 }
3020
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003021 @Override
3022 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07003023 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
3024 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003025 }
3026 }
Michael Jurkad771c962011-08-09 15:00:48 -07003027
3028 public boolean lastDownOnOccupiedCell() {
3029 return mLastDownOnOccupiedCell;
3030 }
Sunny Goyala9116722015-04-29 13:55:58 -07003031
3032 public boolean findVacantCell(int spanX, int spanY, int[] outXY) {
3033 return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied);
3034 }
Sunny Goyal9ca9c132015-04-29 14:57:22 -07003035
3036 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
3037 int x2 = x + spanX - 1;
3038 int y2 = y + spanY - 1;
3039 if (x < 0 || y < 0 || x2 >= mCountX || y2 >= mCountY) {
3040 return false;
3041 }
3042 for (int i = x; i <= x2; i++) {
3043 for (int j = y; j <= y2; j++) {
3044 if (mOccupied[i][j]) {
3045 return false;
3046 }
3047 }
3048 }
3049
3050 return true;
3051 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003052}