blob: dafb79ffcc1ee3bc38cb118141e9ce6b0181a717 [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;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080025import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040026import android.content.res.Resources;
Winson Chungaafa03c2010-06-11 17:34:16 -070027import android.content.res.TypedArray;
Joe Onorato4be866d2010-10-10 11:26:02 -070028import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070029import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080030import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070031import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070032import android.graphics.Point;
Adam Cohenb5ba0972011-09-07 18:02:31 -070033import android.graphics.PorterDuff;
34import android.graphics.PorterDuffXfermode;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080035import android.graphics.Rect;
Adam Cohen482ed822012-03-02 14:15:13 -080036import android.graphics.drawable.ColorDrawable;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070037import android.graphics.drawable.Drawable;
Adam Cohenb5ba0972011-09-07 18:02:31 -070038import android.graphics.drawable.NinePatchDrawable;
Adam Cohen1462de32012-07-24 22:34:36 -070039import android.os.Parcelable;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080040import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070041import android.util.Log;
Adam Cohen1462de32012-07-24 22:34:36 -070042import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080043import android.view.MotionEvent;
44import android.view.View;
45import android.view.ViewDebug;
46import android.view.ViewGroup;
Winson Chungaafa03c2010-06-11 17:34:16 -070047import android.view.animation.Animation;
Winson Chung150fbab2010-09-29 17:14:26 -070048import android.view.animation.DecelerateInterpolator;
Winson Chungaafa03c2010-06-11 17:34:16 -070049import android.view.animation.LayoutAnimationController;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080050
Daniel Sandler325dc232013-06-05 22:57:57 -040051import com.android.launcher3.R;
52import com.android.launcher3.FolderIcon.FolderRingAnimator;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070053
Adam Cohen69ce2e52011-07-03 19:25:21 -070054import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070055import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080056import java.util.Collections;
57import java.util.Comparator;
Adam Cohenbfbfd262011-06-13 16:55:12 -070058import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080059import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070060
Michael Jurkabdb5c532011-02-01 15:05:06 -080061public class CellLayout extends ViewGroup {
Winson Chungaafa03c2010-06-11 17:34:16 -070062 static final String TAG = "CellLayout";
63
Adam Cohen2acce882012-03-28 19:03:19 -070064 private Launcher mLauncher;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080065 private int mCellWidth;
66 private int mCellHeight;
Winson Chung11a1a532013-09-13 11:14:45 -070067 private int mFixedCellWidth;
68 private int mFixedCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070069
Adam Cohend22015c2010-07-26 22:02:18 -070070 private int mCountX;
71 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080072
Adam Cohen234c4cd2011-07-17 21:03:04 -070073 private int mOriginalWidthGap;
74 private int mOriginalHeightGap;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080075 private int mWidthGap;
76 private int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070077 private int mMaxGap;
Adam Cohenebea84d2011-11-09 17:20:41 -080078 private boolean mScrollingTransformsDirty = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080079
80 private final Rect mRect = new Rect();
81 private final CellInfo mCellInfo = new CellInfo();
Winson Chungaafa03c2010-06-11 17:34:16 -070082
Patrick Dubroyde7658b2010-09-27 11:15:43 -070083 // These are temporary variables to prevent having to allocate a new object just to
84 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Winson Chung0be025d2011-05-23 17:45:09 -070085 private final int[] mTmpXY = new int[2];
Patrick Dubroyde7658b2010-09-27 11:15:43 -070086 private final int[] mTmpPoint = new int[2];
Adam Cohen69ce2e52011-07-03 19:25:21 -070087 int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070088
The Android Open Source Project31dd5032009-03-03 19:32:27 -080089 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -080090 boolean[][] mTmpOccupied;
Michael Jurkad771c962011-08-09 15:00:48 -070091 private boolean mLastDownOnOccupiedCell = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080092
Michael Jurkadee05892010-07-27 10:01:56 -070093 private OnTouchListener mInterceptTouchListener;
94
Adam Cohen69ce2e52011-07-03 19:25:21 -070095 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -070096 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -070097
Adam Cohen02dcfcc2013-10-01 12:37:33 -070098 private float FOREGROUND_ALPHA_DAMPER = 0.65f;
Adam Cohenb5ba0972011-09-07 18:02:31 -070099 private int mForegroundAlpha = 0;
Michael Jurka5f1c5092010-09-03 14:15:02 -0700100 private float mBackgroundAlpha;
Adam Cohen1b0aaac2010-10-28 11:11:18 -0700101 private float mBackgroundAlphaMultiplier = 1.0f;
Adam Cohenf34bab52010-09-30 14:11:56 -0700102
Michael Jurka33945b22010-12-21 18:19:38 -0800103 private Drawable mNormalBackground;
Michael Jurka33945b22010-12-21 18:19:38 -0800104 private Drawable mActiveGlowBackground;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700105 private Drawable mOverScrollForegroundDrawable;
106 private Drawable mOverScrollLeft;
107 private Drawable mOverScrollRight;
Michael Jurka18014792010-10-14 09:01:34 -0700108 private Rect mBackgroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700109 private Rect mForegroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700110 private int mForegroundPadding;
Patrick Dubroy1262e362010-10-06 15:49:50 -0700111
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700112 // These values allow a fixed measurement to be set on the CellLayout.
113 private int mFixedWidth = -1;
114 private int mFixedHeight = -1;
115
Michael Jurka33945b22010-12-21 18:19:38 -0800116 // If we're actively dragging something over this screen, mIsDragOverlapping is true
117 private boolean mIsDragOverlapping = false;
Adam Cohendedbd962013-07-11 14:21:49 -0700118 boolean mUseActiveGlowBackground = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700119
Winson Chung150fbab2010-09-29 17:14:26 -0700120 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700121 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohend41fbf52012-02-16 23:53:59 -0800122 private Rect[] mDragOutlines = new Rect[4];
Chet Haase472b2812010-10-14 07:02:04 -0700123 private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700124 private InterruptibleInOutAnimator[] mDragOutlineAnims =
125 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700126
127 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700128 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700129 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700130
Patrick Dubroy96864c32011-03-10 17:17:23 -0800131 private BubbleTextView mPressedOrFocusedIcon;
132
Adam Cohen482ed822012-03-02 14:15:13 -0800133 private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
134 HashMap<CellLayout.LayoutParams, Animator>();
Adam Cohen19f37922012-03-21 11:59:11 -0700135 private HashMap<View, ReorderHintAnimation>
136 mShakeAnimators = new HashMap<View, ReorderHintAnimation>();
137
138 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700139
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700140 // When a drag operation is in progress, holds the nearest cell to the touch point
141 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800142
Joe Onorato4be866d2010-10-10 11:26:02 -0700143 private boolean mDragging = false;
144
Patrick Dubroyce34a972010-10-19 10:34:32 -0700145 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700146 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700147
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800148 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700149 private float mHotseatScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800150
Adam Cohen482ed822012-03-02 14:15:13 -0800151 public static final int MODE_DRAG_OVER = 0;
152 public static final int MODE_ON_DROP = 1;
153 public static final int MODE_ON_DROP_EXTERNAL = 2;
154 public static final int MODE_ACCEPT_DROP = 3;
Adam Cohen19f37922012-03-21 11:59:11 -0700155 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800156 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
157
Adam Cohena897f392012-04-27 18:12:05 -0700158 static final int LANDSCAPE = 0;
159 static final int PORTRAIT = 1;
160
Adam Cohen7bdfc972012-05-22 16:50:35 -0700161 private static final float REORDER_HINT_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700162 private static final int REORDER_ANIMATION_DURATION = 150;
163 private float mReorderHintAnimationMagnitude;
164
Adam Cohen482ed822012-03-02 14:15:13 -0800165 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
166 private Rect mOccupiedRect = new Rect();
167 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700168 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700169 private static final int INVALID_DIRECTION = -100;
Adam Cohenc6cc61d2012-04-04 12:47:08 -0700170 private DropTarget.DragEnforcer mDragEnforcer;
Adam Cohen482ed822012-03-02 14:15:13 -0800171
Winson Chung3a6e7f32013-10-09 15:50:52 -0700172 private Rect mTempRect = new Rect();
173
Romain Guy8a0bff52012-05-06 13:14:33 -0700174 private final static PorterDuffXfermode sAddBlendMode =
175 new PorterDuffXfermode(PorterDuff.Mode.ADD);
Michael Jurkaca993832012-06-29 15:17:04 -0700176 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700177
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800178 public CellLayout(Context context) {
179 this(context, null);
180 }
181
182 public CellLayout(Context context, AttributeSet attrs) {
183 this(context, attrs, 0);
184 }
185
186 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
187 super(context, attrs, defStyle);
Michael Jurka8b805b12012-04-18 14:23:14 -0700188 mDragEnforcer = new DropTarget.DragEnforcer(context);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700189
190 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
191 // the user where a dragged item will land when dropped.
192 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800193 setClipToPadding(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700194 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700195
Winson Chung892c74d2013-08-22 16:15:50 -0700196 LauncherAppState app = LauncherAppState.getInstance();
197 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800198 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
199
Winson Chung11a1a532013-09-13 11:14:45 -0700200 mCellWidth = mCellHeight = -1;
201 mFixedCellHeight = mFixedCellHeight = -1;
Winson Chung5f8afe62013-08-12 16:19:28 -0700202 mWidthGap = mOriginalWidthGap = 0;
203 mHeightGap = mOriginalHeightGap = 0;
204 mMaxGap = Integer.MAX_VALUE;
Winson Chung892c74d2013-08-22 16:15:50 -0700205 mCountX = (int) grid.numColumns;
206 mCountY = (int) grid.numRows;
Michael Jurka0280c3b2010-09-17 15:00:07 -0700207 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800208 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700209 mPreviousReorderDirection[0] = INVALID_DIRECTION;
210 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800211
212 a.recycle();
213
214 setAlwaysDrawnWithCacheEnabled(false);
215
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700216 final Resources res = getResources();
Winson Chung5f8afe62013-08-12 16:19:28 -0700217 mHotseatScale = (float) grid.hotseatIconSize / grid.iconSize;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700218
Adam Cohen410f3cd2013-09-22 12:09:32 -0700219 mNormalBackground = res.getDrawable(R.drawable.screenpanel);
220 mActiveGlowBackground = res.getDrawable(R.drawable.screenpanel_hover);
Michael Jurka33945b22010-12-21 18:19:38 -0800221
Adam Cohenb5ba0972011-09-07 18:02:31 -0700222 mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
223 mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
224 mForegroundPadding =
225 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
Michael Jurka33945b22010-12-21 18:19:38 -0800226
Adam Cohen19f37922012-03-21 11:59:11 -0700227 mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE *
Winson Chung5f8afe62013-08-12 16:19:28 -0700228 grid.iconSizePx);
Adam Cohen19f37922012-03-21 11:59:11 -0700229
Winson Chungb26f3d62011-06-02 10:49:29 -0700230 mNormalBackground.setFilterBitmap(true);
Winson Chungb26f3d62011-06-02 10:49:29 -0700231 mActiveGlowBackground.setFilterBitmap(true);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700232
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700233 // Initialize the data structures used for the drag visualization.
Patrick Dubroyce34a972010-10-19 10:34:32 -0700234 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Winson Chungb8c69f32011-10-19 21:36:08 -0700235 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700236 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800237 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700238 }
239
240 // When dragging things around the home screens, we show a green outline of
241 // where the item will land. The outlines gradually fade out, leaving a trail
242 // behind the drag path.
243 // Set up all the animations that are used to implement this fading.
244 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700245 final float fromAlphaValue = 0;
246 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700247
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700248 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700249
250 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700251 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100252 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700253 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700254 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700255 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700256 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700257 final Bitmap outline = (Bitmap)anim.getTag();
258
259 // If an animation is started and then stopped very quickly, we can still
260 // get spurious updates we've cleared the tag. Guard against this.
261 if (outline == null) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700262 @SuppressWarnings("all") // suppress dead code warning
263 final boolean debug = false;
264 if (debug) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700265 Object val = animation.getAnimatedValue();
266 Log.d(TAG, "anim " + thisIndex + " update: " + val +
267 ", isStopped " + anim.isStopped());
268 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700269 // Try to prevent it from continuing to run
270 animation.cancel();
271 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700272 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800273 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700274 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700275 }
276 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700277 // The animation holds a reference to the drag outline bitmap as long is it's
278 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700279 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700280 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700281 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700282 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700283 anim.setTag(null);
284 }
285 }
286 });
287 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700288 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700289
Michael Jurka18014792010-10-14 09:01:34 -0700290 mBackgroundRect = new Rect();
Adam Cohenb5ba0972011-09-07 18:02:31 -0700291 mForegroundRect = new Rect();
Michael Jurkabea15192010-11-17 12:33:46 -0800292
Michael Jurkaa52570f2012-03-20 03:18:20 -0700293 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
Adam Cohen2374abf2013-04-16 14:56:57 -0700294 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700295 mCountX, mCountY);
Adam Cohen2374abf2013-04-16 14:56:57 -0700296
Michael Jurkaa52570f2012-03-20 03:18:20 -0700297 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700298 }
299
Chris Craik01f2d7f2013-10-01 14:41:56 -0700300 public void enableHardwareLayer(boolean hasLayer) {
301 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700302 }
303
304 public void buildHardwareLayer() {
305 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700306 }
307
Adam Cohen307fe232012-08-16 17:55:58 -0700308 public float getChildrenScale() {
309 return mIsHotseat ? mHotseatScale : 1.0f;
310 }
311
Winson Chung5f8afe62013-08-12 16:19:28 -0700312 public void setCellDimensions(int width, int height) {
Winson Chung11a1a532013-09-13 11:14:45 -0700313 mFixedCellWidth = mCellWidth = width;
314 mFixedCellHeight = mCellHeight = height;
Winson Chung5f8afe62013-08-12 16:19:28 -0700315 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
316 mCountX, mCountY);
317 }
318
Adam Cohen2801caf2011-05-13 20:57:39 -0700319 public void setGridSize(int x, int y) {
320 mCountX = x;
321 mCountY = y;
322 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800323 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700324 mTempRectStack.clear();
Adam Cohen2374abf2013-04-16 14:56:57 -0700325 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
Winson Chung5f8afe62013-08-12 16:19:28 -0700326 mCountX, mCountY);
Adam Cohen76fc0852011-06-17 13:26:23 -0700327 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700328 }
329
Adam Cohen2374abf2013-04-16 14:56:57 -0700330 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
331 public void setInvertIfRtl(boolean invert) {
332 mShortcutsAndWidgets.setInvertIfRtl(invert);
333 }
334
Patrick Dubroy96864c32011-03-10 17:17:23 -0800335 private void invalidateBubbleTextView(BubbleTextView icon) {
336 final int padding = icon.getPressedOrFocusedBackgroundPadding();
Winson Chung4b825dcd2011-06-19 12:41:22 -0700337 invalidate(icon.getLeft() + getPaddingLeft() - padding,
338 icon.getTop() + getPaddingTop() - padding,
339 icon.getRight() + getPaddingLeft() + padding,
340 icon.getBottom() + getPaddingTop() + padding);
Patrick Dubroy96864c32011-03-10 17:17:23 -0800341 }
342
Adam Cohenb5ba0972011-09-07 18:02:31 -0700343 void setOverScrollAmount(float r, boolean left) {
344 if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
345 mOverScrollForegroundDrawable = mOverScrollLeft;
346 } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
347 mOverScrollForegroundDrawable = mOverScrollRight;
348 }
349
Adam Cohen02dcfcc2013-10-01 12:37:33 -0700350 r *= FOREGROUND_ALPHA_DAMPER;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700351 mForegroundAlpha = (int) Math.round((r * 255));
352 mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
353 invalidate();
354 }
355
Patrick Dubroy96864c32011-03-10 17:17:23 -0800356 void setPressedOrFocusedIcon(BubbleTextView icon) {
357 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
358 // requires an expanded clip rect (due to the glow's blur radius)
359 BubbleTextView oldIcon = mPressedOrFocusedIcon;
360 mPressedOrFocusedIcon = icon;
361 if (oldIcon != null) {
362 invalidateBubbleTextView(oldIcon);
363 }
364 if (mPressedOrFocusedIcon != null) {
365 invalidateBubbleTextView(mPressedOrFocusedIcon);
366 }
367 }
368
Michael Jurka33945b22010-12-21 18:19:38 -0800369 void setIsDragOverlapping(boolean isDragOverlapping) {
370 if (mIsDragOverlapping != isDragOverlapping) {
371 mIsDragOverlapping = isDragOverlapping;
Adam Cohendedbd962013-07-11 14:21:49 -0700372 setUseActiveGlowBackground(mIsDragOverlapping);
Michael Jurka33945b22010-12-21 18:19:38 -0800373 invalidate();
374 }
375 }
376
Adam Cohendedbd962013-07-11 14:21:49 -0700377 void setUseActiveGlowBackground(boolean use) {
378 mUseActiveGlowBackground = use;
379 }
380
Michael Jurka33945b22010-12-21 18:19:38 -0800381 boolean getIsDragOverlapping() {
382 return mIsDragOverlapping;
383 }
384
Adam Cohenebea84d2011-11-09 17:20:41 -0800385 protected void setOverscrollTransformsDirty(boolean dirty) {
386 mScrollingTransformsDirty = dirty;
387 }
388
389 protected void resetOverscrollTransforms() {
390 if (mScrollingTransformsDirty) {
391 setOverscrollTransformsDirty(false);
392 setTranslationX(0);
393 setRotationY(0);
394 // It doesn't matter if we pass true or false here, the important thing is that we
395 // pass 0, which results in the overscroll drawable not being drawn any more.
396 setOverScrollAmount(0, false);
397 setPivotX(getMeasuredWidth() / 2);
398 setPivotY(getMeasuredHeight() / 2);
399 }
400 }
401
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700402 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700403 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700404 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
405 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
406 // When we're small, we are either drawn normally or in the "accepts drops" state (during
407 // a drag). However, we also drag the mini hover background *over* one of those two
408 // backgrounds
Winson Chungb26f3d62011-06-02 10:49:29 -0700409 if (mBackgroundAlpha > 0.0f) {
Adam Cohenf34bab52010-09-30 14:11:56 -0700410 Drawable bg;
Michael Jurka33945b22010-12-21 18:19:38 -0800411
Adam Cohendedbd962013-07-11 14:21:49 -0700412 if (mUseActiveGlowBackground) {
Michael Jurka33945b22010-12-21 18:19:38 -0800413 // In the mini case, we draw the active_glow bg *over* the active background
Michael Jurkabdf78552011-10-31 14:34:25 -0700414 bg = mActiveGlowBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700415 } else {
Michael Jurkabdf78552011-10-31 14:34:25 -0700416 bg = mNormalBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700417 }
Michael Jurka33945b22010-12-21 18:19:38 -0800418
419 bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
420 bg.setBounds(mBackgroundRect);
421 bg.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700422 }
Romain Guya6abce82009-11-10 02:54:41 -0800423
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700424 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700425 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700426 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700427 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800428 final Rect r = mDragOutlines[i];
Winson Chung3a6e7f32013-10-09 15:50:52 -0700429 mTempRect.set(r);
430 Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale());
Joe Onorato4be866d2010-10-10 11:26:02 -0700431 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700432 paint.setAlpha((int)(alpha + .5f));
Winson Chung3a6e7f32013-10-09 15:50:52 -0700433 canvas.drawBitmap(b, null, mTempRect, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700434 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700435 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800436
437 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
438 // requires an expanded clip rect (due to the glow's blur radius)
439 if (mPressedOrFocusedIcon != null) {
440 final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
441 final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
442 if (b != null) {
Winson Chung3a6e7f32013-10-09 15:50:52 -0700443 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
444 (mCountX * mCellWidth);
445 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
446 int top = getPaddingTop();
Patrick Dubroy96864c32011-03-10 17:17:23 -0800447 canvas.drawBitmap(b,
Winson Chung3a6e7f32013-10-09 15:50:52 -0700448 mPressedOrFocusedIcon.getLeft() + left - padding,
449 mPressedOrFocusedIcon.getTop() + top - padding,
Patrick Dubroy96864c32011-03-10 17:17:23 -0800450 null);
451 }
452 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700453
Adam Cohen482ed822012-03-02 14:15:13 -0800454 if (DEBUG_VISUALIZE_OCCUPIED) {
455 int[] pt = new int[2];
456 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700457 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800458 for (int i = 0; i < mCountX; i++) {
459 for (int j = 0; j < mCountY; j++) {
460 if (mOccupied[i][j]) {
461 cellToPoint(i, j, pt);
462 canvas.save();
463 canvas.translate(pt[0], pt[1]);
464 cd.draw(canvas);
465 canvas.restore();
466 }
467 }
468 }
469 }
470
Andrew Flynn850d2e72012-04-26 16:51:20 -0700471 int previewOffset = FolderRingAnimator.sPreviewSize;
472
Adam Cohen69ce2e52011-07-03 19:25:21 -0700473 // The folder outer / inner ring image(s)
Winson Chung5f8afe62013-08-12 16:19:28 -0700474 LauncherAppState app = LauncherAppState.getInstance();
475 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Adam Cohen69ce2e52011-07-03 19:25:21 -0700476 for (int i = 0; i < mFolderOuterRings.size(); i++) {
477 FolderRingAnimator fra = mFolderOuterRings.get(i);
478
Adam Cohen5108bc02013-09-20 17:04:51 -0700479 Drawable d;
480 int width, height;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700481 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700482 View child = getChildAt(fra.mCellX, fra.mCellY);
Adam Cohen558f1c22013-10-09 15:15:24 -0700483
Winson Chung89f97052013-09-20 11:32:26 -0700484 if (child != null) {
Adam Cohen558f1c22013-10-09 15:15:24 -0700485 int centerX = mTempLocation[0] + mCellWidth / 2;
486 int centerY = mTempLocation[1] + previewOffset / 2 +
487 child.getPaddingTop() + grid.folderBackgroundOffset;
488
Adam Cohen5108bc02013-09-20 17:04:51 -0700489 // Draw outer ring, if it exists
490 if (FolderIcon.HAS_OUTER_RING) {
491 d = FolderRingAnimator.sSharedOuterRingDrawable;
492 width = (int) (fra.getOuterRingSize() * getChildrenScale());
493 height = width;
494 canvas.save();
495 canvas.translate(centerX - width / 2, centerY - height / 2);
496 d.setBounds(0, 0, width, height);
497 d.draw(canvas);
498 canvas.restore();
499 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700500
Winson Chung89f97052013-09-20 11:32:26 -0700501 // Draw inner ring
502 d = FolderRingAnimator.sSharedInnerRingDrawable;
503 width = (int) (fra.getInnerRingSize() * getChildrenScale());
504 height = width;
Winson Chung89f97052013-09-20 11:32:26 -0700505 canvas.save();
506 canvas.translate(centerX - width / 2, centerY - width / 2);
507 d.setBounds(0, 0, width, height);
508 d.draw(canvas);
509 canvas.restore();
510 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700511 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700512
513 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
514 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
515 int width = d.getIntrinsicWidth();
516 int height = d.getIntrinsicHeight();
517
518 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
Winson Chung5f8afe62013-08-12 16:19:28 -0700519 View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]);
Winson Chung89f97052013-09-20 11:32:26 -0700520 if (child != null) {
521 int centerX = mTempLocation[0] + mCellWidth / 2;
522 int centerY = mTempLocation[1] + previewOffset / 2 +
523 child.getPaddingTop() + grid.folderBackgroundOffset;
Adam Cohenc51934b2011-07-26 21:07:43 -0700524
Winson Chung89f97052013-09-20 11:32:26 -0700525 canvas.save();
526 canvas.translate(centerX - width / 2, centerY - width / 2);
527 d.setBounds(0, 0, width, height);
528 d.draw(canvas);
529 canvas.restore();
530 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700531 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700532 }
533
Adam Cohenb5ba0972011-09-07 18:02:31 -0700534 @Override
535 protected void dispatchDraw(Canvas canvas) {
536 super.dispatchDraw(canvas);
537 if (mForegroundAlpha > 0) {
538 mOverScrollForegroundDrawable.setBounds(mForegroundRect);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700539 mOverScrollForegroundDrawable.draw(canvas);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700540 }
541 }
542
Adam Cohen69ce2e52011-07-03 19:25:21 -0700543 public void showFolderAccept(FolderRingAnimator fra) {
544 mFolderOuterRings.add(fra);
545 }
546
547 public void hideFolderAccept(FolderRingAnimator fra) {
548 if (mFolderOuterRings.contains(fra)) {
549 mFolderOuterRings.remove(fra);
550 }
551 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700552 }
553
Adam Cohenc51934b2011-07-26 21:07:43 -0700554 public void setFolderLeaveBehindCell(int x, int y) {
555 mFolderLeaveBehindCell[0] = x;
556 mFolderLeaveBehindCell[1] = y;
557 invalidate();
558 }
559
560 public void clearFolderLeaveBehind() {
561 mFolderLeaveBehindCell[0] = -1;
562 mFolderLeaveBehindCell[1] = -1;
563 invalidate();
564 }
565
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700566 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700567 public boolean shouldDelayChildPressedState() {
568 return false;
569 }
570
Adam Cohen1462de32012-07-24 22:34:36 -0700571 public void restoreInstanceState(SparseArray<Parcelable> states) {
572 dispatchRestoreInstanceState(states);
573 }
574
Michael Jurkae6235dd2011-10-04 15:02:05 -0700575 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700576 public void cancelLongPress() {
577 super.cancelLongPress();
578
579 // Cancel long press for all children
580 final int count = getChildCount();
581 for (int i = 0; i < count; i++) {
582 final View child = getChildAt(i);
583 child.cancelLongPress();
584 }
585 }
586
Michael Jurkadee05892010-07-27 10:01:56 -0700587 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
588 mInterceptTouchListener = listener;
589 }
590
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800591 int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700592 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800593 }
594
595 int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700596 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800597 }
598
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800599 public void setIsHotseat(boolean isHotseat) {
600 mIsHotseat = isHotseat;
Winson Chung5f8afe62013-08-12 16:19:28 -0700601 mShortcutsAndWidgets.setIsHotseat(isHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800602 }
603
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800604 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Andrew Flynn850d2e72012-04-26 16:51:20 -0700605 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700606 final LayoutParams lp = params;
607
Andrew Flynnde38e422012-05-08 11:22:15 -0700608 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800609 if (child instanceof BubbleTextView) {
610 BubbleTextView bubbleChild = (BubbleTextView) child;
Winson Chung5f8afe62013-08-12 16:19:28 -0700611 bubbleChild.setTextVisibility(!mIsHotseat);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800612 }
613
Adam Cohen307fe232012-08-16 17:55:58 -0700614 child.setScaleX(getChildrenScale());
615 child.setScaleY(getChildrenScale());
616
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800617 // Generate an id for each view, this assumes we have at most 256x256 cells
618 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700619 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700620 // If the horizontal or vertical span is set to -1, it is taken to
621 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700622 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
623 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800624
Winson Chungaafa03c2010-06-11 17:34:16 -0700625 child.setId(childId);
626
Michael Jurkaa52570f2012-03-20 03:18:20 -0700627 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700628
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700629 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700630
Winson Chungaafa03c2010-06-11 17:34:16 -0700631 return true;
632 }
633 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800634 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700635
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800636 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700637 public void removeAllViews() {
638 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700639 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700640 }
641
642 @Override
643 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700644 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700645 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700646 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700647 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700648 }
649
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700650 public void removeViewWithoutMarkingCells(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700651 mShortcutsAndWidgets.removeView(view);
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700652 }
653
Michael Jurka0280c3b2010-09-17 15:00:07 -0700654 @Override
655 public void removeView(View view) {
656 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700657 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700658 }
659
660 @Override
661 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700662 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
663 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700664 }
665
666 @Override
667 public void removeViewInLayout(View view) {
668 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700669 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700670 }
671
672 @Override
673 public void removeViews(int start, int count) {
674 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700675 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700676 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700677 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700678 }
679
680 @Override
681 public void removeViewsInLayout(int start, int count) {
682 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700683 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700684 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700685 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800686 }
687
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800688 @Override
689 protected void onAttachedToWindow() {
690 super.onAttachedToWindow();
Adam Cohendcd297f2013-06-18 13:13:40 -0700691 if (getParent() instanceof Workspace) {
692 Workspace workspace = (Workspace) getParent();
693 mCellInfo.screenId = workspace.getIdForScreen(this);
694 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800695 }
696
Michael Jurkaaf442092010-06-10 17:01:57 -0700697 public void setTagToCellInfoForPoint(int touchX, int touchY) {
698 final CellInfo cellInfo = mCellInfo;
Winson Chungeecf02d2012-03-02 17:14:58 -0800699 Rect frame = mRect;
Michael Jurka8b805b12012-04-18 14:23:14 -0700700 final int x = touchX + getScrollX();
701 final int y = touchY + getScrollY();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700702 final int count = mShortcutsAndWidgets.getChildCount();
Michael Jurkaaf442092010-06-10 17:01:57 -0700703
704 boolean found = false;
705 for (int i = count - 1; i >= 0; i--) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700706 final View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohend4844c32011-02-18 19:25:06 -0800707 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Michael Jurkaaf442092010-06-10 17:01:57 -0700708
Adam Cohen1b607ed2011-03-03 17:26:50 -0800709 if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
710 lp.isLockedToGrid) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700711 child.getHitRect(frame);
Winson Chung0be025d2011-05-23 17:45:09 -0700712
Winson Chungeecf02d2012-03-02 17:14:58 -0800713 float scale = child.getScaleX();
714 frame = new Rect(child.getLeft(), child.getTop(), child.getRight(),
715 child.getBottom());
Winson Chung0be025d2011-05-23 17:45:09 -0700716 // The child hit rect is relative to the CellLayoutChildren parent, so we need to
717 // offset that by this CellLayout's padding to test an (x,y) point that is relative
718 // to this view.
Michael Jurka8b805b12012-04-18 14:23:14 -0700719 frame.offset(getPaddingLeft(), getPaddingTop());
Winson Chungeecf02d2012-03-02 17:14:58 -0800720 frame.inset((int) (frame.width() * (1f - scale) / 2),
721 (int) (frame.height() * (1f - scale) / 2));
Winson Chung0be025d2011-05-23 17:45:09 -0700722
Michael Jurkaaf442092010-06-10 17:01:57 -0700723 if (frame.contains(x, y)) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700724 cellInfo.cell = child;
725 cellInfo.cellX = lp.cellX;
726 cellInfo.cellY = lp.cellY;
727 cellInfo.spanX = lp.cellHSpan;
728 cellInfo.spanY = lp.cellVSpan;
Michael Jurkaaf442092010-06-10 17:01:57 -0700729 found = true;
Michael Jurkaaf442092010-06-10 17:01:57 -0700730 break;
731 }
732 }
733 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700734
Michael Jurkad771c962011-08-09 15:00:48 -0700735 mLastDownOnOccupiedCell = found;
736
Michael Jurkaaf442092010-06-10 17:01:57 -0700737 if (!found) {
Winson Chung0be025d2011-05-23 17:45:09 -0700738 final int cellXY[] = mTmpXY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700739 pointToCellExact(x, y, cellXY);
740
Michael Jurkaaf442092010-06-10 17:01:57 -0700741 cellInfo.cell = null;
742 cellInfo.cellX = cellXY[0];
743 cellInfo.cellY = cellXY[1];
744 cellInfo.spanX = 1;
745 cellInfo.spanY = 1;
Michael Jurkaaf442092010-06-10 17:01:57 -0700746 }
747 setTag(cellInfo);
748 }
749
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800750 @Override
751 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Cohenc1997fd2011-08-15 18:26:39 -0700752 // First we clear the tag to ensure that on every touch down we start with a fresh slate,
753 // even in the case where we return early. Not clearing here was causing bugs whereby on
754 // long-press we'd end up picking up an item from a previous drag operation.
755 final int action = ev.getAction();
756
757 if (action == MotionEvent.ACTION_DOWN) {
758 clearTagCellInfo();
759 }
760
Michael Jurkadee05892010-07-27 10:01:56 -0700761 if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
762 return true;
763 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800764
765 if (action == MotionEvent.ACTION_DOWN) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700766 setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800767 }
Winson Chungeecf02d2012-03-02 17:14:58 -0800768
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800769 return false;
770 }
771
Adam Cohenc1997fd2011-08-15 18:26:39 -0700772 private void clearTagCellInfo() {
773 final CellInfo cellInfo = mCellInfo;
774 cellInfo.cell = null;
775 cellInfo.cellX = -1;
776 cellInfo.cellY = -1;
777 cellInfo.spanX = 0;
778 cellInfo.spanY = 0;
779 setTag(cellInfo);
780 }
781
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800782 public CellInfo getTag() {
Michael Jurka0280c3b2010-09-17 15:00:07 -0700783 return (CellInfo) super.getTag();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800784 }
785
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700786 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700787 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800788 * @param x X coordinate of the point
789 * @param y Y coordinate of the point
790 * @param result Array of 2 ints to hold the x and y coordinate of the cell
791 */
792 void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700793 final int hStartPadding = getPaddingLeft();
794 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800795
796 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
797 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
798
Adam Cohend22015c2010-07-26 22:02:18 -0700799 final int xAxis = mCountX;
800 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800801
802 if (result[0] < 0) result[0] = 0;
803 if (result[0] >= xAxis) result[0] = xAxis - 1;
804 if (result[1] < 0) result[1] = 0;
805 if (result[1] >= yAxis) result[1] = yAxis - 1;
806 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700807
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800808 /**
809 * Given a point, return the cell that most closely encloses that point
810 * @param x X coordinate of the point
811 * @param y Y coordinate of the point
812 * @param result Array of 2 ints to hold the x and y coordinate of the cell
813 */
814 void pointToCellRounded(int x, int y, int[] result) {
815 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
816 }
817
818 /**
819 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700820 *
821 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800822 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700823 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800824 * @param result Array of 2 ints to hold the x and y coordinate of the point
825 */
826 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700827 final int hStartPadding = getPaddingLeft();
828 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800829
830 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
831 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
832 }
833
Adam Cohene3e27a82011-04-15 12:07:39 -0700834 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800835 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700836 *
837 * @param cellX X coordinate of the cell
838 * @param cellY Y coordinate of the cell
839 *
840 * @param result Array of 2 ints to hold the x and y coordinate of the point
841 */
842 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700843 regionToCenterPoint(cellX, cellY, 1, 1, result);
844 }
845
846 /**
847 * Given a cell coordinate and span return the point that represents the center of the regio
848 *
849 * @param cellX X coordinate of the cell
850 * @param cellY Y coordinate of the cell
851 *
852 * @param result Array of 2 ints to hold the x and y coordinate of the point
853 */
854 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700855 final int hStartPadding = getPaddingLeft();
856 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700857 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
858 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
859 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
860 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700861 }
862
Adam Cohen19f37922012-03-21 11:59:11 -0700863 /**
864 * Given a cell coordinate and span fills out a corresponding pixel rect
865 *
866 * @param cellX X coordinate of the cell
867 * @param cellY Y coordinate of the cell
868 * @param result Rect in which to write the result
869 */
870 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
871 final int hStartPadding = getPaddingLeft();
872 final int vStartPadding = getPaddingTop();
873 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
874 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
875 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
876 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
877 }
878
Adam Cohen482ed822012-03-02 14:15:13 -0800879 public float getDistanceFromCell(float x, float y, int[] cell) {
880 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
881 float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) +
882 Math.pow(y - mTmpPoint[1], 2));
883 return distance;
884 }
885
Romain Guy84f296c2009-11-04 15:00:44 -0800886 int getCellWidth() {
887 return mCellWidth;
888 }
889
890 int getCellHeight() {
891 return mCellHeight;
892 }
893
Adam Cohend4844c32011-02-18 19:25:06 -0800894 int getWidthGap() {
895 return mWidthGap;
896 }
897
898 int getHeightGap() {
899 return mHeightGap;
900 }
901
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700902 Rect getContentRect(Rect r) {
903 if (r == null) {
904 r = new Rect();
905 }
906 int left = getPaddingLeft();
907 int top = getPaddingTop();
Michael Jurka8b805b12012-04-18 14:23:14 -0700908 int right = left + getWidth() - getPaddingLeft() - getPaddingRight();
909 int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom();
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700910 r.set(left, top, right, bottom);
911 return r;
912 }
913
Winson Chungfe411c82013-09-26 16:07:17 -0700914 /** Return a rect that has the cellWidth/cellHeight (left, top), and
915 * widthGap/heightGap (right, bottom) */
Winson Chung66700732013-08-20 16:56:15 -0700916 static void getMetrics(Rect metrics, int paddedMeasureWidth,
917 int paddedMeasureHeight, int countX, int countY) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700918 LauncherAppState app = LauncherAppState.getInstance();
919 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Winson Chung66700732013-08-20 16:56:15 -0700920 metrics.set(grid.calculateCellWidth(paddedMeasureWidth, countX),
921 grid.calculateCellHeight(paddedMeasureHeight, countY), 0, 0);
Adam Cohenf4bd5792012-04-27 11:35:29 -0700922 }
923
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700924 public void setFixedSize(int width, int height) {
925 mFixedWidth = width;
926 mFixedHeight = height;
927 }
928
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800929 @Override
930 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700931 LauncherAppState app = LauncherAppState.getInstance();
932 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
933
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800934 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800935 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700936 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
937 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung2d75f122013-09-23 16:53:31 -0700938 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
939 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
Winson Chung11a1a532013-09-13 11:14:45 -0700940 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700941 int cw = grid.calculateCellWidth(childWidthSize, mCountX);
942 int ch = grid.calculateCellHeight(childHeightSize, mCountY);
Winson Chung11a1a532013-09-13 11:14:45 -0700943 if (cw != mCellWidth || ch != mCellHeight) {
944 mCellWidth = cw;
945 mCellHeight = ch;
946 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
947 mHeightGap, mCountX, mCountY);
948 }
Winson Chung5f8afe62013-08-12 16:19:28 -0700949 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700950
Winson Chung2d75f122013-09-23 16:53:31 -0700951 int newWidth = childWidthSize;
952 int newHeight = childHeightSize;
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700953 if (mFixedWidth > 0 && mFixedHeight > 0) {
954 newWidth = mFixedWidth;
955 newHeight = mFixedHeight;
956 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800957 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
958 }
959
Adam Cohend22015c2010-07-26 22:02:18 -0700960 int numWidthGaps = mCountX - 1;
961 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800962
Adam Cohen234c4cd2011-07-17 21:03:04 -0700963 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung2d75f122013-09-23 16:53:31 -0700964 int hSpace = childWidthSize;
965 int vSpace = childHeightSize;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700966 int hFreeSpace = hSpace - (mCountX * mCellWidth);
967 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700968 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
969 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Winson Chung5f8afe62013-08-12 16:19:28 -0700970 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap,
971 mHeightGap, mCountX, mCountY);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700972 } else {
973 mWidthGap = mOriginalWidthGap;
974 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700975 }
Michael Jurka8c920dd2011-01-20 14:16:56 -0800976 int count = getChildCount();
Winson Chung5f8afe62013-08-12 16:19:28 -0700977 int maxWidth = 0;
978 int maxHeight = 0;
Michael Jurka8c920dd2011-01-20 14:16:56 -0800979 for (int i = 0; i < count; i++) {
980 View child = getChildAt(i);
Winson Chung2d75f122013-09-23 16:53:31 -0700981 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth,
982 MeasureSpec.EXACTLY);
983 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight,
984 MeasureSpec.EXACTLY);
Michael Jurka8c920dd2011-01-20 14:16:56 -0800985 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
Winson Chung5f8afe62013-08-12 16:19:28 -0700986 maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
987 maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
Michael Jurka8c920dd2011-01-20 14:16:56 -0800988 }
Winson Chung2d75f122013-09-23 16:53:31 -0700989 if (mFixedWidth > 0 && mFixedHeight > 0) {
990 setMeasuredDimension(maxWidth, maxHeight);
991 } else {
992 setMeasuredDimension(widthSize, heightSize);
993 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800994 }
995
996 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700997 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Winson Chung38848ca2013-10-08 12:03:44 -0700998 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
999 (mCountX * mCellWidth);
1000 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
1001 int top = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001002 int count = getChildCount();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001003 for (int i = 0; i < count; i++) {
Michael Jurka8c920dd2011-01-20 14:16:56 -08001004 View child = getChildAt(i);
Winson Chung2d75f122013-09-23 16:53:31 -07001005 child.layout(left, top,
1006 left + r - l,
1007 top + b - t);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001008 }
1009 }
1010
1011 @Override
Michael Jurkadee05892010-07-27 10:01:56 -07001012 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1013 super.onSizeChanged(w, h, oldw, oldh);
Winson Chung82a9bd22013-10-08 16:02:34 -07001014
1015 // Expand the background drawing bounds by the padding baked into the background drawable
1016 Rect padding = new Rect();
1017 mNormalBackground.getPadding(padding);
1018 mBackgroundRect.set(-padding.left, -padding.top, w + padding.right, h + padding.bottom);
1019
Adam Cohenb5ba0972011-09-07 18:02:31 -07001020 mForegroundRect.set(mForegroundPadding, mForegroundPadding,
Adam Cohen215b4162012-08-30 13:14:08 -07001021 w - mForegroundPadding, h - mForegroundPadding);
Michael Jurkadee05892010-07-27 10:01:56 -07001022 }
1023
1024 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001025 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001026 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001027 }
1028
1029 @Override
1030 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001031 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001032 }
1033
Michael Jurka5f1c5092010-09-03 14:15:02 -07001034 public float getBackgroundAlpha() {
1035 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -07001036 }
1037
Adam Cohen1b0aaac2010-10-28 11:11:18 -07001038 public void setBackgroundAlphaMultiplier(float multiplier) {
Michael Jurkaa3d30ad2012-05-08 13:43:43 -07001039 if (mBackgroundAlphaMultiplier != multiplier) {
1040 mBackgroundAlphaMultiplier = multiplier;
1041 invalidate();
1042 }
Adam Cohen1b0aaac2010-10-28 11:11:18 -07001043 }
1044
Adam Cohenddb82192010-11-10 16:32:54 -08001045 public float getBackgroundAlphaMultiplier() {
1046 return mBackgroundAlphaMultiplier;
1047 }
1048
Michael Jurka5f1c5092010-09-03 14:15:02 -07001049 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -08001050 if (mBackgroundAlpha != alpha) {
1051 mBackgroundAlpha = alpha;
1052 invalidate();
1053 }
Michael Jurkadee05892010-07-27 10:01:56 -07001054 }
1055
Michael Jurkaa52570f2012-03-20 03:18:20 -07001056 public void setShortcutAndWidgetAlpha(float alpha) {
Michael Jurka0142d492010-08-25 17:46:15 -07001057 final int childCount = getChildCount();
1058 for (int i = 0; i < childCount; i++) {
Michael Jurkadee05892010-07-27 10:01:56 -07001059 getChildAt(i).setAlpha(alpha);
1060 }
1061 }
1062
Michael Jurkaa52570f2012-03-20 03:18:20 -07001063 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
1064 if (getChildCount() > 0) {
1065 return (ShortcutAndWidgetContainer) getChildAt(0);
1066 }
1067 return null;
1068 }
1069
Patrick Dubroy440c3602010-07-13 17:50:32 -07001070 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001071 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -07001072 }
1073
Adam Cohen76fc0852011-06-17 13:26:23 -07001074 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -08001075 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001076 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -08001077 boolean[][] occupied = mOccupied;
1078 if (!permanent) {
1079 occupied = mTmpOccupied;
1080 }
1081
Adam Cohen19f37922012-03-21 11:59:11 -07001082 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001083 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1084 final ItemInfo info = (ItemInfo) child.getTag();
1085
1086 // We cancel any existing animations
1087 if (mReorderAnimators.containsKey(lp)) {
1088 mReorderAnimators.get(lp).cancel();
1089 mReorderAnimators.remove(lp);
1090 }
1091
Adam Cohen482ed822012-03-02 14:15:13 -08001092 final int oldX = lp.x;
1093 final int oldY = lp.y;
1094 if (adjustOccupied) {
1095 occupied[lp.cellX][lp.cellY] = false;
1096 occupied[cellX][cellY] = true;
1097 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001098 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001099 if (permanent) {
1100 lp.cellX = info.cellX = cellX;
1101 lp.cellY = info.cellY = cellY;
1102 } else {
1103 lp.tmpCellX = cellX;
1104 lp.tmpCellY = cellY;
1105 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001106 clc.setupLp(lp);
1107 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001108 final int newX = lp.x;
1109 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001110
Adam Cohen76fc0852011-06-17 13:26:23 -07001111 lp.x = oldX;
1112 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001113
Adam Cohen482ed822012-03-02 14:15:13 -08001114 // Exit early if we're not actually moving the view
1115 if (oldX == newX && oldY == newY) {
1116 lp.isLockedToGrid = true;
1117 return true;
1118 }
1119
Michael Jurkaf1ad6082013-03-13 12:55:46 +01001120 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001121 va.setDuration(duration);
1122 mReorderAnimators.put(lp, va);
1123
1124 va.addUpdateListener(new AnimatorUpdateListener() {
1125 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001126 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001127 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001128 lp.x = (int) ((1 - r) * oldX + r * newX);
1129 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001130 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001131 }
1132 });
Adam Cohen482ed822012-03-02 14:15:13 -08001133 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001134 boolean cancelled = false;
1135 public void onAnimationEnd(Animator animation) {
1136 // If the animation was cancelled, it means that another animation
1137 // has interrupted this one, and we don't want to lock the item into
1138 // place just yet.
1139 if (!cancelled) {
1140 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001141 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001142 }
1143 if (mReorderAnimators.containsKey(lp)) {
1144 mReorderAnimators.remove(lp);
1145 }
1146 }
1147 public void onAnimationCancel(Animator animation) {
1148 cancelled = true;
1149 }
1150 });
Adam Cohen482ed822012-03-02 14:15:13 -08001151 va.setStartDelay(delay);
1152 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001153 return true;
1154 }
1155 return false;
1156 }
1157
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001158 /**
1159 * Estimate where the top left cell of the dragged item will land if it is dropped.
1160 *
1161 * @param originX The X value of the top left corner of the item
1162 * @param originY The Y value of the top left corner of the item
1163 * @param spanX The number of horizontal cells that the item spans
1164 * @param spanY The number of vertical cells that the item spans
1165 * @param result The estimated drop cell X and Y.
1166 */
1167 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
Adam Cohend22015c2010-07-26 22:02:18 -07001168 final int countX = mCountX;
1169 final int countY = mCountY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001170
Michael Jurkaa63c4522010-08-19 13:52:27 -07001171 // pointToCellRounded takes the top left of a cell but will pad that with
1172 // cellWidth/2 and cellHeight/2 when finding the matching cell
1173 pointToCellRounded(originX, originY, result);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001174
1175 // If the item isn't fully on this screen, snap to the edges
1176 int rightOverhang = result[0] + spanX - countX;
1177 if (rightOverhang > 0) {
1178 result[0] -= rightOverhang; // Snap to right
1179 }
1180 result[0] = Math.max(0, result[0]); // Snap to left
1181 int bottomOverhang = result[1] + spanY - countY;
1182 if (bottomOverhang > 0) {
1183 result[1] -= bottomOverhang; // Snap to bottom
1184 }
1185 result[1] = Math.max(0, result[1]); // Snap to top
1186 }
1187
Adam Cohen482ed822012-03-02 14:15:13 -08001188 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1189 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001190 final int oldDragCellX = mDragCell[0];
1191 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001192
Adam Cohen2801caf2011-05-13 20:57:39 -07001193 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001194 return;
1195 }
1196
Adam Cohen482ed822012-03-02 14:15:13 -08001197 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1198 mDragCell[0] = cellX;
1199 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001200 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001201 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001202 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001203
Joe Onorato4be866d2010-10-10 11:26:02 -07001204 int left = topLeft[0];
1205 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001206
Winson Chungb8c69f32011-10-19 21:36:08 -07001207 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001208 // When drawing the drag outline, it did not account for margin offsets
1209 // added by the view's parent.
1210 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1211 left += lp.leftMargin;
1212 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001213
Adam Cohen99e8b402011-03-25 19:23:43 -07001214 // Offsets due to the size difference between the View and the dragOutline.
1215 // There is a size difference to account for the outer blur, which may lie
1216 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001217 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001218 // We center about the x axis
1219 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1220 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001221 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001222 if (dragOffset != null && dragRegion != null) {
1223 // Center the drag region *horizontally* in the cell and apply a drag
1224 // outline offset
1225 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1226 - dragRegion.width()) / 2;
Winson Chung69737c32013-10-08 17:00:19 -07001227 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
1228 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
1229 top += dragOffset.y + cellPaddingY;
Winson Chungb8c69f32011-10-19 21:36:08 -07001230 } else {
1231 // Center the drag outline in the cell
1232 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1233 - dragOutline.getWidth()) / 2;
1234 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1235 - dragOutline.getHeight()) / 2;
1236 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001237 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001238 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001239 mDragOutlineAnims[oldIndex].animateOut();
1240 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001241 Rect r = mDragOutlines[mDragOutlineCurrent];
1242 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1243 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001244 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001245 }
Winson Chung150fbab2010-09-29 17:14:26 -07001246
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001247 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1248 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001249 }
1250 }
1251
Adam Cohene0310962011-04-18 16:15:31 -07001252 public void clearDragOutlines() {
1253 final int oldIndex = mDragOutlineCurrent;
1254 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001255 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001256 }
1257
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001258 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001259 * Find a vacant area that will fit the given bounds nearest the requested
1260 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001261 *
Romain Guy51afc022009-05-04 18:03:43 -07001262 * @param pixelX The X location at which you want to search for a vacant area.
1263 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001264 * @param spanX Horizontal span of the object.
1265 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001266 * @param result Array in which to place the result, or null (in which case a new array will
1267 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001268 * @return The X, Y cell of a vacant area that can contain this object,
1269 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001270 */
Adam Cohend41fbf52012-02-16 23:53:59 -08001271 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
1272 int[] result) {
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001273 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001274 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001275
Michael Jurka6a1435d2010-09-27 17:35:12 -07001276 /**
1277 * Find a vacant area that will fit the given bounds nearest the requested
1278 * cell location. Uses Euclidean distance to score multiple vacant areas.
1279 *
1280 * @param pixelX The X location at which you want to search for a vacant area.
1281 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001282 * @param minSpanX The minimum horizontal span required
1283 * @param minSpanY The minimum vertical span required
1284 * @param spanX Horizontal span of the object.
1285 * @param spanY Vertical span of the object.
1286 * @param result Array in which to place the result, or null (in which case a new array will
1287 * be allocated)
1288 * @return The X, Y cell of a vacant area that can contain this object,
1289 * nearest the requested location.
1290 */
1291 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1292 int spanY, int[] result, int[] resultSpan) {
1293 return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
1294 result, resultSpan);
1295 }
1296
1297 /**
1298 * Find a vacant area that will fit the given bounds nearest the requested
1299 * cell location. Uses Euclidean distance to score multiple vacant areas.
1300 *
1301 * @param pixelX The X location at which you want to search for a vacant area.
1302 * @param pixelY The Y location at which you want to search for a vacant area.
Michael Jurka6a1435d2010-09-27 17:35:12 -07001303 * @param spanX Horizontal span of the object.
1304 * @param spanY Vertical span of the object.
Adam Cohendf035382011-04-11 17:22:04 -07001305 * @param ignoreOccupied If true, the result can be an occupied cell
1306 * @param result Array in which to place the result, or null (in which case a new array will
1307 * be allocated)
Michael Jurka6a1435d2010-09-27 17:35:12 -07001308 * @return The X, Y cell of a vacant area that can contain this object,
1309 * nearest the requested location.
1310 */
Adam Cohendf035382011-04-11 17:22:04 -07001311 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
1312 boolean ignoreOccupied, int[] result) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001313 return findNearestArea(pixelX, pixelY, spanX, spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001314 spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08001315 }
1316
1317 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1318 private void lazyInitTempRectStack() {
1319 if (mTempRectStack.isEmpty()) {
1320 for (int i = 0; i < mCountX * mCountY; i++) {
1321 mTempRectStack.push(new Rect());
1322 }
1323 }
1324 }
Adam Cohen482ed822012-03-02 14:15:13 -08001325
Adam Cohend41fbf52012-02-16 23:53:59 -08001326 private void recycleTempRects(Stack<Rect> used) {
1327 while (!used.isEmpty()) {
1328 mTempRectStack.push(used.pop());
1329 }
1330 }
1331
1332 /**
1333 * Find a vacant area that will fit the given bounds nearest the requested
1334 * cell location. Uses Euclidean distance to score multiple vacant areas.
1335 *
1336 * @param pixelX The X location at which you want to search for a vacant area.
1337 * @param pixelY The Y location at which you want to search for a vacant area.
1338 * @param minSpanX The minimum horizontal span required
1339 * @param minSpanY The minimum vertical span required
1340 * @param spanX Horizontal span of the object.
1341 * @param spanY Vertical span of the object.
1342 * @param ignoreOccupied If true, the result can be an occupied cell
1343 * @param result Array in which to place the result, or null (in which case a new array will
1344 * be allocated)
1345 * @return The X, Y cell of a vacant area that can contain this object,
1346 * nearest the requested location.
1347 */
1348 int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001349 View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
1350 boolean[][] occupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001351 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001352 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08001353 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001354
Adam Cohene3e27a82011-04-15 12:07:39 -07001355 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1356 // to the center of the item, but we are searching based on the top-left cell, so
1357 // we translate the point over to correspond to the top-left.
1358 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1359 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1360
Jeff Sharkey70864282009-04-07 21:08:40 -07001361 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001362 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001363 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001364 final Rect bestRect = new Rect(-1, -1, -1, -1);
1365 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001366
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001367 final int countX = mCountX;
1368 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001369
Adam Cohend41fbf52012-02-16 23:53:59 -08001370 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1371 spanX < minSpanX || spanY < minSpanY) {
1372 return bestXY;
1373 }
1374
1375 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001376 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001377 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1378 int ySize = -1;
1379 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001380 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001381 // First, let's see if this thing fits anywhere
1382 for (int i = 0; i < minSpanX; i++) {
1383 for (int j = 0; j < minSpanY; j++) {
Adam Cohendf035382011-04-11 17:22:04 -07001384 if (occupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001385 continue inner;
1386 }
Michael Jurkac28de512010-08-13 11:27:44 -07001387 }
1388 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001389 xSize = minSpanX;
1390 ySize = minSpanY;
1391
1392 // We know that the item will fit at _some_ acceptable size, now let's see
1393 // how big we can make it. We'll alternate between incrementing x and y spans
1394 // until we hit a limit.
1395 boolean incX = true;
1396 boolean hitMaxX = xSize >= spanX;
1397 boolean hitMaxY = ySize >= spanY;
1398 while (!(hitMaxX && hitMaxY)) {
1399 if (incX && !hitMaxX) {
1400 for (int j = 0; j < ySize; j++) {
1401 if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
1402 // We can't move out horizontally
1403 hitMaxX = true;
1404 }
1405 }
1406 if (!hitMaxX) {
1407 xSize++;
1408 }
1409 } else if (!hitMaxY) {
1410 for (int i = 0; i < xSize; i++) {
1411 if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
1412 // We can't move out vertically
1413 hitMaxY = true;
1414 }
1415 }
1416 if (!hitMaxY) {
1417 ySize++;
1418 }
1419 }
1420 hitMaxX |= xSize >= spanX;
1421 hitMaxY |= ySize >= spanY;
1422 incX = !incX;
1423 }
1424 incX = true;
1425 hitMaxX = xSize >= spanX;
1426 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001427 }
Winson Chung0be025d2011-05-23 17:45:09 -07001428 final int[] cellXY = mTmpXY;
Adam Cohene3e27a82011-04-15 12:07:39 -07001429 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001430
Adam Cohend41fbf52012-02-16 23:53:59 -08001431 // We verify that the current rect is not a sub-rect of any of our previous
1432 // candidates. In this case, the current rect is disqualified in favour of the
1433 // containing rect.
1434 Rect currentRect = mTempRectStack.pop();
1435 currentRect.set(x, y, x + xSize, y + ySize);
1436 boolean contained = false;
1437 for (Rect r : validRegions) {
1438 if (r.contains(currentRect)) {
1439 contained = true;
1440 break;
1441 }
1442 }
1443 validRegions.push(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001444 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
1445 + Math.pow(cellXY[1] - pixelY, 2));
Adam Cohen482ed822012-03-02 14:15:13 -08001446
Adam Cohend41fbf52012-02-16 23:53:59 -08001447 if ((distance <= bestDistance && !contained) ||
1448 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001449 bestDistance = distance;
1450 bestXY[0] = x;
1451 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001452 if (resultSpan != null) {
1453 resultSpan[0] = xSize;
1454 resultSpan[1] = ySize;
1455 }
1456 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001457 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001458 }
1459 }
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001460 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08001461 markCellsAsOccupiedForView(ignoreView, occupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001462
Adam Cohenc0dcf592011-06-01 15:30:43 -07001463 // Return -1, -1 if no suitable location found
1464 if (bestDistance == Double.MAX_VALUE) {
1465 bestXY[0] = -1;
1466 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001467 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001468 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001469 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001470 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001471
Adam Cohen482ed822012-03-02 14:15:13 -08001472 /**
1473 * Find a vacant area that will fit the given bounds nearest the requested
1474 * cell location, and will also weigh in a suggested direction vector of the
1475 * desired location. This method computers distance based on unit grid distances,
1476 * not pixel distances.
1477 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001478 * @param cellX The X cell nearest to which you want to search for a vacant area.
1479 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001480 * @param spanX Horizontal span of the object.
1481 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001482 * @param direction The favored direction in which the views should move from x, y
1483 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1484 * matches exactly. Otherwise we find the best matching direction.
1485 * @param occoupied The array which represents which cells in the CellLayout are occupied
1486 * @param blockOccupied The array which represents which cells in the specified block (cellX,
Winson Chung5f8afe62013-08-12 16:19:28 -07001487 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001488 * @param result Array in which to place the result, or null (in which case a new array will
1489 * be allocated)
1490 * @return The X, Y cell of a vacant area that can contain this object,
1491 * nearest the requested location.
1492 */
1493 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001494 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001495 // Keep track of best-scoring drop area
1496 final int[] bestXY = result != null ? result : new int[2];
1497 float bestDistance = Float.MAX_VALUE;
1498 int bestDirectionScore = Integer.MIN_VALUE;
1499
1500 final int countX = mCountX;
1501 final int countY = mCountY;
1502
1503 for (int y = 0; y < countY - (spanY - 1); y++) {
1504 inner:
1505 for (int x = 0; x < countX - (spanX - 1); x++) {
1506 // First, let's see if this thing fits anywhere
1507 for (int i = 0; i < spanX; i++) {
1508 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001509 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001510 continue inner;
1511 }
1512 }
1513 }
1514
1515 float distance = (float)
1516 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1517 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001518 computeDirectionVector(x - cellX, y - cellY, curDirection);
1519 // The direction score is just the dot product of the two candidate direction
1520 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001521 int curDirectionScore = direction[0] * curDirection[0] +
1522 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001523 boolean exactDirectionOnly = false;
1524 boolean directionMatches = direction[0] == curDirection[0] &&
1525 direction[0] == curDirection[0];
1526 if ((directionMatches || !exactDirectionOnly) &&
1527 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001528 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1529 bestDistance = distance;
1530 bestDirectionScore = curDirectionScore;
1531 bestXY[0] = x;
1532 bestXY[1] = y;
1533 }
1534 }
1535 }
1536
1537 // Return -1, -1 if no suitable location found
1538 if (bestDistance == Float.MAX_VALUE) {
1539 bestXY[0] = -1;
1540 bestXY[1] = -1;
1541 }
1542 return bestXY;
1543 }
1544
1545 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001546 int[] direction, ItemConfiguration currentState) {
1547 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001548 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001549 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001550 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1551
Adam Cohen8baab352012-03-20 17:39:21 -07001552 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001553
1554 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001555 c.x = mTempLocation[0];
1556 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001557 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001558 }
Adam Cohen8baab352012-03-20 17:39:21 -07001559 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001560 return success;
1561 }
1562
Adam Cohenf3900c22012-11-16 18:28:11 -08001563 /**
1564 * This helper class defines a cluster of views. It helps with defining complex edges
1565 * of the cluster and determining how those edges interact with other views. The edges
1566 * essentially define a fine-grained boundary around the cluster of views -- like a more
1567 * precise version of a bounding box.
1568 */
1569 private class ViewCluster {
1570 final static int LEFT = 0;
1571 final static int TOP = 1;
1572 final static int RIGHT = 2;
1573 final static int BOTTOM = 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001574
Adam Cohenf3900c22012-11-16 18:28:11 -08001575 ArrayList<View> views;
1576 ItemConfiguration config;
1577 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001578
Adam Cohenf3900c22012-11-16 18:28:11 -08001579 int[] leftEdge = new int[mCountY];
1580 int[] rightEdge = new int[mCountY];
1581 int[] topEdge = new int[mCountX];
1582 int[] bottomEdge = new int[mCountX];
1583 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1584
1585 @SuppressWarnings("unchecked")
1586 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1587 this.views = (ArrayList<View>) views.clone();
1588 this.config = config;
1589 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001590 }
1591
Adam Cohenf3900c22012-11-16 18:28:11 -08001592 void resetEdges() {
1593 for (int i = 0; i < mCountX; i++) {
1594 topEdge[i] = -1;
1595 bottomEdge[i] = -1;
1596 }
1597 for (int i = 0; i < mCountY; i++) {
1598 leftEdge[i] = -1;
1599 rightEdge[i] = -1;
1600 }
1601 leftEdgeDirty = true;
1602 rightEdgeDirty = true;
1603 bottomEdgeDirty = true;
1604 topEdgeDirty = true;
1605 boundingRectDirty = true;
1606 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001607
Adam Cohenf3900c22012-11-16 18:28:11 -08001608 void computeEdge(int which, int[] edge) {
1609 int count = views.size();
1610 for (int i = 0; i < count; i++) {
1611 CellAndSpan cs = config.map.get(views.get(i));
1612 switch (which) {
1613 case LEFT:
1614 int left = cs.x;
1615 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1616 if (left < edge[j] || edge[j] < 0) {
1617 edge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001618 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001619 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001620 break;
1621 case RIGHT:
1622 int right = cs.x + cs.spanX;
1623 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1624 if (right > edge[j]) {
1625 edge[j] = right;
1626 }
1627 }
1628 break;
1629 case TOP:
1630 int top = cs.y;
1631 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1632 if (top < edge[j] || edge[j] < 0) {
1633 edge[j] = top;
1634 }
1635 }
1636 break;
1637 case BOTTOM:
1638 int bottom = cs.y + cs.spanY;
1639 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1640 if (bottom > edge[j]) {
1641 edge[j] = bottom;
1642 }
1643 }
1644 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001645 }
1646 }
1647 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001648
1649 boolean isViewTouchingEdge(View v, int whichEdge) {
1650 CellAndSpan cs = config.map.get(v);
1651
1652 int[] edge = getEdge(whichEdge);
1653
1654 switch (whichEdge) {
1655 case LEFT:
1656 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1657 if (edge[i] == cs.x + cs.spanX) {
1658 return true;
1659 }
1660 }
1661 break;
1662 case RIGHT:
1663 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1664 if (edge[i] == cs.x) {
1665 return true;
1666 }
1667 }
1668 break;
1669 case TOP:
1670 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1671 if (edge[i] == cs.y + cs.spanY) {
1672 return true;
1673 }
1674 }
1675 break;
1676 case BOTTOM:
1677 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1678 if (edge[i] == cs.y) {
1679 return true;
1680 }
1681 }
1682 break;
1683 }
1684 return false;
1685 }
1686
1687 void shift(int whichEdge, int delta) {
1688 for (View v: views) {
1689 CellAndSpan c = config.map.get(v);
1690 switch (whichEdge) {
1691 case LEFT:
1692 c.x -= delta;
1693 break;
1694 case RIGHT:
1695 c.x += delta;
1696 break;
1697 case TOP:
1698 c.y -= delta;
1699 break;
1700 case BOTTOM:
1701 default:
1702 c.y += delta;
1703 break;
1704 }
1705 }
1706 resetEdges();
1707 }
1708
1709 public void addView(View v) {
1710 views.add(v);
1711 resetEdges();
1712 }
1713
1714 public Rect getBoundingRect() {
1715 if (boundingRectDirty) {
1716 boolean first = true;
1717 for (View v: views) {
1718 CellAndSpan c = config.map.get(v);
1719 if (first) {
1720 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1721 first = false;
1722 } else {
1723 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1724 }
1725 }
1726 }
1727 return boundingRect;
1728 }
1729
1730 public int[] getEdge(int which) {
1731 switch (which) {
1732 case LEFT:
1733 return getLeftEdge();
1734 case RIGHT:
1735 return getRightEdge();
1736 case TOP:
1737 return getTopEdge();
1738 case BOTTOM:
1739 default:
1740 return getBottomEdge();
1741 }
1742 }
1743
1744 public int[] getLeftEdge() {
1745 if (leftEdgeDirty) {
1746 computeEdge(LEFT, leftEdge);
1747 }
1748 return leftEdge;
1749 }
1750
1751 public int[] getRightEdge() {
1752 if (rightEdgeDirty) {
1753 computeEdge(RIGHT, rightEdge);
1754 }
1755 return rightEdge;
1756 }
1757
1758 public int[] getTopEdge() {
1759 if (topEdgeDirty) {
1760 computeEdge(TOP, topEdge);
1761 }
1762 return topEdge;
1763 }
1764
1765 public int[] getBottomEdge() {
1766 if (bottomEdgeDirty) {
1767 computeEdge(BOTTOM, bottomEdge);
1768 }
1769 return bottomEdge;
1770 }
1771
1772 PositionComparator comparator = new PositionComparator();
1773 class PositionComparator implements Comparator<View> {
1774 int whichEdge = 0;
1775 public int compare(View left, View right) {
1776 CellAndSpan l = config.map.get(left);
1777 CellAndSpan r = config.map.get(right);
1778 switch (whichEdge) {
1779 case LEFT:
1780 return (r.x + r.spanX) - (l.x + l.spanX);
1781 case RIGHT:
1782 return l.x - r.x;
1783 case TOP:
1784 return (r.y + r.spanY) - (l.y + l.spanY);
1785 case BOTTOM:
1786 default:
1787 return l.y - r.y;
1788 }
1789 }
1790 }
1791
1792 public void sortConfigurationForEdgePush(int edge) {
1793 comparator.whichEdge = edge;
1794 Collections.sort(config.sortedViews, comparator);
1795 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001796 }
1797
Adam Cohenf3900c22012-11-16 18:28:11 -08001798 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1799 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001800
Adam Cohenf3900c22012-11-16 18:28:11 -08001801 ViewCluster cluster = new ViewCluster(views, currentState);
1802 Rect clusterRect = cluster.getBoundingRect();
1803 int whichEdge;
1804 int pushDistance;
1805 boolean fail = false;
1806
1807 // Determine the edge of the cluster that will be leading the push and how far
1808 // the cluster must be shifted.
1809 if (direction[0] < 0) {
1810 whichEdge = ViewCluster.LEFT;
1811 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001812 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001813 whichEdge = ViewCluster.RIGHT;
1814 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1815 } else if (direction[1] < 0) {
1816 whichEdge = ViewCluster.TOP;
1817 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1818 } else {
1819 whichEdge = ViewCluster.BOTTOM;
1820 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001821 }
1822
Adam Cohenf3900c22012-11-16 18:28:11 -08001823 // Break early for invalid push distance.
1824 if (pushDistance <= 0) {
1825 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001826 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001827
1828 // Mark the occupied state as false for the group of views we want to move.
1829 for (View v: views) {
1830 CellAndSpan c = currentState.map.get(v);
1831 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1832 }
1833
1834 // We save the current configuration -- if we fail to find a solution we will revert
1835 // to the initial state. The process of finding a solution modifies the configuration
1836 // in place, hence the need for revert in the failure case.
1837 currentState.save();
1838
1839 // The pushing algorithm is simplified by considering the views in the order in which
1840 // they would be pushed by the cluster. For example, if the cluster is leading with its
1841 // left edge, we consider sort the views by their right edge, from right to left.
1842 cluster.sortConfigurationForEdgePush(whichEdge);
1843
1844 while (pushDistance > 0 && !fail) {
1845 for (View v: currentState.sortedViews) {
1846 // For each view that isn't in the cluster, we see if the leading edge of the
1847 // cluster is contacting the edge of that view. If so, we add that view to the
1848 // cluster.
1849 if (!cluster.views.contains(v) && v != dragView) {
1850 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1851 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1852 if (!lp.canReorder) {
1853 // The push solution includes the all apps button, this is not viable.
1854 fail = true;
1855 break;
1856 }
1857 cluster.addView(v);
1858 CellAndSpan c = currentState.map.get(v);
1859
1860 // Adding view to cluster, mark it as not occupied.
1861 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1862 }
1863 }
1864 }
1865 pushDistance--;
1866
1867 // The cluster has been completed, now we move the whole thing over in the appropriate
1868 // direction.
1869 cluster.shift(whichEdge, 1);
1870 }
1871
1872 boolean foundSolution = false;
1873 clusterRect = cluster.getBoundingRect();
1874
1875 // Due to the nature of the algorithm, the only check required to verify a valid solution
1876 // is to ensure that completed shifted cluster lies completely within the cell layout.
1877 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1878 clusterRect.bottom <= mCountY) {
1879 foundSolution = true;
1880 } else {
1881 currentState.restore();
1882 }
1883
1884 // In either case, we set the occupied array as marked for the location of the views
1885 for (View v: cluster.views) {
1886 CellAndSpan c = currentState.map.get(v);
1887 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1888 }
1889
1890 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001891 }
1892
Adam Cohen482ed822012-03-02 14:15:13 -08001893 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001894 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001895 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001896
Adam Cohen8baab352012-03-20 17:39:21 -07001897 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001898 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001899 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001900 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001901 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001902 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001903 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001904 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001905 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001906 }
1907 }
Adam Cohen8baab352012-03-20 17:39:21 -07001908
Adam Cohen8baab352012-03-20 17:39:21 -07001909 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001910 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001911 CellAndSpan c = currentState.map.get(v);
1912 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1913 }
1914
Adam Cohen47a876d2012-03-19 13:21:41 -07001915 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1916 int top = boundingRect.top;
1917 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001918 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001919 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001920 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001921 CellAndSpan c = currentState.map.get(v);
1922 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001923 }
1924
Adam Cohen482ed822012-03-02 14:15:13 -08001925 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1926
Adam Cohenf3900c22012-11-16 18:28:11 -08001927 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1928 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001929
Adam Cohen8baab352012-03-20 17:39:21 -07001930 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001931 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001932 int deltaX = mTempLocation[0] - boundingRect.left;
1933 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001934 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001935 CellAndSpan c = currentState.map.get(v);
1936 c.x += deltaX;
1937 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001938 }
1939 success = true;
1940 }
Adam Cohen8baab352012-03-20 17:39:21 -07001941
1942 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001943 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001944 CellAndSpan c = currentState.map.get(v);
1945 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001946 }
1947 return success;
1948 }
1949
1950 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1951 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1952 }
1953
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001954 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1955 // to push items in each of the cardinal directions, in an order based on the direction vector
1956 // passed.
1957 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1958 int[] direction, View ignoreView, ItemConfiguration solution) {
1959 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
Winson Chung5f8afe62013-08-12 16:19:28 -07001960 // If the direction vector has two non-zero components, we try pushing
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001961 // separately in each of the components.
1962 int temp = direction[1];
1963 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001964
1965 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001966 ignoreView, solution)) {
1967 return true;
1968 }
1969 direction[1] = temp;
1970 temp = direction[0];
1971 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001972
1973 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001974 ignoreView, solution)) {
1975 return true;
1976 }
1977 // Revert the direction
1978 direction[0] = temp;
1979
1980 // Now we try pushing in each component of the opposite direction
1981 direction[0] *= -1;
1982 direction[1] *= -1;
1983 temp = direction[1];
1984 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001985 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001986 ignoreView, solution)) {
1987 return true;
1988 }
1989
1990 direction[1] = temp;
1991 temp = direction[0];
1992 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08001993 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001994 ignoreView, solution)) {
1995 return true;
1996 }
1997 // revert the direction
1998 direction[0] = temp;
1999 direction[0] *= -1;
2000 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07002001
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002002 } else {
2003 // If the direction vector has a single non-zero component, we push first in the
2004 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08002005 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002006 ignoreView, solution)) {
2007 return true;
2008 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002009 // Then we try the opposite direction
2010 direction[0] *= -1;
2011 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08002012 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002013 ignoreView, solution)) {
2014 return true;
2015 }
2016 // Switch the direction back
2017 direction[0] *= -1;
2018 direction[1] *= -1;
Winson Chung5f8afe62013-08-12 16:19:28 -07002019
2020 // If we have failed to find a push solution with the above, then we try
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002021 // to find a solution by pushing along the perpendicular axis.
2022
2023 // Swap the components
2024 int temp = direction[1];
2025 direction[1] = direction[0];
2026 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08002027 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002028 ignoreView, solution)) {
2029 return true;
2030 }
2031
2032 // Then we try the opposite direction
2033 direction[0] *= -1;
2034 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08002035 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002036 ignoreView, solution)) {
2037 return true;
2038 }
2039 // Switch the direction back
2040 direction[0] *= -1;
2041 direction[1] *= -1;
2042
2043 // Swap the components back
2044 temp = direction[1];
2045 direction[1] = direction[0];
2046 direction[0] = temp;
2047 }
2048 return false;
2049 }
2050
Adam Cohen482ed822012-03-02 14:15:13 -08002051 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07002052 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07002053 // Return early if get invalid cell positions
2054 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08002055
Adam Cohen8baab352012-03-20 17:39:21 -07002056 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08002057 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08002058
Adam Cohen8baab352012-03-20 17:39:21 -07002059 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08002060 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07002061 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07002062 if (c != null) {
2063 c.x = cellX;
2064 c.y = cellY;
2065 }
Adam Cohen482ed822012-03-02 14:15:13 -08002066 }
Adam Cohen482ed822012-03-02 14:15:13 -08002067 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2068 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07002069 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08002070 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002071 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002072 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002073 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08002074 if (Rect.intersects(r0, r1)) {
2075 if (!lp.canReorder) {
2076 return false;
2077 }
2078 mIntersectingViews.add(child);
2079 }
2080 }
Adam Cohen47a876d2012-03-19 13:21:41 -07002081
Winson Chung5f8afe62013-08-12 16:19:28 -07002082 // First we try to find a solution which respects the push mechanic. That is,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002083 // we try to find a solution such that no displaced item travels through another item
2084 // without also displacing that item.
2085 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07002086 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07002087 return true;
2088 }
Adam Cohen47a876d2012-03-19 13:21:41 -07002089
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002090 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08002091 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07002092 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002093 return true;
2094 }
Adam Cohen47a876d2012-03-19 13:21:41 -07002095
Adam Cohen482ed822012-03-02 14:15:13 -08002096 // Ok, they couldn't move as a block, let's move them individually
2097 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07002098 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002099 return false;
2100 }
2101 }
2102 return true;
2103 }
2104
2105 /*
2106 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
2107 * the provided point and the provided cell
2108 */
Adam Cohen47a876d2012-03-19 13:21:41 -07002109 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08002110 double angle = Math.atan(((float) deltaY) / deltaX);
2111
2112 result[0] = 0;
2113 result[1] = 0;
2114 if (Math.abs(Math.cos(angle)) > 0.5f) {
2115 result[0] = (int) Math.signum(deltaX);
2116 }
2117 if (Math.abs(Math.sin(angle)) > 0.5f) {
2118 result[1] = (int) Math.signum(deltaY);
2119 }
2120 }
2121
Adam Cohen8baab352012-03-20 17:39:21 -07002122 private void copyOccupiedArray(boolean[][] occupied) {
2123 for (int i = 0; i < mCountX; i++) {
2124 for (int j = 0; j < mCountY; j++) {
2125 occupied[i][j] = mOccupied[i][j];
2126 }
2127 }
2128 }
2129
Adam Cohen482ed822012-03-02 14:15:13 -08002130 ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
2131 int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07002132 // Copy the current state into the solution. This solution will be manipulated as necessary.
2133 copyCurrentStateToSolution(solution, false);
2134 // Copy the current occupied array into the temporary occupied array. This array will be
2135 // manipulated as necessary to find a solution.
2136 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08002137
2138 // We find the nearest cell into which we would place the dragged item, assuming there's
2139 // nothing in its way.
2140 int result[] = new int[2];
2141 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2142
2143 boolean success = false;
2144 // First we try the exact nearest position of the item being dragged,
2145 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07002146 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
2147 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002148
2149 if (!success) {
2150 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
2151 // x, then 1 in y etc.
2152 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
2153 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
2154 dragView, false, solution);
2155 } else if (spanY > minSpanY) {
2156 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
2157 dragView, true, solution);
2158 }
2159 solution.isSolution = false;
2160 } else {
2161 solution.isSolution = true;
2162 solution.dragViewX = result[0];
2163 solution.dragViewY = result[1];
2164 solution.dragViewSpanX = spanX;
2165 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002166 }
2167 return solution;
2168 }
2169
2170 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002171 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002172 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002173 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002174 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002175 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08002176 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07002177 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002178 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07002179 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002180 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002181 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08002182 }
2183 }
2184
2185 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
2186 for (int i = 0; i < mCountX; i++) {
2187 for (int j = 0; j < mCountY; j++) {
2188 mTmpOccupied[i][j] = false;
2189 }
2190 }
2191
Michael Jurkaa52570f2012-03-20 03:18:20 -07002192 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002193 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002194 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002195 if (child == dragView) continue;
2196 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002197 CellAndSpan c = solution.map.get(child);
2198 if (c != null) {
2199 lp.tmpCellX = c.x;
2200 lp.tmpCellY = c.y;
2201 lp.cellHSpan = c.spanX;
2202 lp.cellVSpan = c.spanY;
2203 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002204 }
2205 }
2206 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2207 solution.dragViewSpanY, mTmpOccupied, true);
2208 }
2209
2210 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2211 commitDragView) {
2212
2213 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2214 for (int i = 0; i < mCountX; i++) {
2215 for (int j = 0; j < mCountY; j++) {
2216 occupied[i][j] = false;
2217 }
2218 }
2219
Michael Jurkaa52570f2012-03-20 03:18:20 -07002220 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002221 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002222 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002223 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002224 CellAndSpan c = solution.map.get(child);
2225 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002226 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2227 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002228 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002229 }
2230 }
2231 if (commitDragView) {
2232 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2233 solution.dragViewSpanY, occupied, true);
2234 }
2235 }
2236
Adam Cohen19f37922012-03-21 11:59:11 -07002237 // This method starts or changes the reorder hint animations
2238 private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
2239 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002240 for (int i = 0; i < childCount; i++) {
2241 View child = mShortcutsAndWidgets.getChildAt(i);
2242 if (child == dragView) continue;
2243 CellAndSpan c = solution.map.get(child);
2244 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2245 if (c != null) {
2246 ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
2247 c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002248 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002249 }
2250 }
2251 }
2252
2253 // Class which represents the reorder hint animations. These animations show that an item is
2254 // in a temporary state, and hint at where the item will return to.
2255 class ReorderHintAnimation {
2256 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002257 float finalDeltaX;
2258 float finalDeltaY;
2259 float initDeltaX;
2260 float initDeltaY;
2261 float finalScale;
2262 float initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002263 private static final int DURATION = 300;
Adam Cohene7587d22012-05-24 18:50:02 -07002264 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002265
2266 public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
2267 int spanX, int spanY) {
2268 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2269 final int x0 = mTmpPoint[0];
2270 final int y0 = mTmpPoint[1];
2271 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2272 final int x1 = mTmpPoint[0];
2273 final int y1 = mTmpPoint[1];
2274 final int dX = x1 - x0;
2275 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002276 finalDeltaX = 0;
2277 finalDeltaY = 0;
Adam Cohen19f37922012-03-21 11:59:11 -07002278 if (dX == dY && dX == 0) {
2279 } else {
2280 if (dY == 0) {
Adam Cohend024f982012-05-23 18:26:45 -07002281 finalDeltaX = - Math.signum(dX) * mReorderHintAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002282 } else if (dX == 0) {
Adam Cohend024f982012-05-23 18:26:45 -07002283 finalDeltaY = - Math.signum(dY) * mReorderHintAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002284 } else {
2285 double angle = Math.atan( (float) (dY) / dX);
Adam Cohend024f982012-05-23 18:26:45 -07002286 finalDeltaX = (int) (- Math.signum(dX) *
Adam Cohenfe41ac62012-05-23 14:00:37 -07002287 Math.abs(Math.cos(angle) * mReorderHintAnimationMagnitude));
Adam Cohend024f982012-05-23 18:26:45 -07002288 finalDeltaY = (int) (- Math.signum(dY) *
Adam Cohenfe41ac62012-05-23 14:00:37 -07002289 Math.abs(Math.sin(angle) * mReorderHintAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002290 }
2291 }
Adam Cohend024f982012-05-23 18:26:45 -07002292 initDeltaX = child.getTranslationX();
2293 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002294 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002295 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002296 this.child = child;
2297 }
2298
Adam Cohend024f982012-05-23 18:26:45 -07002299 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002300 if (mShakeAnimators.containsKey(child)) {
2301 ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002302 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002303 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002304 if (finalDeltaX == 0 && finalDeltaY == 0) {
2305 completeAnimationImmediately();
2306 return;
2307 }
Adam Cohen19f37922012-03-21 11:59:11 -07002308 }
Adam Cohend024f982012-05-23 18:26:45 -07002309 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002310 return;
2311 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002312 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002313 a = va;
Adam Cohen19f37922012-03-21 11:59:11 -07002314 va.setRepeatMode(ValueAnimator.REVERSE);
2315 va.setRepeatCount(ValueAnimator.INFINITE);
Adam Cohen7bdfc972012-05-22 16:50:35 -07002316 va.setDuration(DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002317 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002318 va.addUpdateListener(new AnimatorUpdateListener() {
2319 @Override
2320 public void onAnimationUpdate(ValueAnimator animation) {
2321 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohend024f982012-05-23 18:26:45 -07002322 float x = r * finalDeltaX + (1 - r) * initDeltaX;
2323 float y = r * finalDeltaY + (1 - r) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002324 child.setTranslationX(x);
2325 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002326 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002327 child.setScaleX(s);
2328 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002329 }
2330 });
2331 va.addListener(new AnimatorListenerAdapter() {
2332 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002333 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002334 initDeltaX = 0;
2335 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002336 initScale = getChildrenScale();
Adam Cohen19f37922012-03-21 11:59:11 -07002337 }
2338 });
Adam Cohen19f37922012-03-21 11:59:11 -07002339 mShakeAnimators.put(child, this);
2340 va.start();
2341 }
2342
Adam Cohend024f982012-05-23 18:26:45 -07002343 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002344 if (a != null) {
2345 a.cancel();
2346 }
Adam Cohen19f37922012-03-21 11:59:11 -07002347 }
Adam Cohene7587d22012-05-24 18:50:02 -07002348
Brandon Keely50e6e562012-05-08 16:28:49 -07002349 private void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002350 if (a != null) {
2351 a.cancel();
2352 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002353
Michael Jurka2ecf9952012-06-18 12:52:28 -07002354 AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002355 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002356 s.playTogether(
Adam Cohen307fe232012-08-16 17:55:58 -07002357 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
2358 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
Michael Jurka2ecf9952012-06-18 12:52:28 -07002359 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2360 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002361 );
2362 s.setDuration(REORDER_ANIMATION_DURATION);
2363 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2364 s.start();
2365 }
Adam Cohen19f37922012-03-21 11:59:11 -07002366 }
2367
2368 private void completeAndClearReorderHintAnimations() {
2369 for (ReorderHintAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002370 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002371 }
2372 mShakeAnimators.clear();
2373 }
2374
Adam Cohen482ed822012-03-02 14:15:13 -08002375 private void commitTempPlacement() {
2376 for (int i = 0; i < mCountX; i++) {
2377 for (int j = 0; j < mCountY; j++) {
2378 mOccupied[i][j] = mTmpOccupied[i][j];
2379 }
2380 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002381 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002382 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002383 View child = mShortcutsAndWidgets.getChildAt(i);
2384 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2385 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002386 // We do a null check here because the item info can be null in the case of the
2387 // AllApps button in the hotseat.
2388 if (info != null) {
Adam Cohen487f7dd2012-06-28 18:12:10 -07002389 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
2390 info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
2391 info.requiresDbUpdate = true;
2392 }
Adam Cohen2acce882012-03-28 19:03:19 -07002393 info.cellX = lp.cellX = lp.tmpCellX;
2394 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002395 info.spanX = lp.cellHSpan;
2396 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002397 }
Adam Cohen482ed822012-03-02 14:15:13 -08002398 }
Adam Cohen2acce882012-03-28 19:03:19 -07002399 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002400 }
2401
2402 public void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002403 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002404 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002405 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002406 lp.useTmpCoords = useTempCoords;
2407 }
2408 }
2409
Adam Cohen482ed822012-03-02 14:15:13 -08002410 ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
2411 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2412 int[] result = new int[2];
2413 int[] resultSpan = new int[2];
2414 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
2415 resultSpan);
2416 if (result[0] >= 0 && result[1] >= 0) {
2417 copyCurrentStateToSolution(solution, false);
2418 solution.dragViewX = result[0];
2419 solution.dragViewY = result[1];
2420 solution.dragViewSpanX = resultSpan[0];
2421 solution.dragViewSpanY = resultSpan[1];
2422 solution.isSolution = true;
2423 } else {
2424 solution.isSolution = false;
2425 }
2426 return solution;
2427 }
2428
2429 public void prepareChildForDrag(View child) {
2430 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002431 }
2432
Adam Cohen19f37922012-03-21 11:59:11 -07002433 /* This seems like it should be obvious and straight-forward, but when the direction vector
2434 needs to match with the notion of the dragView pushing other views, we have to employ
2435 a slightly more subtle notion of the direction vector. The question is what two points is
2436 the vector between? The center of the dragView and its desired destination? Not quite, as
2437 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2438 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2439 or right, which helps make pushing feel right.
2440 */
2441 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2442 int spanY, View dragView, int[] resultDirection) {
2443 int[] targetDestination = new int[2];
2444
2445 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2446 Rect dragRect = new Rect();
2447 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2448 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2449
2450 Rect dropRegionRect = new Rect();
2451 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2452 dragView, dropRegionRect, mIntersectingViews);
2453
2454 int dropRegionSpanX = dropRegionRect.width();
2455 int dropRegionSpanY = dropRegionRect.height();
2456
2457 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2458 dropRegionRect.height(), dropRegionRect);
2459
2460 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2461 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2462
2463 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2464 deltaX = 0;
2465 }
2466 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2467 deltaY = 0;
2468 }
2469
2470 if (deltaX == 0 && deltaY == 0) {
2471 // No idea what to do, give a random direction.
2472 resultDirection[0] = 1;
2473 resultDirection[1] = 0;
2474 } else {
2475 computeDirectionVector(deltaX, deltaY, resultDirection);
2476 }
2477 }
2478
2479 // For a given cell and span, fetch the set of views intersecting the region.
2480 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2481 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2482 if (boundingRect != null) {
2483 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2484 }
2485 intersectingViews.clear();
2486 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2487 Rect r1 = new Rect();
2488 final int count = mShortcutsAndWidgets.getChildCount();
2489 for (int i = 0; i < count; i++) {
2490 View child = mShortcutsAndWidgets.getChildAt(i);
2491 if (child == dragView) continue;
2492 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2493 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2494 if (Rect.intersects(r0, r1)) {
2495 mIntersectingViews.add(child);
2496 if (boundingRect != null) {
2497 boundingRect.union(r1);
2498 }
2499 }
2500 }
2501 }
2502
2503 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2504 View dragView, int[] result) {
2505 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2506 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2507 mIntersectingViews);
2508 return !mIntersectingViews.isEmpty();
2509 }
2510
2511 void revertTempState() {
2512 if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
2513 final int count = mShortcutsAndWidgets.getChildCount();
2514 for (int i = 0; i < count; i++) {
2515 View child = mShortcutsAndWidgets.getChildAt(i);
2516 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2517 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2518 lp.tmpCellX = lp.cellX;
2519 lp.tmpCellY = lp.cellY;
2520 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2521 0, false, false);
2522 }
2523 }
2524 completeAndClearReorderHintAnimations();
2525 setItemPlacementDirty(false);
2526 }
2527
Adam Cohenbebf0422012-04-11 18:06:28 -07002528 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2529 View dragView, int[] direction, boolean commit) {
2530 int[] pixelXY = new int[2];
2531 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2532
2533 // First we determine if things have moved enough to cause a different layout
2534 ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY,
2535 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2536
2537 setUseTempCoords(true);
2538 if (swapSolution != null && swapSolution.isSolution) {
2539 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2540 // committing anything or animating anything as we just want to determine if a solution
2541 // exists
2542 copySolutionToTempState(swapSolution, dragView);
2543 setItemPlacementDirty(true);
2544 animateItemsToSolution(swapSolution, dragView, commit);
2545
2546 if (commit) {
2547 commitTempPlacement();
2548 completeAndClearReorderHintAnimations();
2549 setItemPlacementDirty(false);
2550 } else {
2551 beginOrAdjustHintAnimations(swapSolution, dragView,
2552 REORDER_ANIMATION_DURATION);
2553 }
2554 mShortcutsAndWidgets.requestLayout();
2555 }
2556 return swapSolution.isSolution;
2557 }
2558
Adam Cohen482ed822012-03-02 14:15:13 -08002559 int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
2560 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002561 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002562 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002563
2564 if (resultSpan == null) {
2565 resultSpan = new int[2];
2566 }
2567
Adam Cohen19f37922012-03-21 11:59:11 -07002568 // When we are checking drop validity or actually dropping, we don't recompute the
2569 // direction vector, since we want the solution to match the preview, and it's possible
2570 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002571 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2572 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002573 mDirectionVector[0] = mPreviousReorderDirection[0];
2574 mDirectionVector[1] = mPreviousReorderDirection[1];
2575 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002576 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2577 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2578 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002579 }
2580 } else {
2581 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2582 mPreviousReorderDirection[0] = mDirectionVector[0];
2583 mPreviousReorderDirection[1] = mDirectionVector[1];
2584 }
2585
Adam Cohen482ed822012-03-02 14:15:13 -08002586 ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
2587 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2588
2589 // We attempt the approach which doesn't shuffle views at all
2590 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2591 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2592
2593 ItemConfiguration finalSolution = null;
2594 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2595 finalSolution = swapSolution;
2596 } else if (noShuffleSolution.isSolution) {
2597 finalSolution = noShuffleSolution;
2598 }
2599
2600 boolean foundSolution = true;
2601 if (!DESTRUCTIVE_REORDER) {
2602 setUseTempCoords(true);
2603 }
2604
2605 if (finalSolution != null) {
2606 result[0] = finalSolution.dragViewX;
2607 result[1] = finalSolution.dragViewY;
2608 resultSpan[0] = finalSolution.dragViewSpanX;
2609 resultSpan[1] = finalSolution.dragViewSpanY;
2610
2611 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2612 // committing anything or animating anything as we just want to determine if a solution
2613 // exists
2614 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2615 if (!DESTRUCTIVE_REORDER) {
2616 copySolutionToTempState(finalSolution, dragView);
2617 }
2618 setItemPlacementDirty(true);
2619 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2620
Adam Cohen19f37922012-03-21 11:59:11 -07002621 if (!DESTRUCTIVE_REORDER &&
2622 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002623 commitTempPlacement();
Adam Cohen19f37922012-03-21 11:59:11 -07002624 completeAndClearReorderHintAnimations();
2625 setItemPlacementDirty(false);
2626 } else {
2627 beginOrAdjustHintAnimations(finalSolution, dragView,
2628 REORDER_ANIMATION_DURATION);
Adam Cohen482ed822012-03-02 14:15:13 -08002629 }
2630 }
2631 } else {
2632 foundSolution = false;
2633 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2634 }
2635
2636 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2637 setUseTempCoords(false);
2638 }
Adam Cohen482ed822012-03-02 14:15:13 -08002639
Michael Jurkaa52570f2012-03-20 03:18:20 -07002640 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002641 return result;
2642 }
2643
Adam Cohen19f37922012-03-21 11:59:11 -07002644 void setItemPlacementDirty(boolean dirty) {
2645 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002646 }
Adam Cohen19f37922012-03-21 11:59:11 -07002647 boolean isItemPlacementDirty() {
2648 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002649 }
2650
2651 private class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002652 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002653 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2654 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohen482ed822012-03-02 14:15:13 -08002655 boolean isSolution = false;
2656 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2657
Adam Cohenf3900c22012-11-16 18:28:11 -08002658 void save() {
2659 // Copy current state into savedMap
2660 for (View v: map.keySet()) {
2661 map.get(v).copy(savedMap.get(v));
2662 }
2663 }
2664
2665 void restore() {
2666 // Restore current state from savedMap
2667 for (View v: savedMap.keySet()) {
2668 savedMap.get(v).copy(map.get(v));
2669 }
2670 }
2671
2672 void add(View v, CellAndSpan cs) {
2673 map.put(v, cs);
2674 savedMap.put(v, new CellAndSpan());
2675 sortedViews.add(v);
2676 }
2677
Adam Cohen482ed822012-03-02 14:15:13 -08002678 int area() {
2679 return dragViewSpanX * dragViewSpanY;
2680 }
Adam Cohen8baab352012-03-20 17:39:21 -07002681 }
2682
2683 private class CellAndSpan {
2684 int x, y;
2685 int spanX, spanY;
2686
Adam Cohenf3900c22012-11-16 18:28:11 -08002687 public CellAndSpan() {
2688 }
2689
2690 public void copy(CellAndSpan copy) {
2691 copy.x = x;
2692 copy.y = y;
2693 copy.spanX = spanX;
2694 copy.spanY = spanY;
2695 }
2696
Adam Cohen8baab352012-03-20 17:39:21 -07002697 public CellAndSpan(int x, int y, int spanX, int spanY) {
2698 this.x = x;
2699 this.y = y;
2700 this.spanX = spanX;
2701 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002702 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002703
2704 public String toString() {
2705 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2706 }
2707
Adam Cohen482ed822012-03-02 14:15:13 -08002708 }
2709
Adam Cohendf035382011-04-11 17:22:04 -07002710 /**
2711 * Find a vacant area that will fit the given bounds nearest the requested
2712 * cell location. Uses Euclidean distance to score multiple vacant areas.
2713 *
2714 * @param pixelX The X location at which you want to search for a vacant area.
2715 * @param pixelY The Y location at which you want to search for a vacant area.
2716 * @param spanX Horizontal span of the object.
2717 * @param spanY Vertical span of the object.
2718 * @param ignoreView Considers space occupied by this view as unoccupied
2719 * @param result Previously returned value to possibly recycle.
2720 * @return The X, Y cell of a vacant area that can contain this object,
2721 * nearest the requested location.
2722 */
2723 int[] findNearestVacantArea(
2724 int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
2725 return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
2726 }
2727
2728 /**
Adam Cohend41fbf52012-02-16 23:53:59 -08002729 * Find a vacant area that will fit the given bounds nearest the requested
2730 * cell location. Uses Euclidean distance to score multiple vacant areas.
2731 *
2732 * @param pixelX The X location at which you want to search for a vacant area.
2733 * @param pixelY The Y location at which you want to search for a vacant area.
2734 * @param minSpanX The minimum horizontal span required
2735 * @param minSpanY The minimum vertical span required
2736 * @param spanX Horizontal span of the object.
2737 * @param spanY Vertical span of the object.
2738 * @param ignoreView Considers space occupied by this view as unoccupied
2739 * @param result Previously returned value to possibly recycle.
2740 * @return The X, Y cell of a vacant area that can contain this object,
2741 * nearest the requested location.
2742 */
2743 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
2744 int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
Adam Cohen482ed822012-03-02 14:15:13 -08002745 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
2746 result, resultSpan, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08002747 }
2748
2749 /**
Adam Cohendf035382011-04-11 17:22:04 -07002750 * Find a starting cell position that will fit the given bounds nearest the requested
2751 * cell location. Uses Euclidean distance to score multiple vacant areas.
2752 *
2753 * @param pixelX The X location at which you want to search for a vacant area.
2754 * @param pixelY The Y location at which you want to search for a vacant area.
2755 * @param spanX Horizontal span of the object.
2756 * @param spanY Vertical span of the object.
2757 * @param ignoreView Considers space occupied by this view as unoccupied
2758 * @param result Previously returned value to possibly recycle.
2759 * @return The X, Y cell of a vacant area that can contain this object,
2760 * nearest the requested location.
2761 */
2762 int[] findNearestArea(
2763 int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2764 return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
2765 }
2766
Michael Jurka0280c3b2010-09-17 15:00:07 -07002767 boolean existsEmptyCell() {
2768 return findCellForSpan(null, 1, 1);
2769 }
2770
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002771 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002772 * Finds the upper-left coordinate of the first rectangle in the grid that can
2773 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2774 * then this method will only return coordinates for rectangles that contain the cell
2775 * (intersectX, intersectY)
2776 *
2777 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2778 * can be found.
2779 * @param spanX The horizontal span of the cell we want to find.
2780 * @param spanY The vertical span of the cell we want to find.
2781 *
2782 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002783 */
Michael Jurka0280c3b2010-09-17 15:00:07 -07002784 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Adam Cohen482ed822012-03-02 14:15:13 -08002785 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002786 }
2787
2788 /**
2789 * Like above, but ignores any cells occupied by the item "ignoreView"
2790 *
2791 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2792 * can be found.
2793 * @param spanX The horizontal span of the cell we want to find.
2794 * @param spanY The vertical span of the cell we want to find.
2795 * @param ignoreView The home screen item we should treat as not occupying any space
2796 * @return
2797 */
2798 boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
Adam Cohen482ed822012-03-02 14:15:13 -08002799 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
2800 ignoreView, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002801 }
2802
2803 /**
2804 * Like above, but if intersectX and intersectY are not -1, then this method will try to
2805 * return coordinates for rectangles that contain the cell [intersectX, intersectY]
2806 *
2807 * @param spanX The horizontal span of the cell we want to find.
2808 * @param spanY The vertical span of the cell we want to find.
2809 * @param ignoreView The home screen item we should treat as not occupying any space
2810 * @param intersectX The X coordinate of the cell that we should try to overlap
2811 * @param intersectX The Y coordinate of the cell that we should try to overlap
2812 *
2813 * @return True if a vacant cell of the specified dimension was found, false otherwise.
2814 */
2815 boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
2816 int intersectX, int intersectY) {
2817 return findCellForSpanThatIntersectsIgnoring(
Adam Cohen482ed822012-03-02 14:15:13 -08002818 cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002819 }
2820
2821 /**
2822 * The superset of the above two methods
2823 */
2824 boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002825 int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002826 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08002827 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002828
Michael Jurka28750fb2010-09-24 17:43:49 -07002829 boolean foundCell = false;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002830 while (true) {
2831 int startX = 0;
2832 if (intersectX >= 0) {
2833 startX = Math.max(startX, intersectX - (spanX - 1));
2834 }
2835 int endX = mCountX - (spanX - 1);
2836 if (intersectX >= 0) {
2837 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
2838 }
2839 int startY = 0;
2840 if (intersectY >= 0) {
2841 startY = Math.max(startY, intersectY - (spanY - 1));
2842 }
2843 int endY = mCountY - (spanY - 1);
2844 if (intersectY >= 0) {
2845 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
2846 }
2847
Winson Chungbbc60d82010-11-11 16:34:41 -08002848 for (int y = startY; y < endY && !foundCell; y++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002849 inner:
Winson Chungbbc60d82010-11-11 16:34:41 -08002850 for (int x = startX; x < endX; x++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002851 for (int i = 0; i < spanX; i++) {
2852 for (int j = 0; j < spanY; j++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002853 if (occupied[x + i][y + j]) {
Winson Chungbbc60d82010-11-11 16:34:41 -08002854 // small optimization: we can skip to after the column we just found
Michael Jurka0280c3b2010-09-17 15:00:07 -07002855 // an occupied cell
Winson Chungbbc60d82010-11-11 16:34:41 -08002856 x += i;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002857 continue inner;
2858 }
2859 }
2860 }
2861 if (cellXY != null) {
2862 cellXY[0] = x;
2863 cellXY[1] = y;
2864 }
Michael Jurka28750fb2010-09-24 17:43:49 -07002865 foundCell = true;
2866 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002867 }
2868 }
2869 if (intersectX == -1 && intersectY == -1) {
2870 break;
2871 } else {
2872 // if we failed to find anything, try again but without any requirements of
2873 // intersecting
2874 intersectX = -1;
2875 intersectY = -1;
2876 continue;
2877 }
2878 }
2879
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002880 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08002881 markCellsAsOccupiedForView(ignoreView, occupied);
Michael Jurka28750fb2010-09-24 17:43:49 -07002882 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002883 }
2884
2885 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002886 * A drag event has begun over this layout.
2887 * It may have begun over this layout (in which case onDragChild is called first),
2888 * or it may have begun on another layout.
2889 */
2890 void onDragEnter() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002891 mDragEnforcer.onDragEnter();
Winson Chungc07918d2011-07-01 15:35:26 -07002892 mDragging = true;
2893 }
2894
2895 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002896 * Called when drag has left this CellLayout or has been completed (successfully or not)
2897 */
2898 void onDragExit() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002899 mDragEnforcer.onDragExit();
Joe Onorato4be866d2010-10-10 11:26:02 -07002900 // This can actually be called when we aren't in a drag, e.g. when adding a new
2901 // item to this layout via the customize drawer.
2902 // Guard against that case.
2903 if (mDragging) {
2904 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002905 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002906
2907 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002908 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002909 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2910 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002911 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002912 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002913 }
2914
2915 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002916 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002917 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002918 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002919 *
2920 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002921 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002922 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002923 if (child != null) {
2924 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002925 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002926 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002927 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002928 }
2929
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002930 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002931 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002932 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002933 * @param cellX X coordinate of upper left corner expressed as a cell position
2934 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002935 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002936 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002937 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002938 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002939 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002940 final int cellWidth = mCellWidth;
2941 final int cellHeight = mCellHeight;
2942 final int widthGap = mWidthGap;
2943 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002944
Winson Chung4b825dcd2011-06-19 12:41:22 -07002945 final int hStartPadding = getPaddingLeft();
2946 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002947
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002948 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2949 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2950
2951 int x = hStartPadding + cellX * (cellWidth + widthGap);
2952 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002953
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002954 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002955 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002956
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002957 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002958 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002959 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002960 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002961 * @param width Width in pixels
2962 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002963 * @param result An array of length 2 in which to store the result (may be null).
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002964 */
Winson Chung66700732013-08-20 16:56:15 -07002965 public static int[] rectToCell(int width, int height, int[] result) {
Winson Chung5f8afe62013-08-12 16:19:28 -07002966 LauncherAppState app = LauncherAppState.getInstance();
2967 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Winson Chung66700732013-08-20 16:56:15 -07002968 Rect padding = grid.getWorkspacePadding(grid.isLandscape ?
2969 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
Winson Chung5f8afe62013-08-12 16:19:28 -07002970
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002971 // Always assume we're working with the smallest span to make sure we
2972 // reserve enough space in both orientations.
Winson Chung66700732013-08-20 16:56:15 -07002973 int parentWidth = grid.calculateCellWidth(grid.widthPx
2974 - padding.left - padding.right, (int) grid.numColumns);
2975 int parentHeight = grid.calculateCellHeight(grid.heightPx
2976 - padding.top - padding.bottom, (int) grid.numRows);
2977 int smallerSize = Math.min(parentWidth, parentHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002978
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002979 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002980 int spanX = (int) Math.ceil(width / (float) smallerSize);
2981 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002982
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002983 if (result == null) {
2984 return new int[] { spanX, spanY };
2985 }
2986 result[0] = spanX;
2987 result[1] = spanY;
2988 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002989 }
2990
Michael Jurkaf12c75c2011-01-25 22:41:40 -08002991 public int[] cellSpansToSize(int hSpans, int vSpans) {
2992 int[] size = new int[2];
2993 size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
2994 size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
2995 return size;
2996 }
2997
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002998 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08002999 * Calculate the grid spans needed to fit given item
3000 */
3001 public void calculateSpans(ItemInfo info) {
3002 final int minWidth;
3003 final int minHeight;
3004
3005 if (info instanceof LauncherAppWidgetInfo) {
3006 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
3007 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
3008 } else if (info instanceof PendingAddWidgetInfo) {
3009 minWidth = ((PendingAddWidgetInfo) info).minWidth;
3010 minHeight = ((PendingAddWidgetInfo) info).minHeight;
3011 } else {
3012 // It's not a widget, so it must be 1x1
3013 info.spanX = info.spanY = 1;
3014 return;
3015 }
3016 int[] spans = rectToCell(minWidth, minHeight, null);
3017 info.spanX = spans[0];
3018 info.spanY = spans[1];
3019 }
3020
3021 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003022 * Find the first vacant cell, if there is one.
3023 *
3024 * @param vacant Holds the x and y coordinate of the vacant cell
3025 * @param spanX Horizontal cell span.
3026 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -07003027 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003028 * @return True if a vacant cell was found
3029 */
3030 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003031
Michael Jurka0280c3b2010-09-17 15:00:07 -07003032 return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003033 }
3034
3035 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
3036 int xCount, int yCount, boolean[][] occupied) {
3037
Adam Cohen2801caf2011-05-13 20:57:39 -07003038 for (int y = 0; y < yCount; y++) {
3039 for (int x = 0; x < xCount; x++) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003040 boolean available = !occupied[x][y];
3041out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
3042 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
3043 available = available && !occupied[i][j];
3044 if (!available) break out;
3045 }
3046 }
3047
3048 if (available) {
3049 vacant[0] = x;
3050 vacant[1] = y;
3051 return true;
3052 }
3053 }
3054 }
3055
3056 return false;
3057 }
3058
Michael Jurka0280c3b2010-09-17 15:00:07 -07003059 private void clearOccupiedCells() {
3060 for (int x = 0; x < mCountX; x++) {
3061 for (int y = 0; y < mCountY; y++) {
3062 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003063 }
3064 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07003065 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003066
Adam Cohend41fbf52012-02-16 23:53:59 -08003067 public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07003068 markCellsAsUnoccupiedForView(view);
Adam Cohen482ed822012-03-02 14:15:13 -08003069 markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003070 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003071
Adam Cohend4844c32011-02-18 19:25:06 -08003072 public void markCellsAsOccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08003073 markCellsAsOccupiedForView(view, mOccupied);
3074 }
3075 public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07003076 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003077 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08003078 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003079 }
3080
Adam Cohend4844c32011-02-18 19:25:06 -08003081 public void markCellsAsUnoccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08003082 markCellsAsUnoccupiedForView(view, mOccupied);
3083 }
3084 public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07003085 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003086 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08003087 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003088 }
3089
Adam Cohen482ed822012-03-02 14:15:13 -08003090 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
3091 boolean value) {
3092 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003093 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
3094 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08003095 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003096 }
3097 }
3098 }
3099
Adam Cohen2801caf2011-05-13 20:57:39 -07003100 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07003101 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07003102 (Math.max((mCountX - 1), 0) * mWidthGap);
3103 }
3104
3105 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07003106 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07003107 (Math.max((mCountY - 1), 0) * mHeightGap);
3108 }
3109
Michael Jurka66d72172011-04-12 16:29:25 -07003110 public boolean isOccupied(int x, int y) {
3111 if (x < mCountX && y < mCountY) {
3112 return mOccupied[x][y];
3113 } else {
3114 throw new RuntimeException("Position exceeds the bound of this CellLayout");
3115 }
3116 }
3117
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003118 @Override
3119 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
3120 return new CellLayout.LayoutParams(getContext(), attrs);
3121 }
3122
3123 @Override
3124 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
3125 return p instanceof CellLayout.LayoutParams;
3126 }
3127
3128 @Override
3129 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
3130 return new CellLayout.LayoutParams(p);
3131 }
3132
Winson Chungaafa03c2010-06-11 17:34:16 -07003133 public static class CellLayoutAnimationController extends LayoutAnimationController {
3134 public CellLayoutAnimationController(Animation animation, float delay) {
3135 super(animation, delay);
3136 }
3137
3138 @Override
3139 protected long getDelayForView(View view) {
3140 return (int) (Math.random() * 150);
3141 }
3142 }
3143
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003144 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
3145 /**
3146 * Horizontal location of the item in the grid.
3147 */
3148 @ViewDebug.ExportedProperty
3149 public int cellX;
3150
3151 /**
3152 * Vertical location of the item in the grid.
3153 */
3154 @ViewDebug.ExportedProperty
3155 public int cellY;
3156
3157 /**
Adam Cohen482ed822012-03-02 14:15:13 -08003158 * Temporary horizontal location of the item in the grid during reorder
3159 */
3160 public int tmpCellX;
3161
3162 /**
3163 * Temporary vertical location of the item in the grid during reorder
3164 */
3165 public int tmpCellY;
3166
3167 /**
3168 * Indicates that the temporary coordinates should be used to layout the items
3169 */
3170 public boolean useTmpCoords;
3171
3172 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003173 * Number of cells spanned horizontally by the item.
3174 */
3175 @ViewDebug.ExportedProperty
3176 public int cellHSpan;
3177
3178 /**
3179 * Number of cells spanned vertically by the item.
3180 */
3181 @ViewDebug.ExportedProperty
3182 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07003183
Adam Cohen1b607ed2011-03-03 17:26:50 -08003184 /**
3185 * Indicates whether the item will set its x, y, width and height parameters freely,
3186 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
3187 */
Adam Cohend4844c32011-02-18 19:25:06 -08003188 public boolean isLockedToGrid = true;
3189
Adam Cohen482ed822012-03-02 14:15:13 -08003190 /**
Adam Cohenec40b2b2013-07-23 15:52:40 -07003191 * Indicates that this item should use the full extents of its parent.
3192 */
3193 public boolean isFullscreen = false;
3194
3195 /**
Adam Cohen482ed822012-03-02 14:15:13 -08003196 * Indicates whether this item can be reordered. Always true except in the case of the
3197 * the AllApps button.
3198 */
3199 public boolean canReorder = true;
3200
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003201 // X coordinate of the view in the layout.
3202 @ViewDebug.ExportedProperty
3203 int x;
3204 // Y coordinate of the view in the layout.
3205 @ViewDebug.ExportedProperty
3206 int y;
3207
Romain Guy84f296c2009-11-04 15:00:44 -08003208 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07003209
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003210 public LayoutParams(Context c, AttributeSet attrs) {
3211 super(c, attrs);
3212 cellHSpan = 1;
3213 cellVSpan = 1;
3214 }
3215
3216 public LayoutParams(ViewGroup.LayoutParams source) {
3217 super(source);
3218 cellHSpan = 1;
3219 cellVSpan = 1;
3220 }
Winson Chungaafa03c2010-06-11 17:34:16 -07003221
3222 public LayoutParams(LayoutParams source) {
3223 super(source);
3224 this.cellX = source.cellX;
3225 this.cellY = source.cellY;
3226 this.cellHSpan = source.cellHSpan;
3227 this.cellVSpan = source.cellVSpan;
3228 }
3229
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003230 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08003231 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003232 this.cellX = cellX;
3233 this.cellY = cellY;
3234 this.cellHSpan = cellHSpan;
3235 this.cellVSpan = cellVSpan;
3236 }
3237
Adam Cohen2374abf2013-04-16 14:56:57 -07003238 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
3239 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08003240 if (isLockedToGrid) {
3241 final int myCellHSpan = cellHSpan;
3242 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07003243 int myCellX = useTmpCoords ? tmpCellX : cellX;
3244 int myCellY = useTmpCoords ? tmpCellY : cellY;
3245
3246 if (invertHorizontally) {
3247 myCellX = colCount - myCellX - cellHSpan;
3248 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08003249
Adam Cohend4844c32011-02-18 19:25:06 -08003250 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
3251 leftMargin - rightMargin;
3252 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
3253 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08003254 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
3255 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08003256 }
3257 }
Winson Chungaafa03c2010-06-11 17:34:16 -07003258
Winson Chungaafa03c2010-06-11 17:34:16 -07003259 public String toString() {
3260 return "(" + this.cellX + ", " + this.cellY + ")";
3261 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07003262
3263 public void setWidth(int width) {
3264 this.width = width;
3265 }
3266
3267 public int getWidth() {
3268 return width;
3269 }
3270
3271 public void setHeight(int height) {
3272 this.height = height;
3273 }
3274
3275 public int getHeight() {
3276 return height;
3277 }
3278
3279 public void setX(int x) {
3280 this.x = x;
3281 }
3282
3283 public int getX() {
3284 return x;
3285 }
3286
3287 public void setY(int y) {
3288 this.y = y;
3289 }
3290
3291 public int getY() {
3292 return y;
3293 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003294 }
3295
Michael Jurka0280c3b2010-09-17 15:00:07 -07003296 // This class stores info for two purposes:
3297 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
3298 // its spanX, spanY, and the screen it is on
3299 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
3300 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
3301 // the CellLayout that was long clicked
Michael Jurkae5fb0f22011-04-11 13:27:46 -07003302 static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003303 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07003304 int cellX = -1;
3305 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003306 int spanX;
3307 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07003308 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07003309 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003310
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003311 @Override
3312 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07003313 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
3314 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003315 }
3316 }
Michael Jurkad771c962011-08-09 15:00:48 -07003317
3318 public boolean lastDownOnOccupiedCell() {
3319 return mLastDownOnOccupiedCell;
3320 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003321}