blob: 98258d9ddb7365426374a68fb566c7ad5738f3c0 [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;
Chet Haase00397b12010-10-07 11:13:10 -070021import android.animation.TimeInterpolator;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070022import android.animation.ValueAnimator;
23import android.animation.ValueAnimator.AnimatorUpdateListener;
Adam Cohenc9735cf2015-01-23 16:11:55 -080024import android.annotation.TargetApi;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080025import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040026import android.content.res.Resources;
Winson Chungaafa03c2010-06-11 17:34:16 -070027import android.content.res.TypedArray;
Joe Onorato4be866d2010-10-10 11:26:02 -070028import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070029import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080030import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070031import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070032import android.graphics.Point;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080033import android.graphics.Rect;
Adam Cohen482ed822012-03-02 14:15:13 -080034import android.graphics.drawable.ColorDrawable;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070035import android.graphics.drawable.Drawable;
Sunny Goyal2805e632015-05-20 15:35:32 -070036import android.graphics.drawable.TransitionDrawable;
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 Goyalaa8ef112015-06-12 20:04:41 -070052import com.android.launcher3.LauncherSettings.Favorites;
Sunny Goyale9b651e2015-04-24 11:44:51 -070053import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
54import com.android.launcher3.accessibility.FolderAccessibilityHelper;
55import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
Sunny Goyal6c56c682015-07-16 14:09:05 -070056import com.android.launcher3.config.ProviderConfig;
Adam Cohen091440a2015-03-18 14:16:05 -070057import com.android.launcher3.util.Thunk;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070058
Adam Cohen69ce2e52011-07-03 19:25:21 -070059import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070060import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080061import java.util.Collections;
62import java.util.Comparator;
Adam Cohenbfbfd262011-06-13 16:55:12 -070063import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080064import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070065
Sunny Goyal4b6eb262015-05-14 19:24:40 -070066public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
Sunny Goyale9b651e2015-04-24 11:44:51 -070067 public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
68 public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
69
Winson Chungaafa03c2010-06-11 17:34:16 -070070 static final String TAG = "CellLayout";
71
Adam Cohen2acce882012-03-28 19:03:19 -070072 private Launcher mLauncher;
Adam Cohen091440a2015-03-18 14:16:05 -070073 @Thunk int mCellWidth;
74 @Thunk int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070075 private int mFixedCellWidth;
76 private int mFixedCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070077
Adam Cohen091440a2015-03-18 14:16:05 -070078 @Thunk int mCountX;
79 @Thunk int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080080
Adam Cohen234c4cd2011-07-17 21:03:04 -070081 private int mOriginalWidthGap;
82 private int mOriginalHeightGap;
Adam Cohen091440a2015-03-18 14:16:05 -070083 @Thunk int mWidthGap;
84 @Thunk int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070085 private int mMaxGap;
Adam Cohen917e3882013-10-31 15:03:35 -070086 private boolean mDropPending = false;
Adam Cohenc50438c2014-08-19 17:43:05 -070087 private boolean mIsDragTarget = true;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080088
Patrick Dubroyde7658b2010-09-27 11:15:43 -070089 // These are temporary variables to prevent having to allocate a new object just to
90 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Adam Cohen091440a2015-03-18 14:16:05 -070091 @Thunk final int[] mTmpPoint = new int[2];
Sunny Goyal2805e632015-05-20 15:35:32 -070092 @Thunk final 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;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080096
Michael Jurkadee05892010-07-27 10:01:56 -070097 private OnTouchListener mInterceptTouchListener;
Mady Melloref044dd2015-06-02 15:35:07 -070098 private StylusEventHelper mStylusEventHelper;
Michael Jurkadee05892010-07-27 10:01:56 -070099
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
Michael Jurka5f1c5092010-09-03 14:15:02 -0700103 private float mBackgroundAlpha;
Adam Cohenf34bab52010-09-30 14:11:56 -0700104
Sunny Goyal2805e632015-05-20 15:35:32 -0700105 private static final int BACKGROUND_ACTIVATE_DURATION = 120;
106 private final TransitionDrawable mBackground;
107
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700108 // These values allow a fixed measurement to be set on the CellLayout.
109 private int mFixedWidth = -1;
110 private int mFixedHeight = -1;
111
Michael Jurka33945b22010-12-21 18:19:38 -0800112 // If we're actively dragging something over this screen, mIsDragOverlapping is true
113 private boolean mIsDragOverlapping = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700114
Winson Chung150fbab2010-09-29 17:14:26 -0700115 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700116 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohen091440a2015-03-18 14:16:05 -0700117 @Thunk Rect[] mDragOutlines = new Rect[4];
118 @Thunk float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700119 private InterruptibleInOutAnimator[] mDragOutlineAnims =
120 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700121
122 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700123 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700124 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700125
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700126 private final ClickShadowView mTouchFeedbackView;
Patrick Dubroy96864c32011-03-10 17:17:23 -0800127
Sunny Goyal316490e2015-06-02 09:38:28 -0700128 @Thunk HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new HashMap<>();
129 @Thunk HashMap<View, ReorderPreviewAnimation> mShakeAnimators = new HashMap<>();
Adam Cohen19f37922012-03-21 11:59:11 -0700130
131 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700132
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700133 // When a drag operation is in progress, holds the nearest cell to the touch point
134 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800135
Joe Onorato4be866d2010-10-10 11:26:02 -0700136 private boolean mDragging = false;
137
Patrick Dubroyce34a972010-10-19 10:34:32 -0700138 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700139 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700140
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800141 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700142 private float mHotseatScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800143
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800144 public static final int MODE_SHOW_REORDER_HINT = 0;
145 public static final int MODE_DRAG_OVER = 1;
146 public static final int MODE_ON_DROP = 2;
147 public static final int MODE_ON_DROP_EXTERNAL = 3;
148 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700149 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800150 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
151
Adam Cohena897f392012-04-27 18:12:05 -0700152 static final int LANDSCAPE = 0;
153 static final int PORTRAIT = 1;
154
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800155 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700156 private static final int REORDER_ANIMATION_DURATION = 150;
Adam Cohen091440a2015-03-18 14:16:05 -0700157 @Thunk float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700158
Adam Cohen482ed822012-03-02 14:15:13 -0800159 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
160 private Rect mOccupiedRect = new Rect();
161 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700162 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700163 private static final int INVALID_DIRECTION = -100;
Adam Cohen482ed822012-03-02 14:15:13 -0800164
Sunny Goyal2805e632015-05-20 15:35:32 -0700165 private final Rect mTempRect = new Rect();
Winson Chung3a6e7f32013-10-09 15:50:52 -0700166
Michael Jurkaca993832012-06-29 15:17:04 -0700167 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700168
Adam Cohenc9735cf2015-01-23 16:11:55 -0800169 // Related to accessible drag and drop
Sunny Goyale9b651e2015-04-24 11:44:51 -0700170 private DragAndDropAccessibilityDelegate mTouchHelper;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800171 private boolean mUseTouchHelper = false;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800172
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800173 public CellLayout(Context context) {
174 this(context, null);
175 }
176
177 public CellLayout(Context context, AttributeSet attrs) {
178 this(context, attrs, 0);
179 }
180
181 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
182 super(context, attrs, defStyle);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700183
184 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
185 // the user where a dragged item will land when dropped.
186 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800187 setClipToPadding(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700188 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700189
Adam Cohen2e6da152015-05-06 11:42:25 -0700190 DeviceProfile grid = mLauncher.getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800191 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
192
Winson Chung11a1a532013-09-13 11:14:45 -0700193 mCellWidth = mCellHeight = -1;
Nilesh Agrawal5f7099a2014-01-02 15:54:57 -0800194 mFixedCellWidth = mFixedCellHeight = -1;
Winson Chung5f8afe62013-08-12 16:19:28 -0700195 mWidthGap = mOriginalWidthGap = 0;
196 mHeightGap = mOriginalHeightGap = 0;
197 mMaxGap = Integer.MAX_VALUE;
Adam Cohen2e6da152015-05-06 11:42:25 -0700198 mCountX = (int) grid.inv.numColumns;
199 mCountY = (int) grid.inv.numRows;
Michael Jurka0280c3b2010-09-17 15:00:07 -0700200 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800201 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700202 mPreviousReorderDirection[0] = INVALID_DIRECTION;
203 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800204
205 a.recycle();
206
207 setAlwaysDrawnWithCacheEnabled(false);
208
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700209 final Resources res = getResources();
Winson Chung6e1c0d32013-10-25 15:24:24 -0700210 mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700211
Sunny Goyal2805e632015-05-20 15:35:32 -0700212 mBackground = (TransitionDrawable) res.getDrawable(R.drawable.bg_screenpanel);
213 mBackground.setCallback(this);
Winson Chunge8f1d042015-07-31 12:39:57 -0700214 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurka33945b22010-12-21 18:19:38 -0800215
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800216 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
Winson Chung5f8afe62013-08-12 16:19:28 -0700217 grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700218
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700219 // Initialize the data structures used for the drag visualization.
Patrick Dubroyce34a972010-10-19 10:34:32 -0700220 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700221 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700222 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800223 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700224 }
225
226 // When dragging things around the home screens, we show a green outline of
227 // where the item will land. The outlines gradually fade out, leaving a trail
228 // behind the drag path.
229 // Set up all the animations that are used to implement this fading.
230 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700231 final float fromAlphaValue = 0;
232 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700233
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700234 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700235
236 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700237 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100238 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700239 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700240 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700241 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700242 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700243 final Bitmap outline = (Bitmap)anim.getTag();
244
245 // If an animation is started and then stopped very quickly, we can still
246 // get spurious updates we've cleared the tag. Guard against this.
247 if (outline == null) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700248 @SuppressWarnings("all") // suppress dead code warning
249 final boolean debug = false;
250 if (debug) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700251 Object val = animation.getAnimatedValue();
252 Log.d(TAG, "anim " + thisIndex + " update: " + val +
253 ", isStopped " + anim.isStopped());
254 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700255 // Try to prevent it from continuing to run
256 animation.cancel();
257 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700258 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800259 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700260 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700261 }
262 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700263 // The animation holds a reference to the drag outline bitmap as long is it's
264 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700265 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700266 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700267 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700268 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700269 anim.setTag(null);
270 }
271 }
272 });
273 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700274 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700275
Michael Jurkaa52570f2012-03-20 03:18:20 -0700276 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
Adam Cohen2374abf2013-04-16 14:56:57 -0700277 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700278 mCountX, mCountY);
Adam Cohen2374abf2013-04-16 14:56:57 -0700279
Mady Melloref044dd2015-06-02 15:35:07 -0700280 mStylusEventHelper = new StylusEventHelper(this);
281
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700282 mTouchFeedbackView = new ClickShadowView(context);
283 addView(mTouchFeedbackView);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700284 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700285 }
286
Adam Cohenc9735cf2015-01-23 16:11:55 -0800287 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
Sunny Goyale9b651e2015-04-24 11:44:51 -0700288 public void enableAccessibleDrag(boolean enable, int dragType) {
Adam Cohenc9735cf2015-01-23 16:11:55 -0800289 mUseTouchHelper = enable;
290 if (!enable) {
291 ViewCompat.setAccessibilityDelegate(this, null);
292 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
293 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
294 setOnClickListener(mLauncher);
295 } else {
Sunny Goyale9b651e2015-04-24 11:44:51 -0700296 if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
297 !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
298 mTouchHelper = new WorkspaceAccessibilityHelper(this);
299 } else if (dragType == FOLDER_ACCESSIBILITY_DRAG &&
300 !(mTouchHelper instanceof FolderAccessibilityHelper)) {
301 mTouchHelper = new FolderAccessibilityHelper(this);
302 }
Adam Cohenc9735cf2015-01-23 16:11:55 -0800303 ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
304 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
305 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
306 setOnClickListener(mTouchHelper);
307 }
308
309 // Invalidate the accessibility hierarchy
310 if (getParent() != null) {
311 getParent().notifySubtreeAccessibilityStateChanged(
312 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
313 }
314 }
315
316 @Override
317 public boolean dispatchHoverEvent(MotionEvent event) {
318 // Always attempt to dispatch hover events to accessibility first.
319 if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
320 return true;
321 }
322 return super.dispatchHoverEvent(event);
323 }
324
325 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800326 public boolean onInterceptTouchEvent(MotionEvent ev) {
327 if (mUseTouchHelper ||
328 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
329 return true;
330 }
331 return false;
332 }
333
Mady Melloref044dd2015-06-02 15:35:07 -0700334 @Override
335 public boolean onTouchEvent(MotionEvent ev) {
336 boolean handled = super.onTouchEvent(ev);
337 // Stylus button press on a home screen should not switch between overview mode and
338 // the home screen mode, however, once in overview mode stylus button press should be
339 // enabled to allow rearranging the different home screens. So check what mode
340 // the workspace is in, and only perform stylus button presses while in overview mode.
341 if (mLauncher.mWorkspace.isInOverviewMode()
342 && mStylusEventHelper.checkAndPerformStylusEvent(ev)) {
343 return true;
344 }
345 return handled;
346 }
347
Chris Craik01f2d7f2013-10-01 14:41:56 -0700348 public void enableHardwareLayer(boolean hasLayer) {
349 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700350 }
351
352 public void buildHardwareLayer() {
353 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700354 }
355
Adam Cohen307fe232012-08-16 17:55:58 -0700356 public float getChildrenScale() {
357 return mIsHotseat ? mHotseatScale : 1.0f;
358 }
359
Winson Chung5f8afe62013-08-12 16:19:28 -0700360 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700361 mFixedCellWidth = mCellWidth = width;
362 mFixedCellHeight = mCellHeight = height;
Winson Chung5f8afe62013-08-12 16:19:28 -0700363 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
364 mCountX, mCountY);
365 }
366
Adam Cohen2801caf2011-05-13 20:57:39 -0700367 public void setGridSize(int x, int y) {
368 mCountX = x;
369 mCountY = y;
370 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800371 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700372 mTempRectStack.clear();
Adam Cohen2374abf2013-04-16 14:56:57 -0700373 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700374 mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700375 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700376 }
377
Adam Cohen2374abf2013-04-16 14:56:57 -0700378 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
379 public void setInvertIfRtl(boolean invert) {
380 mShortcutsAndWidgets.setInvertIfRtl(invert);
381 }
382
Adam Cohen917e3882013-10-31 15:03:35 -0700383 public void setDropPending(boolean pending) {
384 mDropPending = pending;
385 }
386
387 public boolean isDropPending() {
388 return mDropPending;
389 }
390
Sunny Goyal4b6eb262015-05-14 19:24:40 -0700391 @Override
392 public void setPressedIcon(BubbleTextView icon, Bitmap background) {
Sunny Goyal508da152014-08-14 10:53:27 -0700393 if (icon == null || background == null) {
394 mTouchFeedbackView.setBitmap(null);
395 mTouchFeedbackView.animate().cancel();
396 } else {
Sunny Goyal508da152014-08-14 10:53:27 -0700397 if (mTouchFeedbackView.setBitmap(background)) {
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700398 mTouchFeedbackView.alignWithIconView(icon, mShortcutsAndWidgets);
399 mTouchFeedbackView.animateShadow();
Sunny Goyal508da152014-08-14 10:53:27 -0700400 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800401 }
402 }
403
Adam Cohenc50438c2014-08-19 17:43:05 -0700404 void disableDragTarget() {
405 mIsDragTarget = false;
406 }
407
408 boolean isDragTarget() {
409 return mIsDragTarget;
410 }
411
412 void setIsDragOverlapping(boolean isDragOverlapping) {
413 if (mIsDragOverlapping != isDragOverlapping) {
414 mIsDragOverlapping = isDragOverlapping;
Sunny Goyal2805e632015-05-20 15:35:32 -0700415 if (mIsDragOverlapping) {
416 mBackground.startTransition(BACKGROUND_ACTIVATE_DURATION);
417 } else {
Winson Chunge8f1d042015-07-31 12:39:57 -0700418 if (mBackgroundAlpha > 0f) {
419 mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
420 } else {
421 mBackground.resetTransition();
422 }
Sunny Goyal2805e632015-05-20 15:35:32 -0700423 }
Adam Cohenc50438c2014-08-19 17:43:05 -0700424 invalidate();
425 }
426 }
427
Michael Jurka33945b22010-12-21 18:19:38 -0800428 boolean getIsDragOverlapping() {
429 return mIsDragOverlapping;
430 }
431
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700432 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700433 protected void onDraw(Canvas canvas) {
Sunny Goyal05739772015-05-19 19:59:09 -0700434 if (!mIsDragTarget) {
435 return;
436 }
437
Michael Jurka3e7c7632010-10-02 16:01:03 -0700438 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
439 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
440 // When we're small, we are either drawn normally or in the "accepts drops" state (during
441 // a drag). However, we also drag the mini hover background *over* one of those two
442 // backgrounds
Sunny Goyal05739772015-05-19 19:59:09 -0700443 if (mBackgroundAlpha > 0.0f) {
Sunny Goyal2805e632015-05-20 15:35:32 -0700444 mBackground.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700445 }
Romain Guya6abce82009-11-10 02:54:41 -0800446
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700447 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700448 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700449 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700450 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800451 final Rect r = mDragOutlines[i];
Winson Chung3a6e7f32013-10-09 15:50:52 -0700452 mTempRect.set(r);
453 Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale());
Joe Onorato4be866d2010-10-10 11:26:02 -0700454 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700455 paint.setAlpha((int)(alpha + .5f));
Winson Chung3a6e7f32013-10-09 15:50:52 -0700456 canvas.drawBitmap(b, null, mTempRect, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700457 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700458 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800459
Adam Cohen482ed822012-03-02 14:15:13 -0800460 if (DEBUG_VISUALIZE_OCCUPIED) {
461 int[] pt = new int[2];
462 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700463 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800464 for (int i = 0; i < mCountX; i++) {
465 for (int j = 0; j < mCountY; j++) {
466 if (mOccupied[i][j]) {
467 cellToPoint(i, j, pt);
468 canvas.save();
469 canvas.translate(pt[0], pt[1]);
470 cd.draw(canvas);
471 canvas.restore();
472 }
473 }
474 }
475 }
476
Andrew Flynn850d2e72012-04-26 16:51:20 -0700477 int previewOffset = FolderRingAnimator.sPreviewSize;
478
Adam Cohen69ce2e52011-07-03 19:25:21 -0700479 // The folder outer / inner ring image(s)
Adam Cohen2e6da152015-05-06 11:42:25 -0700480 DeviceProfile grid = mLauncher.getDeviceProfile();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700481 for (int i = 0; i < mFolderOuterRings.size(); i++) {
482 FolderRingAnimator fra = mFolderOuterRings.get(i);
483
Adam Cohen5108bc02013-09-20 17:04:51 -0700484 Drawable d;
485 int width, height;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700486 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700487 View child = getChildAt(fra.mCellX, fra.mCellY);
Adam Cohen558f1c22013-10-09 15:15:24 -0700488
Winson Chung89f97052013-09-20 11:32:26 -0700489 if (child != null) {
Adam Cohen558f1c22013-10-09 15:15:24 -0700490 int centerX = mTempLocation[0] + mCellWidth / 2;
491 int centerY = mTempLocation[1] + previewOffset / 2 +
492 child.getPaddingTop() + grid.folderBackgroundOffset;
493
Adam Cohen5108bc02013-09-20 17:04:51 -0700494 // Draw outer ring, if it exists
495 if (FolderIcon.HAS_OUTER_RING) {
496 d = FolderRingAnimator.sSharedOuterRingDrawable;
497 width = (int) (fra.getOuterRingSize() * getChildrenScale());
498 height = width;
499 canvas.save();
500 canvas.translate(centerX - width / 2, centerY - height / 2);
501 d.setBounds(0, 0, width, height);
502 d.draw(canvas);
503 canvas.restore();
504 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700505
Winson Chung89f97052013-09-20 11:32:26 -0700506 // Draw inner ring
507 d = FolderRingAnimator.sSharedInnerRingDrawable;
508 width = (int) (fra.getInnerRingSize() * getChildrenScale());
509 height = width;
Winson Chung89f97052013-09-20 11:32:26 -0700510 canvas.save();
511 canvas.translate(centerX - width / 2, centerY - width / 2);
512 d.setBounds(0, 0, width, height);
513 d.draw(canvas);
514 canvas.restore();
515 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700516 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700517
518 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
519 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
520 int width = d.getIntrinsicWidth();
521 int height = d.getIntrinsicHeight();
522
523 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700524 View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]);
Winson Chung89f97052013-09-20 11:32:26 -0700525 if (child != null) {
526 int centerX = mTempLocation[0] + mCellWidth / 2;
527 int centerY = mTempLocation[1] + previewOffset / 2 +
528 child.getPaddingTop() + grid.folderBackgroundOffset;
Adam Cohenc51934b2011-07-26 21:07:43 -0700529
Winson Chung89f97052013-09-20 11:32:26 -0700530 canvas.save();
531 canvas.translate(centerX - width / 2, centerY - width / 2);
532 d.setBounds(0, 0, width, height);
533 d.draw(canvas);
534 canvas.restore();
535 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700536 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700537 }
538
539 public void showFolderAccept(FolderRingAnimator fra) {
540 mFolderOuterRings.add(fra);
541 }
542
543 public void hideFolderAccept(FolderRingAnimator fra) {
544 if (mFolderOuterRings.contains(fra)) {
545 mFolderOuterRings.remove(fra);
546 }
547 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700548 }
549
Adam Cohenc51934b2011-07-26 21:07:43 -0700550 public void setFolderLeaveBehindCell(int x, int y) {
551 mFolderLeaveBehindCell[0] = x;
552 mFolderLeaveBehindCell[1] = y;
553 invalidate();
554 }
555
556 public void clearFolderLeaveBehind() {
557 mFolderLeaveBehindCell[0] = -1;
558 mFolderLeaveBehindCell[1] = -1;
559 invalidate();
560 }
561
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700562 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700563 public boolean shouldDelayChildPressedState() {
564 return false;
565 }
566
Adam Cohen1462de32012-07-24 22:34:36 -0700567 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700568 try {
569 dispatchRestoreInstanceState(states);
570 } catch (IllegalArgumentException ex) {
Sunny Goyal6c56c682015-07-16 14:09:05 -0700571 if (ProviderConfig.IS_DOGFOOD_BUILD) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700572 throw ex;
573 }
574 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
575 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
576 }
Adam Cohen1462de32012-07-24 22:34:36 -0700577 }
578
Michael Jurkae6235dd2011-10-04 15:02:05 -0700579 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700580 public void cancelLongPress() {
581 super.cancelLongPress();
582
583 // Cancel long press for all children
584 final int count = getChildCount();
585 for (int i = 0; i < count; i++) {
586 final View child = getChildAt(i);
587 child.cancelLongPress();
588 }
589 }
590
Michael Jurkadee05892010-07-27 10:01:56 -0700591 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
592 mInterceptTouchListener = listener;
593 }
594
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800595 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700596 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800597 }
598
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800599 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700600 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800601 }
602
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800603 public void setIsHotseat(boolean isHotseat) {
604 mIsHotseat = isHotseat;
Winson Chung5f8afe62013-08-12 16:19:28 -0700605 mShortcutsAndWidgets.setIsHotseat(isHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800606 }
607
Sunny Goyale9b651e2015-04-24 11:44:51 -0700608 public boolean isHotseat() {
609 return mIsHotseat;
610 }
611
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800612 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700613 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700614 final LayoutParams lp = params;
615
Andrew Flynnde38e422012-05-08 11:22:15 -0700616 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800617 if (child instanceof BubbleTextView) {
618 BubbleTextView bubbleChild = (BubbleTextView) child;
Winson Chung5f8afe62013-08-12 16:19:28 -0700619 bubbleChild.setTextVisibility(!mIsHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800620 }
621
Adam Cohen307fe232012-08-16 17:55:58 -0700622 child.setScaleX(getChildrenScale());
623 child.setScaleY(getChildrenScale());
624
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800625 // Generate an id for each view, this assumes we have at most 256x256 cells
626 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700627 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700628 // If the horizontal or vertical span is set to -1, it is taken to
629 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700630 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
631 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800632
Winson Chungaafa03c2010-06-11 17:34:16 -0700633 child.setId(childId);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700634 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700635
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700636 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700637
Winson Chungaafa03c2010-06-11 17:34:16 -0700638 return true;
639 }
640 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800641 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700642
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800643 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700644 public void removeAllViews() {
645 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700646 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700647 }
648
649 @Override
650 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700651 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700652 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700653 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700654 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700655 }
656
657 @Override
658 public void removeView(View view) {
659 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700660 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700661 }
662
663 @Override
664 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700665 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
666 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700667 }
668
669 @Override
670 public void removeViewInLayout(View view) {
671 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700672 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700673 }
674
675 @Override
676 public void removeViews(int start, int count) {
677 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700678 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700679 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700680 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700681 }
682
683 @Override
684 public void removeViewsInLayout(int start, int count) {
685 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700686 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700687 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700688 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800689 }
690
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700691 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700692 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800693 * @param x X coordinate of the point
694 * @param y Y coordinate of the point
695 * @param result Array of 2 ints to hold the x and y coordinate of the cell
696 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700697 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700698 final int hStartPadding = getPaddingLeft();
699 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800700
701 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
702 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
703
Adam Cohend22015c2010-07-26 22:02:18 -0700704 final int xAxis = mCountX;
705 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800706
707 if (result[0] < 0) result[0] = 0;
708 if (result[0] >= xAxis) result[0] = xAxis - 1;
709 if (result[1] < 0) result[1] = 0;
710 if (result[1] >= yAxis) result[1] = yAxis - 1;
711 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700712
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800713 /**
714 * Given a point, return the cell that most closely encloses that point
715 * @param x X coordinate of the point
716 * @param y Y coordinate of the point
717 * @param result Array of 2 ints to hold the x and y coordinate of the cell
718 */
719 void pointToCellRounded(int x, int y, int[] result) {
720 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
721 }
722
723 /**
724 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700725 *
726 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800727 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700728 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800729 * @param result Array of 2 ints to hold the x and y coordinate of the point
730 */
731 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700732 final int hStartPadding = getPaddingLeft();
733 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800734
735 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
736 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
737 }
738
Adam Cohene3e27a82011-04-15 12:07:39 -0700739 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800740 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700741 *
742 * @param cellX X coordinate of the cell
743 * @param cellY Y coordinate of the cell
744 *
745 * @param result Array of 2 ints to hold the x and y coordinate of the point
746 */
747 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700748 regionToCenterPoint(cellX, cellY, 1, 1, result);
749 }
750
751 /**
752 * Given a cell coordinate and span return the point that represents the center of the regio
753 *
754 * @param cellX X coordinate of the cell
755 * @param cellY Y coordinate of the cell
756 *
757 * @param result Array of 2 ints to hold the x and y coordinate of the point
758 */
759 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700760 final int hStartPadding = getPaddingLeft();
761 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700762 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
763 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
764 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
765 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700766 }
767
Adam Cohen19f37922012-03-21 11:59:11 -0700768 /**
769 * Given a cell coordinate and span fills out a corresponding pixel rect
770 *
771 * @param cellX X coordinate of the cell
772 * @param cellY Y coordinate of the cell
773 * @param result Rect in which to write the result
774 */
775 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
776 final int hStartPadding = getPaddingLeft();
777 final int vStartPadding = getPaddingTop();
778 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
779 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
780 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
781 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
782 }
783
Adam Cohen482ed822012-03-02 14:15:13 -0800784 public float getDistanceFromCell(float x, float y, int[] cell) {
785 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700786 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800787 }
788
Romain Guy84f296c2009-11-04 15:00:44 -0800789 int getCellWidth() {
790 return mCellWidth;
791 }
792
793 int getCellHeight() {
794 return mCellHeight;
795 }
796
Adam Cohend4844c32011-02-18 19:25:06 -0800797 int getWidthGap() {
798 return mWidthGap;
799 }
800
801 int getHeightGap() {
802 return mHeightGap;
803 }
804
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700805 public void setFixedSize(int width, int height) {
806 mFixedWidth = width;
807 mFixedHeight = height;
808 }
809
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800810 @Override
811 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800812 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800813 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700814 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
815 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700816 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
817 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700818 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Sunny Goyalc6205602015-05-21 20:46:33 -0700819 int cw = DeviceProfile.calculateCellWidth(childWidthSize, mCountX);
820 int ch = DeviceProfile.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700821 if (cw != mCellWidth || ch != mCellHeight) {
822 mCellWidth = cw;
823 mCellHeight = ch;
824 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
825 mHeightGap, mCountX, mCountY);
826 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700827 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700828
Winson Chung2d75f122013-09-23 16:53:31 -0700829 int newWidth = childWidthSize;
830 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700831 if (mFixedWidth > 0 && mFixedHeight > 0) {
832 newWidth = mFixedWidth;
833 newHeight = mFixedHeight;
834 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800835 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
836 }
837
Adam Cohend22015c2010-07-26 22:02:18 -0700838 int numWidthGaps = mCountX - 1;
839 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800840
Adam Cohen234c4cd2011-07-17 21:03:04 -0700841 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700842 int hSpace = childWidthSize;
843 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700844 int hFreeSpace = hSpace - (mCountX * mCellWidth);
845 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700846 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
847 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -0700848 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
849 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700850 } else {
851 mWidthGap = mOriginalWidthGap;
852 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700853 }
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700854
855 // Make the feedback view large enough to hold the blur bitmap.
856 mTouchFeedbackView.measure(
857 MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
858 MeasureSpec.EXACTLY),
859 MeasureSpec.makeMeasureSpec(mCellHeight + mTouchFeedbackView.getExtraSize(),
860 MeasureSpec.EXACTLY));
861
862 mShortcutsAndWidgets.measure(
863 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
864 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
865
866 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
867 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -0700868 if (mFixedWidth > 0 && mFixedHeight > 0) {
869 setMeasuredDimension(maxWidth, maxHeight);
870 } else {
871 setMeasuredDimension(widthSize, heightSize);
872 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800873 }
874
875 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700876 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Winson Chung38848ca2013-10-08 12:03:44 -0700877 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
878 (mCountX * mCellWidth);
879 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
880 int top = getPaddingTop();
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700881
882 mTouchFeedbackView.layout(left, top,
883 left + mTouchFeedbackView.getMeasuredWidth(),
884 top + mTouchFeedbackView.getMeasuredHeight());
885 mShortcutsAndWidgets.layout(left, top,
886 left + r - l,
887 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800888 }
889
890 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700891 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
892 super.onSizeChanged(w, h, oldw, oldh);
Winson Chung82a9bd22013-10-08 16:02:34 -0700893
894 // Expand the background drawing bounds by the padding baked into the background drawable
Sunny Goyal2805e632015-05-20 15:35:32 -0700895 mBackground.getPadding(mTempRect);
896 mBackground.setBounds(-mTempRect.left, -mTempRect.top,
897 w + mTempRect.right, h + mTempRect.bottom);
Michael Jurkadee05892010-07-27 10:01:56 -0700898 }
899
900 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800901 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700902 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800903 }
904
905 @Override
906 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700907 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800908 }
909
Michael Jurka5f1c5092010-09-03 14:15:02 -0700910 public float getBackgroundAlpha() {
911 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -0700912 }
913
Michael Jurka5f1c5092010-09-03 14:15:02 -0700914 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -0800915 if (mBackgroundAlpha != alpha) {
916 mBackgroundAlpha = alpha;
Sunny Goyal2805e632015-05-20 15:35:32 -0700917 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurkaafaa0502011-12-13 18:22:50 -0800918 }
Michael Jurkadee05892010-07-27 10:01:56 -0700919 }
920
Sunny Goyal2805e632015-05-20 15:35:32 -0700921 @Override
922 protected boolean verifyDrawable(Drawable who) {
923 return super.verifyDrawable(who) || (mIsDragTarget && who == mBackground);
924 }
925
Michael Jurkaa52570f2012-03-20 03:18:20 -0700926 public void setShortcutAndWidgetAlpha(float alpha) {
Sunny Goyal02b50812014-09-10 15:44:42 -0700927 mShortcutsAndWidgets.setAlpha(alpha);
Michael Jurkadee05892010-07-27 10:01:56 -0700928 }
929
Michael Jurkaa52570f2012-03-20 03:18:20 -0700930 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700931 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700932 }
933
Patrick Dubroy440c3602010-07-13 17:50:32 -0700934 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700935 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700936 }
937
Adam Cohen76fc0852011-06-17 13:26:23 -0700938 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800939 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700940 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800941 boolean[][] occupied = mOccupied;
942 if (!permanent) {
943 occupied = mTmpOccupied;
944 }
945
Adam Cohen19f37922012-03-21 11:59:11 -0700946 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700947 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
948 final ItemInfo info = (ItemInfo) child.getTag();
949
950 // We cancel any existing animations
951 if (mReorderAnimators.containsKey(lp)) {
952 mReorderAnimators.get(lp).cancel();
953 mReorderAnimators.remove(lp);
954 }
955
Adam Cohen482ed822012-03-02 14:15:13 -0800956 final int oldX = lp.x;
957 final int oldY = lp.y;
958 if (adjustOccupied) {
959 occupied[lp.cellX][lp.cellY] = false;
960 occupied[cellX][cellY] = true;
961 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700962 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -0800963 if (permanent) {
964 lp.cellX = info.cellX = cellX;
965 lp.cellY = info.cellY = cellY;
966 } else {
967 lp.tmpCellX = cellX;
968 lp.tmpCellY = cellY;
969 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700970 clc.setupLp(lp);
971 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800972 final int newX = lp.x;
973 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700974
Adam Cohen76fc0852011-06-17 13:26:23 -0700975 lp.x = oldX;
976 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -0700977
Adam Cohen482ed822012-03-02 14:15:13 -0800978 // Exit early if we're not actually moving the view
979 if (oldX == newX && oldY == newY) {
980 lp.isLockedToGrid = true;
981 return true;
982 }
983
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100984 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -0800985 va.setDuration(duration);
986 mReorderAnimators.put(lp, va);
987
988 va.addUpdateListener(new AnimatorUpdateListener() {
989 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -0700990 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -0800991 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -0700992 lp.x = (int) ((1 - r) * oldX + r * newX);
993 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -0700994 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -0700995 }
996 });
Adam Cohen482ed822012-03-02 14:15:13 -0800997 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700998 boolean cancelled = false;
999 public void onAnimationEnd(Animator animation) {
1000 // If the animation was cancelled, it means that another animation
1001 // has interrupted this one, and we don't want to lock the item into
1002 // place just yet.
1003 if (!cancelled) {
1004 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001005 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001006 }
1007 if (mReorderAnimators.containsKey(lp)) {
1008 mReorderAnimators.remove(lp);
1009 }
1010 }
1011 public void onAnimationCancel(Animator animation) {
1012 cancelled = true;
1013 }
1014 });
Adam Cohen482ed822012-03-02 14:15:13 -08001015 va.setStartDelay(delay);
1016 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001017 return true;
1018 }
1019 return false;
1020 }
1021
Adam Cohen482ed822012-03-02 14:15:13 -08001022 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1023 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001024 final int oldDragCellX = mDragCell[0];
1025 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001026
Adam Cohen2801caf2011-05-13 20:57:39 -07001027 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001028 return;
1029 }
1030
Adam Cohen482ed822012-03-02 14:15:13 -08001031 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1032 mDragCell[0] = cellX;
1033 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001034 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001035 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001036 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001037
Joe Onorato4be866d2010-10-10 11:26:02 -07001038 int left = topLeft[0];
1039 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001040
Winson Chungb8c69f32011-10-19 21:36:08 -07001041 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001042 // When drawing the drag outline, it did not account for margin offsets
1043 // added by the view's parent.
1044 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1045 left += lp.leftMargin;
1046 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001047
Adam Cohen99e8b402011-03-25 19:23:43 -07001048 // Offsets due to the size difference between the View and the dragOutline.
1049 // There is a size difference to account for the outer blur, which may lie
1050 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001051 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001052 // We center about the x axis
1053 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1054 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001055 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001056 if (dragOffset != null && dragRegion != null) {
1057 // Center the drag region *horizontally* in the cell and apply a drag
1058 // outline offset
1059 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1060 - dragRegion.width()) / 2;
Winson Chung69737c32013-10-08 17:00:19 -07001061 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1062 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1063 top += dragOffset.y + cellPaddingY;
Winson Chungb8c69f32011-10-19 21:36:08 -07001064 } else {
1065 // Center the drag outline in the cell
1066 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1067 - dragOutline.getWidth()) / 2;
1068 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1069 - dragOutline.getHeight()) / 2;
1070 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001071 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001072 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001073 mDragOutlineAnims[oldIndex].animateOut();
1074 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001075 Rect r = mDragOutlines[mDragOutlineCurrent];
1076 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1077 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001078 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001079 }
Winson Chung150fbab2010-09-29 17:14:26 -07001080
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001081 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1082 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001083 }
1084 }
1085
Adam Cohene0310962011-04-18 16:15:31 -07001086 public void clearDragOutlines() {
1087 final int oldIndex = mDragOutlineCurrent;
1088 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001089 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001090 }
1091
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001092 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001093 * Find a vacant area that will fit the given bounds nearest the requested
1094 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001095 *
Romain Guy51afc022009-05-04 18:03:43 -07001096 * @param pixelX The X location at which you want to search for a vacant area.
1097 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001098 * @param minSpanX The minimum horizontal span required
1099 * @param minSpanY The minimum vertical span required
1100 * @param spanX Horizontal span of the object.
1101 * @param spanY Vertical span of the object.
1102 * @param result Array in which to place the result, or null (in which case a new array will
1103 * be allocated)
1104 * @return The X, Y cell of a vacant area that can contain this object,
1105 * nearest the requested location.
1106 */
1107 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1108 int spanY, int[] result, int[] resultSpan) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001109 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true,
Adam Cohend41fbf52012-02-16 23:53:59 -08001110 result, resultSpan);
1111 }
1112
Adam Cohend41fbf52012-02-16 23:53:59 -08001113 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1114 private void lazyInitTempRectStack() {
1115 if (mTempRectStack.isEmpty()) {
1116 for (int i = 0; i < mCountX * mCountY; i++) {
1117 mTempRectStack.push(new Rect());
1118 }
1119 }
1120 }
Adam Cohen482ed822012-03-02 14:15:13 -08001121
Adam Cohend41fbf52012-02-16 23:53:59 -08001122 private void recycleTempRects(Stack<Rect> used) {
1123 while (!used.isEmpty()) {
1124 mTempRectStack.push(used.pop());
1125 }
1126 }
1127
1128 /**
1129 * Find a vacant area that will fit the given bounds nearest the requested
1130 * cell location. Uses Euclidean distance to score multiple vacant areas.
1131 *
1132 * @param pixelX The X location at which you want to search for a vacant area.
1133 * @param pixelY The Y location at which you want to search for a vacant area.
1134 * @param minSpanX The minimum horizontal span required
1135 * @param minSpanY The minimum vertical span required
1136 * @param spanX Horizontal span of the object.
1137 * @param spanY Vertical span of the object.
1138 * @param ignoreOccupied If true, the result can be an occupied cell
1139 * @param result Array in which to place the result, or null (in which case a new array will
1140 * be allocated)
1141 * @return The X, Y cell of a vacant area that can contain this object,
1142 * nearest the requested location.
1143 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001144 private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1145 int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001146 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001147
Adam Cohene3e27a82011-04-15 12:07:39 -07001148 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1149 // to the center of the item, but we are searching based on the top-left cell, so
1150 // we translate the point over to correspond to the top-left.
1151 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1152 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1153
Jeff Sharkey70864282009-04-07 21:08:40 -07001154 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001155 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001156 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001157 final Rect bestRect = new Rect(-1, -1, -1, -1);
1158 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001159
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001160 final int countX = mCountX;
1161 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001162
Adam Cohend41fbf52012-02-16 23:53:59 -08001163 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1164 spanX < minSpanX || spanY < minSpanY) {
1165 return bestXY;
1166 }
1167
1168 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001169 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001170 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1171 int ySize = -1;
1172 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001173 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001174 // First, let's see if this thing fits anywhere
1175 for (int i = 0; i < minSpanX; i++) {
1176 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001177 if (mOccupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001178 continue inner;
1179 }
Michael Jurkac28de512010-08-13 11:27:44 -07001180 }
1181 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001182 xSize = minSpanX;
1183 ySize = minSpanY;
1184
1185 // We know that the item will fit at _some_ acceptable size, now let's see
1186 // how big we can make it. We'll alternate between incrementing x and y spans
1187 // until we hit a limit.
1188 boolean incX = true;
1189 boolean hitMaxX = xSize >= spanX;
1190 boolean hitMaxY = ySize >= spanY;
1191 while (!(hitMaxX && hitMaxY)) {
1192 if (incX && !hitMaxX) {
1193 for (int j = 0; j < ySize; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001194 if (x + xSize > countX -1 || mOccupied[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001195 // We can't move out horizontally
1196 hitMaxX = true;
1197 }
1198 }
1199 if (!hitMaxX) {
1200 xSize++;
1201 }
1202 } else if (!hitMaxY) {
1203 for (int i = 0; i < xSize; i++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001204 if (y + ySize > countY - 1 || mOccupied[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001205 // We can't move out vertically
1206 hitMaxY = true;
1207 }
1208 }
1209 if (!hitMaxY) {
1210 ySize++;
1211 }
1212 }
1213 hitMaxX |= xSize >= spanX;
1214 hitMaxY |= ySize >= spanY;
1215 incX = !incX;
1216 }
1217 incX = true;
1218 hitMaxX = xSize >= spanX;
1219 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001220 }
Sunny Goyal2805e632015-05-20 15:35:32 -07001221 final int[] cellXY = mTmpPoint;
Adam Cohene3e27a82011-04-15 12:07:39 -07001222 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001223
Adam Cohend41fbf52012-02-16 23:53:59 -08001224 // We verify that the current rect is not a sub-rect of any of our previous
1225 // candidates. In this case, the current rect is disqualified in favour of the
1226 // containing rect.
1227 Rect currentRect = mTempRectStack.pop();
1228 currentRect.set(x, y, x + xSize, y + ySize);
1229 boolean contained = false;
1230 for (Rect r : validRegions) {
1231 if (r.contains(currentRect)) {
1232 contained = true;
1233 break;
1234 }
1235 }
1236 validRegions.push(currentRect);
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001237 double distance = Math.hypot(cellXY[0] - pixelX, cellXY[1] - pixelY);
Adam Cohen482ed822012-03-02 14:15:13 -08001238
Adam Cohend41fbf52012-02-16 23:53:59 -08001239 if ((distance <= bestDistance && !contained) ||
1240 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001241 bestDistance = distance;
1242 bestXY[0] = x;
1243 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001244 if (resultSpan != null) {
1245 resultSpan[0] = xSize;
1246 resultSpan[1] = ySize;
1247 }
1248 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001249 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001250 }
1251 }
1252
Adam Cohenc0dcf592011-06-01 15:30:43 -07001253 // Return -1, -1 if no suitable location found
1254 if (bestDistance == Double.MAX_VALUE) {
1255 bestXY[0] = -1;
1256 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001257 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001258 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001259 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001260 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001261
Adam Cohen482ed822012-03-02 14:15:13 -08001262 /**
1263 * Find a vacant area that will fit the given bounds nearest the requested
1264 * cell location, and will also weigh in a suggested direction vector of the
1265 * desired location. This method computers distance based on unit grid distances,
1266 * not pixel distances.
1267 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001268 * @param cellX The X cell nearest to which you want to search for a vacant area.
1269 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001270 * @param spanX Horizontal span of the object.
1271 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001272 * @param direction The favored direction in which the views should move from x, y
1273 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1274 * matches exactly. Otherwise we find the best matching direction.
1275 * @param occoupied The array which represents which cells in the CellLayout are occupied
1276 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001277 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001278 * @param result Array in which to place the result, or null (in which case a new array will
1279 * be allocated)
1280 * @return The X, Y cell of a vacant area that can contain this object,
1281 * nearest the requested location.
1282 */
1283 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001284 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001285 // Keep track of best-scoring drop area
1286 final int[] bestXY = result != null ? result : new int[2];
1287 float bestDistance = Float.MAX_VALUE;
1288 int bestDirectionScore = Integer.MIN_VALUE;
1289
1290 final int countX = mCountX;
1291 final int countY = mCountY;
1292
1293 for (int y = 0; y < countY - (spanY - 1); y++) {
1294 inner:
1295 for (int x = 0; x < countX - (spanX - 1); x++) {
1296 // First, let's see if this thing fits anywhere
1297 for (int i = 0; i < spanX; i++) {
1298 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001299 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001300 continue inner;
1301 }
1302 }
1303 }
1304
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001305 float distance = (float) Math.hypot(x - cellX, y - cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001306 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001307 computeDirectionVector(x - cellX, y - cellY, curDirection);
1308 // The direction score is just the dot product of the two candidate direction
1309 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001310 int curDirectionScore = direction[0] * curDirection[0] +
1311 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001312 boolean exactDirectionOnly = false;
1313 boolean directionMatches = direction[0] == curDirection[0] &&
1314 direction[0] == curDirection[0];
1315 if ((directionMatches || !exactDirectionOnly) &&
1316 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001317 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1318 bestDistance = distance;
1319 bestDirectionScore = curDirectionScore;
1320 bestXY[0] = x;
1321 bestXY[1] = y;
1322 }
1323 }
1324 }
1325
1326 // Return -1, -1 if no suitable location found
1327 if (bestDistance == Float.MAX_VALUE) {
1328 bestXY[0] = -1;
1329 bestXY[1] = -1;
1330 }
1331 return bestXY;
1332 }
1333
1334 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001335 int[] direction, ItemConfiguration currentState) {
1336 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001337 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001338 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001339 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1340
Adam Cohen8baab352012-03-20 17:39:21 -07001341 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001342
1343 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001344 c.x = mTempLocation[0];
1345 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001346 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001347 }
Adam Cohen8baab352012-03-20 17:39:21 -07001348 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001349 return success;
1350 }
1351
Adam Cohenf3900c22012-11-16 18:28:11 -08001352 /**
1353 * This helper class defines a cluster of views. It helps with defining complex edges
1354 * of the cluster and determining how those edges interact with other views. The edges
1355 * essentially define a fine-grained boundary around the cluster of views -- like a more
1356 * precise version of a bounding box.
1357 */
1358 private class ViewCluster {
1359 final static int LEFT = 0;
1360 final static int TOP = 1;
1361 final static int RIGHT = 2;
1362 final static int BOTTOM = 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001363
Adam Cohenf3900c22012-11-16 18:28:11 -08001364 ArrayList<View> views;
1365 ItemConfiguration config;
1366 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001367
Adam Cohenf3900c22012-11-16 18:28:11 -08001368 int[] leftEdge = new int[mCountY];
1369 int[] rightEdge = new int[mCountY];
1370 int[] topEdge = new int[mCountX];
1371 int[] bottomEdge = new int[mCountX];
1372 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1373
1374 @SuppressWarnings("unchecked")
1375 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1376 this.views = (ArrayList<View>) views.clone();
1377 this.config = config;
1378 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001379 }
1380
Adam Cohenf3900c22012-11-16 18:28:11 -08001381 void resetEdges() {
1382 for (int i = 0; i < mCountX; i++) {
1383 topEdge[i] = -1;
1384 bottomEdge[i] = -1;
1385 }
1386 for (int i = 0; i < mCountY; i++) {
1387 leftEdge[i] = -1;
1388 rightEdge[i] = -1;
1389 }
1390 leftEdgeDirty = true;
1391 rightEdgeDirty = true;
1392 bottomEdgeDirty = true;
1393 topEdgeDirty = true;
1394 boundingRectDirty = true;
1395 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001396
Adam Cohenf3900c22012-11-16 18:28:11 -08001397 void computeEdge(int which, int[] edge) {
1398 int count = views.size();
1399 for (int i = 0; i < count; i++) {
1400 CellAndSpan cs = config.map.get(views.get(i));
1401 switch (which) {
1402 case LEFT:
1403 int left = cs.x;
1404 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1405 if (left < edge[j] || edge[j] < 0) {
1406 edge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001407 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001408 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001409 break;
1410 case RIGHT:
1411 int right = cs.x + cs.spanX;
1412 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1413 if (right > edge[j]) {
1414 edge[j] = right;
1415 }
1416 }
1417 break;
1418 case TOP:
1419 int top = cs.y;
1420 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1421 if (top < edge[j] || edge[j] < 0) {
1422 edge[j] = top;
1423 }
1424 }
1425 break;
1426 case BOTTOM:
1427 int bottom = cs.y + cs.spanY;
1428 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1429 if (bottom > edge[j]) {
1430 edge[j] = bottom;
1431 }
1432 }
1433 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001434 }
1435 }
1436 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001437
1438 boolean isViewTouchingEdge(View v, int whichEdge) {
1439 CellAndSpan cs = config.map.get(v);
1440
1441 int[] edge = getEdge(whichEdge);
1442
1443 switch (whichEdge) {
1444 case LEFT:
1445 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1446 if (edge[i] == cs.x + cs.spanX) {
1447 return true;
1448 }
1449 }
1450 break;
1451 case RIGHT:
1452 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1453 if (edge[i] == cs.x) {
1454 return true;
1455 }
1456 }
1457 break;
1458 case TOP:
1459 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1460 if (edge[i] == cs.y + cs.spanY) {
1461 return true;
1462 }
1463 }
1464 break;
1465 case BOTTOM:
1466 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1467 if (edge[i] == cs.y) {
1468 return true;
1469 }
1470 }
1471 break;
1472 }
1473 return false;
1474 }
1475
1476 void shift(int whichEdge, int delta) {
1477 for (View v: views) {
1478 CellAndSpan c = config.map.get(v);
1479 switch (whichEdge) {
1480 case LEFT:
1481 c.x -= delta;
1482 break;
1483 case RIGHT:
1484 c.x += delta;
1485 break;
1486 case TOP:
1487 c.y -= delta;
1488 break;
1489 case BOTTOM:
1490 default:
1491 c.y += delta;
1492 break;
1493 }
1494 }
1495 resetEdges();
1496 }
1497
1498 public void addView(View v) {
1499 views.add(v);
1500 resetEdges();
1501 }
1502
1503 public Rect getBoundingRect() {
1504 if (boundingRectDirty) {
1505 boolean first = true;
1506 for (View v: views) {
1507 CellAndSpan c = config.map.get(v);
1508 if (first) {
1509 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1510 first = false;
1511 } else {
1512 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1513 }
1514 }
1515 }
1516 return boundingRect;
1517 }
1518
1519 public int[] getEdge(int which) {
1520 switch (which) {
1521 case LEFT:
1522 return getLeftEdge();
1523 case RIGHT:
1524 return getRightEdge();
1525 case TOP:
1526 return getTopEdge();
1527 case BOTTOM:
1528 default:
1529 return getBottomEdge();
1530 }
1531 }
1532
1533 public int[] getLeftEdge() {
1534 if (leftEdgeDirty) {
1535 computeEdge(LEFT, leftEdge);
1536 }
1537 return leftEdge;
1538 }
1539
1540 public int[] getRightEdge() {
1541 if (rightEdgeDirty) {
1542 computeEdge(RIGHT, rightEdge);
1543 }
1544 return rightEdge;
1545 }
1546
1547 public int[] getTopEdge() {
1548 if (topEdgeDirty) {
1549 computeEdge(TOP, topEdge);
1550 }
1551 return topEdge;
1552 }
1553
1554 public int[] getBottomEdge() {
1555 if (bottomEdgeDirty) {
1556 computeEdge(BOTTOM, bottomEdge);
1557 }
1558 return bottomEdge;
1559 }
1560
1561 PositionComparator comparator = new PositionComparator();
1562 class PositionComparator implements Comparator<View> {
1563 int whichEdge = 0;
1564 public int compare(View left, View right) {
1565 CellAndSpan l = config.map.get(left);
1566 CellAndSpan r = config.map.get(right);
1567 switch (whichEdge) {
1568 case LEFT:
1569 return (r.x + r.spanX) - (l.x + l.spanX);
1570 case RIGHT:
1571 return l.x - r.x;
1572 case TOP:
1573 return (r.y + r.spanY) - (l.y + l.spanY);
1574 case BOTTOM:
1575 default:
1576 return l.y - r.y;
1577 }
1578 }
1579 }
1580
1581 public void sortConfigurationForEdgePush(int edge) {
1582 comparator.whichEdge = edge;
1583 Collections.sort(config.sortedViews, comparator);
1584 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001585 }
1586
Adam Cohenf3900c22012-11-16 18:28:11 -08001587 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1588 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001589
Adam Cohenf3900c22012-11-16 18:28:11 -08001590 ViewCluster cluster = new ViewCluster(views, currentState);
1591 Rect clusterRect = cluster.getBoundingRect();
1592 int whichEdge;
1593 int pushDistance;
1594 boolean fail = false;
1595
1596 // Determine the edge of the cluster that will be leading the push and how far
1597 // the cluster must be shifted.
1598 if (direction[0] < 0) {
1599 whichEdge = ViewCluster.LEFT;
1600 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001601 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001602 whichEdge = ViewCluster.RIGHT;
1603 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1604 } else if (direction[1] < 0) {
1605 whichEdge = ViewCluster.TOP;
1606 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1607 } else {
1608 whichEdge = ViewCluster.BOTTOM;
1609 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001610 }
1611
Adam Cohenf3900c22012-11-16 18:28:11 -08001612 // Break early for invalid push distance.
1613 if (pushDistance <= 0) {
1614 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001615 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001616
1617 // Mark the occupied state as false for the group of views we want to move.
1618 for (View v: views) {
1619 CellAndSpan c = currentState.map.get(v);
1620 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1621 }
1622
1623 // We save the current configuration -- if we fail to find a solution we will revert
1624 // to the initial state. The process of finding a solution modifies the configuration
1625 // in place, hence the need for revert in the failure case.
1626 currentState.save();
1627
1628 // The pushing algorithm is simplified by considering the views in the order in which
1629 // they would be pushed by the cluster. For example, if the cluster is leading with its
1630 // left edge, we consider sort the views by their right edge, from right to left.
1631 cluster.sortConfigurationForEdgePush(whichEdge);
1632
1633 while (pushDistance > 0 && !fail) {
1634 for (View v: currentState.sortedViews) {
1635 // For each view that isn't in the cluster, we see if the leading edge of the
1636 // cluster is contacting the edge of that view. If so, we add that view to the
1637 // cluster.
1638 if (!cluster.views.contains(v) && v != dragView) {
1639 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1640 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1641 if (!lp.canReorder) {
1642 // The push solution includes the all apps button, this is not viable.
1643 fail = true;
1644 break;
1645 }
1646 cluster.addView(v);
1647 CellAndSpan c = currentState.map.get(v);
1648
1649 // Adding view to cluster, mark it as not occupied.
1650 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1651 }
1652 }
1653 }
1654 pushDistance--;
1655
1656 // The cluster has been completed, now we move the whole thing over in the appropriate
1657 // direction.
1658 cluster.shift(whichEdge, 1);
1659 }
1660
1661 boolean foundSolution = false;
1662 clusterRect = cluster.getBoundingRect();
1663
1664 // Due to the nature of the algorithm, the only check required to verify a valid solution
1665 // is to ensure that completed shifted cluster lies completely within the cell layout.
1666 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1667 clusterRect.bottom <= mCountY) {
1668 foundSolution = true;
1669 } else {
1670 currentState.restore();
1671 }
1672
1673 // In either case, we set the occupied array as marked for the location of the views
1674 for (View v: cluster.views) {
1675 CellAndSpan c = currentState.map.get(v);
1676 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1677 }
1678
1679 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001680 }
1681
Adam Cohen482ed822012-03-02 14:15:13 -08001682 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001683 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001684 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001685
Adam Cohen8baab352012-03-20 17:39:21 -07001686 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001687 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001688 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001689 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001690 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001691 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001692 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001693 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001694 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001695 }
1696 }
Adam Cohen8baab352012-03-20 17:39:21 -07001697
Adam Cohen8baab352012-03-20 17:39:21 -07001698 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001699 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001700 CellAndSpan c = currentState.map.get(v);
1701 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1702 }
1703
Adam Cohen47a876d2012-03-19 13:21:41 -07001704 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1705 int top = boundingRect.top;
1706 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001707 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001708 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001709 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001710 CellAndSpan c = currentState.map.get(v);
1711 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001712 }
1713
Adam Cohen482ed822012-03-02 14:15:13 -08001714 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1715
Adam Cohenf3900c22012-11-16 18:28:11 -08001716 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1717 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001718
Adam Cohen8baab352012-03-20 17:39:21 -07001719 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001720 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001721 int deltaX = mTempLocation[0] - boundingRect.left;
1722 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001723 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001724 CellAndSpan c = currentState.map.get(v);
1725 c.x += deltaX;
1726 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001727 }
1728 success = true;
1729 }
Adam Cohen8baab352012-03-20 17:39:21 -07001730
1731 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001732 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001733 CellAndSpan c = currentState.map.get(v);
1734 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001735 }
1736 return success;
1737 }
1738
1739 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1740 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1741 }
1742
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001743 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1744 // to push items in each of the cardinal directions, in an order based on the direction vector
1745 // passed.
1746 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1747 int[] direction, View ignoreView, ItemConfiguration solution) {
1748 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001749 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001750 // separately in each of the components.
1751 int temp = direction[1];
1752 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001753
1754 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001755 ignoreView, solution)) {
1756 return true;
1757 }
1758 direction[1] = temp;
1759 temp = direction[0];
1760 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001761
1762 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001763 ignoreView, solution)) {
1764 return true;
1765 }
1766 // Revert the direction
1767 direction[0] = temp;
1768
1769 // Now we try pushing in each component of the opposite direction
1770 direction[0] *= -1;
1771 direction[1] *= -1;
1772 temp = direction[1];
1773 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001774 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001775 ignoreView, solution)) {
1776 return true;
1777 }
1778
1779 direction[1] = temp;
1780 temp = direction[0];
1781 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001782 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001783 ignoreView, solution)) {
1784 return true;
1785 }
1786 // revert the direction
1787 direction[0] = temp;
1788 direction[0] *= -1;
1789 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001790
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001791 } else {
1792 // If the direction vector has a single non-zero component, we push first in the
1793 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08001794 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001795 ignoreView, solution)) {
1796 return true;
1797 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001798 // Then we try the opposite direction
1799 direction[0] *= -1;
1800 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001801 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001802 ignoreView, solution)) {
1803 return true;
1804 }
1805 // Switch the direction back
1806 direction[0] *= -1;
1807 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001808
1809 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001810 // to find a solution by pushing along the perpendicular axis.
1811
1812 // Swap the components
1813 int temp = direction[1];
1814 direction[1] = direction[0];
1815 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08001816 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001817 ignoreView, solution)) {
1818 return true;
1819 }
1820
1821 // Then we try the opposite direction
1822 direction[0] *= -1;
1823 direction[1] *= -1;
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 // Switch the direction back
1829 direction[0] *= -1;
1830 direction[1] *= -1;
1831
1832 // Swap the components back
1833 temp = direction[1];
1834 direction[1] = direction[0];
1835 direction[0] = temp;
1836 }
1837 return false;
1838 }
1839
Adam Cohen482ed822012-03-02 14:15:13 -08001840 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001841 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001842 // Return early if get invalid cell positions
1843 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001844
Adam Cohen8baab352012-03-20 17:39:21 -07001845 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001846 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001847
Adam Cohen8baab352012-03-20 17:39:21 -07001848 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001849 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001850 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001851 if (c != null) {
1852 c.x = cellX;
1853 c.y = cellY;
1854 }
Adam Cohen482ed822012-03-02 14:15:13 -08001855 }
Adam Cohen482ed822012-03-02 14:15:13 -08001856 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1857 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001858 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001859 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001860 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001861 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001862 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001863 if (Rect.intersects(r0, r1)) {
1864 if (!lp.canReorder) {
1865 return false;
1866 }
1867 mIntersectingViews.add(child);
1868 }
1869 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001870
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001871 solution.intersectingViews = new ArrayList<View>(mIntersectingViews);
1872
Winson Chung5f8afe62013-08-12 16:19:28 -07001873 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001874 // we try to find a solution such that no displaced item travels through another item
1875 // without also displacing that item.
1876 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001877 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001878 return true;
1879 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001880
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001881 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08001882 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001883 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001884 return true;
1885 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001886
Adam Cohen482ed822012-03-02 14:15:13 -08001887 // Ok, they couldn't move as a block, let's move them individually
1888 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001889 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001890 return false;
1891 }
1892 }
1893 return true;
1894 }
1895
1896 /*
1897 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1898 * the provided point and the provided cell
1899 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001900 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001901 double angle = Math.atan(((float) deltaY) / deltaX);
1902
1903 result[0] = 0;
1904 result[1] = 0;
1905 if (Math.abs(Math.cos(angle)) > 0.5f) {
1906 result[0] = (int) Math.signum(deltaX);
1907 }
1908 if (Math.abs(Math.sin(angle)) > 0.5f) {
1909 result[1] = (int) Math.signum(deltaY);
1910 }
1911 }
1912
Adam Cohen8baab352012-03-20 17:39:21 -07001913 private void copyOccupiedArray(boolean[][] occupied) {
1914 for (int i = 0; i < mCountX; i++) {
1915 for (int j = 0; j < mCountY; j++) {
1916 occupied[i][j] = mOccupied[i][j];
1917 }
1918 }
1919 }
1920
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001921 private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001922 int spanX, int spanY, int[] direction, View dragView, boolean decX,
1923 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001924 // Copy the current state into the solution. This solution will be manipulated as necessary.
1925 copyCurrentStateToSolution(solution, false);
1926 // Copy the current occupied array into the temporary occupied array. This array will be
1927 // manipulated as necessary to find a solution.
1928 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001929
1930 // We find the nearest cell into which we would place the dragged item, assuming there's
1931 // nothing in its way.
1932 int result[] = new int[2];
1933 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1934
1935 boolean success = false;
1936 // First we try the exact nearest position of the item being dragged,
1937 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001938 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1939 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001940
1941 if (!success) {
1942 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1943 // x, then 1 in y etc.
1944 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001945 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
1946 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001947 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001948 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
1949 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001950 }
1951 solution.isSolution = false;
1952 } else {
1953 solution.isSolution = true;
1954 solution.dragViewX = result[0];
1955 solution.dragViewY = result[1];
1956 solution.dragViewSpanX = spanX;
1957 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08001958 }
1959 return solution;
1960 }
1961
1962 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001963 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001964 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001965 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001966 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001967 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08001968 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07001969 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001970 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001971 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001972 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001973 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08001974 }
1975 }
1976
1977 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
1978 for (int i = 0; i < mCountX; i++) {
1979 for (int j = 0; j < mCountY; j++) {
1980 mTmpOccupied[i][j] = false;
1981 }
1982 }
1983
Michael Jurkaa52570f2012-03-20 03:18:20 -07001984 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001985 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001986 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001987 if (child == dragView) continue;
1988 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001989 CellAndSpan c = solution.map.get(child);
1990 if (c != null) {
1991 lp.tmpCellX = c.x;
1992 lp.tmpCellY = c.y;
1993 lp.cellHSpan = c.spanX;
1994 lp.cellVSpan = c.spanY;
1995 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001996 }
1997 }
1998 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
1999 solution.dragViewSpanY, mTmpOccupied, true);
2000 }
2001
2002 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2003 commitDragView) {
2004
2005 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2006 for (int i = 0; i < mCountX; i++) {
2007 for (int j = 0; j < mCountY; j++) {
2008 occupied[i][j] = false;
2009 }
2010 }
2011
Michael Jurkaa52570f2012-03-20 03:18:20 -07002012 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002013 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002014 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002015 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002016 CellAndSpan c = solution.map.get(child);
2017 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002018 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2019 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002020 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002021 }
2022 }
2023 if (commitDragView) {
2024 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2025 solution.dragViewSpanY, occupied, true);
2026 }
2027 }
2028
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002029
2030 // This method starts or changes the reorder preview animations
2031 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
2032 View dragView, int delay, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07002033 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002034 for (int i = 0; i < childCount; i++) {
2035 View child = mShortcutsAndWidgets.getChildAt(i);
2036 if (child == dragView) continue;
2037 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002038 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
2039 != null && !solution.intersectingViews.contains(child);
2040
Adam Cohen19f37922012-03-21 11:59:11 -07002041 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002042 if (c != null && !skip) {
2043 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
2044 lp.cellY, c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002045 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002046 }
2047 }
2048 }
2049
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002050 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07002051 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002052 class ReorderPreviewAnimation {
Adam Cohen19f37922012-03-21 11:59:11 -07002053 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002054 float finalDeltaX;
2055 float finalDeltaY;
2056 float initDeltaX;
2057 float initDeltaY;
2058 float finalScale;
2059 float initScale;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002060 int mode;
2061 boolean repeating = false;
2062 private static final int PREVIEW_DURATION = 300;
2063 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
2064
2065 public static final int MODE_HINT = 0;
2066 public static final int MODE_PREVIEW = 1;
2067
Adam Cohene7587d22012-05-24 18:50:02 -07002068 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002069
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002070 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
2071 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07002072 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2073 final int x0 = mTmpPoint[0];
2074 final int y0 = mTmpPoint[1];
2075 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2076 final int x1 = mTmpPoint[0];
2077 final int y1 = mTmpPoint[1];
2078 final int dX = x1 - x0;
2079 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002080 finalDeltaX = 0;
2081 finalDeltaY = 0;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002082 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07002083 if (dX == dY && dX == 0) {
2084 } else {
2085 if (dY == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002086 finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002087 } else if (dX == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002088 finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002089 } else {
2090 double angle = Math.atan( (float) (dY) / dX);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002091 finalDeltaX = (int) (- dir * Math.signum(dX) *
2092 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
2093 finalDeltaY = (int) (- dir * Math.signum(dY) *
2094 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002095 }
2096 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002097 this.mode = mode;
Adam Cohend024f982012-05-23 18:26:45 -07002098 initDeltaX = child.getTranslationX();
2099 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002100 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002101 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002102 this.child = child;
2103 }
2104
Adam Cohend024f982012-05-23 18:26:45 -07002105 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002106 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002107 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002108 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002109 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002110 if (finalDeltaX == 0 && finalDeltaY == 0) {
2111 completeAnimationImmediately();
2112 return;
2113 }
Adam Cohen19f37922012-03-21 11:59:11 -07002114 }
Adam Cohend024f982012-05-23 18:26:45 -07002115 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002116 return;
2117 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002118 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002119 a = va;
Adam Cohen19f37922012-03-21 11:59:11 -07002120 va.setRepeatMode(ValueAnimator.REVERSE);
2121 va.setRepeatCount(ValueAnimator.INFINITE);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002122 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002123 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002124 va.addUpdateListener(new AnimatorUpdateListener() {
2125 @Override
2126 public void onAnimationUpdate(ValueAnimator animation) {
2127 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002128 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2129 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2130 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002131 child.setTranslationX(x);
2132 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002133 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002134 child.setScaleX(s);
2135 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002136 }
2137 });
2138 va.addListener(new AnimatorListenerAdapter() {
2139 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002140 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002141 initDeltaX = 0;
2142 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002143 initScale = getChildrenScale();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002144 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002145 }
2146 });
Adam Cohen19f37922012-03-21 11:59:11 -07002147 mShakeAnimators.put(child, this);
2148 va.start();
2149 }
2150
Adam Cohend024f982012-05-23 18:26:45 -07002151 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002152 if (a != null) {
2153 a.cancel();
2154 }
Adam Cohen19f37922012-03-21 11:59:11 -07002155 }
Adam Cohene7587d22012-05-24 18:50:02 -07002156
Adam Cohen091440a2015-03-18 14:16:05 -07002157 @Thunk void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002158 if (a != null) {
2159 a.cancel();
2160 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002161
Sunny Goyal5d2fc322015-07-06 22:52:49 -07002162 a = new LauncherViewPropertyAnimator(child)
2163 .scaleX(getChildrenScale())
2164 .scaleY(getChildrenScale())
2165 .translationX(0)
2166 .translationY(0)
2167 .setDuration(REORDER_ANIMATION_DURATION);
2168 a.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2169 a.start();
Brandon Keely50e6e562012-05-08 16:28:49 -07002170 }
Adam Cohen19f37922012-03-21 11:59:11 -07002171 }
2172
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002173 private void completeAndClearReorderPreviewAnimations() {
2174 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002175 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002176 }
2177 mShakeAnimators.clear();
2178 }
2179
Adam Cohen482ed822012-03-02 14:15:13 -08002180 private void commitTempPlacement() {
2181 for (int i = 0; i < mCountX; i++) {
2182 for (int j = 0; j < mCountY; j++) {
2183 mOccupied[i][j] = mTmpOccupied[i][j];
2184 }
2185 }
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002186
2187 long screenId = mLauncher.getWorkspace().getIdForScreen(this);
2188 int container = Favorites.CONTAINER_DESKTOP;
2189
2190 if (mLauncher.isHotseatLayout(this)) {
2191 screenId = -1;
2192 container = Favorites.CONTAINER_HOTSEAT;
2193 }
2194
Michael Jurkaa52570f2012-03-20 03:18:20 -07002195 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002196 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002197 View child = mShortcutsAndWidgets.getChildAt(i);
2198 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2199 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002200 // We do a null check here because the item info can be null in the case of the
2201 // AllApps button in the hotseat.
2202 if (info != null) {
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002203 final boolean requiresDbUpdate = (info.cellX != lp.tmpCellX
2204 || info.cellY != lp.tmpCellY || info.spanX != lp.cellHSpan
2205 || info.spanY != lp.cellVSpan);
2206
Adam Cohen2acce882012-03-28 19:03:19 -07002207 info.cellX = lp.cellX = lp.tmpCellX;
2208 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002209 info.spanX = lp.cellHSpan;
2210 info.spanY = lp.cellVSpan;
Sunny Goyalaa8ef112015-06-12 20:04:41 -07002211
2212 if (requiresDbUpdate) {
2213 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId,
2214 info.cellX, info.cellY, info.spanX, info.spanY);
2215 }
Adam Cohen2acce882012-03-28 19:03:19 -07002216 }
Adam Cohen482ed822012-03-02 14:15:13 -08002217 }
2218 }
2219
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002220 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002221 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002222 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002223 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002224 lp.useTmpCoords = useTempCoords;
2225 }
2226 }
2227
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002228 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002229 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2230 int[] result = new int[2];
2231 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002232 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08002233 resultSpan);
2234 if (result[0] >= 0 && result[1] >= 0) {
2235 copyCurrentStateToSolution(solution, false);
2236 solution.dragViewX = result[0];
2237 solution.dragViewY = result[1];
2238 solution.dragViewSpanX = resultSpan[0];
2239 solution.dragViewSpanY = resultSpan[1];
2240 solution.isSolution = true;
2241 } else {
2242 solution.isSolution = false;
2243 }
2244 return solution;
2245 }
2246
2247 public void prepareChildForDrag(View child) {
2248 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002249 }
2250
Adam Cohen19f37922012-03-21 11:59:11 -07002251 /* This seems like it should be obvious and straight-forward, but when the direction vector
2252 needs to match with the notion of the dragView pushing other views, we have to employ
2253 a slightly more subtle notion of the direction vector. The question is what two points is
2254 the vector between? The center of the dragView and its desired destination? Not quite, as
2255 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2256 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2257 or right, which helps make pushing feel right.
2258 */
2259 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2260 int spanY, View dragView, int[] resultDirection) {
2261 int[] targetDestination = new int[2];
2262
2263 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2264 Rect dragRect = new Rect();
2265 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2266 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2267
2268 Rect dropRegionRect = new Rect();
2269 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2270 dragView, dropRegionRect, mIntersectingViews);
2271
2272 int dropRegionSpanX = dropRegionRect.width();
2273 int dropRegionSpanY = dropRegionRect.height();
2274
2275 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2276 dropRegionRect.height(), dropRegionRect);
2277
2278 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2279 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2280
2281 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2282 deltaX = 0;
2283 }
2284 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2285 deltaY = 0;
2286 }
2287
2288 if (deltaX == 0 && deltaY == 0) {
2289 // No idea what to do, give a random direction.
2290 resultDirection[0] = 1;
2291 resultDirection[1] = 0;
2292 } else {
2293 computeDirectionVector(deltaX, deltaY, resultDirection);
2294 }
2295 }
2296
2297 // For a given cell and span, fetch the set of views intersecting the region.
2298 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2299 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2300 if (boundingRect != null) {
2301 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2302 }
2303 intersectingViews.clear();
2304 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2305 Rect r1 = new Rect();
2306 final int count = mShortcutsAndWidgets.getChildCount();
2307 for (int i = 0; i < count; i++) {
2308 View child = mShortcutsAndWidgets.getChildAt(i);
2309 if (child == dragView) continue;
2310 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2311 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2312 if (Rect.intersects(r0, r1)) {
2313 mIntersectingViews.add(child);
2314 if (boundingRect != null) {
2315 boundingRect.union(r1);
2316 }
2317 }
2318 }
2319 }
2320
2321 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2322 View dragView, int[] result) {
2323 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2324 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2325 mIntersectingViews);
2326 return !mIntersectingViews.isEmpty();
2327 }
2328
2329 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002330 completeAndClearReorderPreviewAnimations();
2331 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2332 final int count = mShortcutsAndWidgets.getChildCount();
2333 for (int i = 0; i < count; i++) {
2334 View child = mShortcutsAndWidgets.getChildAt(i);
2335 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2336 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2337 lp.tmpCellX = lp.cellX;
2338 lp.tmpCellY = lp.cellY;
2339 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2340 0, false, false);
2341 }
Adam Cohen19f37922012-03-21 11:59:11 -07002342 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002343 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002344 }
Adam Cohen19f37922012-03-21 11:59:11 -07002345 }
2346
Adam Cohenbebf0422012-04-11 18:06:28 -07002347 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2348 View dragView, int[] direction, boolean commit) {
2349 int[] pixelXY = new int[2];
2350 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2351
2352 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002353 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002354 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2355
2356 setUseTempCoords(true);
2357 if (swapSolution != null && swapSolution.isSolution) {
2358 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2359 // committing anything or animating anything as we just want to determine if a solution
2360 // exists
2361 copySolutionToTempState(swapSolution, dragView);
2362 setItemPlacementDirty(true);
2363 animateItemsToSolution(swapSolution, dragView, commit);
2364
2365 if (commit) {
2366 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002367 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002368 setItemPlacementDirty(false);
2369 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002370 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2371 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002372 }
2373 mShortcutsAndWidgets.requestLayout();
2374 }
2375 return swapSolution.isSolution;
2376 }
2377
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002378 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002379 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002380 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002381 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002382
2383 if (resultSpan == null) {
2384 resultSpan = new int[2];
2385 }
2386
Adam Cohen19f37922012-03-21 11:59:11 -07002387 // When we are checking drop validity or actually dropping, we don't recompute the
2388 // direction vector, since we want the solution to match the preview, and it's possible
2389 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002390 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2391 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002392 mDirectionVector[0] = mPreviousReorderDirection[0];
2393 mDirectionVector[1] = mPreviousReorderDirection[1];
2394 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002395 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2396 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2397 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002398 }
2399 } else {
2400 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2401 mPreviousReorderDirection[0] = mDirectionVector[0];
2402 mPreviousReorderDirection[1] = mDirectionVector[1];
2403 }
2404
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002405 // Find a solution involving pushing / displacing any items in the way
2406 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002407 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2408
2409 // We attempt the approach which doesn't shuffle views at all
2410 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2411 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2412
2413 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002414
2415 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2416 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002417 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2418 finalSolution = swapSolution;
2419 } else if (noShuffleSolution.isSolution) {
2420 finalSolution = noShuffleSolution;
2421 }
2422
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002423 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002424 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002425 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2426 ReorderPreviewAnimation.MODE_HINT);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002427 result[0] = finalSolution.dragViewX;
2428 result[1] = finalSolution.dragViewY;
2429 resultSpan[0] = finalSolution.dragViewSpanX;
2430 resultSpan[1] = finalSolution.dragViewSpanY;
2431 } else {
2432 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2433 }
2434 return result;
2435 }
2436
Adam Cohen482ed822012-03-02 14:15:13 -08002437 boolean foundSolution = true;
2438 if (!DESTRUCTIVE_REORDER) {
2439 setUseTempCoords(true);
2440 }
2441
2442 if (finalSolution != null) {
2443 result[0] = finalSolution.dragViewX;
2444 result[1] = finalSolution.dragViewY;
2445 resultSpan[0] = finalSolution.dragViewSpanX;
2446 resultSpan[1] = finalSolution.dragViewSpanY;
2447
2448 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2449 // committing anything or animating anything as we just want to determine if a solution
2450 // exists
2451 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2452 if (!DESTRUCTIVE_REORDER) {
2453 copySolutionToTempState(finalSolution, dragView);
2454 }
2455 setItemPlacementDirty(true);
2456 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2457
Adam Cohen19f37922012-03-21 11:59:11 -07002458 if (!DESTRUCTIVE_REORDER &&
2459 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002460 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002461 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002462 setItemPlacementDirty(false);
2463 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002464 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2465 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002466 }
2467 }
2468 } else {
2469 foundSolution = false;
2470 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2471 }
2472
2473 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2474 setUseTempCoords(false);
2475 }
Adam Cohen482ed822012-03-02 14:15:13 -08002476
Michael Jurkaa52570f2012-03-20 03:18:20 -07002477 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002478 return result;
2479 }
2480
Adam Cohen19f37922012-03-21 11:59:11 -07002481 void setItemPlacementDirty(boolean dirty) {
2482 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002483 }
Adam Cohen19f37922012-03-21 11:59:11 -07002484 boolean isItemPlacementDirty() {
2485 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002486 }
2487
Adam Cohen091440a2015-03-18 14:16:05 -07002488 @Thunk class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002489 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002490 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2491 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002492 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002493 boolean isSolution = false;
2494 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2495
Adam Cohenf3900c22012-11-16 18:28:11 -08002496 void save() {
2497 // Copy current state into savedMap
2498 for (View v: map.keySet()) {
2499 map.get(v).copy(savedMap.get(v));
2500 }
2501 }
2502
2503 void restore() {
2504 // Restore current state from savedMap
2505 for (View v: savedMap.keySet()) {
2506 savedMap.get(v).copy(map.get(v));
2507 }
2508 }
2509
2510 void add(View v, CellAndSpan cs) {
2511 map.put(v, cs);
2512 savedMap.put(v, new CellAndSpan());
2513 sortedViews.add(v);
2514 }
2515
Adam Cohen482ed822012-03-02 14:15:13 -08002516 int area() {
2517 return dragViewSpanX * dragViewSpanY;
2518 }
Adam Cohen8baab352012-03-20 17:39:21 -07002519 }
2520
2521 private class CellAndSpan {
2522 int x, y;
2523 int spanX, spanY;
2524
Adam Cohenf3900c22012-11-16 18:28:11 -08002525 public CellAndSpan() {
2526 }
2527
2528 public void copy(CellAndSpan copy) {
2529 copy.x = x;
2530 copy.y = y;
2531 copy.spanX = spanX;
2532 copy.spanY = spanY;
2533 }
2534
Adam Cohen8baab352012-03-20 17:39:21 -07002535 public CellAndSpan(int x, int y, int spanX, int spanY) {
2536 this.x = x;
2537 this.y = y;
2538 this.spanX = spanX;
2539 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002540 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002541
2542 public String toString() {
2543 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2544 }
2545
Adam Cohen482ed822012-03-02 14:15:13 -08002546 }
2547
Adam Cohendf035382011-04-11 17:22:04 -07002548 /**
Adam Cohendf035382011-04-11 17:22:04 -07002549 * Find a starting cell position that will fit the given bounds nearest the requested
2550 * cell location. Uses Euclidean distance to score multiple vacant areas.
2551 *
2552 * @param pixelX The X location at which you want to search for a vacant area.
2553 * @param pixelY The Y location at which you want to search for a vacant area.
2554 * @param spanX Horizontal span of the object.
2555 * @param spanY Vertical span of the object.
2556 * @param ignoreView Considers space occupied by this view as unoccupied
2557 * @param result Previously returned value to possibly recycle.
2558 * @return The X, Y cell of a vacant area that can contain this object,
2559 * nearest the requested location.
2560 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002561 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2562 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002563 }
2564
Michael Jurka0280c3b2010-09-17 15:00:07 -07002565 boolean existsEmptyCell() {
2566 return findCellForSpan(null, 1, 1);
2567 }
2568
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002569 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002570 * Finds the upper-left coordinate of the first rectangle in the grid that can
2571 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2572 * then this method will only return coordinates for rectangles that contain the cell
2573 * (intersectX, intersectY)
2574 *
2575 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2576 * can be found.
2577 * @param spanX The horizontal span of the cell we want to find.
2578 * @param spanY The vertical span of the cell we want to find.
2579 *
2580 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002581 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002582 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Michael Jurka28750fb2010-09-24 17:43:49 -07002583 boolean foundCell = false;
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002584 final int endX = mCountX - (spanX - 1);
2585 final int endY = mCountY - (spanY - 1);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002586
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002587 for (int y = 0; y < endY && !foundCell; y++) {
2588 inner:
2589 for (int x = 0; x < endX; x++) {
2590 for (int i = 0; i < spanX; i++) {
2591 for (int j = 0; j < spanY; j++) {
2592 if (mOccupied[x + i][y + j]) {
2593 // small optimization: we can skip to after the column we just found
2594 // an occupied cell
2595 x += i;
2596 continue inner;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002597 }
2598 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002599 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002600 if (cellXY != null) {
2601 cellXY[0] = x;
2602 cellXY[1] = y;
2603 }
2604 foundCell = true;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002605 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002606 }
2607 }
2608
Michael Jurka28750fb2010-09-24 17:43:49 -07002609 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002610 }
2611
2612 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002613 * A drag event has begun over this layout.
2614 * It may have begun over this layout (in which case onDragChild is called first),
2615 * or it may have begun on another layout.
2616 */
2617 void onDragEnter() {
Winson Chungc07918d2011-07-01 15:35:26 -07002618 mDragging = true;
2619 }
2620
2621 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002622 * Called when drag has left this CellLayout or has been completed (successfully or not)
2623 */
2624 void onDragExit() {
Joe Onorato4be866d2010-10-10 11:26:02 -07002625 // This can actually be called when we aren't in a drag, e.g. when adding a new
2626 // item to this layout via the customize drawer.
2627 // Guard against that case.
2628 if (mDragging) {
2629 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002630 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002631
2632 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002633 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002634 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2635 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002636 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002637 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002638 }
2639
2640 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002641 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002642 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002643 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002644 *
2645 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002646 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002647 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002648 if (child != null) {
2649 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002650 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002651 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002652 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002653 }
2654
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002655 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002656 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002657 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002658 * @param cellX X coordinate of upper left corner expressed as a cell position
2659 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002660 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002661 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002662 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002663 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002664 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002665 final int cellWidth = mCellWidth;
2666 final int cellHeight = mCellHeight;
2667 final int widthGap = mWidthGap;
2668 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002669
Winson Chung4b825dcd2011-06-19 12:41:22 -07002670 final int hStartPadding = getPaddingLeft();
2671 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002672
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002673 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2674 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2675
2676 int x = hStartPadding + cellX * (cellWidth + widthGap);
2677 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002678
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002679 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002680 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002681
Michael Jurka0280c3b2010-09-17 15:00:07 -07002682 private void clearOccupiedCells() {
2683 for (int x = 0; x < mCountX; x++) {
2684 for (int y = 0; y < mCountY; y++) {
2685 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002686 }
2687 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002688 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002689
Adam Cohend4844c32011-02-18 19:25:06 -08002690 public void markCellsAsOccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002691 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002692 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002693 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002694 }
2695
Adam Cohend4844c32011-02-18 19:25:06 -08002696 public void markCellsAsUnoccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002697 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002698 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002699 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002700 }
2701
Adam Cohen482ed822012-03-02 14:15:13 -08002702 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2703 boolean value) {
2704 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002705 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2706 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002707 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002708 }
2709 }
2710 }
2711
Adam Cohen2801caf2011-05-13 20:57:39 -07002712 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002713 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002714 (Math.max((mCountX - 1), 0) * mWidthGap);
2715 }
2716
2717 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002718 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002719 (Math.max((mCountY - 1), 0) * mHeightGap);
2720 }
2721
Michael Jurka66d72172011-04-12 16:29:25 -07002722 public boolean isOccupied(int x, int y) {
2723 if (x < mCountX && y < mCountY) {
2724 return mOccupied[x][y];
2725 } else {
2726 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2727 }
2728 }
2729
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002730 @Override
2731 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2732 return new CellLayout.LayoutParams(getContext(), attrs);
2733 }
2734
2735 @Override
2736 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2737 return p instanceof CellLayout.LayoutParams;
2738 }
2739
2740 @Override
2741 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2742 return new CellLayout.LayoutParams(p);
2743 }
2744
2745 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2746 /**
2747 * Horizontal location of the item in the grid.
2748 */
2749 @ViewDebug.ExportedProperty
2750 public int cellX;
2751
2752 /**
2753 * Vertical location of the item in the grid.
2754 */
2755 @ViewDebug.ExportedProperty
2756 public int cellY;
2757
2758 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002759 * Temporary horizontal location of the item in the grid during reorder
2760 */
2761 public int tmpCellX;
2762
2763 /**
2764 * Temporary vertical location of the item in the grid during reorder
2765 */
2766 public int tmpCellY;
2767
2768 /**
2769 * Indicates that the temporary coordinates should be used to layout the items
2770 */
2771 public boolean useTmpCoords;
2772
2773 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002774 * Number of cells spanned horizontally by the item.
2775 */
2776 @ViewDebug.ExportedProperty
2777 public int cellHSpan;
2778
2779 /**
2780 * Number of cells spanned vertically by the item.
2781 */
2782 @ViewDebug.ExportedProperty
2783 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002784
Adam Cohen1b607ed2011-03-03 17:26:50 -08002785 /**
2786 * Indicates whether the item will set its x, y, width and height parameters freely,
2787 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2788 */
Adam Cohend4844c32011-02-18 19:25:06 -08002789 public boolean isLockedToGrid = true;
2790
Adam Cohen482ed822012-03-02 14:15:13 -08002791 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07002792 * Indicates that this item should use the full extents of its parent.
2793 */
2794 public boolean isFullscreen = false;
2795
2796 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002797 * Indicates whether this item can be reordered. Always true except in the case of the
2798 * the AllApps button.
2799 */
2800 public boolean canReorder = true;
2801
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002802 // X coordinate of the view in the layout.
2803 @ViewDebug.ExportedProperty
2804 int x;
2805 // Y coordinate of the view in the layout.
2806 @ViewDebug.ExportedProperty
2807 int y;
2808
Romain Guy84f296c2009-11-04 15:00:44 -08002809 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002810
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002811 public LayoutParams(Context c, AttributeSet attrs) {
2812 super(c, attrs);
2813 cellHSpan = 1;
2814 cellVSpan = 1;
2815 }
2816
2817 public LayoutParams(ViewGroup.LayoutParams source) {
2818 super(source);
2819 cellHSpan = 1;
2820 cellVSpan = 1;
2821 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002822
2823 public LayoutParams(LayoutParams source) {
2824 super(source);
2825 this.cellX = source.cellX;
2826 this.cellY = source.cellY;
2827 this.cellHSpan = source.cellHSpan;
2828 this.cellVSpan = source.cellVSpan;
2829 }
2830
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002831 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002832 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002833 this.cellX = cellX;
2834 this.cellY = cellY;
2835 this.cellHSpan = cellHSpan;
2836 this.cellVSpan = cellVSpan;
2837 }
2838
Adam Cohen2374abf2013-04-16 14:56:57 -07002839 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
2840 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08002841 if (isLockedToGrid) {
2842 final int myCellHSpan = cellHSpan;
2843 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07002844 int myCellX = useTmpCoords ? tmpCellX : cellX;
2845 int myCellY = useTmpCoords ? tmpCellY : cellY;
2846
2847 if (invertHorizontally) {
2848 myCellX = colCount - myCellX - cellHSpan;
2849 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08002850
Adam Cohend4844c32011-02-18 19:25:06 -08002851 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2852 leftMargin - rightMargin;
2853 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2854 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002855 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2856 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002857 }
2858 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002859
Winson Chungaafa03c2010-06-11 17:34:16 -07002860 public String toString() {
2861 return "(" + this.cellX + ", " + this.cellY + ")";
2862 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002863
2864 public void setWidth(int width) {
2865 this.width = width;
2866 }
2867
2868 public int getWidth() {
2869 return width;
2870 }
2871
2872 public void setHeight(int height) {
2873 this.height = height;
2874 }
2875
2876 public int getHeight() {
2877 return height;
2878 }
2879
2880 public void setX(int x) {
2881 this.x = x;
2882 }
2883
2884 public int getX() {
2885 return x;
2886 }
2887
2888 public void setY(int y) {
2889 this.y = y;
2890 }
2891
2892 public int getY() {
2893 return y;
2894 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002895 }
2896
Michael Jurka0280c3b2010-09-17 15:00:07 -07002897 // This class stores info for two purposes:
2898 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2899 // its spanX, spanY, and the screen it is on
2900 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2901 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2902 // the CellLayout that was long clicked
Sunny Goyal83a8f042015-05-19 12:52:12 -07002903 public static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002904 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07002905 int cellX = -1;
2906 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002907 int spanX;
2908 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07002909 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07002910 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002911
Sunny Goyal83a8f042015-05-19 12:52:12 -07002912 public CellInfo(View v, ItemInfo info) {
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002913 cell = v;
2914 cellX = info.cellX;
2915 cellY = info.cellY;
2916 spanX = info.spanX;
2917 spanY = info.spanY;
2918 screenId = info.screenId;
2919 container = info.container;
2920 }
2921
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002922 @Override
2923 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002924 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2925 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002926 }
2927 }
Michael Jurkad771c962011-08-09 15:00:48 -07002928
Sunny Goyala9116722015-04-29 13:55:58 -07002929 public boolean findVacantCell(int spanX, int spanY, int[] outXY) {
2930 return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied);
2931 }
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002932
2933 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
2934 int x2 = x + spanX - 1;
2935 int y2 = y + spanY - 1;
2936 if (x < 0 || y < 0 || x2 >= mCountX || y2 >= mCountY) {
2937 return false;
2938 }
2939 for (int i = x; i <= x2; i++) {
2940 for (int j = y; j <= y2; j++) {
2941 if (mOccupied[i][j]) {
2942 return false;
2943 }
2944 }
2945 }
2946
2947 return true;
2948 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002949}