blob: d48873762b150797aaeb4c3d4fe27172cefb5da6 [file] [log] [blame]
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Daniel Sandler325dc232013-06-05 22:57:57 -040017package com.android.launcher3;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080018
Joe Onorato4be866d2010-10-10 11:26:02 -070019import android.animation.Animator;
Michael Jurka629758f2012-06-14 16:18:21 -070020import android.animation.AnimatorListenerAdapter;
Brandon Keely50e6e562012-05-08 16:28:49 -070021import android.animation.AnimatorSet;
Chet Haase00397b12010-10-07 11:13:10 -070022import android.animation.TimeInterpolator;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070023import android.animation.ValueAnimator;
24import android.animation.ValueAnimator.AnimatorUpdateListener;
Adam Cohenc9735cf2015-01-23 16:11:55 -080025import android.annotation.TargetApi;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080026import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040027import android.content.res.Resources;
Winson Chungaafa03c2010-06-11 17:34:16 -070028import android.content.res.TypedArray;
Joe Onorato4be866d2010-10-10 11:26:02 -070029import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070030import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080031import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070032import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070033import android.graphics.Point;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080034import android.graphics.Rect;
Adam Cohen482ed822012-03-02 14:15:13 -080035import android.graphics.drawable.ColorDrawable;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070036import android.graphics.drawable.Drawable;
Sunny Goyal2805e632015-05-20 15:35:32 -070037import android.graphics.drawable.TransitionDrawable;
Adam Cohenc9735cf2015-01-23 16:11:55 -080038import android.os.Build;
Adam Cohen1462de32012-07-24 22:34:36 -070039import android.os.Parcelable;
Tony Wickham489fc562015-09-02 14:45:39 -070040import android.os.PowerManager;
Adam Cohenc9735cf2015-01-23 16:11:55 -080041import android.support.v4.view.ViewCompat;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080042import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070043import android.util.Log;
Adam Cohen1462de32012-07-24 22:34:36 -070044import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080045import android.view.MotionEvent;
46import android.view.View;
47import android.view.ViewDebug;
48import android.view.ViewGroup;
Adam Cohenc9735cf2015-01-23 16:11:55 -080049import android.view.accessibility.AccessibilityEvent;
Winson Chung150fbab2010-09-29 17:14:26 -070050import android.view.animation.DecelerateInterpolator;
Tony Wickham489fc562015-09-02 14:45:39 -070051import android.widget.Toast;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080052
Sunny Goyal4b6eb262015-05-14 19:24:40 -070053import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
Daniel Sandler325dc232013-06-05 22:57:57 -040054import com.android.launcher3.FolderIcon.FolderRingAnimator;
Sunny Goyale9b651e2015-04-24 11:44:51 -070055import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
56import com.android.launcher3.accessibility.FolderAccessibilityHelper;
57import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
Adam Cohen091440a2015-03-18 14:16:05 -070058import com.android.launcher3.util.Thunk;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070059
Adam Cohen69ce2e52011-07-03 19:25:21 -070060import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070061import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080062import java.util.Collections;
63import java.util.Comparator;
Adam Cohenbfbfd262011-06-13 16:55:12 -070064import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080065import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070066
Sunny Goyal4b6eb262015-05-14 19:24:40 -070067public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
Sunny Goyale9b651e2015-04-24 11:44:51 -070068 public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
69 public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
70
Winson Chungaafa03c2010-06-11 17:34:16 -070071 static final String TAG = "CellLayout";
72
Adam Cohen2acce882012-03-28 19:03:19 -070073 private Launcher mLauncher;
Adam Cohen091440a2015-03-18 14:16:05 -070074 @Thunk int mCellWidth;
75 @Thunk int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070076 private int mFixedCellWidth;
77 private int mFixedCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070078
Adam Cohen091440a2015-03-18 14:16:05 -070079 @Thunk int mCountX;
80 @Thunk int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080081
Adam Cohen234c4cd2011-07-17 21:03:04 -070082 private int mOriginalWidthGap;
83 private int mOriginalHeightGap;
Adam Cohen091440a2015-03-18 14:16:05 -070084 @Thunk int mWidthGap;
85 @Thunk int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070086 private int mMaxGap;
Adam Cohen917e3882013-10-31 15:03:35 -070087 private boolean mDropPending = false;
Adam Cohenc50438c2014-08-19 17:43:05 -070088 private boolean mIsDragTarget = true;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080089
Patrick Dubroyde7658b2010-09-27 11:15:43 -070090 // These are temporary variables to prevent having to allocate a new object just to
91 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Adam Cohen091440a2015-03-18 14:16:05 -070092 @Thunk final int[] mTmpPoint = new int[2];
Sunny Goyal2805e632015-05-20 15:35:32 -070093 @Thunk final int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070094
The Android Open Source Project31dd5032009-03-03 19:32:27 -080095 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -080096 boolean[][] mTmpOccupied;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080097
Michael Jurkadee05892010-07-27 10:01:56 -070098 private OnTouchListener mInterceptTouchListener;
Mady Melloref044dd2015-06-02 15:35:07 -070099 private StylusEventHelper mStylusEventHelper;
Michael Jurkadee05892010-07-27 10:01:56 -0700100
Adam Cohen69ce2e52011-07-03 19:25:21 -0700101 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -0700102 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -0700103
Michael Jurka5f1c5092010-09-03 14:15:02 -0700104 private float mBackgroundAlpha;
Adam Cohenf34bab52010-09-30 14:11:56 -0700105
Sunny Goyal2805e632015-05-20 15:35:32 -0700106 private static final int BACKGROUND_ACTIVATE_DURATION = 120;
107 private final TransitionDrawable mBackground;
108
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700109 // These values allow a fixed measurement to be set on the CellLayout.
110 private int mFixedWidth = -1;
111 private int mFixedHeight = -1;
112
Michael Jurka33945b22010-12-21 18:19:38 -0800113 // If we're actively dragging something over this screen, mIsDragOverlapping is true
114 private boolean mIsDragOverlapping = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700115
Winson Chung150fbab2010-09-29 17:14:26 -0700116 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700117 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohen091440a2015-03-18 14:16:05 -0700118 @Thunk Rect[] mDragOutlines = new Rect[4];
119 @Thunk float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700120 private InterruptibleInOutAnimator[] mDragOutlineAnims =
121 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700122
123 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700124 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700125 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700126
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700127 private final ClickShadowView mTouchFeedbackView;
Patrick Dubroy96864c32011-03-10 17:17:23 -0800128
Sunny Goyal316490e2015-06-02 09:38:28 -0700129 @Thunk HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new HashMap<>();
130 @Thunk HashMap<View, ReorderPreviewAnimation> mShakeAnimators = new HashMap<>();
Adam Cohen19f37922012-03-21 11:59:11 -0700131
132 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700133
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700134 // When a drag operation is in progress, holds the nearest cell to the touch point
135 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800136
Joe Onorato4be866d2010-10-10 11:26:02 -0700137 private boolean mDragging = false;
138
Patrick Dubroyce34a972010-10-19 10:34:32 -0700139 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700140 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700141
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800142 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700143 private float mHotseatScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800144
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800145 public static final int MODE_SHOW_REORDER_HINT = 0;
146 public static final int MODE_DRAG_OVER = 1;
147 public static final int MODE_ON_DROP = 2;
148 public static final int MODE_ON_DROP_EXTERNAL = 3;
149 public static final int MODE_ACCEPT_DROP = 4;
Adam Cohen19f37922012-03-21 11:59:11 -0700150 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800151 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
152
Adam Cohena897f392012-04-27 18:12:05 -0700153 static final int LANDSCAPE = 0;
154 static final int PORTRAIT = 1;
155
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800156 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700157 private static final int REORDER_ANIMATION_DURATION = 150;
Adam Cohen091440a2015-03-18 14:16:05 -0700158 @Thunk float mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -0700159
Adam Cohen482ed822012-03-02 14:15:13 -0800160 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
161 private Rect mOccupiedRect = new Rect();
162 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700163 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700164 private static final int INVALID_DIRECTION = -100;
Adam Cohen482ed822012-03-02 14:15:13 -0800165
Sunny Goyal2805e632015-05-20 15:35:32 -0700166 private final Rect mTempRect = new Rect();
Winson Chung3a6e7f32013-10-09 15:50:52 -0700167
Michael Jurkaca993832012-06-29 15:17:04 -0700168 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700169
Adam Cohenc9735cf2015-01-23 16:11:55 -0800170 // Related to accessible drag and drop
Sunny Goyale9b651e2015-04-24 11:44:51 -0700171 private DragAndDropAccessibilityDelegate mTouchHelper;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800172 private boolean mUseTouchHelper = false;
Adam Cohenc9735cf2015-01-23 16:11:55 -0800173
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800174 public CellLayout(Context context) {
175 this(context, null);
176 }
177
178 public CellLayout(Context context, AttributeSet attrs) {
179 this(context, attrs, 0);
180 }
181
182 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
183 super(context, attrs, defStyle);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700184
185 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
186 // the user where a dragged item will land when dropped.
187 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800188 setClipToPadding(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700189 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700190
Adam Cohen2e6da152015-05-06 11:42:25 -0700191 DeviceProfile grid = mLauncher.getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800192 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
193
Winson Chung11a1a532013-09-13 11:14:45 -0700194 mCellWidth = mCellHeight = -1;
Nilesh Agrawal5f7099a2014-01-02 15:54:57 -0800195 mFixedCellWidth = mFixedCellHeight = -1;
Winson Chung5f8afe62013-08-12 16:19:28 -0700196 mWidthGap = mOriginalWidthGap = 0;
197 mHeightGap = mOriginalHeightGap = 0;
198 mMaxGap = Integer.MAX_VALUE;
Adam Cohen2e6da152015-05-06 11:42:25 -0700199 mCountX = (int) grid.inv.numColumns;
200 mCountY = (int) grid.inv.numRows;
Michael Jurka0280c3b2010-09-17 15:00:07 -0700201 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800202 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700203 mPreviousReorderDirection[0] = INVALID_DIRECTION;
204 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800205
206 a.recycle();
207
208 setAlwaysDrawnWithCacheEnabled(false);
209
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700210 final Resources res = getResources();
Winson Chung6e1c0d32013-10-25 15:24:24 -0700211 mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700212
Sunny Goyal2805e632015-05-20 15:35:32 -0700213 mBackground = (TransitionDrawable) res.getDrawable(R.drawable.bg_screenpanel);
214 mBackground.setCallback(this);
Winson Chunge8f1d042015-07-31 12:39:57 -0700215 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurka33945b22010-12-21 18:19:38 -0800216
Adam Cohenfa3c58f2013-12-06 16:10:55 -0800217 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
Winson Chung5f8afe62013-08-12 16:19:28 -0700218 grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700219
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700220 // Initialize the data structures used for the drag visualization.
Patrick Dubroyce34a972010-10-19 10:34:32 -0700221 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700222 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700223 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800224 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700225 }
226
227 // When dragging things around the home screens, we show a green outline of
228 // where the item will land. The outlines gradually fade out, leaving a trail
229 // behind the drag path.
230 // Set up all the animations that are used to implement this fading.
231 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700232 final float fromAlphaValue = 0;
233 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700234
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700235 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700236
237 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700238 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100239 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700240 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700241 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700242 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700243 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700244 final Bitmap outline = (Bitmap)anim.getTag();
245
246 // If an animation is started and then stopped very quickly, we can still
247 // get spurious updates we've cleared the tag. Guard against this.
248 if (outline == null) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700249 @SuppressWarnings("all") // suppress dead code warning
250 final boolean debug = false;
251 if (debug) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700252 Object val = animation.getAnimatedValue();
253 Log.d(TAG, "anim " + thisIndex + " update: " + val +
254 ", isStopped " + anim.isStopped());
255 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700256 // Try to prevent it from continuing to run
257 animation.cancel();
258 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700259 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800260 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700261 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700262 }
263 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700264 // The animation holds a reference to the drag outline bitmap as long is it's
265 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700266 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700267 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700268 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700269 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700270 anim.setTag(null);
271 }
272 }
273 });
274 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700275 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700276
Michael Jurkaa52570f2012-03-20 03:18:20 -0700277 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
Adam Cohen2374abf2013-04-16 14:56:57 -0700278 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700279 mCountX, mCountY);
Adam Cohen2374abf2013-04-16 14:56:57 -0700280
Mady Melloref044dd2015-06-02 15:35:07 -0700281 mStylusEventHelper = new StylusEventHelper(this);
282
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700283 mTouchFeedbackView = new ClickShadowView(context);
284 addView(mTouchFeedbackView);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700285 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700286 }
287
Adam Cohenc9735cf2015-01-23 16:11:55 -0800288 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
Sunny Goyale9b651e2015-04-24 11:44:51 -0700289 public void enableAccessibleDrag(boolean enable, int dragType) {
Adam Cohenc9735cf2015-01-23 16:11:55 -0800290 mUseTouchHelper = enable;
291 if (!enable) {
292 ViewCompat.setAccessibilityDelegate(this, null);
293 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
294 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
295 setOnClickListener(mLauncher);
296 } else {
Sunny Goyale9b651e2015-04-24 11:44:51 -0700297 if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
298 !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
299 mTouchHelper = new WorkspaceAccessibilityHelper(this);
300 } else if (dragType == FOLDER_ACCESSIBILITY_DRAG &&
301 !(mTouchHelper instanceof FolderAccessibilityHelper)) {
302 mTouchHelper = new FolderAccessibilityHelper(this);
303 }
Adam Cohenc9735cf2015-01-23 16:11:55 -0800304 ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
305 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
306 getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
307 setOnClickListener(mTouchHelper);
308 }
309
310 // Invalidate the accessibility hierarchy
311 if (getParent() != null) {
312 getParent().notifySubtreeAccessibilityStateChanged(
313 this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
314 }
315 }
316
317 @Override
318 public boolean dispatchHoverEvent(MotionEvent event) {
319 // Always attempt to dispatch hover events to accessibility first.
320 if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
321 return true;
322 }
323 return super.dispatchHoverEvent(event);
324 }
325
326 @Override
Adam Cohenc9735cf2015-01-23 16:11:55 -0800327 public boolean onInterceptTouchEvent(MotionEvent ev) {
328 if (mUseTouchHelper ||
329 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
330 return true;
331 }
332 return false;
333 }
334
Mady Melloref044dd2015-06-02 15:35:07 -0700335 @Override
336 public boolean onTouchEvent(MotionEvent ev) {
337 boolean handled = super.onTouchEvent(ev);
338 // Stylus button press on a home screen should not switch between overview mode and
339 // the home screen mode, however, once in overview mode stylus button press should be
340 // enabled to allow rearranging the different home screens. So check what mode
341 // the workspace is in, and only perform stylus button presses while in overview mode.
342 if (mLauncher.mWorkspace.isInOverviewMode()
343 && mStylusEventHelper.checkAndPerformStylusEvent(ev)) {
344 return true;
345 }
346 return handled;
347 }
348
Chris Craik01f2d7f2013-10-01 14:41:56 -0700349 public void enableHardwareLayer(boolean hasLayer) {
350 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700351 }
352
353 public void buildHardwareLayer() {
354 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700355 }
356
Adam Cohen307fe232012-08-16 17:55:58 -0700357 public float getChildrenScale() {
358 return mIsHotseat ? mHotseatScale : 1.0f;
359 }
360
Winson Chung5f8afe62013-08-12 16:19:28 -0700361 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700362 mFixedCellWidth = mCellWidth = width;
363 mFixedCellHeight = mCellHeight = height;
Winson Chung5f8afe62013-08-12 16:19:28 -0700364 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
365 mCountX, mCountY);
366 }
367
Adam Cohen2801caf2011-05-13 20:57:39 -0700368 public void setGridSize(int x, int y) {
369 mCountX = x;
370 mCountY = y;
371 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800372 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700373 mTempRectStack.clear();
Adam Cohen2374abf2013-04-16 14:56:57 -0700374 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700375 mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700376 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700377 }
378
Adam Cohen2374abf2013-04-16 14:56:57 -0700379 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
380 public void setInvertIfRtl(boolean invert) {
381 mShortcutsAndWidgets.setInvertIfRtl(invert);
382 }
383
Adam Cohen917e3882013-10-31 15:03:35 -0700384 public void setDropPending(boolean pending) {
385 mDropPending = pending;
386 }
387
388 public boolean isDropPending() {
389 return mDropPending;
390 }
391
Sunny Goyal4b6eb262015-05-14 19:24:40 -0700392 @Override
393 public void setPressedIcon(BubbleTextView icon, Bitmap background) {
Sunny Goyal508da152014-08-14 10:53:27 -0700394 if (icon == null || background == null) {
395 mTouchFeedbackView.setBitmap(null);
396 mTouchFeedbackView.animate().cancel();
397 } else {
Sunny Goyal508da152014-08-14 10:53:27 -0700398 if (mTouchFeedbackView.setBitmap(background)) {
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700399 mTouchFeedbackView.alignWithIconView(icon, mShortcutsAndWidgets);
400 mTouchFeedbackView.animateShadow();
Sunny Goyal508da152014-08-14 10:53:27 -0700401 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800402 }
403 }
404
Adam Cohenc50438c2014-08-19 17:43:05 -0700405 void disableDragTarget() {
406 mIsDragTarget = false;
407 }
408
409 boolean isDragTarget() {
410 return mIsDragTarget;
411 }
412
413 void setIsDragOverlapping(boolean isDragOverlapping) {
414 if (mIsDragOverlapping != isDragOverlapping) {
415 mIsDragOverlapping = isDragOverlapping;
Sunny Goyal2805e632015-05-20 15:35:32 -0700416 if (mIsDragOverlapping) {
417 mBackground.startTransition(BACKGROUND_ACTIVATE_DURATION);
418 } else {
Winson Chunge8f1d042015-07-31 12:39:57 -0700419 if (mBackgroundAlpha > 0f) {
420 mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
421 } else {
422 mBackground.resetTransition();
423 }
Sunny Goyal2805e632015-05-20 15:35:32 -0700424 }
Adam Cohenc50438c2014-08-19 17:43:05 -0700425 invalidate();
426 }
427 }
428
Michael Jurka33945b22010-12-21 18:19:38 -0800429 boolean getIsDragOverlapping() {
430 return mIsDragOverlapping;
431 }
432
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700433 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700434 protected void onDraw(Canvas canvas) {
Sunny Goyal05739772015-05-19 19:59:09 -0700435 if (!mIsDragTarget) {
436 return;
437 }
438
Michael Jurka3e7c7632010-10-02 16:01:03 -0700439 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
440 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
441 // When we're small, we are either drawn normally or in the "accepts drops" state (during
442 // a drag). However, we also drag the mini hover background *over* one of those two
443 // backgrounds
Sunny Goyal05739772015-05-19 19:59:09 -0700444 if (mBackgroundAlpha > 0.0f) {
Sunny Goyal2805e632015-05-20 15:35:32 -0700445 mBackground.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700446 }
Romain Guya6abce82009-11-10 02:54:41 -0800447
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700448 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700449 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700450 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700451 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800452 final Rect r = mDragOutlines[i];
Winson Chung3a6e7f32013-10-09 15:50:52 -0700453 mTempRect.set(r);
454 Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale());
Joe Onorato4be866d2010-10-10 11:26:02 -0700455 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700456 paint.setAlpha((int)(alpha + .5f));
Winson Chung3a6e7f32013-10-09 15:50:52 -0700457 canvas.drawBitmap(b, null, mTempRect, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700458 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700459 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800460
Adam Cohen482ed822012-03-02 14:15:13 -0800461 if (DEBUG_VISUALIZE_OCCUPIED) {
462 int[] pt = new int[2];
463 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700464 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800465 for (int i = 0; i < mCountX; i++) {
466 for (int j = 0; j < mCountY; j++) {
467 if (mOccupied[i][j]) {
468 cellToPoint(i, j, pt);
469 canvas.save();
470 canvas.translate(pt[0], pt[1]);
471 cd.draw(canvas);
472 canvas.restore();
473 }
474 }
475 }
476 }
477
Andrew Flynn850d2e72012-04-26 16:51:20 -0700478 int previewOffset = FolderRingAnimator.sPreviewSize;
479
Adam Cohen69ce2e52011-07-03 19:25:21 -0700480 // The folder outer / inner ring image(s)
Adam Cohen2e6da152015-05-06 11:42:25 -0700481 DeviceProfile grid = mLauncher.getDeviceProfile();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700482 for (int i = 0; i < mFolderOuterRings.size(); i++) {
483 FolderRingAnimator fra = mFolderOuterRings.get(i);
484
Adam Cohen5108bc02013-09-20 17:04:51 -0700485 Drawable d;
486 int width, height;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700487 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700488 View child = getChildAt(fra.mCellX, fra.mCellY);
Adam Cohen558f1c22013-10-09 15:15:24 -0700489
Winson Chung89f97052013-09-20 11:32:26 -0700490 if (child != null) {
Adam Cohen558f1c22013-10-09 15:15:24 -0700491 int centerX = mTempLocation[0] + mCellWidth / 2;
492 int centerY = mTempLocation[1] + previewOffset / 2 +
493 child.getPaddingTop() + grid.folderBackgroundOffset;
494
Adam Cohen5108bc02013-09-20 17:04:51 -0700495 // Draw outer ring, if it exists
496 if (FolderIcon.HAS_OUTER_RING) {
497 d = FolderRingAnimator.sSharedOuterRingDrawable;
498 width = (int) (fra.getOuterRingSize() * getChildrenScale());
499 height = width;
500 canvas.save();
501 canvas.translate(centerX - width / 2, centerY - height / 2);
502 d.setBounds(0, 0, width, height);
503 d.draw(canvas);
504 canvas.restore();
505 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700506
Winson Chung89f97052013-09-20 11:32:26 -0700507 // Draw inner ring
508 d = FolderRingAnimator.sSharedInnerRingDrawable;
509 width = (int) (fra.getInnerRingSize() * getChildrenScale());
510 height = width;
Winson Chung89f97052013-09-20 11:32:26 -0700511 canvas.save();
512 canvas.translate(centerX - width / 2, centerY - width / 2);
513 d.setBounds(0, 0, width, height);
514 d.draw(canvas);
515 canvas.restore();
516 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700517 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700518
519 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
520 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
521 int width = d.getIntrinsicWidth();
522 int height = d.getIntrinsicHeight();
523
524 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700525 View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]);
Winson Chung89f97052013-09-20 11:32:26 -0700526 if (child != null) {
527 int centerX = mTempLocation[0] + mCellWidth / 2;
528 int centerY = mTempLocation[1] + previewOffset / 2 +
529 child.getPaddingTop() + grid.folderBackgroundOffset;
Adam Cohenc51934b2011-07-26 21:07:43 -0700530
Winson Chung89f97052013-09-20 11:32:26 -0700531 canvas.save();
532 canvas.translate(centerX - width / 2, centerY - width / 2);
533 d.setBounds(0, 0, width, height);
534 d.draw(canvas);
535 canvas.restore();
536 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700537 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700538 }
539
540 public void showFolderAccept(FolderRingAnimator fra) {
541 mFolderOuterRings.add(fra);
542 }
543
544 public void hideFolderAccept(FolderRingAnimator fra) {
545 if (mFolderOuterRings.contains(fra)) {
546 mFolderOuterRings.remove(fra);
547 }
548 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700549 }
550
Adam Cohenc51934b2011-07-26 21:07:43 -0700551 public void setFolderLeaveBehindCell(int x, int y) {
552 mFolderLeaveBehindCell[0] = x;
553 mFolderLeaveBehindCell[1] = y;
554 invalidate();
555 }
556
557 public void clearFolderLeaveBehind() {
558 mFolderLeaveBehindCell[0] = -1;
559 mFolderLeaveBehindCell[1] = -1;
560 invalidate();
561 }
562
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700563 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700564 public boolean shouldDelayChildPressedState() {
565 return false;
566 }
567
Adam Cohen1462de32012-07-24 22:34:36 -0700568 public void restoreInstanceState(SparseArray<Parcelable> states) {
Sunny Goyal33a152f2014-07-22 12:13:14 -0700569 try {
570 dispatchRestoreInstanceState(states);
571 } catch (IllegalArgumentException ex) {
572 if (LauncherAppState.isDogfoodBuild()) {
573 throw ex;
574 }
575 // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
576 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex);
577 }
Adam Cohen1462de32012-07-24 22:34:36 -0700578 }
579
Michael Jurkae6235dd2011-10-04 15:02:05 -0700580 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700581 public void cancelLongPress() {
582 super.cancelLongPress();
583
584 // Cancel long press for all children
585 final int count = getChildCount();
586 for (int i = 0; i < count; i++) {
587 final View child = getChildAt(i);
588 child.cancelLongPress();
589 }
590 }
591
Michael Jurkadee05892010-07-27 10:01:56 -0700592 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
593 mInterceptTouchListener = listener;
594 }
595
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800596 public int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700597 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800598 }
599
Hyunyoung Songee3e6a72015-02-20 14:25:27 -0800600 public int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700601 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800602 }
603
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800604 public void setIsHotseat(boolean isHotseat) {
605 mIsHotseat = isHotseat;
Winson Chung5f8afe62013-08-12 16:19:28 -0700606 mShortcutsAndWidgets.setIsHotseat(isHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800607 }
608
Sunny Goyale9b651e2015-04-24 11:44:51 -0700609 public boolean isHotseat() {
610 return mIsHotseat;
611 }
612
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800613 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700614 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700615 final LayoutParams lp = params;
616
Andrew Flynnde38e422012-05-08 11:22:15 -0700617 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800618 if (child instanceof BubbleTextView) {
619 BubbleTextView bubbleChild = (BubbleTextView) child;
Winson Chung5f8afe62013-08-12 16:19:28 -0700620 bubbleChild.setTextVisibility(!mIsHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800621 }
622
Adam Cohen307fe232012-08-16 17:55:58 -0700623 child.setScaleX(getChildrenScale());
624 child.setScaleY(getChildrenScale());
625
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800626 // Generate an id for each view, this assumes we have at most 256x256 cells
627 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700628 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700629 // If the horizontal or vertical span is set to -1, it is taken to
630 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700631 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
632 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800633
Winson Chungaafa03c2010-06-11 17:34:16 -0700634 child.setId(childId);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700635 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700636
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700637 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700638
Winson Chungaafa03c2010-06-11 17:34:16 -0700639 return true;
640 }
641 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800642 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700643
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800644 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700645 public void removeAllViews() {
646 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700647 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700648 }
649
650 @Override
651 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700652 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700653 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700654 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700655 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700656 }
657
658 @Override
659 public void removeView(View view) {
660 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700661 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700662 }
663
664 @Override
665 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700666 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
667 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700668 }
669
670 @Override
671 public void removeViewInLayout(View view) {
672 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700673 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700674 }
675
676 @Override
677 public void removeViews(int start, int count) {
678 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700679 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700680 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700681 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700682 }
683
684 @Override
685 public void removeViewsInLayout(int start, int count) {
686 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700687 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700688 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700689 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800690 }
691
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700692 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700693 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800694 * @param x X coordinate of the point
695 * @param y Y coordinate of the point
696 * @param result Array of 2 ints to hold the x and y coordinate of the cell
697 */
Sunny Goyale9b651e2015-04-24 11:44:51 -0700698 public void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700699 final int hStartPadding = getPaddingLeft();
700 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800701
702 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
703 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
704
Adam Cohend22015c2010-07-26 22:02:18 -0700705 final int xAxis = mCountX;
706 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800707
708 if (result[0] < 0) result[0] = 0;
709 if (result[0] >= xAxis) result[0] = xAxis - 1;
710 if (result[1] < 0) result[1] = 0;
711 if (result[1] >= yAxis) result[1] = yAxis - 1;
712 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700713
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800714 /**
715 * Given a point, return the cell that most closely encloses that point
716 * @param x X coordinate of the point
717 * @param y Y coordinate of the point
718 * @param result Array of 2 ints to hold the x and y coordinate of the cell
719 */
720 void pointToCellRounded(int x, int y, int[] result) {
721 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
722 }
723
724 /**
725 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700726 *
727 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800728 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700729 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800730 * @param result Array of 2 ints to hold the x and y coordinate of the point
731 */
732 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700733 final int hStartPadding = getPaddingLeft();
734 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800735
736 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
737 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
738 }
739
Adam Cohene3e27a82011-04-15 12:07:39 -0700740 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800741 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700742 *
743 * @param cellX X coordinate of the cell
744 * @param cellY Y coordinate of the cell
745 *
746 * @param result Array of 2 ints to hold the x and y coordinate of the point
747 */
748 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700749 regionToCenterPoint(cellX, cellY, 1, 1, result);
750 }
751
752 /**
753 * Given a cell coordinate and span return the point that represents the center of the regio
754 *
755 * @param cellX X coordinate of the cell
756 * @param cellY Y coordinate of the cell
757 *
758 * @param result Array of 2 ints to hold the x and y coordinate of the point
759 */
760 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700761 final int hStartPadding = getPaddingLeft();
762 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700763 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
764 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
765 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
766 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700767 }
768
Adam Cohen19f37922012-03-21 11:59:11 -0700769 /**
770 * Given a cell coordinate and span fills out a corresponding pixel rect
771 *
772 * @param cellX X coordinate of the cell
773 * @param cellY Y coordinate of the cell
774 * @param result Rect in which to write the result
775 */
776 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
777 final int hStartPadding = getPaddingLeft();
778 final int vStartPadding = getPaddingTop();
779 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
780 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
781 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
782 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
783 }
784
Adam Cohen482ed822012-03-02 14:15:13 -0800785 public float getDistanceFromCell(float x, float y, int[] cell) {
786 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
Sunny Goyalf7a29e82015-04-24 15:20:43 -0700787 return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
Adam Cohen482ed822012-03-02 14:15:13 -0800788 }
789
Romain Guy84f296c2009-11-04 15:00:44 -0800790 int getCellWidth() {
791 return mCellWidth;
792 }
793
794 int getCellHeight() {
795 return mCellHeight;
796 }
797
Adam Cohend4844c32011-02-18 19:25:06 -0800798 int getWidthGap() {
799 return mWidthGap;
800 }
801
802 int getHeightGap() {
803 return mHeightGap;
804 }
805
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700806 public void setFixedSize(int width, int height) {
807 mFixedWidth = width;
808 mFixedHeight = height;
809 }
810
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800811 @Override
812 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800813 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800814 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700815 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
816 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700817 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
818 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700819 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Sunny Goyalc6205602015-05-21 20:46:33 -0700820 int cw = DeviceProfile.calculateCellWidth(childWidthSize, mCountX);
821 int ch = DeviceProfile.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700822 if (cw != mCellWidth || ch != mCellHeight) {
823 mCellWidth = cw;
824 mCellHeight = ch;
825 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
826 mHeightGap, mCountX, mCountY);
827 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700828 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700829
Winson Chung2d75f122013-09-23 16:53:31 -0700830 int newWidth = childWidthSize;
831 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700832 if (mFixedWidth > 0 && mFixedHeight > 0) {
833 newWidth = mFixedWidth;
834 newHeight = mFixedHeight;
835 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800836 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
837 }
838
Adam Cohend22015c2010-07-26 22:02:18 -0700839 int numWidthGaps = mCountX - 1;
840 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800841
Adam Cohen234c4cd2011-07-17 21:03:04 -0700842 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700843 int hSpace = childWidthSize;
844 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700845 int hFreeSpace = hSpace - (mCountX * mCellWidth);
846 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700847 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
848 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -0700849 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
850 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700851 } else {
852 mWidthGap = mOriginalWidthGap;
853 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700854 }
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700855
856 // Make the feedback view large enough to hold the blur bitmap.
857 mTouchFeedbackView.measure(
858 MeasureSpec.makeMeasureSpec(mCellWidth + mTouchFeedbackView.getExtraSize(),
859 MeasureSpec.EXACTLY),
860 MeasureSpec.makeMeasureSpec(mCellHeight + mTouchFeedbackView.getExtraSize(),
861 MeasureSpec.EXACTLY));
862
863 mShortcutsAndWidgets.measure(
864 MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
865 MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
866
867 int maxWidth = mShortcutsAndWidgets.getMeasuredWidth();
868 int maxHeight = mShortcutsAndWidgets.getMeasuredHeight();
Winson Chung2d75f122013-09-23 16:53:31 -0700869 if (mFixedWidth > 0 && mFixedHeight > 0) {
870 setMeasuredDimension(maxWidth, maxHeight);
871 } else {
872 setMeasuredDimension(widthSize, heightSize);
873 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800874 }
875
876 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700877 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Winson Chung38848ca2013-10-08 12:03:44 -0700878 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
879 (mCountX * mCellWidth);
880 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
881 int top = getPaddingTop();
Sunny Goyal4fe5a372015-05-14 19:55:10 -0700882
883 mTouchFeedbackView.layout(left, top,
884 left + mTouchFeedbackView.getMeasuredWidth(),
885 top + mTouchFeedbackView.getMeasuredHeight());
886 mShortcutsAndWidgets.layout(left, top,
887 left + r - l,
888 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800889 }
890
891 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700892 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
893 super.onSizeChanged(w, h, oldw, oldh);
Winson Chung82a9bd22013-10-08 16:02:34 -0700894
895 // Expand the background drawing bounds by the padding baked into the background drawable
Sunny Goyal2805e632015-05-20 15:35:32 -0700896 mBackground.getPadding(mTempRect);
897 mBackground.setBounds(-mTempRect.left, -mTempRect.top,
898 w + mTempRect.right, h + mTempRect.bottom);
Michael Jurkadee05892010-07-27 10:01:56 -0700899 }
900
901 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800902 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700903 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800904 }
905
906 @Override
907 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700908 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800909 }
910
Michael Jurka5f1c5092010-09-03 14:15:02 -0700911 public float getBackgroundAlpha() {
912 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -0700913 }
914
Michael Jurka5f1c5092010-09-03 14:15:02 -0700915 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -0800916 if (mBackgroundAlpha != alpha) {
917 mBackgroundAlpha = alpha;
Sunny Goyal2805e632015-05-20 15:35:32 -0700918 mBackground.setAlpha((int) (mBackgroundAlpha * 255));
Michael Jurkaafaa0502011-12-13 18:22:50 -0800919 }
Michael Jurkadee05892010-07-27 10:01:56 -0700920 }
921
Sunny Goyal2805e632015-05-20 15:35:32 -0700922 @Override
923 protected boolean verifyDrawable(Drawable who) {
924 return super.verifyDrawable(who) || (mIsDragTarget && who == mBackground);
925 }
926
Michael Jurkaa52570f2012-03-20 03:18:20 -0700927 public void setShortcutAndWidgetAlpha(float alpha) {
Sunny Goyal02b50812014-09-10 15:44:42 -0700928 mShortcutsAndWidgets.setAlpha(alpha);
Michael Jurkadee05892010-07-27 10:01:56 -0700929 }
930
Michael Jurkaa52570f2012-03-20 03:18:20 -0700931 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
Sunny Goyaldcbcc862014-08-12 15:58:36 -0700932 return mShortcutsAndWidgets;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700933 }
934
Patrick Dubroy440c3602010-07-13 17:50:32 -0700935 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700936 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -0700937 }
938
Adam Cohen76fc0852011-06-17 13:26:23 -0700939 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -0800940 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700941 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -0800942 boolean[][] occupied = mOccupied;
943 if (!permanent) {
944 occupied = mTmpOccupied;
945 }
946
Adam Cohen19f37922012-03-21 11:59:11 -0700947 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700948 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
949 final ItemInfo info = (ItemInfo) child.getTag();
950
951 // We cancel any existing animations
952 if (mReorderAnimators.containsKey(lp)) {
953 mReorderAnimators.get(lp).cancel();
954 mReorderAnimators.remove(lp);
955 }
956
Adam Cohen482ed822012-03-02 14:15:13 -0800957 final int oldX = lp.x;
958 final int oldY = lp.y;
959 if (adjustOccupied) {
960 occupied[lp.cellX][lp.cellY] = false;
961 occupied[cellX][cellY] = true;
962 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700963 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -0800964 if (permanent) {
965 lp.cellX = info.cellX = cellX;
966 lp.cellY = info.cellY = cellY;
967 } else {
968 lp.tmpCellX = cellX;
969 lp.tmpCellY = cellY;
970 }
Adam Cohenbfbfd262011-06-13 16:55:12 -0700971 clc.setupLp(lp);
972 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800973 final int newX = lp.x;
974 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700975
Adam Cohen76fc0852011-06-17 13:26:23 -0700976 lp.x = oldX;
977 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -0700978
Adam Cohen482ed822012-03-02 14:15:13 -0800979 // Exit early if we're not actually moving the view
980 if (oldX == newX && oldY == newY) {
981 lp.isLockedToGrid = true;
982 return true;
983 }
984
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100985 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -0800986 va.setDuration(duration);
987 mReorderAnimators.put(lp, va);
988
989 va.addUpdateListener(new AnimatorUpdateListener() {
990 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -0700991 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -0800992 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -0700993 lp.x = (int) ((1 - r) * oldX + r * newX);
994 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -0700995 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -0700996 }
997 });
Adam Cohen482ed822012-03-02 14:15:13 -0800998 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -0700999 boolean cancelled = false;
1000 public void onAnimationEnd(Animator animation) {
1001 // If the animation was cancelled, it means that another animation
1002 // has interrupted this one, and we don't want to lock the item into
1003 // place just yet.
1004 if (!cancelled) {
1005 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001006 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001007 }
1008 if (mReorderAnimators.containsKey(lp)) {
1009 mReorderAnimators.remove(lp);
1010 }
1011 }
1012 public void onAnimationCancel(Animator animation) {
1013 cancelled = true;
1014 }
1015 });
Adam Cohen482ed822012-03-02 14:15:13 -08001016 va.setStartDelay(delay);
1017 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001018 return true;
1019 }
1020 return false;
1021 }
1022
Adam Cohen482ed822012-03-02 14:15:13 -08001023 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1024 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001025 final int oldDragCellX = mDragCell[0];
1026 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001027
Adam Cohen2801caf2011-05-13 20:57:39 -07001028 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001029 return;
1030 }
1031
Adam Cohen482ed822012-03-02 14:15:13 -08001032 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1033 mDragCell[0] = cellX;
1034 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001035 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001036 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001037 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001038
Joe Onorato4be866d2010-10-10 11:26:02 -07001039 int left = topLeft[0];
1040 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001041
Winson Chungb8c69f32011-10-19 21:36:08 -07001042 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001043 // When drawing the drag outline, it did not account for margin offsets
1044 // added by the view's parent.
1045 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1046 left += lp.leftMargin;
1047 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001048
Adam Cohen99e8b402011-03-25 19:23:43 -07001049 // Offsets due to the size difference between the View and the dragOutline.
1050 // There is a size difference to account for the outer blur, which may lie
1051 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001052 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001053 // We center about the x axis
1054 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1055 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001056 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001057 if (dragOffset != null && dragRegion != null) {
1058 // Center the drag region *horizontally* in the cell and apply a drag
1059 // outline offset
1060 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1061 - dragRegion.width()) / 2;
Winson Chung69737c32013-10-08 17:00:19 -07001062 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1063 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1064 top += dragOffset.y + cellPaddingY;
Winson Chungb8c69f32011-10-19 21:36:08 -07001065 } else {
1066 // Center the drag outline in the cell
1067 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1068 - dragOutline.getWidth()) / 2;
1069 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1070 - dragOutline.getHeight()) / 2;
1071 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001072 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001073 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001074 mDragOutlineAnims[oldIndex].animateOut();
1075 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001076 Rect r = mDragOutlines[mDragOutlineCurrent];
1077 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1078 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001079 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001080 }
Winson Chung150fbab2010-09-29 17:14:26 -07001081
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001082 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1083 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001084 }
1085 }
1086
Adam Cohene0310962011-04-18 16:15:31 -07001087 public void clearDragOutlines() {
1088 final int oldIndex = mDragOutlineCurrent;
1089 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001090 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001091 }
1092
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001093 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001094 * Find a vacant area that will fit the given bounds nearest the requested
1095 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001096 *
Romain Guy51afc022009-05-04 18:03:43 -07001097 * @param pixelX The X location at which you want to search for a vacant area.
1098 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001099 * @param spanX Horizontal span of the object.
1100 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001101 * @param result Array in which to place the result, or null (in which case a new array will
1102 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001103 * @return The X, Y cell of a vacant area that can contain this object,
1104 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001105 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001106 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
1107 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, spanX, spanY, result, null);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001108 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001109
Michael Jurka6a1435d2010-09-27 17:35:12 -07001110 /**
1111 * Find a vacant area that will fit the given bounds nearest the requested
1112 * cell location. Uses Euclidean distance to score multiple vacant areas.
1113 *
1114 * @param pixelX The X location at which you want to search for a vacant area.
1115 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001116 * @param minSpanX The minimum horizontal span required
1117 * @param minSpanY The minimum vertical span required
1118 * @param spanX Horizontal span of the object.
1119 * @param spanY Vertical span of the object.
1120 * @param result Array in which to place the result, or null (in which case a new array will
1121 * be allocated)
1122 * @return The X, Y cell of a vacant area that can contain this object,
1123 * nearest the requested location.
1124 */
1125 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1126 int spanY, int[] result, int[] resultSpan) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001127 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, true,
Adam Cohend41fbf52012-02-16 23:53:59 -08001128 result, resultSpan);
1129 }
1130
Adam Cohend41fbf52012-02-16 23:53:59 -08001131 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1132 private void lazyInitTempRectStack() {
1133 if (mTempRectStack.isEmpty()) {
1134 for (int i = 0; i < mCountX * mCountY; i++) {
1135 mTempRectStack.push(new Rect());
1136 }
1137 }
1138 }
Adam Cohen482ed822012-03-02 14:15:13 -08001139
Adam Cohend41fbf52012-02-16 23:53:59 -08001140 private void recycleTempRects(Stack<Rect> used) {
1141 while (!used.isEmpty()) {
1142 mTempRectStack.push(used.pop());
1143 }
1144 }
1145
1146 /**
1147 * Find a vacant area that will fit the given bounds nearest the requested
1148 * cell location. Uses Euclidean distance to score multiple vacant areas.
1149 *
1150 * @param pixelX The X location at which you want to search for a vacant area.
1151 * @param pixelY The Y location at which you want to search for a vacant area.
1152 * @param minSpanX The minimum horizontal span required
1153 * @param minSpanY The minimum vertical span required
1154 * @param spanX Horizontal span of the object.
1155 * @param spanY Vertical span of the object.
1156 * @param ignoreOccupied If true, the result can be an occupied cell
1157 * @param result Array in which to place the result, or null (in which case a new array will
1158 * be allocated)
1159 * @return The X, Y cell of a vacant area that can contain this object,
1160 * nearest the requested location.
1161 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001162 private int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1163 int spanY, boolean ignoreOccupied, int[] result, int[] resultSpan) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001164 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001165
Adam Cohene3e27a82011-04-15 12:07:39 -07001166 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1167 // to the center of the item, but we are searching based on the top-left cell, so
1168 // we translate the point over to correspond to the top-left.
1169 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1170 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1171
Jeff Sharkey70864282009-04-07 21:08:40 -07001172 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001173 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001174 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001175 final Rect bestRect = new Rect(-1, -1, -1, -1);
1176 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001177
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001178 final int countX = mCountX;
1179 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001180
Adam Cohend41fbf52012-02-16 23:53:59 -08001181 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1182 spanX < minSpanX || spanY < minSpanY) {
1183 return bestXY;
1184 }
1185
1186 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001187 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001188 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1189 int ySize = -1;
1190 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001191 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001192 // First, let's see if this thing fits anywhere
1193 for (int i = 0; i < minSpanX; i++) {
1194 for (int j = 0; j < minSpanY; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001195 if (mOccupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001196 continue inner;
1197 }
Michael Jurkac28de512010-08-13 11:27:44 -07001198 }
1199 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001200 xSize = minSpanX;
1201 ySize = minSpanY;
1202
1203 // We know that the item will fit at _some_ acceptable size, now let's see
1204 // how big we can make it. We'll alternate between incrementing x and y spans
1205 // until we hit a limit.
1206 boolean incX = true;
1207 boolean hitMaxX = xSize >= spanX;
1208 boolean hitMaxY = ySize >= spanY;
1209 while (!(hitMaxX && hitMaxY)) {
1210 if (incX && !hitMaxX) {
1211 for (int j = 0; j < ySize; j++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001212 if (x + xSize > countX -1 || mOccupied[x + xSize][y + j]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001213 // We can't move out horizontally
1214 hitMaxX = true;
1215 }
1216 }
1217 if (!hitMaxX) {
1218 xSize++;
1219 }
1220 } else if (!hitMaxY) {
1221 for (int i = 0; i < xSize; i++) {
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001222 if (y + ySize > countY - 1 || mOccupied[x + i][y + ySize]) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001223 // We can't move out vertically
1224 hitMaxY = true;
1225 }
1226 }
1227 if (!hitMaxY) {
1228 ySize++;
1229 }
1230 }
1231 hitMaxX |= xSize >= spanX;
1232 hitMaxY |= ySize >= spanY;
1233 incX = !incX;
1234 }
1235 incX = true;
1236 hitMaxX = xSize >= spanX;
1237 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001238 }
Sunny Goyal2805e632015-05-20 15:35:32 -07001239 final int[] cellXY = mTmpPoint;
Adam Cohene3e27a82011-04-15 12:07:39 -07001240 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001241
Adam Cohend41fbf52012-02-16 23:53:59 -08001242 // We verify that the current rect is not a sub-rect of any of our previous
1243 // candidates. In this case, the current rect is disqualified in favour of the
1244 // containing rect.
1245 Rect currentRect = mTempRectStack.pop();
1246 currentRect.set(x, y, x + xSize, y + ySize);
1247 boolean contained = false;
1248 for (Rect r : validRegions) {
1249 if (r.contains(currentRect)) {
1250 contained = true;
1251 break;
1252 }
1253 }
1254 validRegions.push(currentRect);
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001255 double distance = Math.hypot(cellXY[0] - pixelX, cellXY[1] - pixelY);
Adam Cohen482ed822012-03-02 14:15:13 -08001256
Adam Cohend41fbf52012-02-16 23:53:59 -08001257 if ((distance <= bestDistance && !contained) ||
1258 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001259 bestDistance = distance;
1260 bestXY[0] = x;
1261 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001262 if (resultSpan != null) {
1263 resultSpan[0] = xSize;
1264 resultSpan[1] = ySize;
1265 }
1266 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001267 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001268 }
1269 }
1270
Adam Cohenc0dcf592011-06-01 15:30:43 -07001271 // Return -1, -1 if no suitable location found
1272 if (bestDistance == Double.MAX_VALUE) {
1273 bestXY[0] = -1;
1274 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001275 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001276 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001277 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001278 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001279
Adam Cohen482ed822012-03-02 14:15:13 -08001280 /**
1281 * Find a vacant area that will fit the given bounds nearest the requested
1282 * cell location, and will also weigh in a suggested direction vector of the
1283 * desired location. This method computers distance based on unit grid distances,
1284 * not pixel distances.
1285 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001286 * @param cellX The X cell nearest to which you want to search for a vacant area.
1287 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001288 * @param spanX Horizontal span of the object.
1289 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001290 * @param direction The favored direction in which the views should move from x, y
1291 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1292 * matches exactly. Otherwise we find the best matching direction.
1293 * @param occoupied The array which represents which cells in the CellLayout are occupied
1294 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001295 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001296 * @param result Array in which to place the result, or null (in which case a new array will
1297 * be allocated)
1298 * @return The X, Y cell of a vacant area that can contain this object,
1299 * nearest the requested location.
1300 */
1301 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001302 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001303 // Keep track of best-scoring drop area
1304 final int[] bestXY = result != null ? result : new int[2];
1305 float bestDistance = Float.MAX_VALUE;
1306 int bestDirectionScore = Integer.MIN_VALUE;
1307
1308 final int countX = mCountX;
1309 final int countY = mCountY;
1310
1311 for (int y = 0; y < countY - (spanY - 1); y++) {
1312 inner:
1313 for (int x = 0; x < countX - (spanX - 1); x++) {
1314 // First, let's see if this thing fits anywhere
1315 for (int i = 0; i < spanX; i++) {
1316 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001317 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001318 continue inner;
1319 }
1320 }
1321 }
1322
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001323 float distance = (float) Math.hypot(x - cellX, y - cellY);
Adam Cohen482ed822012-03-02 14:15:13 -08001324 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001325 computeDirectionVector(x - cellX, y - cellY, curDirection);
1326 // The direction score is just the dot product of the two candidate direction
1327 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001328 int curDirectionScore = direction[0] * curDirection[0] +
1329 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001330 boolean exactDirectionOnly = false;
1331 boolean directionMatches = direction[0] == curDirection[0] &&
1332 direction[0] == curDirection[0];
1333 if ((directionMatches || !exactDirectionOnly) &&
1334 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001335 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1336 bestDistance = distance;
1337 bestDirectionScore = curDirectionScore;
1338 bestXY[0] = x;
1339 bestXY[1] = y;
1340 }
1341 }
1342 }
1343
1344 // Return -1, -1 if no suitable location found
1345 if (bestDistance == Float.MAX_VALUE) {
1346 bestXY[0] = -1;
1347 bestXY[1] = -1;
1348 }
1349 return bestXY;
1350 }
1351
1352 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001353 int[] direction, ItemConfiguration currentState) {
1354 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001355 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001356 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001357 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1358
Adam Cohen8baab352012-03-20 17:39:21 -07001359 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001360
1361 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001362 c.x = mTempLocation[0];
1363 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001364 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001365 }
Adam Cohen8baab352012-03-20 17:39:21 -07001366 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001367 return success;
1368 }
1369
Adam Cohenf3900c22012-11-16 18:28:11 -08001370 /**
1371 * This helper class defines a cluster of views. It helps with defining complex edges
1372 * of the cluster and determining how those edges interact with other views. The edges
1373 * essentially define a fine-grained boundary around the cluster of views -- like a more
1374 * precise version of a bounding box.
1375 */
1376 private class ViewCluster {
1377 final static int LEFT = 0;
1378 final static int TOP = 1;
1379 final static int RIGHT = 2;
1380 final static int BOTTOM = 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001381
Adam Cohenf3900c22012-11-16 18:28:11 -08001382 ArrayList<View> views;
1383 ItemConfiguration config;
1384 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001385
Adam Cohenf3900c22012-11-16 18:28:11 -08001386 int[] leftEdge = new int[mCountY];
1387 int[] rightEdge = new int[mCountY];
1388 int[] topEdge = new int[mCountX];
1389 int[] bottomEdge = new int[mCountX];
1390 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1391
1392 @SuppressWarnings("unchecked")
1393 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1394 this.views = (ArrayList<View>) views.clone();
1395 this.config = config;
1396 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001397 }
1398
Adam Cohenf3900c22012-11-16 18:28:11 -08001399 void resetEdges() {
1400 for (int i = 0; i < mCountX; i++) {
1401 topEdge[i] = -1;
1402 bottomEdge[i] = -1;
1403 }
1404 for (int i = 0; i < mCountY; i++) {
1405 leftEdge[i] = -1;
1406 rightEdge[i] = -1;
1407 }
1408 leftEdgeDirty = true;
1409 rightEdgeDirty = true;
1410 bottomEdgeDirty = true;
1411 topEdgeDirty = true;
1412 boundingRectDirty = true;
1413 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001414
Adam Cohenf3900c22012-11-16 18:28:11 -08001415 void computeEdge(int which, int[] edge) {
1416 int count = views.size();
1417 for (int i = 0; i < count; i++) {
1418 CellAndSpan cs = config.map.get(views.get(i));
1419 switch (which) {
1420 case LEFT:
1421 int left = cs.x;
1422 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1423 if (left < edge[j] || edge[j] < 0) {
1424 edge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001425 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001426 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001427 break;
1428 case RIGHT:
1429 int right = cs.x + cs.spanX;
1430 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1431 if (right > edge[j]) {
1432 edge[j] = right;
1433 }
1434 }
1435 break;
1436 case TOP:
1437 int top = cs.y;
1438 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1439 if (top < edge[j] || edge[j] < 0) {
1440 edge[j] = top;
1441 }
1442 }
1443 break;
1444 case BOTTOM:
1445 int bottom = cs.y + cs.spanY;
1446 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1447 if (bottom > edge[j]) {
1448 edge[j] = bottom;
1449 }
1450 }
1451 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001452 }
1453 }
1454 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001455
1456 boolean isViewTouchingEdge(View v, int whichEdge) {
1457 CellAndSpan cs = config.map.get(v);
1458
1459 int[] edge = getEdge(whichEdge);
1460
1461 switch (whichEdge) {
1462 case LEFT:
1463 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1464 if (edge[i] == cs.x + cs.spanX) {
1465 return true;
1466 }
1467 }
1468 break;
1469 case RIGHT:
1470 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1471 if (edge[i] == cs.x) {
1472 return true;
1473 }
1474 }
1475 break;
1476 case TOP:
1477 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1478 if (edge[i] == cs.y + cs.spanY) {
1479 return true;
1480 }
1481 }
1482 break;
1483 case BOTTOM:
1484 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1485 if (edge[i] == cs.y) {
1486 return true;
1487 }
1488 }
1489 break;
1490 }
1491 return false;
1492 }
1493
1494 void shift(int whichEdge, int delta) {
1495 for (View v: views) {
1496 CellAndSpan c = config.map.get(v);
1497 switch (whichEdge) {
1498 case LEFT:
1499 c.x -= delta;
1500 break;
1501 case RIGHT:
1502 c.x += delta;
1503 break;
1504 case TOP:
1505 c.y -= delta;
1506 break;
1507 case BOTTOM:
1508 default:
1509 c.y += delta;
1510 break;
1511 }
1512 }
1513 resetEdges();
1514 }
1515
1516 public void addView(View v) {
1517 views.add(v);
1518 resetEdges();
1519 }
1520
1521 public Rect getBoundingRect() {
1522 if (boundingRectDirty) {
1523 boolean first = true;
1524 for (View v: views) {
1525 CellAndSpan c = config.map.get(v);
1526 if (first) {
1527 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1528 first = false;
1529 } else {
1530 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1531 }
1532 }
1533 }
1534 return boundingRect;
1535 }
1536
1537 public int[] getEdge(int which) {
1538 switch (which) {
1539 case LEFT:
1540 return getLeftEdge();
1541 case RIGHT:
1542 return getRightEdge();
1543 case TOP:
1544 return getTopEdge();
1545 case BOTTOM:
1546 default:
1547 return getBottomEdge();
1548 }
1549 }
1550
1551 public int[] getLeftEdge() {
1552 if (leftEdgeDirty) {
1553 computeEdge(LEFT, leftEdge);
1554 }
1555 return leftEdge;
1556 }
1557
1558 public int[] getRightEdge() {
1559 if (rightEdgeDirty) {
1560 computeEdge(RIGHT, rightEdge);
1561 }
1562 return rightEdge;
1563 }
1564
1565 public int[] getTopEdge() {
1566 if (topEdgeDirty) {
1567 computeEdge(TOP, topEdge);
1568 }
1569 return topEdge;
1570 }
1571
1572 public int[] getBottomEdge() {
1573 if (bottomEdgeDirty) {
1574 computeEdge(BOTTOM, bottomEdge);
1575 }
1576 return bottomEdge;
1577 }
1578
1579 PositionComparator comparator = new PositionComparator();
1580 class PositionComparator implements Comparator<View> {
1581 int whichEdge = 0;
1582 public int compare(View left, View right) {
1583 CellAndSpan l = config.map.get(left);
1584 CellAndSpan r = config.map.get(right);
1585 switch (whichEdge) {
1586 case LEFT:
1587 return (r.x + r.spanX) - (l.x + l.spanX);
1588 case RIGHT:
1589 return l.x - r.x;
1590 case TOP:
1591 return (r.y + r.spanY) - (l.y + l.spanY);
1592 case BOTTOM:
1593 default:
1594 return l.y - r.y;
1595 }
1596 }
1597 }
1598
1599 public void sortConfigurationForEdgePush(int edge) {
1600 comparator.whichEdge = edge;
1601 Collections.sort(config.sortedViews, comparator);
1602 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001603 }
1604
Adam Cohenf3900c22012-11-16 18:28:11 -08001605 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1606 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001607
Adam Cohenf3900c22012-11-16 18:28:11 -08001608 ViewCluster cluster = new ViewCluster(views, currentState);
1609 Rect clusterRect = cluster.getBoundingRect();
1610 int whichEdge;
1611 int pushDistance;
1612 boolean fail = false;
1613
1614 // Determine the edge of the cluster that will be leading the push and how far
1615 // the cluster must be shifted.
1616 if (direction[0] < 0) {
1617 whichEdge = ViewCluster.LEFT;
1618 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001619 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001620 whichEdge = ViewCluster.RIGHT;
1621 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1622 } else if (direction[1] < 0) {
1623 whichEdge = ViewCluster.TOP;
1624 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1625 } else {
1626 whichEdge = ViewCluster.BOTTOM;
1627 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001628 }
1629
Adam Cohenf3900c22012-11-16 18:28:11 -08001630 // Break early for invalid push distance.
1631 if (pushDistance <= 0) {
1632 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001633 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001634
1635 // Mark the occupied state as false for the group of views we want to move.
1636 for (View v: views) {
1637 CellAndSpan c = currentState.map.get(v);
1638 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1639 }
1640
1641 // We save the current configuration -- if we fail to find a solution we will revert
1642 // to the initial state. The process of finding a solution modifies the configuration
1643 // in place, hence the need for revert in the failure case.
1644 currentState.save();
1645
1646 // The pushing algorithm is simplified by considering the views in the order in which
1647 // they would be pushed by the cluster. For example, if the cluster is leading with its
1648 // left edge, we consider sort the views by their right edge, from right to left.
1649 cluster.sortConfigurationForEdgePush(whichEdge);
1650
1651 while (pushDistance > 0 && !fail) {
1652 for (View v: currentState.sortedViews) {
1653 // For each view that isn't in the cluster, we see if the leading edge of the
1654 // cluster is contacting the edge of that view. If so, we add that view to the
1655 // cluster.
1656 if (!cluster.views.contains(v) && v != dragView) {
1657 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1658 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1659 if (!lp.canReorder) {
1660 // The push solution includes the all apps button, this is not viable.
1661 fail = true;
1662 break;
1663 }
1664 cluster.addView(v);
1665 CellAndSpan c = currentState.map.get(v);
1666
1667 // Adding view to cluster, mark it as not occupied.
1668 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1669 }
1670 }
1671 }
1672 pushDistance--;
1673
1674 // The cluster has been completed, now we move the whole thing over in the appropriate
1675 // direction.
1676 cluster.shift(whichEdge, 1);
1677 }
1678
1679 boolean foundSolution = false;
1680 clusterRect = cluster.getBoundingRect();
1681
1682 // Due to the nature of the algorithm, the only check required to verify a valid solution
1683 // is to ensure that completed shifted cluster lies completely within the cell layout.
1684 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1685 clusterRect.bottom <= mCountY) {
1686 foundSolution = true;
1687 } else {
1688 currentState.restore();
1689 }
1690
1691 // In either case, we set the occupied array as marked for the location of the views
1692 for (View v: cluster.views) {
1693 CellAndSpan c = currentState.map.get(v);
1694 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1695 }
1696
1697 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001698 }
1699
Adam Cohen482ed822012-03-02 14:15:13 -08001700 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001701 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001702 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001703
Adam Cohen8baab352012-03-20 17:39:21 -07001704 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001705 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001706 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001707 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001708 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001709 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001710 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001711 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001712 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001713 }
1714 }
Adam Cohen8baab352012-03-20 17:39:21 -07001715
Adam Cohen8baab352012-03-20 17:39:21 -07001716 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001717 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001718 CellAndSpan c = currentState.map.get(v);
1719 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1720 }
1721
Adam Cohen47a876d2012-03-19 13:21:41 -07001722 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1723 int top = boundingRect.top;
1724 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001725 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001726 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001727 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001728 CellAndSpan c = currentState.map.get(v);
1729 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001730 }
1731
Adam Cohen482ed822012-03-02 14:15:13 -08001732 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1733
Adam Cohenf3900c22012-11-16 18:28:11 -08001734 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1735 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001736
Adam Cohen8baab352012-03-20 17:39:21 -07001737 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001738 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001739 int deltaX = mTempLocation[0] - boundingRect.left;
1740 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001741 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001742 CellAndSpan c = currentState.map.get(v);
1743 c.x += deltaX;
1744 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001745 }
1746 success = true;
1747 }
Adam Cohen8baab352012-03-20 17:39:21 -07001748
1749 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001750 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001751 CellAndSpan c = currentState.map.get(v);
1752 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001753 }
1754 return success;
1755 }
1756
1757 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1758 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1759 }
1760
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001761 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1762 // to push items in each of the cardinal directions, in an order based on the direction vector
1763 // passed.
1764 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1765 int[] direction, View ignoreView, ItemConfiguration solution) {
1766 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001767 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001768 // separately in each of the components.
1769 int temp = direction[1];
1770 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001771
1772 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001773 ignoreView, solution)) {
1774 return true;
1775 }
1776 direction[1] = temp;
1777 temp = direction[0];
1778 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001779
1780 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001781 ignoreView, solution)) {
1782 return true;
1783 }
1784 // Revert the direction
1785 direction[0] = temp;
1786
1787 // Now we try pushing in each component of the opposite direction
1788 direction[0] *= -1;
1789 direction[1] *= -1;
1790 temp = direction[1];
1791 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001792 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001793 ignoreView, solution)) {
1794 return true;
1795 }
1796
1797 direction[1] = temp;
1798 temp = direction[0];
1799 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001800 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001801 ignoreView, solution)) {
1802 return true;
1803 }
1804 // revert the direction
1805 direction[0] = temp;
1806 direction[0] *= -1;
1807 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001808
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001809 } else {
1810 // If the direction vector has a single non-zero component, we push first in the
1811 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08001812 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001813 ignoreView, solution)) {
1814 return true;
1815 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001816 // Then we try the opposite direction
1817 direction[0] *= -1;
1818 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001819 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001820 ignoreView, solution)) {
1821 return true;
1822 }
1823 // Switch the direction back
1824 direction[0] *= -1;
1825 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07001826
1827 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001828 // to find a solution by pushing along the perpendicular axis.
1829
1830 // Swap the components
1831 int temp = direction[1];
1832 direction[1] = direction[0];
1833 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08001834 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001835 ignoreView, solution)) {
1836 return true;
1837 }
1838
1839 // Then we try the opposite direction
1840 direction[0] *= -1;
1841 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08001842 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001843 ignoreView, solution)) {
1844 return true;
1845 }
1846 // Switch the direction back
1847 direction[0] *= -1;
1848 direction[1] *= -1;
1849
1850 // Swap the components back
1851 temp = direction[1];
1852 direction[1] = direction[0];
1853 direction[0] = temp;
1854 }
1855 return false;
1856 }
1857
Adam Cohen482ed822012-03-02 14:15:13 -08001858 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001859 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001860 // Return early if get invalid cell positions
1861 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001862
Adam Cohen8baab352012-03-20 17:39:21 -07001863 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001864 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001865
Adam Cohen8baab352012-03-20 17:39:21 -07001866 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001867 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001868 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001869 if (c != null) {
1870 c.x = cellX;
1871 c.y = cellY;
1872 }
Adam Cohen482ed822012-03-02 14:15:13 -08001873 }
Adam Cohen482ed822012-03-02 14:15:13 -08001874 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1875 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001876 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001877 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001878 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001879 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001880 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001881 if (Rect.intersects(r0, r1)) {
1882 if (!lp.canReorder) {
1883 return false;
1884 }
1885 mIntersectingViews.add(child);
1886 }
1887 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001888
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001889 solution.intersectingViews = new ArrayList<View>(mIntersectingViews);
1890
Winson Chung5f8afe62013-08-12 16:19:28 -07001891 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001892 // we try to find a solution such that no displaced item travels through another item
1893 // without also displacing that item.
1894 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001895 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001896 return true;
1897 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001898
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001899 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08001900 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001901 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001902 return true;
1903 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001904
Adam Cohen482ed822012-03-02 14:15:13 -08001905 // Ok, they couldn't move as a block, let's move them individually
1906 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001907 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001908 return false;
1909 }
1910 }
1911 return true;
1912 }
1913
1914 /*
1915 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1916 * the provided point and the provided cell
1917 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001918 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001919 double angle = Math.atan(((float) deltaY) / deltaX);
1920
1921 result[0] = 0;
1922 result[1] = 0;
1923 if (Math.abs(Math.cos(angle)) > 0.5f) {
1924 result[0] = (int) Math.signum(deltaX);
1925 }
1926 if (Math.abs(Math.sin(angle)) > 0.5f) {
1927 result[1] = (int) Math.signum(deltaY);
1928 }
1929 }
1930
Adam Cohen8baab352012-03-20 17:39:21 -07001931 private void copyOccupiedArray(boolean[][] occupied) {
1932 for (int i = 0; i < mCountX; i++) {
1933 for (int j = 0; j < mCountY; j++) {
1934 occupied[i][j] = mOccupied[i][j];
1935 }
1936 }
1937 }
1938
Sunny Goyalf7a29e82015-04-24 15:20:43 -07001939 private ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001940 int spanX, int spanY, int[] direction, View dragView, boolean decX,
1941 ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001942 // Copy the current state into the solution. This solution will be manipulated as necessary.
1943 copyCurrentStateToSolution(solution, false);
1944 // Copy the current occupied array into the temporary occupied array. This array will be
1945 // manipulated as necessary to find a solution.
1946 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001947
1948 // We find the nearest cell into which we would place the dragged item, assuming there's
1949 // nothing in its way.
1950 int result[] = new int[2];
1951 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1952
1953 boolean success = false;
1954 // First we try the exact nearest position of the item being dragged,
1955 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001956 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1957 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001958
1959 if (!success) {
1960 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1961 // x, then 1 in y etc.
1962 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001963 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
1964 direction, dragView, false, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001965 } else if (spanY > minSpanY) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08001966 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
1967 direction, dragView, true, solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001968 }
1969 solution.isSolution = false;
1970 } else {
1971 solution.isSolution = true;
1972 solution.dragViewX = result[0];
1973 solution.dragViewY = result[1];
1974 solution.dragViewSpanX = spanX;
1975 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08001976 }
1977 return solution;
1978 }
1979
1980 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001981 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001982 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001983 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001984 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001985 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08001986 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07001987 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001988 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001989 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001990 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001991 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08001992 }
1993 }
1994
1995 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
1996 for (int i = 0; i < mCountX; i++) {
1997 for (int j = 0; j < mCountY; j++) {
1998 mTmpOccupied[i][j] = false;
1999 }
2000 }
2001
Michael Jurkaa52570f2012-03-20 03:18:20 -07002002 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002003 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002004 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002005 if (child == dragView) continue;
2006 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002007 CellAndSpan c = solution.map.get(child);
2008 if (c != null) {
2009 lp.tmpCellX = c.x;
2010 lp.tmpCellY = c.y;
2011 lp.cellHSpan = c.spanX;
2012 lp.cellVSpan = c.spanY;
2013 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002014 }
2015 }
2016 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2017 solution.dragViewSpanY, mTmpOccupied, true);
2018 }
2019
2020 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2021 commitDragView) {
2022
2023 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2024 for (int i = 0; i < mCountX; i++) {
2025 for (int j = 0; j < mCountY; j++) {
2026 occupied[i][j] = false;
2027 }
2028 }
2029
Michael Jurkaa52570f2012-03-20 03:18:20 -07002030 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002031 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002032 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002033 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002034 CellAndSpan c = solution.map.get(child);
2035 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002036 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2037 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002038 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002039 }
2040 }
2041 if (commitDragView) {
2042 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2043 solution.dragViewSpanY, occupied, true);
2044 }
2045 }
2046
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002047
2048 // This method starts or changes the reorder preview animations
2049 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution,
2050 View dragView, int delay, int mode) {
Adam Cohen19f37922012-03-21 11:59:11 -07002051 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002052 for (int i = 0; i < childCount; i++) {
2053 View child = mShortcutsAndWidgets.getChildAt(i);
2054 if (child == dragView) continue;
2055 CellAndSpan c = solution.map.get(child);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002056 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews
2057 != null && !solution.intersectingViews.contains(child);
2058
Adam Cohen19f37922012-03-21 11:59:11 -07002059 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002060 if (c != null && !skip) {
2061 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX,
2062 lp.cellY, c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002063 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002064 }
2065 }
2066 }
2067
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002068 // Class which represents the reorder preview animations. These animations show that an item is
Adam Cohen19f37922012-03-21 11:59:11 -07002069 // in a temporary state, and hint at where the item will return to.
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002070 class ReorderPreviewAnimation {
Adam Cohen19f37922012-03-21 11:59:11 -07002071 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002072 float finalDeltaX;
2073 float finalDeltaY;
2074 float initDeltaX;
2075 float initDeltaY;
2076 float finalScale;
2077 float initScale;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002078 int mode;
2079 boolean repeating = false;
2080 private static final int PREVIEW_DURATION = 300;
2081 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT;
2082
2083 public static final int MODE_HINT = 0;
2084 public static final int MODE_PREVIEW = 1;
2085
Adam Cohene7587d22012-05-24 18:50:02 -07002086 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002087
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002088 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1,
2089 int cellY1, int spanX, int spanY) {
Adam Cohen19f37922012-03-21 11:59:11 -07002090 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2091 final int x0 = mTmpPoint[0];
2092 final int y0 = mTmpPoint[1];
2093 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2094 final int x1 = mTmpPoint[0];
2095 final int y1 = mTmpPoint[1];
2096 final int dX = x1 - x0;
2097 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002098 finalDeltaX = 0;
2099 finalDeltaY = 0;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002100 int dir = mode == MODE_HINT ? -1 : 1;
Adam Cohen19f37922012-03-21 11:59:11 -07002101 if (dX == dY && dX == 0) {
2102 } else {
2103 if (dY == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002104 finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002105 } else if (dX == 0) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002106 finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002107 } else {
2108 double angle = Math.atan( (float) (dY) / dX);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002109 finalDeltaX = (int) (- dir * Math.signum(dX) *
2110 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude));
2111 finalDeltaY = (int) (- dir * Math.signum(dY) *
2112 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002113 }
2114 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002115 this.mode = mode;
Adam Cohend024f982012-05-23 18:26:45 -07002116 initDeltaX = child.getTranslationX();
2117 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002118 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002119 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002120 this.child = child;
2121 }
2122
Adam Cohend024f982012-05-23 18:26:45 -07002123 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002124 if (mShakeAnimators.containsKey(child)) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002125 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002126 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002127 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002128 if (finalDeltaX == 0 && finalDeltaY == 0) {
2129 completeAnimationImmediately();
2130 return;
2131 }
Adam Cohen19f37922012-03-21 11:59:11 -07002132 }
Adam Cohend024f982012-05-23 18:26:45 -07002133 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002134 return;
2135 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002136 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002137 a = va;
Tony Wickham489fc562015-09-02 14:45:39 -07002138
2139 // Animations are disabled in power save mode, causing the repeated animation to jump
2140 // spastically between beginning and end states. Since this looks bad, we don't repeat
2141 // the animation in power save mode.
2142 PowerManager powerManager = (PowerManager) getContext()
2143 .getSystemService(Context.POWER_SERVICE);
2144 boolean powerSaverOn = Utilities.ATLEAST_LOLLIPOP && powerManager.isPowerSaveMode();
2145 if (!powerSaverOn) {
2146 va.setRepeatMode(ValueAnimator.REVERSE);
2147 va.setRepeatCount(ValueAnimator.INFINITE);
2148 }
2149
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002150 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002151 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002152 va.addUpdateListener(new AnimatorUpdateListener() {
2153 @Override
2154 public void onAnimationUpdate(ValueAnimator animation) {
2155 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002156 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r;
2157 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
2158 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002159 child.setTranslationX(x);
2160 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002161 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002162 child.setScaleX(s);
2163 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002164 }
2165 });
2166 va.addListener(new AnimatorListenerAdapter() {
2167 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002168 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002169 initDeltaX = 0;
2170 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002171 initScale = getChildrenScale();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002172 repeating = true;
Adam Cohen19f37922012-03-21 11:59:11 -07002173 }
2174 });
Adam Cohen19f37922012-03-21 11:59:11 -07002175 mShakeAnimators.put(child, this);
2176 va.start();
2177 }
2178
Adam Cohend024f982012-05-23 18:26:45 -07002179 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002180 if (a != null) {
2181 a.cancel();
2182 }
Adam Cohen19f37922012-03-21 11:59:11 -07002183 }
Adam Cohene7587d22012-05-24 18:50:02 -07002184
Adam Cohen091440a2015-03-18 14:16:05 -07002185 @Thunk void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002186 if (a != null) {
2187 a.cancel();
2188 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002189
Michael Jurka2ecf9952012-06-18 12:52:28 -07002190 AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002191 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002192 s.playTogether(
Adam Cohen307fe232012-08-16 17:55:58 -07002193 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
2194 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
Michael Jurka2ecf9952012-06-18 12:52:28 -07002195 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2196 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002197 );
2198 s.setDuration(REORDER_ANIMATION_DURATION);
2199 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2200 s.start();
2201 }
Adam Cohen19f37922012-03-21 11:59:11 -07002202 }
2203
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002204 private void completeAndClearReorderPreviewAnimations() {
2205 for (ReorderPreviewAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002206 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002207 }
2208 mShakeAnimators.clear();
2209 }
2210
Adam Cohen482ed822012-03-02 14:15:13 -08002211 private void commitTempPlacement() {
2212 for (int i = 0; i < mCountX; i++) {
2213 for (int j = 0; j < mCountY; j++) {
2214 mOccupied[i][j] = mTmpOccupied[i][j];
2215 }
2216 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002217 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002218 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002219 View child = mShortcutsAndWidgets.getChildAt(i);
2220 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2221 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002222 // We do a null check here because the item info can be null in the case of the
2223 // AllApps button in the hotseat.
2224 if (info != null) {
Adam Cohen487f7dd2012-06-28 18:12:10 -07002225 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
2226 info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
2227 info.requiresDbUpdate = true;
2228 }
Adam Cohen2acce882012-03-28 19:03:19 -07002229 info.cellX = lp.cellX = lp.tmpCellX;
2230 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002231 info.spanX = lp.cellHSpan;
2232 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002233 }
Adam Cohen482ed822012-03-02 14:15:13 -08002234 }
Adam Cohen2acce882012-03-28 19:03:19 -07002235 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002236 }
2237
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002238 private void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002239 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002240 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002241 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002242 lp.useTmpCoords = useTempCoords;
2243 }
2244 }
2245
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002246 private ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002247 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2248 int[] result = new int[2];
2249 int[] resultSpan = new int[2];
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002250 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
Adam Cohen482ed822012-03-02 14:15:13 -08002251 resultSpan);
2252 if (result[0] >= 0 && result[1] >= 0) {
2253 copyCurrentStateToSolution(solution, false);
2254 solution.dragViewX = result[0];
2255 solution.dragViewY = result[1];
2256 solution.dragViewSpanX = resultSpan[0];
2257 solution.dragViewSpanY = resultSpan[1];
2258 solution.isSolution = true;
2259 } else {
2260 solution.isSolution = false;
2261 }
2262 return solution;
2263 }
2264
2265 public void prepareChildForDrag(View child) {
2266 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002267 }
2268
Adam Cohen19f37922012-03-21 11:59:11 -07002269 /* This seems like it should be obvious and straight-forward, but when the direction vector
2270 needs to match with the notion of the dragView pushing other views, we have to employ
2271 a slightly more subtle notion of the direction vector. The question is what two points is
2272 the vector between? The center of the dragView and its desired destination? Not quite, as
2273 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2274 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2275 or right, which helps make pushing feel right.
2276 */
2277 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2278 int spanY, View dragView, int[] resultDirection) {
2279 int[] targetDestination = new int[2];
2280
2281 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2282 Rect dragRect = new Rect();
2283 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2284 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2285
2286 Rect dropRegionRect = new Rect();
2287 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2288 dragView, dropRegionRect, mIntersectingViews);
2289
2290 int dropRegionSpanX = dropRegionRect.width();
2291 int dropRegionSpanY = dropRegionRect.height();
2292
2293 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2294 dropRegionRect.height(), dropRegionRect);
2295
2296 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2297 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2298
2299 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2300 deltaX = 0;
2301 }
2302 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2303 deltaY = 0;
2304 }
2305
2306 if (deltaX == 0 && deltaY == 0) {
2307 // No idea what to do, give a random direction.
2308 resultDirection[0] = 1;
2309 resultDirection[1] = 0;
2310 } else {
2311 computeDirectionVector(deltaX, deltaY, resultDirection);
2312 }
2313 }
2314
2315 // For a given cell and span, fetch the set of views intersecting the region.
2316 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2317 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2318 if (boundingRect != null) {
2319 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2320 }
2321 intersectingViews.clear();
2322 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2323 Rect r1 = new Rect();
2324 final int count = mShortcutsAndWidgets.getChildCount();
2325 for (int i = 0; i < count; i++) {
2326 View child = mShortcutsAndWidgets.getChildAt(i);
2327 if (child == dragView) continue;
2328 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2329 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2330 if (Rect.intersects(r0, r1)) {
2331 mIntersectingViews.add(child);
2332 if (boundingRect != null) {
2333 boundingRect.union(r1);
2334 }
2335 }
2336 }
2337 }
2338
2339 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2340 View dragView, int[] result) {
2341 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2342 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2343 mIntersectingViews);
2344 return !mIntersectingViews.isEmpty();
2345 }
2346
2347 void revertTempState() {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002348 completeAndClearReorderPreviewAnimations();
2349 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) {
2350 final int count = mShortcutsAndWidgets.getChildCount();
2351 for (int i = 0; i < count; i++) {
2352 View child = mShortcutsAndWidgets.getChildAt(i);
2353 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2354 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2355 lp.tmpCellX = lp.cellX;
2356 lp.tmpCellY = lp.cellY;
2357 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2358 0, false, false);
2359 }
Adam Cohen19f37922012-03-21 11:59:11 -07002360 }
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002361 setItemPlacementDirty(false);
Adam Cohen19f37922012-03-21 11:59:11 -07002362 }
Adam Cohen19f37922012-03-21 11:59:11 -07002363 }
2364
Adam Cohenbebf0422012-04-11 18:06:28 -07002365 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2366 View dragView, int[] direction, boolean commit) {
2367 int[] pixelXY = new int[2];
2368 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2369
2370 // First we determine if things have moved enough to cause a different layout
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002371 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY,
Adam Cohenbebf0422012-04-11 18:06:28 -07002372 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2373
2374 setUseTempCoords(true);
2375 if (swapSolution != null && swapSolution.isSolution) {
2376 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2377 // committing anything or animating anything as we just want to determine if a solution
2378 // exists
2379 copySolutionToTempState(swapSolution, dragView);
2380 setItemPlacementDirty(true);
2381 animateItemsToSolution(swapSolution, dragView, commit);
2382
2383 if (commit) {
2384 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002385 completeAndClearReorderPreviewAnimations();
Adam Cohenbebf0422012-04-11 18:06:28 -07002386 setItemPlacementDirty(false);
2387 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002388 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView,
2389 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohenbebf0422012-04-11 18:06:28 -07002390 }
2391 mShortcutsAndWidgets.requestLayout();
2392 }
2393 return swapSolution.isSolution;
2394 }
2395
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002396 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002397 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002398 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002399 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002400
2401 if (resultSpan == null) {
2402 resultSpan = new int[2];
2403 }
2404
Adam Cohen19f37922012-03-21 11:59:11 -07002405 // When we are checking drop validity or actually dropping, we don't recompute the
2406 // direction vector, since we want the solution to match the preview, and it's possible
2407 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002408 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2409 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002410 mDirectionVector[0] = mPreviousReorderDirection[0];
2411 mDirectionVector[1] = mPreviousReorderDirection[1];
2412 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002413 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2414 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2415 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002416 }
2417 } else {
2418 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2419 mPreviousReorderDirection[0] = mDirectionVector[0];
2420 mPreviousReorderDirection[1] = mDirectionVector[1];
2421 }
2422
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002423 // Find a solution involving pushing / displacing any items in the way
2424 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002425 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2426
2427 // We attempt the approach which doesn't shuffle views at all
2428 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2429 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2430
2431 ItemConfiguration finalSolution = null;
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002432
2433 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
2434 // favor a solution in which the item is not resized, but
Adam Cohen482ed822012-03-02 14:15:13 -08002435 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2436 finalSolution = swapSolution;
2437 } else if (noShuffleSolution.isSolution) {
2438 finalSolution = noShuffleSolution;
2439 }
2440
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002441 if (mode == MODE_SHOW_REORDER_HINT) {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002442 if (finalSolution != null) {
Adam Cohenfe692872013-12-11 14:47:23 -08002443 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0,
2444 ReorderPreviewAnimation.MODE_HINT);
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002445 result[0] = finalSolution.dragViewX;
2446 result[1] = finalSolution.dragViewY;
2447 resultSpan[0] = finalSolution.dragViewSpanX;
2448 resultSpan[1] = finalSolution.dragViewSpanY;
2449 } else {
2450 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2451 }
2452 return result;
2453 }
2454
Adam Cohen482ed822012-03-02 14:15:13 -08002455 boolean foundSolution = true;
2456 if (!DESTRUCTIVE_REORDER) {
2457 setUseTempCoords(true);
2458 }
2459
2460 if (finalSolution != null) {
2461 result[0] = finalSolution.dragViewX;
2462 result[1] = finalSolution.dragViewY;
2463 resultSpan[0] = finalSolution.dragViewSpanX;
2464 resultSpan[1] = finalSolution.dragViewSpanY;
2465
2466 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2467 // committing anything or animating anything as we just want to determine if a solution
2468 // exists
2469 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2470 if (!DESTRUCTIVE_REORDER) {
2471 copySolutionToTempState(finalSolution, dragView);
2472 }
2473 setItemPlacementDirty(true);
2474 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2475
Adam Cohen19f37922012-03-21 11:59:11 -07002476 if (!DESTRUCTIVE_REORDER &&
2477 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002478 commitTempPlacement();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002479 completeAndClearReorderPreviewAnimations();
Adam Cohen19f37922012-03-21 11:59:11 -07002480 setItemPlacementDirty(false);
2481 } else {
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002482 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView,
2483 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW);
Adam Cohen482ed822012-03-02 14:15:13 -08002484 }
2485 }
2486 } else {
2487 foundSolution = false;
2488 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2489 }
2490
2491 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2492 setUseTempCoords(false);
2493 }
Adam Cohen482ed822012-03-02 14:15:13 -08002494
Michael Jurkaa52570f2012-03-20 03:18:20 -07002495 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002496 return result;
2497 }
2498
Adam Cohen19f37922012-03-21 11:59:11 -07002499 void setItemPlacementDirty(boolean dirty) {
2500 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002501 }
Adam Cohen19f37922012-03-21 11:59:11 -07002502 boolean isItemPlacementDirty() {
2503 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002504 }
2505
Adam Cohen091440a2015-03-18 14:16:05 -07002506 @Thunk class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002507 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002508 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2509 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohenfa3c58f2013-12-06 16:10:55 -08002510 ArrayList<View> intersectingViews;
Adam Cohen482ed822012-03-02 14:15:13 -08002511 boolean isSolution = false;
2512 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2513
Adam Cohenf3900c22012-11-16 18:28:11 -08002514 void save() {
2515 // Copy current state into savedMap
2516 for (View v: map.keySet()) {
2517 map.get(v).copy(savedMap.get(v));
2518 }
2519 }
2520
2521 void restore() {
2522 // Restore current state from savedMap
2523 for (View v: savedMap.keySet()) {
2524 savedMap.get(v).copy(map.get(v));
2525 }
2526 }
2527
2528 void add(View v, CellAndSpan cs) {
2529 map.put(v, cs);
2530 savedMap.put(v, new CellAndSpan());
2531 sortedViews.add(v);
2532 }
2533
Adam Cohen482ed822012-03-02 14:15:13 -08002534 int area() {
2535 return dragViewSpanX * dragViewSpanY;
2536 }
Adam Cohen8baab352012-03-20 17:39:21 -07002537 }
2538
2539 private class CellAndSpan {
2540 int x, y;
2541 int spanX, spanY;
2542
Adam Cohenf3900c22012-11-16 18:28:11 -08002543 public CellAndSpan() {
2544 }
2545
2546 public void copy(CellAndSpan copy) {
2547 copy.x = x;
2548 copy.y = y;
2549 copy.spanX = spanX;
2550 copy.spanY = spanY;
2551 }
2552
Adam Cohen8baab352012-03-20 17:39:21 -07002553 public CellAndSpan(int x, int y, int spanX, int spanY) {
2554 this.x = x;
2555 this.y = y;
2556 this.spanX = spanX;
2557 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002558 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002559
2560 public String toString() {
2561 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2562 }
2563
Adam Cohen482ed822012-03-02 14:15:13 -08002564 }
2565
Adam Cohendf035382011-04-11 17:22:04 -07002566 /**
Adam Cohendf035382011-04-11 17:22:04 -07002567 * Find a starting cell position that will fit the given bounds nearest the requested
2568 * cell location. Uses Euclidean distance to score multiple vacant areas.
2569 *
2570 * @param pixelX The X location at which you want to search for a vacant area.
2571 * @param pixelY The Y location at which you want to search for a vacant area.
2572 * @param spanX Horizontal span of the object.
2573 * @param spanY Vertical span of the object.
2574 * @param ignoreView Considers space occupied by this view as unoccupied
2575 * @param result Previously returned value to possibly recycle.
2576 * @return The X, Y cell of a vacant area that can contain this object,
2577 * nearest the requested location.
2578 */
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002579 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2580 return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null);
Adam Cohendf035382011-04-11 17:22:04 -07002581 }
2582
Michael Jurka0280c3b2010-09-17 15:00:07 -07002583 boolean existsEmptyCell() {
2584 return findCellForSpan(null, 1, 1);
2585 }
2586
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002587 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002588 * Finds the upper-left coordinate of the first rectangle in the grid that can
2589 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2590 * then this method will only return coordinates for rectangles that contain the cell
2591 * (intersectX, intersectY)
2592 *
2593 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2594 * can be found.
2595 * @param spanX The horizontal span of the cell we want to find.
2596 * @param spanY The vertical span of the cell we want to find.
2597 *
2598 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002599 */
Hyunyoung Song3f471442015-04-08 19:01:34 -07002600 public boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Michael Jurka28750fb2010-09-24 17:43:49 -07002601 boolean foundCell = false;
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002602 final int endX = mCountX - (spanX - 1);
2603 final int endY = mCountY - (spanY - 1);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002604
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002605 for (int y = 0; y < endY && !foundCell; y++) {
2606 inner:
2607 for (int x = 0; x < endX; x++) {
2608 for (int i = 0; i < spanX; i++) {
2609 for (int j = 0; j < spanY; j++) {
2610 if (mOccupied[x + i][y + j]) {
2611 // small optimization: we can skip to after the column we just found
2612 // an occupied cell
2613 x += i;
2614 continue inner;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002615 }
2616 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002617 }
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002618 if (cellXY != null) {
2619 cellXY[0] = x;
2620 cellXY[1] = y;
2621 }
2622 foundCell = true;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002623 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002624 }
2625 }
2626
Michael Jurka28750fb2010-09-24 17:43:49 -07002627 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002628 }
2629
2630 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002631 * A drag event has begun over this layout.
2632 * It may have begun over this layout (in which case onDragChild is called first),
2633 * or it may have begun on another layout.
2634 */
2635 void onDragEnter() {
Winson Chungc07918d2011-07-01 15:35:26 -07002636 mDragging = true;
2637 }
2638
2639 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002640 * Called when drag has left this CellLayout or has been completed (successfully or not)
2641 */
2642 void onDragExit() {
Joe Onorato4be866d2010-10-10 11:26:02 -07002643 // This can actually be called when we aren't in a drag, e.g. when adding a new
2644 // item to this layout via the customize drawer.
2645 // Guard against that case.
2646 if (mDragging) {
2647 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002648 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002649
2650 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002651 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002652 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2653 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002654 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002655 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002656 }
2657
2658 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002659 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002660 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002661 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002662 *
2663 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002664 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002665 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002666 if (child != null) {
2667 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002668 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002669 child.requestLayout();
Tony Wickham1cdb6d02015-09-17 11:08:27 -07002670 markCellsAsOccupiedForView(child);
Romain Guyd94533d2009-08-17 10:01:15 -07002671 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002672 }
2673
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002674 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002675 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002676 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002677 * @param cellX X coordinate of upper left corner expressed as a cell position
2678 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002679 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002680 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002681 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002682 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002683 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002684 final int cellWidth = mCellWidth;
2685 final int cellHeight = mCellHeight;
2686 final int widthGap = mWidthGap;
2687 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002688
Winson Chung4b825dcd2011-06-19 12:41:22 -07002689 final int hStartPadding = getPaddingLeft();
2690 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002691
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002692 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2693 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2694
2695 int x = hStartPadding + cellX * (cellWidth + widthGap);
2696 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002697
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002698 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002699 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002700
Michael Jurka0280c3b2010-09-17 15:00:07 -07002701 private void clearOccupiedCells() {
2702 for (int x = 0; x < mCountX; x++) {
2703 for (int y = 0; y < mCountY; y++) {
2704 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002705 }
2706 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002707 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002708
Adam Cohend4844c32011-02-18 19:25:06 -08002709 public void markCellsAsOccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002710 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002711 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002712 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002713 }
2714
Adam Cohend4844c32011-02-18 19:25:06 -08002715 public void markCellsAsUnoccupiedForView(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002716 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002717 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Sunny Goyalf7a29e82015-04-24 15:20:43 -07002718 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, mOccupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002719 }
2720
Adam Cohen482ed822012-03-02 14:15:13 -08002721 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2722 boolean value) {
2723 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002724 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2725 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002726 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002727 }
2728 }
2729 }
2730
Adam Cohen2801caf2011-05-13 20:57:39 -07002731 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002732 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002733 (Math.max((mCountX - 1), 0) * mWidthGap);
2734 }
2735
2736 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002737 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002738 (Math.max((mCountY - 1), 0) * mHeightGap);
2739 }
2740
Michael Jurka66d72172011-04-12 16:29:25 -07002741 public boolean isOccupied(int x, int y) {
2742 if (x < mCountX && y < mCountY) {
2743 return mOccupied[x][y];
2744 } else {
2745 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2746 }
2747 }
2748
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002749 @Override
2750 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2751 return new CellLayout.LayoutParams(getContext(), attrs);
2752 }
2753
2754 @Override
2755 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2756 return p instanceof CellLayout.LayoutParams;
2757 }
2758
2759 @Override
2760 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2761 return new CellLayout.LayoutParams(p);
2762 }
2763
2764 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2765 /**
2766 * Horizontal location of the item in the grid.
2767 */
2768 @ViewDebug.ExportedProperty
2769 public int cellX;
2770
2771 /**
2772 * Vertical location of the item in the grid.
2773 */
2774 @ViewDebug.ExportedProperty
2775 public int cellY;
2776
2777 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002778 * Temporary horizontal location of the item in the grid during reorder
2779 */
2780 public int tmpCellX;
2781
2782 /**
2783 * Temporary vertical location of the item in the grid during reorder
2784 */
2785 public int tmpCellY;
2786
2787 /**
2788 * Indicates that the temporary coordinates should be used to layout the items
2789 */
2790 public boolean useTmpCoords;
2791
2792 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002793 * Number of cells spanned horizontally by the item.
2794 */
2795 @ViewDebug.ExportedProperty
2796 public int cellHSpan;
2797
2798 /**
2799 * Number of cells spanned vertically by the item.
2800 */
2801 @ViewDebug.ExportedProperty
2802 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002803
Adam Cohen1b607ed2011-03-03 17:26:50 -08002804 /**
2805 * Indicates whether the item will set its x, y, width and height parameters freely,
2806 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2807 */
Adam Cohend4844c32011-02-18 19:25:06 -08002808 public boolean isLockedToGrid = true;
2809
Adam Cohen482ed822012-03-02 14:15:13 -08002810 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07002811 * Indicates that this item should use the full extents of its parent.
2812 */
2813 public boolean isFullscreen = false;
2814
2815 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002816 * Indicates whether this item can be reordered. Always true except in the case of the
2817 * the AllApps button.
2818 */
2819 public boolean canReorder = true;
2820
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002821 // X coordinate of the view in the layout.
2822 @ViewDebug.ExportedProperty
2823 int x;
2824 // Y coordinate of the view in the layout.
2825 @ViewDebug.ExportedProperty
2826 int y;
2827
Romain Guy84f296c2009-11-04 15:00:44 -08002828 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002829
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002830 public LayoutParams(Context c, AttributeSet attrs) {
2831 super(c, attrs);
2832 cellHSpan = 1;
2833 cellVSpan = 1;
2834 }
2835
2836 public LayoutParams(ViewGroup.LayoutParams source) {
2837 super(source);
2838 cellHSpan = 1;
2839 cellVSpan = 1;
2840 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002841
2842 public LayoutParams(LayoutParams source) {
2843 super(source);
2844 this.cellX = source.cellX;
2845 this.cellY = source.cellY;
2846 this.cellHSpan = source.cellHSpan;
2847 this.cellVSpan = source.cellVSpan;
2848 }
2849
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002850 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002851 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002852 this.cellX = cellX;
2853 this.cellY = cellY;
2854 this.cellHSpan = cellHSpan;
2855 this.cellVSpan = cellVSpan;
2856 }
2857
Adam Cohen2374abf2013-04-16 14:56:57 -07002858 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
2859 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08002860 if (isLockedToGrid) {
2861 final int myCellHSpan = cellHSpan;
2862 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07002863 int myCellX = useTmpCoords ? tmpCellX : cellX;
2864 int myCellY = useTmpCoords ? tmpCellY : cellY;
2865
2866 if (invertHorizontally) {
2867 myCellX = colCount - myCellX - cellHSpan;
2868 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08002869
Adam Cohend4844c32011-02-18 19:25:06 -08002870 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2871 leftMargin - rightMargin;
2872 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2873 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002874 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2875 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002876 }
2877 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002878
Winson Chungaafa03c2010-06-11 17:34:16 -07002879 public String toString() {
2880 return "(" + this.cellX + ", " + this.cellY + ")";
2881 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002882
2883 public void setWidth(int width) {
2884 this.width = width;
2885 }
2886
2887 public int getWidth() {
2888 return width;
2889 }
2890
2891 public void setHeight(int height) {
2892 this.height = height;
2893 }
2894
2895 public int getHeight() {
2896 return height;
2897 }
2898
2899 public void setX(int x) {
2900 this.x = x;
2901 }
2902
2903 public int getX() {
2904 return x;
2905 }
2906
2907 public void setY(int y) {
2908 this.y = y;
2909 }
2910
2911 public int getY() {
2912 return y;
2913 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002914 }
2915
Michael Jurka0280c3b2010-09-17 15:00:07 -07002916 // This class stores info for two purposes:
2917 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2918 // its spanX, spanY, and the screen it is on
2919 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2920 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2921 // the CellLayout that was long clicked
Sunny Goyal83a8f042015-05-19 12:52:12 -07002922 public static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002923 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07002924 int cellX = -1;
2925 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002926 int spanX;
2927 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07002928 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07002929 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002930
Sunny Goyal83a8f042015-05-19 12:52:12 -07002931 public CellInfo(View v, ItemInfo info) {
Adam Cohene0aaa0d2014-05-12 12:44:22 -07002932 cell = v;
2933 cellX = info.cellX;
2934 cellY = info.cellY;
2935 spanX = info.spanX;
2936 spanY = info.spanY;
2937 screenId = info.screenId;
2938 container = info.container;
2939 }
2940
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002941 @Override
2942 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002943 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2944 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002945 }
2946 }
Michael Jurkad771c962011-08-09 15:00:48 -07002947
Sunny Goyala9116722015-04-29 13:55:58 -07002948 public boolean findVacantCell(int spanX, int spanY, int[] outXY) {
2949 return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied);
2950 }
Sunny Goyal9ca9c132015-04-29 14:57:22 -07002951
2952 public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
2953 int x2 = x + spanX - 1;
2954 int y2 = y + spanY - 1;
2955 if (x < 0 || y < 0 || x2 >= mCountX || y2 >= mCountY) {
2956 return false;
2957 }
2958 for (int i = x; i <= x2; i++) {
2959 for (int j = y; j <= y2; j++) {
2960 if (mOccupied[i][j]) {
2961 return false;
2962 }
2963 }
2964 }
2965
2966 return true;
2967 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002968}