blob: c158f413ad85909681454931bd628ef0fc0386a7 [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
Joe Onoratoa5902522009-07-30 13:37:37 -070017package com.android.launcher2;
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;
22import android.animation.ObjectAnimator;
Chet Haase00397b12010-10-07 11:13:10 -070023import android.animation.TimeInterpolator;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070024import android.animation.ValueAnimator;
25import android.animation.ValueAnimator.AnimatorUpdateListener;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080026import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040027import android.content.res.Resources;
Winson Chungaafa03c2010-06-11 17:34:16 -070028import android.content.res.TypedArray;
Joe Onorato4be866d2010-10-10 11:26:02 -070029import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070030import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080031import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070032import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070033import android.graphics.Point;
Adam Cohenb5ba0972011-09-07 18:02:31 -070034import android.graphics.PorterDuff;
35import android.graphics.PorterDuffXfermode;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080036import android.graphics.Rect;
Adam Cohen482ed822012-03-02 14:15:13 -080037import android.graphics.drawable.ColorDrawable;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070038import android.graphics.drawable.Drawable;
Adam Cohenb5ba0972011-09-07 18:02:31 -070039import android.graphics.drawable.NinePatchDrawable;
Adam Cohen1462de32012-07-24 22:34:36 -070040import android.os.Parcelable;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080041import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070042import android.util.Log;
Adam Cohen1462de32012-07-24 22:34:36 -070043import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080044import android.view.MotionEvent;
45import android.view.View;
46import android.view.ViewDebug;
47import android.view.ViewGroup;
Winson Chungaafa03c2010-06-11 17:34:16 -070048import android.view.animation.Animation;
Winson Chung150fbab2010-09-29 17:14:26 -070049import android.view.animation.DecelerateInterpolator;
Winson Chungaafa03c2010-06-11 17:34:16 -070050import android.view.animation.LayoutAnimationController;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080051
Adam Cohen66396872011-04-15 17:50:36 -070052import com.android.launcher.R;
Adam Cohen69ce2e52011-07-03 19:25:21 -070053import com.android.launcher2.FolderIcon.FolderRingAnimator;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070054
Adam Cohen69ce2e52011-07-03 19:25:21 -070055import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070056import java.util.Arrays;
Adam Cohenbfbfd262011-06-13 16:55:12 -070057import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080058import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070059
Michael Jurkabdb5c532011-02-01 15:05:06 -080060public class CellLayout extends ViewGroup {
Winson Chungaafa03c2010-06-11 17:34:16 -070061 static final String TAG = "CellLayout";
62
Adam Cohen2acce882012-03-28 19:03:19 -070063 private Launcher mLauncher;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080064 private int mCellWidth;
65 private int mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070066
Adam Cohend22015c2010-07-26 22:02:18 -070067 private int mCountX;
68 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080069
Adam Cohen234c4cd2011-07-17 21:03:04 -070070 private int mOriginalWidthGap;
71 private int mOriginalHeightGap;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080072 private int mWidthGap;
73 private int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070074 private int mMaxGap;
Adam Cohenebea84d2011-11-09 17:20:41 -080075 private boolean mScrollingTransformsDirty = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080076
77 private final Rect mRect = new Rect();
78 private final CellInfo mCellInfo = new CellInfo();
Winson Chungaafa03c2010-06-11 17:34:16 -070079
Patrick Dubroyde7658b2010-09-27 11:15:43 -070080 // These are temporary variables to prevent having to allocate a new object just to
81 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Winson Chung0be025d2011-05-23 17:45:09 -070082 private final int[] mTmpXY = new int[2];
Patrick Dubroyde7658b2010-09-27 11:15:43 -070083 private final int[] mTmpPoint = new int[2];
Adam Cohen69ce2e52011-07-03 19:25:21 -070084 int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070085
The Android Open Source Project31dd5032009-03-03 19:32:27 -080086 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -080087 boolean[][] mTmpOccupied;
Michael Jurkad771c962011-08-09 15:00:48 -070088 private boolean mLastDownOnOccupiedCell = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080089
Michael Jurkadee05892010-07-27 10:01:56 -070090 private OnTouchListener mInterceptTouchListener;
91
Adam Cohen69ce2e52011-07-03 19:25:21 -070092 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -070093 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -070094
Adam Cohenb5ba0972011-09-07 18:02:31 -070095 private int mForegroundAlpha = 0;
Michael Jurka5f1c5092010-09-03 14:15:02 -070096 private float mBackgroundAlpha;
Adam Cohen1b0aaac2010-10-28 11:11:18 -070097 private float mBackgroundAlphaMultiplier = 1.0f;
Adam Cohenf34bab52010-09-30 14:11:56 -070098
Michael Jurka33945b22010-12-21 18:19:38 -080099 private Drawable mNormalBackground;
Michael Jurka33945b22010-12-21 18:19:38 -0800100 private Drawable mActiveGlowBackground;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700101 private Drawable mOverScrollForegroundDrawable;
102 private Drawable mOverScrollLeft;
103 private Drawable mOverScrollRight;
Michael Jurka18014792010-10-14 09:01:34 -0700104 private Rect mBackgroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700105 private Rect mForegroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700106 private int mForegroundPadding;
Patrick Dubroy1262e362010-10-06 15:49:50 -0700107
Michael Jurka33945b22010-12-21 18:19:38 -0800108 // If we're actively dragging something over this screen, mIsDragOverlapping is true
109 private boolean mIsDragOverlapping = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700110 private final Point mDragCenter = new Point();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700111
Winson Chung150fbab2010-09-29 17:14:26 -0700112 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700113 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohend41fbf52012-02-16 23:53:59 -0800114 private Rect[] mDragOutlines = new Rect[4];
Chet Haase472b2812010-10-14 07:02:04 -0700115 private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700116 private InterruptibleInOutAnimator[] mDragOutlineAnims =
117 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700118
119 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700120 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700121 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700122
Patrick Dubroy96864c32011-03-10 17:17:23 -0800123 private BubbleTextView mPressedOrFocusedIcon;
124
Adam Cohen482ed822012-03-02 14:15:13 -0800125 private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
126 HashMap<CellLayout.LayoutParams, Animator>();
Adam Cohen19f37922012-03-21 11:59:11 -0700127 private HashMap<View, ReorderHintAnimation>
128 mShakeAnimators = new HashMap<View, ReorderHintAnimation>();
129
130 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700131
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700132 // When a drag operation is in progress, holds the nearest cell to the touch point
133 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800134
Joe Onorato4be866d2010-10-10 11:26:02 -0700135 private boolean mDragging = false;
136
Patrick Dubroyce34a972010-10-19 10:34:32 -0700137 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700138 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700139
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800140 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700141 private float mHotseatScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800142
Adam Cohen482ed822012-03-02 14:15:13 -0800143 public static final int MODE_DRAG_OVER = 0;
144 public static final int MODE_ON_DROP = 1;
145 public static final int MODE_ON_DROP_EXTERNAL = 2;
146 public static final int MODE_ACCEPT_DROP = 3;
Adam Cohen19f37922012-03-21 11:59:11 -0700147 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800148 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
149
Adam Cohena897f392012-04-27 18:12:05 -0700150 static final int LANDSCAPE = 0;
151 static final int PORTRAIT = 1;
152
Adam Cohen7bdfc972012-05-22 16:50:35 -0700153 private static final float REORDER_HINT_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700154 private static final int REORDER_ANIMATION_DURATION = 150;
155 private float mReorderHintAnimationMagnitude;
156
Adam Cohen482ed822012-03-02 14:15:13 -0800157 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
158 private Rect mOccupiedRect = new Rect();
159 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700160 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700161 private static final int INVALID_DIRECTION = -100;
Adam Cohenc6cc61d2012-04-04 12:47:08 -0700162 private DropTarget.DragEnforcer mDragEnforcer;
Adam Cohen482ed822012-03-02 14:15:13 -0800163
Romain Guy8a0bff52012-05-06 13:14:33 -0700164 private final static PorterDuffXfermode sAddBlendMode =
165 new PorterDuffXfermode(PorterDuff.Mode.ADD);
Michael Jurkaca993832012-06-29 15:17:04 -0700166 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700167
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800168 public CellLayout(Context context) {
169 this(context, null);
170 }
171
172 public CellLayout(Context context, AttributeSet attrs) {
173 this(context, attrs, 0);
174 }
175
176 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
177 super(context, attrs, defStyle);
Michael Jurka8b805b12012-04-18 14:23:14 -0700178 mDragEnforcer = new DropTarget.DragEnforcer(context);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700179
180 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
181 // the user where a dragged item will land when dropped.
182 setWillNotDraw(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700183 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700184
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800185 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
186
Adam Cohenf4bd5792012-04-27 11:35:29 -0700187 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
188 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700189 mWidthGap = mOriginalWidthGap = a.getDimensionPixelSize(R.styleable.CellLayout_widthGap, 0);
190 mHeightGap = mOriginalHeightGap = a.getDimensionPixelSize(R.styleable.CellLayout_heightGap, 0);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700191 mMaxGap = a.getDimensionPixelSize(R.styleable.CellLayout_maxGap, 0);
Adam Cohend22015c2010-07-26 22:02:18 -0700192 mCountX = LauncherModel.getCellCountX();
193 mCountY = LauncherModel.getCellCountY();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700194 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800195 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700196 mPreviousReorderDirection[0] = INVALID_DIRECTION;
197 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800198
199 a.recycle();
200
201 setAlwaysDrawnWithCacheEnabled(false);
202
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700203 final Resources res = getResources();
Adam Cohen307fe232012-08-16 17:55:58 -0700204 mHotseatScale = (res.getInteger(R.integer.hotseat_item_scale_percentage) / 100f);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700205
Winson Chung967289b2011-06-30 18:09:30 -0700206 mNormalBackground = res.getDrawable(R.drawable.homescreen_blue_normal_holo);
Winson Chungdea74b72011-09-13 18:06:43 -0700207 mActiveGlowBackground = res.getDrawable(R.drawable.homescreen_blue_strong_holo);
Michael Jurka33945b22010-12-21 18:19:38 -0800208
Adam Cohenb5ba0972011-09-07 18:02:31 -0700209 mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
210 mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
211 mForegroundPadding =
212 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
Michael Jurka33945b22010-12-21 18:19:38 -0800213
Adam Cohen19f37922012-03-21 11:59:11 -0700214 mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE *
215 res.getDimensionPixelSize(R.dimen.app_icon_size));
216
Winson Chungb26f3d62011-06-02 10:49:29 -0700217 mNormalBackground.setFilterBitmap(true);
Winson Chungb26f3d62011-06-02 10:49:29 -0700218 mActiveGlowBackground.setFilterBitmap(true);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700219
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700220 // Initialize the data structures used for the drag visualization.
Winson Chung150fbab2010-09-29 17:14:26 -0700221
Patrick Dubroyce34a972010-10-19 10:34:32 -0700222 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700223
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700224
Winson Chungb8c69f32011-10-19 21:36:08 -0700225 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700226 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800227 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700228 }
229
230 // When dragging things around the home screens, we show a green outline of
231 // where the item will land. The outlines gradually fade out, leaving a trail
232 // behind the drag path.
233 // Set up all the animations that are used to implement this fading.
234 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700235 final float fromAlphaValue = 0;
236 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700237
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700238 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700239
240 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700241 final InterruptibleInOutAnimator anim =
242 new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700243 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700244 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700245 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700246 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700247 final Bitmap outline = (Bitmap)anim.getTag();
248
249 // If an animation is started and then stopped very quickly, we can still
250 // get spurious updates we've cleared the tag. Guard against this.
251 if (outline == null) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700252 @SuppressWarnings("all") // suppress dead code warning
253 final boolean debug = false;
254 if (debug) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700255 Object val = animation.getAnimatedValue();
256 Log.d(TAG, "anim " + thisIndex + " update: " + val +
257 ", isStopped " + anim.isStopped());
258 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700259 // Try to prevent it from continuing to run
260 animation.cancel();
261 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700262 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800263 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700264 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700265 }
266 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700267 // The animation holds a reference to the drag outline bitmap as long is it's
268 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700269 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700270 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700271 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700272 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700273 anim.setTag(null);
274 }
275 }
276 });
277 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700278 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700279
Michael Jurka18014792010-10-14 09:01:34 -0700280 mBackgroundRect = new Rect();
Adam Cohenb5ba0972011-09-07 18:02:31 -0700281 mForegroundRect = new Rect();
Michael Jurkabea15192010-11-17 12:33:46 -0800282
Michael Jurkaa52570f2012-03-20 03:18:20 -0700283 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
284 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
285 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700286 }
287
Michael Jurkaf6440da2011-04-05 14:50:34 -0700288 static int widthInPortrait(Resources r, int numCells) {
289 // We use this method from Workspace to figure out how many rows/columns Launcher should
290 // have. We ignore the left/right padding on CellLayout because it turns out in our design
291 // the padding extends outside the visible screen size, but it looked fine anyway.
Michael Jurkaf6440da2011-04-05 14:50:34 -0700292 int cellWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700293 int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
294 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
Michael Jurkaf6440da2011-04-05 14:50:34 -0700295
Winson Chung4b825dcd2011-06-19 12:41:22 -0700296 return minGap * (numCells - 1) + cellWidth * numCells;
Michael Jurkaf6440da2011-04-05 14:50:34 -0700297 }
298
Michael Jurkaf6440da2011-04-05 14:50:34 -0700299 static int heightInLandscape(Resources r, int numCells) {
300 // We use this method from Workspace to figure out how many rows/columns Launcher should
301 // have. We ignore the left/right padding on CellLayout because it turns out in our design
302 // the padding extends outside the visible screen size, but it looked fine anyway.
Michael Jurkaf6440da2011-04-05 14:50:34 -0700303 int cellHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700304 int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
305 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
Michael Jurkaf6440da2011-04-05 14:50:34 -0700306
Winson Chung4b825dcd2011-06-19 12:41:22 -0700307 return minGap * (numCells - 1) + cellHeight * numCells;
Michael Jurkaf6440da2011-04-05 14:50:34 -0700308 }
309
Adam Cohen2801caf2011-05-13 20:57:39 -0700310 public void enableHardwareLayers() {
Michael Jurkaca993832012-06-29 15:17:04 -0700311 mShortcutsAndWidgets.setLayerType(LAYER_TYPE_HARDWARE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700312 }
313
314 public void disableHardwareLayers() {
Michael Jurkaca993832012-06-29 15:17:04 -0700315 mShortcutsAndWidgets.setLayerType(LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700316 }
317
318 public void buildHardwareLayer() {
319 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700320 }
321
Adam Cohen307fe232012-08-16 17:55:58 -0700322 public float getChildrenScale() {
323 return mIsHotseat ? mHotseatScale : 1.0f;
324 }
325
Adam Cohen2801caf2011-05-13 20:57:39 -0700326 public void setGridSize(int x, int y) {
327 mCountX = x;
328 mCountY = y;
329 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800330 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700331 mTempRectStack.clear();
Adam Cohen76fc0852011-06-17 13:26:23 -0700332 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700333 }
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
350 mForegroundAlpha = (int) Math.round((r * 255));
351 mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
352 invalidate();
353 }
354
Patrick Dubroy96864c32011-03-10 17:17:23 -0800355 void setPressedOrFocusedIcon(BubbleTextView icon) {
356 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
357 // requires an expanded clip rect (due to the glow's blur radius)
358 BubbleTextView oldIcon = mPressedOrFocusedIcon;
359 mPressedOrFocusedIcon = icon;
360 if (oldIcon != null) {
361 invalidateBubbleTextView(oldIcon);
362 }
363 if (mPressedOrFocusedIcon != null) {
364 invalidateBubbleTextView(mPressedOrFocusedIcon);
365 }
366 }
367
Michael Jurka33945b22010-12-21 18:19:38 -0800368 void setIsDragOverlapping(boolean isDragOverlapping) {
369 if (mIsDragOverlapping != isDragOverlapping) {
370 mIsDragOverlapping = isDragOverlapping;
371 invalidate();
372 }
373 }
374
375 boolean getIsDragOverlapping() {
376 return mIsDragOverlapping;
377 }
378
Adam Cohenebea84d2011-11-09 17:20:41 -0800379 protected void setOverscrollTransformsDirty(boolean dirty) {
380 mScrollingTransformsDirty = dirty;
381 }
382
383 protected void resetOverscrollTransforms() {
384 if (mScrollingTransformsDirty) {
385 setOverscrollTransformsDirty(false);
386 setTranslationX(0);
387 setRotationY(0);
388 // It doesn't matter if we pass true or false here, the important thing is that we
389 // pass 0, which results in the overscroll drawable not being drawn any more.
390 setOverScrollAmount(0, false);
391 setPivotX(getMeasuredWidth() / 2);
392 setPivotY(getMeasuredHeight() / 2);
393 }
394 }
395
Adam Cohen307fe232012-08-16 17:55:58 -0700396 public void scaleRect(Rect r, float scale) {
397 if (scale != 1.0f) {
398 r.left = (int) (r.left * scale + 0.5f);
399 r.top = (int) (r.top * scale + 0.5f);
400 r.right = (int) (r.right * scale + 0.5f);
401 r.bottom = (int) (r.bottom * scale + 0.5f);
402 }
403 }
404
405 Rect temp = new Rect();
406 void scaleRectAboutCenter(Rect in, Rect out, float scale) {
407 int cx = in.centerX();
408 int cy = in.centerY();
409 out.set(in);
410 out.offset(-cx, -cy);
411 scaleRect(out, scale);
412 out.offset(cx, cy);
413 }
414
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700415 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700416 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700417 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
418 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
419 // When we're small, we are either drawn normally or in the "accepts drops" state (during
420 // a drag). However, we also drag the mini hover background *over* one of those two
421 // backgrounds
Winson Chungb26f3d62011-06-02 10:49:29 -0700422 if (mBackgroundAlpha > 0.0f) {
Adam Cohenf34bab52010-09-30 14:11:56 -0700423 Drawable bg;
Michael Jurka33945b22010-12-21 18:19:38 -0800424
425 if (mIsDragOverlapping) {
426 // In the mini case, we draw the active_glow bg *over* the active background
Michael Jurkabdf78552011-10-31 14:34:25 -0700427 bg = mActiveGlowBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700428 } else {
Michael Jurkabdf78552011-10-31 14:34:25 -0700429 bg = mNormalBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700430 }
Michael Jurka33945b22010-12-21 18:19:38 -0800431
432 bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
433 bg.setBounds(mBackgroundRect);
434 bg.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700435 }
Romain Guya6abce82009-11-10 02:54:41 -0800436
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700437 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700438 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700439 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700440 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800441 final Rect r = mDragOutlines[i];
Adam Cohen307fe232012-08-16 17:55:58 -0700442 scaleRectAboutCenter(r, temp, getChildrenScale());
Joe Onorato4be866d2010-10-10 11:26:02 -0700443 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700444 paint.setAlpha((int)(alpha + .5f));
Adam Cohen307fe232012-08-16 17:55:58 -0700445 canvas.drawBitmap(b, null, temp, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700446 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700447 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800448
449 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
450 // requires an expanded clip rect (due to the glow's blur radius)
451 if (mPressedOrFocusedIcon != null) {
452 final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
453 final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
454 if (b != null) {
455 canvas.drawBitmap(b,
Winson Chung4b825dcd2011-06-19 12:41:22 -0700456 mPressedOrFocusedIcon.getLeft() + getPaddingLeft() - padding,
457 mPressedOrFocusedIcon.getTop() + getPaddingTop() - padding,
Patrick Dubroy96864c32011-03-10 17:17:23 -0800458 null);
459 }
460 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700461
Adam Cohen482ed822012-03-02 14:15:13 -0800462 if (DEBUG_VISUALIZE_OCCUPIED) {
463 int[] pt = new int[2];
464 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700465 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800466 for (int i = 0; i < mCountX; i++) {
467 for (int j = 0; j < mCountY; j++) {
468 if (mOccupied[i][j]) {
469 cellToPoint(i, j, pt);
470 canvas.save();
471 canvas.translate(pt[0], pt[1]);
472 cd.draw(canvas);
473 canvas.restore();
474 }
475 }
476 }
477 }
478
Andrew Flynn850d2e72012-04-26 16:51:20 -0700479 int previewOffset = FolderRingAnimator.sPreviewSize;
480
Adam Cohen69ce2e52011-07-03 19:25:21 -0700481 // The folder outer / inner ring image(s)
482 for (int i = 0; i < mFolderOuterRings.size(); i++) {
483 FolderRingAnimator fra = mFolderOuterRings.get(i);
484
485 // Draw outer ring
486 Drawable d = FolderRingAnimator.sSharedOuterRingDrawable;
487 int width = (int) fra.getOuterRingSize();
488 int height = width;
489 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
490
491 int centerX = mTempLocation[0] + mCellWidth / 2;
Andrew Flynn850d2e72012-04-26 16:51:20 -0700492 int centerY = mTempLocation[1] + previewOffset / 2;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700493
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
500 // Draw inner ring
501 d = FolderRingAnimator.sSharedInnerRingDrawable;
502 width = (int) fra.getInnerRingSize();
503 height = width;
504 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
505
506 centerX = mTempLocation[0] + mCellWidth / 2;
Andrew Flynn850d2e72012-04-26 16:51:20 -0700507 centerY = mTempLocation[1] + previewOffset / 2;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700508 canvas.save();
509 canvas.translate(centerX - width / 2, centerY - width / 2);
510 d.setBounds(0, 0, width, height);
511 d.draw(canvas);
512 canvas.restore();
513 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700514
515 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
516 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
517 int width = d.getIntrinsicWidth();
518 int height = d.getIntrinsicHeight();
519
520 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
521 int centerX = mTempLocation[0] + mCellWidth / 2;
Andrew Flynn850d2e72012-04-26 16:51:20 -0700522 int centerY = mTempLocation[1] + previewOffset / 2;
Adam Cohenc51934b2011-07-26 21:07:43 -0700523
524 canvas.save();
525 canvas.translate(centerX - width / 2, centerY - width / 2);
526 d.setBounds(0, 0, width, height);
527 d.draw(canvas);
528 canvas.restore();
529 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700530 }
531
Adam Cohenb5ba0972011-09-07 18:02:31 -0700532 @Override
533 protected void dispatchDraw(Canvas canvas) {
534 super.dispatchDraw(canvas);
535 if (mForegroundAlpha > 0) {
536 mOverScrollForegroundDrawable.setBounds(mForegroundRect);
537 Paint p = ((NinePatchDrawable) mOverScrollForegroundDrawable).getPaint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700538 p.setXfermode(sAddBlendMode);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700539 mOverScrollForegroundDrawable.draw(canvas);
540 p.setXfermode(null);
541 }
542 }
543
Adam Cohen69ce2e52011-07-03 19:25:21 -0700544 public void showFolderAccept(FolderRingAnimator fra) {
545 mFolderOuterRings.add(fra);
546 }
547
548 public void hideFolderAccept(FolderRingAnimator fra) {
549 if (mFolderOuterRings.contains(fra)) {
550 mFolderOuterRings.remove(fra);
551 }
552 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700553 }
554
Adam Cohenc51934b2011-07-26 21:07:43 -0700555 public void setFolderLeaveBehindCell(int x, int y) {
556 mFolderLeaveBehindCell[0] = x;
557 mFolderLeaveBehindCell[1] = y;
558 invalidate();
559 }
560
561 public void clearFolderLeaveBehind() {
562 mFolderLeaveBehindCell[0] = -1;
563 mFolderLeaveBehindCell[1] = -1;
564 invalidate();
565 }
566
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700567 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700568 public boolean shouldDelayChildPressedState() {
569 return false;
570 }
571
Adam Cohen1462de32012-07-24 22:34:36 -0700572 public void restoreInstanceState(SparseArray<Parcelable> states) {
573 dispatchRestoreInstanceState(states);
574 }
575
Michael Jurkae6235dd2011-10-04 15:02:05 -0700576 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700577 public void cancelLongPress() {
578 super.cancelLongPress();
579
580 // Cancel long press for all children
581 final int count = getChildCount();
582 for (int i = 0; i < count; i++) {
583 final View child = getChildAt(i);
584 child.cancelLongPress();
585 }
586 }
587
Michael Jurkadee05892010-07-27 10:01:56 -0700588 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
589 mInterceptTouchListener = listener;
590 }
591
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800592 int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700593 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800594 }
595
596 int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700597 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800598 }
599
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800600 public void setIsHotseat(boolean isHotseat) {
601 mIsHotseat = isHotseat;
602 }
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;
611
Andrew Flynnde38e422012-05-08 11:22:15 -0700612 Resources res = getResources();
613 if (mIsHotseat) {
614 bubbleChild.setTextColor(res.getColor(android.R.color.transparent));
615 } else {
616 bubbleChild.setTextColor(res.getColor(R.color.workspace_icon_text_color));
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800617 }
618 }
619
Adam Cohen307fe232012-08-16 17:55:58 -0700620 child.setScaleX(getChildrenScale());
621 child.setScaleY(getChildrenScale());
622
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800623 // Generate an id for each view, this assumes we have at most 256x256 cells
624 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700625 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700626 // If the horizontal or vertical span is set to -1, it is taken to
627 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700628 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
629 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800630
Winson Chungaafa03c2010-06-11 17:34:16 -0700631 child.setId(childId);
632
Michael Jurkaa52570f2012-03-20 03:18:20 -0700633 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700634
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700635 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700636
Winson Chungaafa03c2010-06-11 17:34:16 -0700637 return true;
638 }
639 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800640 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700641
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800642 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700643 public void removeAllViews() {
644 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700645 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700646 }
647
648 @Override
649 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700650 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700651 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700652 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700653 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700654 }
655
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700656 public void removeViewWithoutMarkingCells(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700657 mShortcutsAndWidgets.removeView(view);
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700658 }
659
Michael Jurka0280c3b2010-09-17 15:00:07 -0700660 @Override
661 public void removeView(View view) {
662 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700663 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700664 }
665
666 @Override
667 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700668 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
669 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700670 }
671
672 @Override
673 public void removeViewInLayout(View view) {
674 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700675 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700676 }
677
678 @Override
679 public void removeViews(int start, int count) {
680 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700681 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700682 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700683 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700684 }
685
686 @Override
687 public void removeViewsInLayout(int start, int count) {
688 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700689 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700690 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700691 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800692 }
693
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800694 @Override
695 protected void onAttachedToWindow() {
696 super.onAttachedToWindow();
697 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
698 }
699
Michael Jurkaaf442092010-06-10 17:01:57 -0700700 public void setTagToCellInfoForPoint(int touchX, int touchY) {
701 final CellInfo cellInfo = mCellInfo;
Winson Chungeecf02d2012-03-02 17:14:58 -0800702 Rect frame = mRect;
Michael Jurka8b805b12012-04-18 14:23:14 -0700703 final int x = touchX + getScrollX();
704 final int y = touchY + getScrollY();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700705 final int count = mShortcutsAndWidgets.getChildCount();
Michael Jurkaaf442092010-06-10 17:01:57 -0700706
707 boolean found = false;
708 for (int i = count - 1; i >= 0; i--) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700709 final View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohend4844c32011-02-18 19:25:06 -0800710 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Michael Jurkaaf442092010-06-10 17:01:57 -0700711
Adam Cohen1b607ed2011-03-03 17:26:50 -0800712 if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
713 lp.isLockedToGrid) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700714 child.getHitRect(frame);
Winson Chung0be025d2011-05-23 17:45:09 -0700715
Winson Chungeecf02d2012-03-02 17:14:58 -0800716 float scale = child.getScaleX();
717 frame = new Rect(child.getLeft(), child.getTop(), child.getRight(),
718 child.getBottom());
Winson Chung0be025d2011-05-23 17:45:09 -0700719 // The child hit rect is relative to the CellLayoutChildren parent, so we need to
720 // offset that by this CellLayout's padding to test an (x,y) point that is relative
721 // to this view.
Michael Jurka8b805b12012-04-18 14:23:14 -0700722 frame.offset(getPaddingLeft(), getPaddingTop());
Winson Chungeecf02d2012-03-02 17:14:58 -0800723 frame.inset((int) (frame.width() * (1f - scale) / 2),
724 (int) (frame.height() * (1f - scale) / 2));
Winson Chung0be025d2011-05-23 17:45:09 -0700725
Michael Jurkaaf442092010-06-10 17:01:57 -0700726 if (frame.contains(x, y)) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700727 cellInfo.cell = child;
728 cellInfo.cellX = lp.cellX;
729 cellInfo.cellY = lp.cellY;
730 cellInfo.spanX = lp.cellHSpan;
731 cellInfo.spanY = lp.cellVSpan;
Michael Jurkaaf442092010-06-10 17:01:57 -0700732 found = true;
Michael Jurkaaf442092010-06-10 17:01:57 -0700733 break;
734 }
735 }
736 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700737
Michael Jurkad771c962011-08-09 15:00:48 -0700738 mLastDownOnOccupiedCell = found;
739
Michael Jurkaaf442092010-06-10 17:01:57 -0700740 if (!found) {
Winson Chung0be025d2011-05-23 17:45:09 -0700741 final int cellXY[] = mTmpXY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700742 pointToCellExact(x, y, cellXY);
743
Michael Jurkaaf442092010-06-10 17:01:57 -0700744 cellInfo.cell = null;
745 cellInfo.cellX = cellXY[0];
746 cellInfo.cellY = cellXY[1];
747 cellInfo.spanX = 1;
748 cellInfo.spanY = 1;
Michael Jurkaaf442092010-06-10 17:01:57 -0700749 }
750 setTag(cellInfo);
751 }
752
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800753 @Override
754 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Cohenc1997fd2011-08-15 18:26:39 -0700755 // First we clear the tag to ensure that on every touch down we start with a fresh slate,
756 // even in the case where we return early. Not clearing here was causing bugs whereby on
757 // long-press we'd end up picking up an item from a previous drag operation.
758 final int action = ev.getAction();
759
760 if (action == MotionEvent.ACTION_DOWN) {
761 clearTagCellInfo();
762 }
763
Michael Jurkadee05892010-07-27 10:01:56 -0700764 if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
765 return true;
766 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800767
768 if (action == MotionEvent.ACTION_DOWN) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700769 setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800770 }
Winson Chungeecf02d2012-03-02 17:14:58 -0800771
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800772 return false;
773 }
774
Adam Cohenc1997fd2011-08-15 18:26:39 -0700775 private void clearTagCellInfo() {
776 final CellInfo cellInfo = mCellInfo;
777 cellInfo.cell = null;
778 cellInfo.cellX = -1;
779 cellInfo.cellY = -1;
780 cellInfo.spanX = 0;
781 cellInfo.spanY = 0;
782 setTag(cellInfo);
783 }
784
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800785 public CellInfo getTag() {
Michael Jurka0280c3b2010-09-17 15:00:07 -0700786 return (CellInfo) super.getTag();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800787 }
788
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700789 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700790 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800791 * @param x X coordinate of the point
792 * @param y Y coordinate of the point
793 * @param result Array of 2 ints to hold the x and y coordinate of the cell
794 */
795 void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700796 final int hStartPadding = getPaddingLeft();
797 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800798
799 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
800 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
801
Adam Cohend22015c2010-07-26 22:02:18 -0700802 final int xAxis = mCountX;
803 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800804
805 if (result[0] < 0) result[0] = 0;
806 if (result[0] >= xAxis) result[0] = xAxis - 1;
807 if (result[1] < 0) result[1] = 0;
808 if (result[1] >= yAxis) result[1] = yAxis - 1;
809 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700810
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800811 /**
812 * Given a point, return the cell that most closely encloses that point
813 * @param x X coordinate of the point
814 * @param y Y coordinate of the point
815 * @param result Array of 2 ints to hold the x and y coordinate of the cell
816 */
817 void pointToCellRounded(int x, int y, int[] result) {
818 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
819 }
820
821 /**
822 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700823 *
824 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800825 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700826 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800827 * @param result Array of 2 ints to hold the x and y coordinate of the point
828 */
829 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700830 final int hStartPadding = getPaddingLeft();
831 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800832
833 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
834 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
835 }
836
Adam Cohene3e27a82011-04-15 12:07:39 -0700837 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800838 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700839 *
840 * @param cellX X coordinate of the cell
841 * @param cellY Y coordinate of the cell
842 *
843 * @param result Array of 2 ints to hold the x and y coordinate of the point
844 */
845 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700846 regionToCenterPoint(cellX, cellY, 1, 1, result);
847 }
848
849 /**
850 * Given a cell coordinate and span return the point that represents the center of the regio
851 *
852 * @param cellX X coordinate of the cell
853 * @param cellY Y coordinate of the cell
854 *
855 * @param result Array of 2 ints to hold the x and y coordinate of the point
856 */
857 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700858 final int hStartPadding = getPaddingLeft();
859 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700860 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
861 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
862 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
863 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700864 }
865
Adam Cohen19f37922012-03-21 11:59:11 -0700866 /**
867 * Given a cell coordinate and span fills out a corresponding pixel rect
868 *
869 * @param cellX X coordinate of the cell
870 * @param cellY Y coordinate of the cell
871 * @param result Rect in which to write the result
872 */
873 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
874 final int hStartPadding = getPaddingLeft();
875 final int vStartPadding = getPaddingTop();
876 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
877 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
878 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
879 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
880 }
881
Adam Cohen482ed822012-03-02 14:15:13 -0800882 public float getDistanceFromCell(float x, float y, int[] cell) {
883 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
884 float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) +
885 Math.pow(y - mTmpPoint[1], 2));
886 return distance;
887 }
888
Romain Guy84f296c2009-11-04 15:00:44 -0800889 int getCellWidth() {
890 return mCellWidth;
891 }
892
893 int getCellHeight() {
894 return mCellHeight;
895 }
896
Adam Cohend4844c32011-02-18 19:25:06 -0800897 int getWidthGap() {
898 return mWidthGap;
899 }
900
901 int getHeightGap() {
902 return mHeightGap;
903 }
904
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700905 Rect getContentRect(Rect r) {
906 if (r == null) {
907 r = new Rect();
908 }
909 int left = getPaddingLeft();
910 int top = getPaddingTop();
Michael Jurka8b805b12012-04-18 14:23:14 -0700911 int right = left + getWidth() - getPaddingLeft() - getPaddingRight();
912 int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom();
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700913 r.set(left, top, right, bottom);
914 return r;
915 }
916
Adam Cohena897f392012-04-27 18:12:05 -0700917 static void getMetrics(Rect metrics, Resources res, int measureWidth, int measureHeight,
918 int countX, int countY, int orientation) {
919 int numWidthGaps = countX - 1;
920 int numHeightGaps = countY - 1;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700921
922 int widthGap;
923 int heightGap;
924 int cellWidth;
925 int cellHeight;
926 int paddingLeft;
927 int paddingRight;
928 int paddingTop;
929 int paddingBottom;
930
Adam Cohena897f392012-04-27 18:12:05 -0700931 int maxGap = res.getDimensionPixelSize(R.dimen.workspace_max_gap);
Adam Cohenf4bd5792012-04-27 11:35:29 -0700932 if (orientation == LANDSCAPE) {
933 cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_land);
934 cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_land);
935 widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_land);
936 heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_land);
937 paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_land);
938 paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_land);
939 paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_land);
940 paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_land);
941 } else {
942 // PORTRAIT
943 cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_port);
944 cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_port);
945 widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_port);
946 heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_port);
947 paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_port);
948 paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_port);
949 paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_port);
950 paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_port);
951 }
952
953 if (widthGap < 0 || heightGap < 0) {
954 int hSpace = measureWidth - paddingLeft - paddingRight;
955 int vSpace = measureHeight - paddingTop - paddingBottom;
Adam Cohena897f392012-04-27 18:12:05 -0700956 int hFreeSpace = hSpace - (countX * cellWidth);
957 int vFreeSpace = vSpace - (countY * cellHeight);
958 widthGap = Math.min(maxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
959 heightGap = Math.min(maxGap, numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Adam Cohenf4bd5792012-04-27 11:35:29 -0700960 }
961 metrics.set(cellWidth, cellHeight, widthGap, heightGap);
962 }
963
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800964 @Override
965 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800966 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700967 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
968
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800969 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
970 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700971
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800972 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
973 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
974 }
975
Adam Cohend22015c2010-07-26 22:02:18 -0700976 int numWidthGaps = mCountX - 1;
977 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800978
Adam Cohen234c4cd2011-07-17 21:03:04 -0700979 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Michael Jurkadd13e3d2012-05-01 12:38:17 -0700980 int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight();
981 int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom();
Adam Cohenf4bd5792012-04-27 11:35:29 -0700982 int hFreeSpace = hSpace - (mCountX * mCellWidth);
983 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700984 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
985 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700986 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700987 } else {
988 mWidthGap = mOriginalWidthGap;
989 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700990 }
Michael Jurka5f1c5092010-09-03 14:15:02 -0700991
Michael Jurka8c920dd2011-01-20 14:16:56 -0800992 // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
993 int newWidth = widthSpecSize;
994 int newHeight = heightSpecSize;
Michael Jurka5f1c5092010-09-03 14:15:02 -0700995 if (widthSpecMode == MeasureSpec.AT_MOST) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700996 newWidth = getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Winson Chungece7f5b2010-10-22 14:54:12 -0700997 ((mCountX - 1) * mWidthGap);
Michael Jurka8b805b12012-04-18 14:23:14 -0700998 newHeight = getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Winson Chungece7f5b2010-10-22 14:54:12 -0700999 ((mCountY - 1) * mHeightGap);
Michael Jurka5f1c5092010-09-03 14:15:02 -07001000 setMeasuredDimension(newWidth, newHeight);
Michael Jurka5f1c5092010-09-03 14:15:02 -07001001 }
Michael Jurka8c920dd2011-01-20 14:16:56 -08001002
1003 int count = getChildCount();
1004 for (int i = 0; i < count; i++) {
1005 View child = getChildAt(i);
Michael Jurka8b805b12012-04-18 14:23:14 -07001006 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() -
1007 getPaddingRight(), MeasureSpec.EXACTLY);
1008 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() -
1009 getPaddingBottom(), MeasureSpec.EXACTLY);
Michael Jurka8c920dd2011-01-20 14:16:56 -08001010 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
1011 }
1012 setMeasuredDimension(newWidth, newHeight);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001013 }
1014
1015 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -07001016 protected void onLayout(boolean changed, int l, int t, int r, int b) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001017 int count = getChildCount();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001018 for (int i = 0; i < count; i++) {
Michael Jurka8c920dd2011-01-20 14:16:56 -08001019 View child = getChildAt(i);
Michael Jurka8b805b12012-04-18 14:23:14 -07001020 child.layout(getPaddingLeft(), getPaddingTop(),
1021 r - l - getPaddingRight(), b - t - getPaddingBottom());
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001022 }
1023 }
1024
1025 @Override
Michael Jurkadee05892010-07-27 10:01:56 -07001026 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1027 super.onSizeChanged(w, h, oldw, oldh);
Michael Jurka18014792010-10-14 09:01:34 -07001028 mBackgroundRect.set(0, 0, w, h);
Adam Cohenb5ba0972011-09-07 18:02:31 -07001029 mForegroundRect.set(mForegroundPadding, mForegroundPadding,
1030 w - 2 * mForegroundPadding, h - 2 * mForegroundPadding);
Michael Jurkadee05892010-07-27 10:01:56 -07001031 }
1032
1033 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001034 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001035 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001036 }
1037
1038 @Override
1039 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001040 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001041 }
1042
Michael Jurka5f1c5092010-09-03 14:15:02 -07001043 public float getBackgroundAlpha() {
1044 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -07001045 }
1046
Adam Cohen1b0aaac2010-10-28 11:11:18 -07001047 public void setBackgroundAlphaMultiplier(float multiplier) {
Michael Jurkaa3d30ad2012-05-08 13:43:43 -07001048 if (mBackgroundAlphaMultiplier != multiplier) {
1049 mBackgroundAlphaMultiplier = multiplier;
1050 invalidate();
1051 }
Adam Cohen1b0aaac2010-10-28 11:11:18 -07001052 }
1053
Adam Cohenddb82192010-11-10 16:32:54 -08001054 public float getBackgroundAlphaMultiplier() {
1055 return mBackgroundAlphaMultiplier;
1056 }
1057
Michael Jurka5f1c5092010-09-03 14:15:02 -07001058 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -08001059 if (mBackgroundAlpha != alpha) {
1060 mBackgroundAlpha = alpha;
1061 invalidate();
1062 }
Michael Jurkadee05892010-07-27 10:01:56 -07001063 }
1064
Michael Jurkaa52570f2012-03-20 03:18:20 -07001065 public void setShortcutAndWidgetAlpha(float alpha) {
Michael Jurka0142d492010-08-25 17:46:15 -07001066 final int childCount = getChildCount();
1067 for (int i = 0; i < childCount; i++) {
Michael Jurkadee05892010-07-27 10:01:56 -07001068 getChildAt(i).setAlpha(alpha);
1069 }
1070 }
1071
Michael Jurkaa52570f2012-03-20 03:18:20 -07001072 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
1073 if (getChildCount() > 0) {
1074 return (ShortcutAndWidgetContainer) getChildAt(0);
1075 }
1076 return null;
1077 }
1078
Patrick Dubroy440c3602010-07-13 17:50:32 -07001079 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001080 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -07001081 }
1082
Adam Cohen76fc0852011-06-17 13:26:23 -07001083 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -08001084 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001085 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -08001086 boolean[][] occupied = mOccupied;
1087 if (!permanent) {
1088 occupied = mTmpOccupied;
1089 }
1090
Adam Cohen19f37922012-03-21 11:59:11 -07001091 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001092 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1093 final ItemInfo info = (ItemInfo) child.getTag();
1094
1095 // We cancel any existing animations
1096 if (mReorderAnimators.containsKey(lp)) {
1097 mReorderAnimators.get(lp).cancel();
1098 mReorderAnimators.remove(lp);
1099 }
1100
Adam Cohen482ed822012-03-02 14:15:13 -08001101 final int oldX = lp.x;
1102 final int oldY = lp.y;
1103 if (adjustOccupied) {
1104 occupied[lp.cellX][lp.cellY] = false;
1105 occupied[cellX][cellY] = true;
1106 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001107 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001108 if (permanent) {
1109 lp.cellX = info.cellX = cellX;
1110 lp.cellY = info.cellY = cellY;
1111 } else {
1112 lp.tmpCellX = cellX;
1113 lp.tmpCellY = cellY;
1114 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001115 clc.setupLp(lp);
1116 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001117 final int newX = lp.x;
1118 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001119
Adam Cohen76fc0852011-06-17 13:26:23 -07001120 lp.x = oldX;
1121 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001122
Adam Cohen482ed822012-03-02 14:15:13 -08001123 // Exit early if we're not actually moving the view
1124 if (oldX == newX && oldY == newY) {
1125 lp.isLockedToGrid = true;
1126 return true;
1127 }
1128
Michael Jurka2ecf9952012-06-18 12:52:28 -07001129 ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001130 va.setDuration(duration);
1131 mReorderAnimators.put(lp, va);
1132
1133 va.addUpdateListener(new AnimatorUpdateListener() {
1134 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001135 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001136 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001137 lp.x = (int) ((1 - r) * oldX + r * newX);
1138 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001139 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001140 }
1141 });
Adam Cohen482ed822012-03-02 14:15:13 -08001142 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001143 boolean cancelled = false;
1144 public void onAnimationEnd(Animator animation) {
1145 // If the animation was cancelled, it means that another animation
1146 // has interrupted this one, and we don't want to lock the item into
1147 // place just yet.
1148 if (!cancelled) {
1149 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001150 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001151 }
1152 if (mReorderAnimators.containsKey(lp)) {
1153 mReorderAnimators.remove(lp);
1154 }
1155 }
1156 public void onAnimationCancel(Animator animation) {
1157 cancelled = true;
1158 }
1159 });
Adam Cohen482ed822012-03-02 14:15:13 -08001160 va.setStartDelay(delay);
1161 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001162 return true;
1163 }
1164 return false;
1165 }
1166
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001167 /**
1168 * Estimate where the top left cell of the dragged item will land if it is dropped.
1169 *
1170 * @param originX The X value of the top left corner of the item
1171 * @param originY The Y value of the top left corner of the item
1172 * @param spanX The number of horizontal cells that the item spans
1173 * @param spanY The number of vertical cells that the item spans
1174 * @param result The estimated drop cell X and Y.
1175 */
1176 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
Adam Cohend22015c2010-07-26 22:02:18 -07001177 final int countX = mCountX;
1178 final int countY = mCountY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001179
Michael Jurkaa63c4522010-08-19 13:52:27 -07001180 // pointToCellRounded takes the top left of a cell but will pad that with
1181 // cellWidth/2 and cellHeight/2 when finding the matching cell
1182 pointToCellRounded(originX, originY, result);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001183
1184 // If the item isn't fully on this screen, snap to the edges
1185 int rightOverhang = result[0] + spanX - countX;
1186 if (rightOverhang > 0) {
1187 result[0] -= rightOverhang; // Snap to right
1188 }
1189 result[0] = Math.max(0, result[0]); // Snap to left
1190 int bottomOverhang = result[1] + spanY - countY;
1191 if (bottomOverhang > 0) {
1192 result[1] -= bottomOverhang; // Snap to bottom
1193 }
1194 result[1] = Math.max(0, result[1]); // Snap to top
1195 }
1196
Adam Cohen482ed822012-03-02 14:15:13 -08001197 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1198 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001199 final int oldDragCellX = mDragCell[0];
1200 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001201
Winson Chungb8c69f32011-10-19 21:36:08 -07001202 if (v != null && dragOffset == null) {
Winson Chunga9abd0e2010-10-27 17:18:37 -07001203 mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
1204 } else {
1205 mDragCenter.set(originX, originY);
1206 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001207
Adam Cohen2801caf2011-05-13 20:57:39 -07001208 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001209 return;
1210 }
1211
Adam Cohen482ed822012-03-02 14:15:13 -08001212 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1213 mDragCell[0] = cellX;
1214 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001215 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001216 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001217 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001218
Joe Onorato4be866d2010-10-10 11:26:02 -07001219 int left = topLeft[0];
1220 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001221
Winson Chungb8c69f32011-10-19 21:36:08 -07001222 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001223 // When drawing the drag outline, it did not account for margin offsets
1224 // added by the view's parent.
1225 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1226 left += lp.leftMargin;
1227 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001228
Adam Cohen99e8b402011-03-25 19:23:43 -07001229 // Offsets due to the size difference between the View and the dragOutline.
1230 // There is a size difference to account for the outer blur, which may lie
1231 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001232 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001233 // We center about the x axis
1234 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1235 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001236 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001237 if (dragOffset != null && dragRegion != null) {
1238 // Center the drag region *horizontally* in the cell and apply a drag
1239 // outline offset
1240 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1241 - dragRegion.width()) / 2;
1242 top += dragOffset.y;
1243 } else {
1244 // Center the drag outline in the cell
1245 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1246 - dragOutline.getWidth()) / 2;
1247 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1248 - dragOutline.getHeight()) / 2;
1249 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001250 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001251 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001252 mDragOutlineAnims[oldIndex].animateOut();
1253 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001254 Rect r = mDragOutlines[mDragOutlineCurrent];
1255 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1256 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001257 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001258 }
Winson Chung150fbab2010-09-29 17:14:26 -07001259
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001260 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1261 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001262 }
1263 }
1264
Adam Cohene0310962011-04-18 16:15:31 -07001265 public void clearDragOutlines() {
1266 final int oldIndex = mDragOutlineCurrent;
1267 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001268 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001269 }
1270
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001271 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001272 * Find a vacant area that will fit the given bounds nearest the requested
1273 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001274 *
Romain Guy51afc022009-05-04 18:03:43 -07001275 * @param pixelX The X location at which you want to search for a vacant area.
1276 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001277 * @param spanX Horizontal span of the object.
1278 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001279 * @param result Array in which to place the result, or null (in which case a new array will
1280 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001281 * @return The X, Y cell of a vacant area that can contain this object,
1282 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001283 */
Adam Cohend41fbf52012-02-16 23:53:59 -08001284 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
1285 int[] result) {
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001286 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001287 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001288
Michael Jurka6a1435d2010-09-27 17:35:12 -07001289 /**
1290 * Find a vacant area that will fit the given bounds nearest the requested
1291 * cell location. Uses Euclidean distance to score multiple vacant areas.
1292 *
1293 * @param pixelX The X location at which you want to search for a vacant area.
1294 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001295 * @param minSpanX The minimum horizontal span required
1296 * @param minSpanY The minimum vertical span required
1297 * @param spanX Horizontal span of the object.
1298 * @param spanY Vertical span of the object.
1299 * @param result Array in which to place the result, or null (in which case a new array will
1300 * be allocated)
1301 * @return The X, Y cell of a vacant area that can contain this object,
1302 * nearest the requested location.
1303 */
1304 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1305 int spanY, int[] result, int[] resultSpan) {
1306 return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
1307 result, resultSpan);
1308 }
1309
1310 /**
1311 * Find a vacant area that will fit the given bounds nearest the requested
1312 * cell location. Uses Euclidean distance to score multiple vacant areas.
1313 *
1314 * @param pixelX The X location at which you want to search for a vacant area.
1315 * @param pixelY The Y location at which you want to search for a vacant area.
Michael Jurka6a1435d2010-09-27 17:35:12 -07001316 * @param spanX Horizontal span of the object.
1317 * @param spanY Vertical span of the object.
Adam Cohendf035382011-04-11 17:22:04 -07001318 * @param ignoreOccupied If true, the result can be an occupied cell
1319 * @param result Array in which to place the result, or null (in which case a new array will
1320 * be allocated)
Michael Jurka6a1435d2010-09-27 17:35:12 -07001321 * @return The X, Y cell of a vacant area that can contain this object,
1322 * nearest the requested location.
1323 */
Adam Cohendf035382011-04-11 17:22:04 -07001324 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
1325 boolean ignoreOccupied, int[] result) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001326 return findNearestArea(pixelX, pixelY, spanX, spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001327 spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08001328 }
1329
1330 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1331 private void lazyInitTempRectStack() {
1332 if (mTempRectStack.isEmpty()) {
1333 for (int i = 0; i < mCountX * mCountY; i++) {
1334 mTempRectStack.push(new Rect());
1335 }
1336 }
1337 }
Adam Cohen482ed822012-03-02 14:15:13 -08001338
Adam Cohend41fbf52012-02-16 23:53:59 -08001339 private void recycleTempRects(Stack<Rect> used) {
1340 while (!used.isEmpty()) {
1341 mTempRectStack.push(used.pop());
1342 }
1343 }
1344
1345 /**
1346 * Find a vacant area that will fit the given bounds nearest the requested
1347 * cell location. Uses Euclidean distance to score multiple vacant areas.
1348 *
1349 * @param pixelX The X location at which you want to search for a vacant area.
1350 * @param pixelY The Y location at which you want to search for a vacant area.
1351 * @param minSpanX The minimum horizontal span required
1352 * @param minSpanY The minimum vertical span required
1353 * @param spanX Horizontal span of the object.
1354 * @param spanY Vertical span of the object.
1355 * @param ignoreOccupied If true, the result can be an occupied cell
1356 * @param result Array in which to place the result, or null (in which case a new array will
1357 * be allocated)
1358 * @return The X, Y cell of a vacant area that can contain this object,
1359 * nearest the requested location.
1360 */
1361 int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001362 View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
1363 boolean[][] occupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001364 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001365 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08001366 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001367
Adam Cohene3e27a82011-04-15 12:07:39 -07001368 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1369 // to the center of the item, but we are searching based on the top-left cell, so
1370 // we translate the point over to correspond to the top-left.
1371 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1372 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1373
Jeff Sharkey70864282009-04-07 21:08:40 -07001374 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001375 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001376 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001377 final Rect bestRect = new Rect(-1, -1, -1, -1);
1378 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001379
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001380 final int countX = mCountX;
1381 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001382
Adam Cohend41fbf52012-02-16 23:53:59 -08001383 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1384 spanX < minSpanX || spanY < minSpanY) {
1385 return bestXY;
1386 }
1387
1388 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001389 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001390 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1391 int ySize = -1;
1392 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001393 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001394 // First, let's see if this thing fits anywhere
1395 for (int i = 0; i < minSpanX; i++) {
1396 for (int j = 0; j < minSpanY; j++) {
Adam Cohendf035382011-04-11 17:22:04 -07001397 if (occupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001398 continue inner;
1399 }
Michael Jurkac28de512010-08-13 11:27:44 -07001400 }
1401 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001402 xSize = minSpanX;
1403 ySize = minSpanY;
1404
1405 // We know that the item will fit at _some_ acceptable size, now let's see
1406 // how big we can make it. We'll alternate between incrementing x and y spans
1407 // until we hit a limit.
1408 boolean incX = true;
1409 boolean hitMaxX = xSize >= spanX;
1410 boolean hitMaxY = ySize >= spanY;
1411 while (!(hitMaxX && hitMaxY)) {
1412 if (incX && !hitMaxX) {
1413 for (int j = 0; j < ySize; j++) {
1414 if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
1415 // We can't move out horizontally
1416 hitMaxX = true;
1417 }
1418 }
1419 if (!hitMaxX) {
1420 xSize++;
1421 }
1422 } else if (!hitMaxY) {
1423 for (int i = 0; i < xSize; i++) {
1424 if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
1425 // We can't move out vertically
1426 hitMaxY = true;
1427 }
1428 }
1429 if (!hitMaxY) {
1430 ySize++;
1431 }
1432 }
1433 hitMaxX |= xSize >= spanX;
1434 hitMaxY |= ySize >= spanY;
1435 incX = !incX;
1436 }
1437 incX = true;
1438 hitMaxX = xSize >= spanX;
1439 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001440 }
Winson Chung0be025d2011-05-23 17:45:09 -07001441 final int[] cellXY = mTmpXY;
Adam Cohene3e27a82011-04-15 12:07:39 -07001442 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001443
Adam Cohend41fbf52012-02-16 23:53:59 -08001444 // We verify that the current rect is not a sub-rect of any of our previous
1445 // candidates. In this case, the current rect is disqualified in favour of the
1446 // containing rect.
1447 Rect currentRect = mTempRectStack.pop();
1448 currentRect.set(x, y, x + xSize, y + ySize);
1449 boolean contained = false;
1450 for (Rect r : validRegions) {
1451 if (r.contains(currentRect)) {
1452 contained = true;
1453 break;
1454 }
1455 }
1456 validRegions.push(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001457 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
1458 + Math.pow(cellXY[1] - pixelY, 2));
Adam Cohen482ed822012-03-02 14:15:13 -08001459
Adam Cohend41fbf52012-02-16 23:53:59 -08001460 if ((distance <= bestDistance && !contained) ||
1461 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001462 bestDistance = distance;
1463 bestXY[0] = x;
1464 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001465 if (resultSpan != null) {
1466 resultSpan[0] = xSize;
1467 resultSpan[1] = ySize;
1468 }
1469 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001470 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001471 }
1472 }
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001473 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08001474 markCellsAsOccupiedForView(ignoreView, occupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001475
Adam Cohenc0dcf592011-06-01 15:30:43 -07001476 // Return -1, -1 if no suitable location found
1477 if (bestDistance == Double.MAX_VALUE) {
1478 bestXY[0] = -1;
1479 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001480 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001481 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001482 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001483 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001484
Adam Cohen482ed822012-03-02 14:15:13 -08001485 /**
1486 * Find a vacant area that will fit the given bounds nearest the requested
1487 * cell location, and will also weigh in a suggested direction vector of the
1488 * desired location. This method computers distance based on unit grid distances,
1489 * not pixel distances.
1490 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001491 * @param cellX The X cell nearest to which you want to search for a vacant area.
1492 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001493 * @param spanX Horizontal span of the object.
1494 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001495 * @param direction The favored direction in which the views should move from x, y
1496 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1497 * matches exactly. Otherwise we find the best matching direction.
1498 * @param occoupied The array which represents which cells in the CellLayout are occupied
1499 * @param blockOccupied The array which represents which cells in the specified block (cellX,
1500 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001501 * @param result Array in which to place the result, or null (in which case a new array will
1502 * be allocated)
1503 * @return The X, Y cell of a vacant area that can contain this object,
1504 * nearest the requested location.
1505 */
1506 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001507 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001508 // Keep track of best-scoring drop area
1509 final int[] bestXY = result != null ? result : new int[2];
1510 float bestDistance = Float.MAX_VALUE;
1511 int bestDirectionScore = Integer.MIN_VALUE;
1512
1513 final int countX = mCountX;
1514 final int countY = mCountY;
1515
1516 for (int y = 0; y < countY - (spanY - 1); y++) {
1517 inner:
1518 for (int x = 0; x < countX - (spanX - 1); x++) {
1519 // First, let's see if this thing fits anywhere
1520 for (int i = 0; i < spanX; i++) {
1521 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001522 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001523 continue inner;
1524 }
1525 }
1526 }
1527
1528 float distance = (float)
1529 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1530 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001531 computeDirectionVector(x - cellX, y - cellY, curDirection);
1532 // The direction score is just the dot product of the two candidate direction
1533 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001534 int curDirectionScore = direction[0] * curDirection[0] +
1535 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001536 boolean exactDirectionOnly = false;
1537 boolean directionMatches = direction[0] == curDirection[0] &&
1538 direction[0] == curDirection[0];
1539 if ((directionMatches || !exactDirectionOnly) &&
1540 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001541 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1542 bestDistance = distance;
1543 bestDirectionScore = curDirectionScore;
1544 bestXY[0] = x;
1545 bestXY[1] = y;
1546 }
1547 }
1548 }
1549
1550 // Return -1, -1 if no suitable location found
1551 if (bestDistance == Float.MAX_VALUE) {
1552 bestXY[0] = -1;
1553 bestXY[1] = -1;
1554 }
1555 return bestXY;
1556 }
1557
Adam Cohen47a876d2012-03-19 13:21:41 -07001558 private int[] findNearestAreaInDirection(int cellX, int cellY, int spanX, int spanY,
1559 int[] direction,boolean[][] occupied,
1560 boolean blockOccupied[][], int[] result) {
1561 // Keep track of best-scoring drop area
1562 final int[] bestXY = result != null ? result : new int[2];
1563 bestXY[0] = -1;
1564 bestXY[1] = -1;
1565 float bestDistance = Float.MAX_VALUE;
1566
1567 // We use this to march in a single direction
Adam Cohen5b53f292012-03-29 14:30:35 -07001568 if ((direction[0] != 0 && direction[1] != 0) ||
1569 (direction[0] == 0 && direction[1] == 0)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001570 return bestXY;
1571 }
1572
1573 // This will only incrememnet one of x or y based on the assertion above
1574 int x = cellX + direction[0];
1575 int y = cellY + direction[1];
1576 while (x >= 0 && x + spanX <= mCountX && y >= 0 && y + spanY <= mCountY) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001577 boolean fail = false;
1578 for (int i = 0; i < spanX; i++) {
1579 for (int j = 0; j < spanY; j++) {
1580 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
1581 fail = true;
1582 }
1583 }
1584 }
1585 if (!fail) {
1586 float distance = (float)
1587 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1588 if (Float.compare(distance, bestDistance) < 0) {
1589 bestDistance = distance;
1590 bestXY[0] = x;
1591 bestXY[1] = y;
1592 }
1593 }
1594 x += direction[0];
1595 y += direction[1];
1596 }
1597 return bestXY;
1598 }
1599
Adam Cohen482ed822012-03-02 14:15:13 -08001600 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001601 int[] direction, ItemConfiguration currentState) {
1602 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001603 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001604 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001605 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1606
Adam Cohen8baab352012-03-20 17:39:21 -07001607 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001608
1609 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001610 c.x = mTempLocation[0];
1611 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001612 success = true;
1613
1614 }
Adam Cohen8baab352012-03-20 17:39:21 -07001615 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001616 return success;
1617 }
1618
Adam Cohena56dc102012-07-13 13:41:42 -07001619 // This method looks in the specified direction to see if there are additional views adjacent
1620 // to the current set of views in the. If there is, then these views are added to the current
1621 // set of views. This is performed iteratively, giving a cascading push behaviour.
Adam Cohen47a876d2012-03-19 13:21:41 -07001622 private boolean addViewInDirection(ArrayList<View> views, Rect boundingRect, int[] direction,
Adam Cohen19f37922012-03-21 11:59:11 -07001623 boolean[][] occupied, View dragView, ItemConfiguration currentState) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001624 boolean found = false;
1625
Michael Jurkaa52570f2012-03-20 03:18:20 -07001626 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen47a876d2012-03-19 13:21:41 -07001627 Rect r0 = new Rect(boundingRect);
1628 Rect r1 = new Rect();
1629
Adam Cohena56dc102012-07-13 13:41:42 -07001630 // First, we consider the rect of the views that we are trying to translate
Adam Cohen47a876d2012-03-19 13:21:41 -07001631 int deltaX = 0;
1632 int deltaY = 0;
1633 if (direction[1] < 0) {
Adam Cohena56dc102012-07-13 13:41:42 -07001634 r0.set(r0.left, r0.top - 1, r0.right, r0.bottom - 1);
Adam Cohen47a876d2012-03-19 13:21:41 -07001635 deltaY = -1;
1636 } else if (direction[1] > 0) {
Adam Cohena56dc102012-07-13 13:41:42 -07001637 r0.set(r0.left, r0.top + 1, r0.right, r0.bottom + 1);
Adam Cohen47a876d2012-03-19 13:21:41 -07001638 deltaY = 1;
1639 } else if (direction[0] < 0) {
Adam Cohena56dc102012-07-13 13:41:42 -07001640 r0.set(r0.left - 1, r0.top, r0.right - 1, r0.bottom);
Adam Cohen47a876d2012-03-19 13:21:41 -07001641 deltaX = -1;
1642 } else if (direction[0] > 0) {
Adam Cohena56dc102012-07-13 13:41:42 -07001643 r0.set(r0.left + 1, r0.top, r0.right + 1, r0.bottom);
Adam Cohen47a876d2012-03-19 13:21:41 -07001644 deltaX = 1;
1645 }
1646
Adam Cohena56dc102012-07-13 13:41:42 -07001647 // Now we see which views, if any, are being overlapped by shifting the current group
1648 // of views in the desired direction.
Adam Cohen47a876d2012-03-19 13:21:41 -07001649 for (int i = 0; i < childCount; i++) {
Adam Cohena56dc102012-07-13 13:41:42 -07001650 // We don't need to worry about views already in our group, or the current drag view.
Michael Jurkaa52570f2012-03-20 03:18:20 -07001651 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen19f37922012-03-21 11:59:11 -07001652 if (views.contains(child) || child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001653 CellAndSpan c = currentState.map.get(child);
Adam Cohen47a876d2012-03-19 13:21:41 -07001654
Adam Cohen8baab352012-03-20 17:39:21 -07001655 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1656 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen47a876d2012-03-19 13:21:41 -07001657 if (Rect.intersects(r0, r1)) {
1658 if (!lp.canReorder) {
1659 return false;
1660 }
Adam Cohena56dc102012-07-13 13:41:42 -07001661 // First we verify that the view in question is at the border of the extents
1662 // of the block of items we are pushing
1663 if ((direction[0] < 0 && c.x == r0.left) ||
1664 (direction[0] > 0 && c.x == r0.right - 1) ||
1665 (direction[1] < 0 && c.y == r0.top) ||
1666 (direction[1] > 0 && c.y == r0.bottom - 1)) {
1667 boolean pushed = false;
1668 // Since the bounding rect is a course description of the region (there can
1669 // be holes at the edge of the block), we need to check to verify that a solid
1670 // piece is intersecting. This ensures that interlocking is possible.
1671 for (int x = c.x; x < c.x + c.spanX; x++) {
1672 for (int y = c.y; y < c.y + c.spanY; y++) {
1673 if (occupied[x - deltaX][y - deltaY]) {
1674 pushed = true;
1675 break;
1676 }
1677 if (pushed) break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001678 }
1679 }
Adam Cohena56dc102012-07-13 13:41:42 -07001680 if (pushed) {
1681 views.add(child);
1682 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1683 found = true;
1684 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001685 }
1686 }
1687 }
1688 return found;
1689 }
1690
Adam Cohen482ed822012-03-02 14:15:13 -08001691 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohen19f37922012-03-21 11:59:11 -07001692 int[] direction, boolean push, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001693 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001694
Adam Cohen8baab352012-03-20 17:39:21 -07001695 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001696 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001697 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001698 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001699 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001700 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001701 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001702 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001703 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001704 }
1705 }
Adam Cohen8baab352012-03-20 17:39:21 -07001706
1707 @SuppressWarnings("unchecked")
1708 ArrayList<View> dup = (ArrayList<View>) views.clone();
1709 // We try and expand the group of views in the direction vector passed, based on
1710 // whether they are physically adjacent, ie. based on "push mechanics".
Adam Cohen19f37922012-03-21 11:59:11 -07001711 while (push && addViewInDirection(dup, boundingRect, direction, mTmpOccupied, dragView,
Adam Cohen8baab352012-03-20 17:39:21 -07001712 currentState)) {
1713 }
1714
1715 // Mark the occupied state as false for the group of views we want to move.
1716 for (View v: dup) {
1717 CellAndSpan c = currentState.map.get(v);
1718 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1719 }
1720
Adam Cohen47a876d2012-03-19 13:21:41 -07001721 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1722 int top = boundingRect.top;
1723 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001724 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001725 // for interlocking.
Adam Cohen8baab352012-03-20 17:39:21 -07001726 for (View v: dup) {
1727 CellAndSpan c = currentState.map.get(v);
1728 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001729 }
1730
Adam Cohen482ed822012-03-02 14:15:13 -08001731 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1732
Adam Cohen8baab352012-03-20 17:39:21 -07001733 if (push) {
1734 findNearestAreaInDirection(boundingRect.left, boundingRect.top, boundingRect.width(),
1735 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
1736 } else {
1737 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1738 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
1739 }
Adam Cohen482ed822012-03-02 14:15:13 -08001740
Adam Cohen8baab352012-03-20 17:39:21 -07001741 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001742 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001743 int deltaX = mTempLocation[0] - boundingRect.left;
1744 int deltaY = mTempLocation[1] - boundingRect.top;
1745 for (View v: dup) {
1746 CellAndSpan c = currentState.map.get(v);
1747 c.x += deltaX;
1748 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001749 }
1750 success = true;
1751 }
Adam Cohen8baab352012-03-20 17:39:21 -07001752
1753 // In either case, we set the occupied array as marked for the location of the views
1754 for (View v: dup) {
1755 CellAndSpan c = currentState.map.get(v);
1756 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001757 }
1758 return success;
1759 }
1760
1761 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1762 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1763 }
1764
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001765 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1766 // to push items in each of the cardinal directions, in an order based on the direction vector
1767 // passed.
1768 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1769 int[] direction, View ignoreView, ItemConfiguration solution) {
1770 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
1771 // If the direction vector has two non-zero components, we try pushing
1772 // separately in each of the components.
1773 int temp = direction[1];
1774 direction[1] = 0;
1775 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1776 ignoreView, solution)) {
1777 return true;
1778 }
1779 direction[1] = temp;
1780 temp = direction[0];
1781 direction[0] = 0;
1782 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1783 ignoreView, solution)) {
1784 return true;
1785 }
1786 // Revert the direction
1787 direction[0] = temp;
1788
1789 // Now we try pushing in each component of the opposite direction
1790 direction[0] *= -1;
1791 direction[1] *= -1;
1792 temp = direction[1];
1793 direction[1] = 0;
1794 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1795 ignoreView, solution)) {
1796 return true;
1797 }
1798
1799 direction[1] = temp;
1800 temp = direction[0];
1801 direction[0] = 0;
1802 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1803 ignoreView, solution)) {
1804 return true;
1805 }
1806 // revert the direction
1807 direction[0] = temp;
1808 direction[0] *= -1;
1809 direction[1] *= -1;
1810
1811 } else {
1812 // If the direction vector has a single non-zero component, we push first in the
1813 // direction of the vector
1814 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1815 ignoreView, solution)) {
1816 return true;
1817 }
1818
1819 // Then we try the opposite direction
1820 direction[0] *= -1;
1821 direction[1] *= -1;
1822 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1823 ignoreView, solution)) {
1824 return true;
1825 }
1826 // Switch the direction back
1827 direction[0] *= -1;
1828 direction[1] *= -1;
1829
1830 // If we have failed to find a push solution with the above, then we try
1831 // to find a solution by pushing along the perpendicular axis.
1832
1833 // Swap the components
1834 int temp = direction[1];
1835 direction[1] = direction[0];
1836 direction[0] = temp;
1837 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1838 ignoreView, solution)) {
1839 return true;
1840 }
1841
1842 // Then we try the opposite direction
1843 direction[0] *= -1;
1844 direction[1] *= -1;
1845 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1846 ignoreView, solution)) {
1847 return true;
1848 }
1849 // Switch the direction back
1850 direction[0] *= -1;
1851 direction[1] *= -1;
1852
1853 // Swap the components back
1854 temp = direction[1];
1855 direction[1] = direction[0];
1856 direction[0] = temp;
1857 }
1858 return false;
1859 }
1860
Adam Cohen482ed822012-03-02 14:15:13 -08001861 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001862 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001863 // Return early if get invalid cell positions
1864 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001865
Adam Cohen8baab352012-03-20 17:39:21 -07001866 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001867 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001868
Adam Cohen8baab352012-03-20 17:39:21 -07001869 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001870 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001871 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001872 if (c != null) {
1873 c.x = cellX;
1874 c.y = cellY;
1875 }
Adam Cohen482ed822012-03-02 14:15:13 -08001876 }
Adam Cohen482ed822012-03-02 14:15:13 -08001877 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1878 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001879 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001880 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001881 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001882 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001883 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001884 if (Rect.intersects(r0, r1)) {
1885 if (!lp.canReorder) {
1886 return false;
1887 }
1888 mIntersectingViews.add(child);
1889 }
1890 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001891
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001892 // First we try to find a solution which respects the push mechanic. That is,
1893 // we try to find a solution such that no displaced item travels through another item
1894 // without also displacing that item.
1895 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001896 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001897 return true;
1898 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001899
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001900 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohen19f37922012-03-21 11:59:11 -07001901 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, false, ignoreView,
1902 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001903 return true;
1904 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001905
Adam Cohen482ed822012-03-02 14:15:13 -08001906 // Ok, they couldn't move as a block, let's move them individually
1907 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001908 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001909 return false;
1910 }
1911 }
1912 return true;
1913 }
1914
1915 /*
1916 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1917 * the provided point and the provided cell
1918 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001919 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001920 double angle = Math.atan(((float) deltaY) / deltaX);
1921
1922 result[0] = 0;
1923 result[1] = 0;
1924 if (Math.abs(Math.cos(angle)) > 0.5f) {
1925 result[0] = (int) Math.signum(deltaX);
1926 }
1927 if (Math.abs(Math.sin(angle)) > 0.5f) {
1928 result[1] = (int) Math.signum(deltaY);
1929 }
1930 }
1931
Adam Cohen8baab352012-03-20 17:39:21 -07001932 private void copyOccupiedArray(boolean[][] occupied) {
1933 for (int i = 0; i < mCountX; i++) {
1934 for (int j = 0; j < mCountY; j++) {
1935 occupied[i][j] = mOccupied[i][j];
1936 }
1937 }
1938 }
1939
Adam Cohen482ed822012-03-02 14:15:13 -08001940 ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1941 int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001942 // Copy the current state into the solution. This solution will be manipulated as necessary.
1943 copyCurrentStateToSolution(solution, false);
1944 // Copy the current occupied array into the temporary occupied array. This array will be
1945 // manipulated as necessary to find a solution.
1946 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001947
1948 // We find the nearest cell into which we would place the dragged item, assuming there's
1949 // nothing in its way.
1950 int result[] = new int[2];
1951 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1952
1953 boolean success = false;
1954 // First we try the exact nearest position of the item being dragged,
1955 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001956 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1957 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001958
1959 if (!success) {
1960 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1961 // x, then 1 in y etc.
1962 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
1963 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
1964 dragView, false, solution);
1965 } else if (spanY > minSpanY) {
1966 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
1967 dragView, true, solution);
1968 }
1969 solution.isSolution = false;
1970 } else {
1971 solution.isSolution = true;
1972 solution.dragViewX = result[0];
1973 solution.dragViewY = result[1];
1974 solution.dragViewSpanX = spanX;
1975 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08001976 }
1977 return solution;
1978 }
1979
1980 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001981 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001982 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001983 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001984 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001985 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08001986 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07001987 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001988 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001989 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001990 }
Adam Cohen8baab352012-03-20 17:39:21 -07001991 solution.map.put(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08001992 }
1993 }
1994
1995 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
1996 for (int i = 0; i < mCountX; i++) {
1997 for (int j = 0; j < mCountY; j++) {
1998 mTmpOccupied[i][j] = false;
1999 }
2000 }
2001
Michael Jurkaa52570f2012-03-20 03:18:20 -07002002 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002003 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002004 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002005 if (child == dragView) continue;
2006 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002007 CellAndSpan c = solution.map.get(child);
2008 if (c != null) {
2009 lp.tmpCellX = c.x;
2010 lp.tmpCellY = c.y;
2011 lp.cellHSpan = c.spanX;
2012 lp.cellVSpan = c.spanY;
2013 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002014 }
2015 }
2016 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2017 solution.dragViewSpanY, mTmpOccupied, true);
2018 }
2019
2020 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2021 commitDragView) {
2022
2023 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2024 for (int i = 0; i < mCountX; i++) {
2025 for (int j = 0; j < mCountY; j++) {
2026 occupied[i][j] = false;
2027 }
2028 }
2029
Michael Jurkaa52570f2012-03-20 03:18:20 -07002030 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002031 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002032 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002033 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002034 CellAndSpan c = solution.map.get(child);
2035 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002036 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2037 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002038 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002039 }
2040 }
2041 if (commitDragView) {
2042 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2043 solution.dragViewSpanY, occupied, true);
2044 }
2045 }
2046
Adam Cohen19f37922012-03-21 11:59:11 -07002047 // This method starts or changes the reorder hint animations
2048 private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
2049 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002050 for (int i = 0; i < childCount; i++) {
2051 View child = mShortcutsAndWidgets.getChildAt(i);
2052 if (child == dragView) continue;
2053 CellAndSpan c = solution.map.get(child);
2054 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2055 if (c != null) {
2056 ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
2057 c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002058 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002059 }
2060 }
2061 }
2062
2063 // Class which represents the reorder hint animations. These animations show that an item is
2064 // in a temporary state, and hint at where the item will return to.
2065 class ReorderHintAnimation {
2066 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002067 float finalDeltaX;
2068 float finalDeltaY;
2069 float initDeltaX;
2070 float initDeltaY;
2071 float finalScale;
2072 float initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002073 private static final int DURATION = 300;
Adam Cohene7587d22012-05-24 18:50:02 -07002074 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002075
2076 public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
2077 int spanX, int spanY) {
2078 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2079 final int x0 = mTmpPoint[0];
2080 final int y0 = mTmpPoint[1];
2081 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2082 final int x1 = mTmpPoint[0];
2083 final int y1 = mTmpPoint[1];
2084 final int dX = x1 - x0;
2085 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002086 finalDeltaX = 0;
2087 finalDeltaY = 0;
Adam Cohen19f37922012-03-21 11:59:11 -07002088 if (dX == dY && dX == 0) {
2089 } else {
2090 if (dY == 0) {
Adam Cohend024f982012-05-23 18:26:45 -07002091 finalDeltaX = - Math.signum(dX) * mReorderHintAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002092 } else if (dX == 0) {
Adam Cohend024f982012-05-23 18:26:45 -07002093 finalDeltaY = - Math.signum(dY) * mReorderHintAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002094 } else {
2095 double angle = Math.atan( (float) (dY) / dX);
Adam Cohend024f982012-05-23 18:26:45 -07002096 finalDeltaX = (int) (- Math.signum(dX) *
Adam Cohenfe41ac62012-05-23 14:00:37 -07002097 Math.abs(Math.cos(angle) * mReorderHintAnimationMagnitude));
Adam Cohend024f982012-05-23 18:26:45 -07002098 finalDeltaY = (int) (- Math.signum(dY) *
Adam Cohenfe41ac62012-05-23 14:00:37 -07002099 Math.abs(Math.sin(angle) * mReorderHintAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002100 }
2101 }
Adam Cohend024f982012-05-23 18:26:45 -07002102 initDeltaX = child.getTranslationX();
2103 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002104 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002105 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002106 this.child = child;
2107 }
2108
Adam Cohend024f982012-05-23 18:26:45 -07002109 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002110 if (mShakeAnimators.containsKey(child)) {
2111 ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002112 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002113 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002114 if (finalDeltaX == 0 && finalDeltaY == 0) {
2115 completeAnimationImmediately();
2116 return;
2117 }
Adam Cohen19f37922012-03-21 11:59:11 -07002118 }
Adam Cohend024f982012-05-23 18:26:45 -07002119 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002120 return;
2121 }
Michael Jurka2ecf9952012-06-18 12:52:28 -07002122 ValueAnimator va = LauncherAnimUtils.ofFloat(0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002123 a = va;
Adam Cohen19f37922012-03-21 11:59:11 -07002124 va.setRepeatMode(ValueAnimator.REVERSE);
2125 va.setRepeatCount(ValueAnimator.INFINITE);
Adam Cohen7bdfc972012-05-22 16:50:35 -07002126 va.setDuration(DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002127 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002128 va.addUpdateListener(new AnimatorUpdateListener() {
2129 @Override
2130 public void onAnimationUpdate(ValueAnimator animation) {
2131 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohend024f982012-05-23 18:26:45 -07002132 float x = r * finalDeltaX + (1 - r) * initDeltaX;
2133 float y = r * finalDeltaY + (1 - r) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002134 child.setTranslationX(x);
2135 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002136 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002137 child.setScaleX(s);
2138 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002139 }
2140 });
2141 va.addListener(new AnimatorListenerAdapter() {
2142 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002143 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002144 initDeltaX = 0;
2145 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002146 initScale = getChildrenScale();
Adam Cohen19f37922012-03-21 11:59:11 -07002147 }
2148 });
Adam Cohen19f37922012-03-21 11:59:11 -07002149 mShakeAnimators.put(child, this);
2150 va.start();
2151 }
2152
Adam Cohend024f982012-05-23 18:26:45 -07002153 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002154 if (a != null) {
2155 a.cancel();
2156 }
Adam Cohen19f37922012-03-21 11:59:11 -07002157 }
Adam Cohene7587d22012-05-24 18:50:02 -07002158
Brandon Keely50e6e562012-05-08 16:28:49 -07002159 private void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002160 if (a != null) {
2161 a.cancel();
2162 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002163
Michael Jurka2ecf9952012-06-18 12:52:28 -07002164 AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002165 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002166 s.playTogether(
Adam Cohen307fe232012-08-16 17:55:58 -07002167 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
2168 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
Michael Jurka2ecf9952012-06-18 12:52:28 -07002169 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2170 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002171 );
2172 s.setDuration(REORDER_ANIMATION_DURATION);
2173 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2174 s.start();
2175 }
Adam Cohen19f37922012-03-21 11:59:11 -07002176 }
2177
2178 private void completeAndClearReorderHintAnimations() {
2179 for (ReorderHintAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002180 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002181 }
2182 mShakeAnimators.clear();
2183 }
2184
Adam Cohen482ed822012-03-02 14:15:13 -08002185 private void commitTempPlacement() {
2186 for (int i = 0; i < mCountX; i++) {
2187 for (int j = 0; j < mCountY; j++) {
2188 mOccupied[i][j] = mTmpOccupied[i][j];
2189 }
2190 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002191 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002192 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002193 View child = mShortcutsAndWidgets.getChildAt(i);
2194 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2195 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002196 // We do a null check here because the item info can be null in the case of the
2197 // AllApps button in the hotseat.
2198 if (info != null) {
Adam Cohen487f7dd2012-06-28 18:12:10 -07002199 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
2200 info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
2201 info.requiresDbUpdate = true;
2202 }
Adam Cohen2acce882012-03-28 19:03:19 -07002203 info.cellX = lp.cellX = lp.tmpCellX;
2204 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002205 info.spanX = lp.cellHSpan;
2206 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002207 }
Adam Cohen482ed822012-03-02 14:15:13 -08002208 }
Adam Cohen2acce882012-03-28 19:03:19 -07002209 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002210 }
2211
2212 public void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002213 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002214 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002215 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002216 lp.useTmpCoords = useTempCoords;
2217 }
2218 }
2219
Adam Cohen482ed822012-03-02 14:15:13 -08002220 ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
2221 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2222 int[] result = new int[2];
2223 int[] resultSpan = new int[2];
2224 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
2225 resultSpan);
2226 if (result[0] >= 0 && result[1] >= 0) {
2227 copyCurrentStateToSolution(solution, false);
2228 solution.dragViewX = result[0];
2229 solution.dragViewY = result[1];
2230 solution.dragViewSpanX = resultSpan[0];
2231 solution.dragViewSpanY = resultSpan[1];
2232 solution.isSolution = true;
2233 } else {
2234 solution.isSolution = false;
2235 }
2236 return solution;
2237 }
2238
2239 public void prepareChildForDrag(View child) {
2240 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002241 }
2242
Adam Cohen19f37922012-03-21 11:59:11 -07002243 /* This seems like it should be obvious and straight-forward, but when the direction vector
2244 needs to match with the notion of the dragView pushing other views, we have to employ
2245 a slightly more subtle notion of the direction vector. The question is what two points is
2246 the vector between? The center of the dragView and its desired destination? Not quite, as
2247 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2248 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2249 or right, which helps make pushing feel right.
2250 */
2251 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2252 int spanY, View dragView, int[] resultDirection) {
2253 int[] targetDestination = new int[2];
2254
2255 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2256 Rect dragRect = new Rect();
2257 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2258 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2259
2260 Rect dropRegionRect = new Rect();
2261 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2262 dragView, dropRegionRect, mIntersectingViews);
2263
2264 int dropRegionSpanX = dropRegionRect.width();
2265 int dropRegionSpanY = dropRegionRect.height();
2266
2267 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2268 dropRegionRect.height(), dropRegionRect);
2269
2270 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2271 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2272
2273 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2274 deltaX = 0;
2275 }
2276 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2277 deltaY = 0;
2278 }
2279
2280 if (deltaX == 0 && deltaY == 0) {
2281 // No idea what to do, give a random direction.
2282 resultDirection[0] = 1;
2283 resultDirection[1] = 0;
2284 } else {
2285 computeDirectionVector(deltaX, deltaY, resultDirection);
2286 }
2287 }
2288
2289 // For a given cell and span, fetch the set of views intersecting the region.
2290 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2291 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2292 if (boundingRect != null) {
2293 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2294 }
2295 intersectingViews.clear();
2296 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2297 Rect r1 = new Rect();
2298 final int count = mShortcutsAndWidgets.getChildCount();
2299 for (int i = 0; i < count; i++) {
2300 View child = mShortcutsAndWidgets.getChildAt(i);
2301 if (child == dragView) continue;
2302 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2303 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2304 if (Rect.intersects(r0, r1)) {
2305 mIntersectingViews.add(child);
2306 if (boundingRect != null) {
2307 boundingRect.union(r1);
2308 }
2309 }
2310 }
2311 }
2312
2313 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2314 View dragView, int[] result) {
2315 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2316 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2317 mIntersectingViews);
2318 return !mIntersectingViews.isEmpty();
2319 }
2320
2321 void revertTempState() {
2322 if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
2323 final int count = mShortcutsAndWidgets.getChildCount();
2324 for (int i = 0; i < count; i++) {
2325 View child = mShortcutsAndWidgets.getChildAt(i);
2326 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2327 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2328 lp.tmpCellX = lp.cellX;
2329 lp.tmpCellY = lp.cellY;
2330 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2331 0, false, false);
2332 }
2333 }
2334 completeAndClearReorderHintAnimations();
2335 setItemPlacementDirty(false);
2336 }
2337
Adam Cohenbebf0422012-04-11 18:06:28 -07002338 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2339 View dragView, int[] direction, boolean commit) {
2340 int[] pixelXY = new int[2];
2341 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2342
2343 // First we determine if things have moved enough to cause a different layout
2344 ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY,
2345 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2346
2347 setUseTempCoords(true);
2348 if (swapSolution != null && swapSolution.isSolution) {
2349 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2350 // committing anything or animating anything as we just want to determine if a solution
2351 // exists
2352 copySolutionToTempState(swapSolution, dragView);
2353 setItemPlacementDirty(true);
2354 animateItemsToSolution(swapSolution, dragView, commit);
2355
2356 if (commit) {
2357 commitTempPlacement();
2358 completeAndClearReorderHintAnimations();
2359 setItemPlacementDirty(false);
2360 } else {
2361 beginOrAdjustHintAnimations(swapSolution, dragView,
2362 REORDER_ANIMATION_DURATION);
2363 }
2364 mShortcutsAndWidgets.requestLayout();
2365 }
2366 return swapSolution.isSolution;
2367 }
2368
Adam Cohen482ed822012-03-02 14:15:13 -08002369 int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
2370 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002371 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002372 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002373
2374 if (resultSpan == null) {
2375 resultSpan = new int[2];
2376 }
2377
Adam Cohen19f37922012-03-21 11:59:11 -07002378 // When we are checking drop validity or actually dropping, we don't recompute the
2379 // direction vector, since we want the solution to match the preview, and it's possible
2380 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002381 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2382 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002383 mDirectionVector[0] = mPreviousReorderDirection[0];
2384 mDirectionVector[1] = mPreviousReorderDirection[1];
2385 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002386 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2387 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2388 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002389 }
2390 } else {
2391 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2392 mPreviousReorderDirection[0] = mDirectionVector[0];
2393 mPreviousReorderDirection[1] = mDirectionVector[1];
2394 }
2395
Adam Cohen482ed822012-03-02 14:15:13 -08002396 ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
2397 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2398
2399 // We attempt the approach which doesn't shuffle views at all
2400 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2401 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2402
2403 ItemConfiguration finalSolution = null;
2404 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2405 finalSolution = swapSolution;
2406 } else if (noShuffleSolution.isSolution) {
2407 finalSolution = noShuffleSolution;
2408 }
2409
2410 boolean foundSolution = true;
2411 if (!DESTRUCTIVE_REORDER) {
2412 setUseTempCoords(true);
2413 }
2414
2415 if (finalSolution != null) {
2416 result[0] = finalSolution.dragViewX;
2417 result[1] = finalSolution.dragViewY;
2418 resultSpan[0] = finalSolution.dragViewSpanX;
2419 resultSpan[1] = finalSolution.dragViewSpanY;
2420
2421 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2422 // committing anything or animating anything as we just want to determine if a solution
2423 // exists
2424 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2425 if (!DESTRUCTIVE_REORDER) {
2426 copySolutionToTempState(finalSolution, dragView);
2427 }
2428 setItemPlacementDirty(true);
2429 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2430
Adam Cohen19f37922012-03-21 11:59:11 -07002431 if (!DESTRUCTIVE_REORDER &&
2432 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002433 commitTempPlacement();
Adam Cohen19f37922012-03-21 11:59:11 -07002434 completeAndClearReorderHintAnimations();
2435 setItemPlacementDirty(false);
2436 } else {
2437 beginOrAdjustHintAnimations(finalSolution, dragView,
2438 REORDER_ANIMATION_DURATION);
Adam Cohen482ed822012-03-02 14:15:13 -08002439 }
2440 }
2441 } else {
2442 foundSolution = false;
2443 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2444 }
2445
2446 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2447 setUseTempCoords(false);
2448 }
Adam Cohen482ed822012-03-02 14:15:13 -08002449
Michael Jurkaa52570f2012-03-20 03:18:20 -07002450 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002451 return result;
2452 }
2453
Adam Cohen19f37922012-03-21 11:59:11 -07002454 void setItemPlacementDirty(boolean dirty) {
2455 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002456 }
Adam Cohen19f37922012-03-21 11:59:11 -07002457 boolean isItemPlacementDirty() {
2458 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002459 }
2460
2461 private class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002462 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohen482ed822012-03-02 14:15:13 -08002463 boolean isSolution = false;
2464 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2465
2466 int area() {
2467 return dragViewSpanX * dragViewSpanY;
2468 }
Adam Cohen8baab352012-03-20 17:39:21 -07002469 }
2470
2471 private class CellAndSpan {
2472 int x, y;
2473 int spanX, spanY;
2474
2475 public CellAndSpan(int x, int y, int spanX, int spanY) {
2476 this.x = x;
2477 this.y = y;
2478 this.spanX = spanX;
2479 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002480 }
2481 }
2482
Adam Cohendf035382011-04-11 17:22:04 -07002483 /**
2484 * Find a vacant area that will fit the given bounds nearest the requested
2485 * cell location. Uses Euclidean distance to score multiple vacant areas.
2486 *
2487 * @param pixelX The X location at which you want to search for a vacant area.
2488 * @param pixelY The Y location at which you want to search for a vacant area.
2489 * @param spanX Horizontal span of the object.
2490 * @param spanY Vertical span of the object.
2491 * @param ignoreView Considers space occupied by this view as unoccupied
2492 * @param result Previously returned value to possibly recycle.
2493 * @return The X, Y cell of a vacant area that can contain this object,
2494 * nearest the requested location.
2495 */
2496 int[] findNearestVacantArea(
2497 int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
2498 return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
2499 }
2500
2501 /**
Adam Cohend41fbf52012-02-16 23:53:59 -08002502 * Find a vacant area that will fit the given bounds nearest the requested
2503 * cell location. Uses Euclidean distance to score multiple vacant areas.
2504 *
2505 * @param pixelX The X location at which you want to search for a vacant area.
2506 * @param pixelY The Y location at which you want to search for a vacant area.
2507 * @param minSpanX The minimum horizontal span required
2508 * @param minSpanY The minimum vertical span required
2509 * @param spanX Horizontal span of the object.
2510 * @param spanY Vertical span of the object.
2511 * @param ignoreView Considers space occupied by this view as unoccupied
2512 * @param result Previously returned value to possibly recycle.
2513 * @return The X, Y cell of a vacant area that can contain this object,
2514 * nearest the requested location.
2515 */
2516 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
2517 int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
Adam Cohen482ed822012-03-02 14:15:13 -08002518 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
2519 result, resultSpan, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08002520 }
2521
2522 /**
Adam Cohendf035382011-04-11 17:22:04 -07002523 * Find a starting cell position that will fit the given bounds nearest the requested
2524 * cell location. Uses Euclidean distance to score multiple vacant areas.
2525 *
2526 * @param pixelX The X location at which you want to search for a vacant area.
2527 * @param pixelY The Y location at which you want to search for a vacant area.
2528 * @param spanX Horizontal span of the object.
2529 * @param spanY Vertical span of the object.
2530 * @param ignoreView Considers space occupied by this view as unoccupied
2531 * @param result Previously returned value to possibly recycle.
2532 * @return The X, Y cell of a vacant area that can contain this object,
2533 * nearest the requested location.
2534 */
2535 int[] findNearestArea(
2536 int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2537 return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
2538 }
2539
Michael Jurka0280c3b2010-09-17 15:00:07 -07002540 boolean existsEmptyCell() {
2541 return findCellForSpan(null, 1, 1);
2542 }
2543
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002544 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002545 * Finds the upper-left coordinate of the first rectangle in the grid that can
2546 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2547 * then this method will only return coordinates for rectangles that contain the cell
2548 * (intersectX, intersectY)
2549 *
2550 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2551 * can be found.
2552 * @param spanX The horizontal span of the cell we want to find.
2553 * @param spanY The vertical span of the cell we want to find.
2554 *
2555 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002556 */
Michael Jurka0280c3b2010-09-17 15:00:07 -07002557 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Adam Cohen482ed822012-03-02 14:15:13 -08002558 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002559 }
2560
2561 /**
2562 * Like above, but ignores any cells occupied by the item "ignoreView"
2563 *
2564 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2565 * can be found.
2566 * @param spanX The horizontal span of the cell we want to find.
2567 * @param spanY The vertical span of the cell we want to find.
2568 * @param ignoreView The home screen item we should treat as not occupying any space
2569 * @return
2570 */
2571 boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
Adam Cohen482ed822012-03-02 14:15:13 -08002572 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
2573 ignoreView, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002574 }
2575
2576 /**
2577 * Like above, but if intersectX and intersectY are not -1, then this method will try to
2578 * return coordinates for rectangles that contain the cell [intersectX, intersectY]
2579 *
2580 * @param spanX The horizontal span of the cell we want to find.
2581 * @param spanY The vertical span of the cell we want to find.
2582 * @param ignoreView The home screen item we should treat as not occupying any space
2583 * @param intersectX The X coordinate of the cell that we should try to overlap
2584 * @param intersectX The Y coordinate of the cell that we should try to overlap
2585 *
2586 * @return True if a vacant cell of the specified dimension was found, false otherwise.
2587 */
2588 boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
2589 int intersectX, int intersectY) {
2590 return findCellForSpanThatIntersectsIgnoring(
Adam Cohen482ed822012-03-02 14:15:13 -08002591 cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002592 }
2593
2594 /**
2595 * The superset of the above two methods
2596 */
2597 boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002598 int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002599 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08002600 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002601
Michael Jurka28750fb2010-09-24 17:43:49 -07002602 boolean foundCell = false;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002603 while (true) {
2604 int startX = 0;
2605 if (intersectX >= 0) {
2606 startX = Math.max(startX, intersectX - (spanX - 1));
2607 }
2608 int endX = mCountX - (spanX - 1);
2609 if (intersectX >= 0) {
2610 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
2611 }
2612 int startY = 0;
2613 if (intersectY >= 0) {
2614 startY = Math.max(startY, intersectY - (spanY - 1));
2615 }
2616 int endY = mCountY - (spanY - 1);
2617 if (intersectY >= 0) {
2618 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
2619 }
2620
Winson Chungbbc60d82010-11-11 16:34:41 -08002621 for (int y = startY; y < endY && !foundCell; y++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002622 inner:
Winson Chungbbc60d82010-11-11 16:34:41 -08002623 for (int x = startX; x < endX; x++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002624 for (int i = 0; i < spanX; i++) {
2625 for (int j = 0; j < spanY; j++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002626 if (occupied[x + i][y + j]) {
Winson Chungbbc60d82010-11-11 16:34:41 -08002627 // small optimization: we can skip to after the column we just found
Michael Jurka0280c3b2010-09-17 15:00:07 -07002628 // an occupied cell
Winson Chungbbc60d82010-11-11 16:34:41 -08002629 x += i;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002630 continue inner;
2631 }
2632 }
2633 }
2634 if (cellXY != null) {
2635 cellXY[0] = x;
2636 cellXY[1] = y;
2637 }
Michael Jurka28750fb2010-09-24 17:43:49 -07002638 foundCell = true;
2639 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002640 }
2641 }
2642 if (intersectX == -1 && intersectY == -1) {
2643 break;
2644 } else {
2645 // if we failed to find anything, try again but without any requirements of
2646 // intersecting
2647 intersectX = -1;
2648 intersectY = -1;
2649 continue;
2650 }
2651 }
2652
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002653 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08002654 markCellsAsOccupiedForView(ignoreView, occupied);
Michael Jurka28750fb2010-09-24 17:43:49 -07002655 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002656 }
2657
2658 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002659 * A drag event has begun over this layout.
2660 * It may have begun over this layout (in which case onDragChild is called first),
2661 * or it may have begun on another layout.
2662 */
2663 void onDragEnter() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002664 mDragEnforcer.onDragEnter();
Winson Chungc07918d2011-07-01 15:35:26 -07002665 mDragging = true;
2666 }
2667
2668 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002669 * Called when drag has left this CellLayout or has been completed (successfully or not)
2670 */
2671 void onDragExit() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002672 mDragEnforcer.onDragExit();
Joe Onorato4be866d2010-10-10 11:26:02 -07002673 // This can actually be called when we aren't in a drag, e.g. when adding a new
2674 // item to this layout via the customize drawer.
2675 // Guard against that case.
2676 if (mDragging) {
2677 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002678 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002679
2680 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002681 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002682 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2683 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002684 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002685 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002686 }
2687
2688 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002689 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002690 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002691 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002692 *
2693 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002694 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002695 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002696 if (child != null) {
2697 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002698 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002699 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002700 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002701 }
2702
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002703 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002704 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002705 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002706 * @param cellX X coordinate of upper left corner expressed as a cell position
2707 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002708 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002709 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002710 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002711 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002712 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002713 final int cellWidth = mCellWidth;
2714 final int cellHeight = mCellHeight;
2715 final int widthGap = mWidthGap;
2716 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002717
Winson Chung4b825dcd2011-06-19 12:41:22 -07002718 final int hStartPadding = getPaddingLeft();
2719 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002720
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002721 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2722 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2723
2724 int x = hStartPadding + cellX * (cellWidth + widthGap);
2725 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002726
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002727 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002728 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002729
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002730 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002731 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002732 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002733 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002734 * @param width Width in pixels
2735 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002736 * @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 -08002737 */
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002738 public int[] rectToCell(int width, int height, int[] result) {
Michael Jurka9987a5c2010-10-08 16:58:12 -07002739 return rectToCell(getResources(), width, height, result);
2740 }
2741
2742 public static int[] rectToCell(Resources resources, int width, int height, int[] result) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002743 // Always assume we're working with the smallest span to make sure we
2744 // reserve enough space in both orientations.
Joe Onorato79e56262009-09-21 15:23:04 -04002745 int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
2746 int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002747 int smallerSize = Math.min(actualWidth, actualHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002748
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002749 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002750 int spanX = (int) Math.ceil(width / (float) smallerSize);
2751 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002752
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002753 if (result == null) {
2754 return new int[] { spanX, spanY };
2755 }
2756 result[0] = spanX;
2757 result[1] = spanY;
2758 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002759 }
2760
Michael Jurkaf12c75c2011-01-25 22:41:40 -08002761 public int[] cellSpansToSize(int hSpans, int vSpans) {
2762 int[] size = new int[2];
2763 size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
2764 size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
2765 return size;
2766 }
2767
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002768 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08002769 * Calculate the grid spans needed to fit given item
2770 */
2771 public void calculateSpans(ItemInfo info) {
2772 final int minWidth;
2773 final int minHeight;
2774
2775 if (info instanceof LauncherAppWidgetInfo) {
2776 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
2777 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
2778 } else if (info instanceof PendingAddWidgetInfo) {
2779 minWidth = ((PendingAddWidgetInfo) info).minWidth;
2780 minHeight = ((PendingAddWidgetInfo) info).minHeight;
2781 } else {
2782 // It's not a widget, so it must be 1x1
2783 info.spanX = info.spanY = 1;
2784 return;
2785 }
2786 int[] spans = rectToCell(minWidth, minHeight, null);
2787 info.spanX = spans[0];
2788 info.spanY = spans[1];
2789 }
2790
2791 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002792 * Find the first vacant cell, if there is one.
2793 *
2794 * @param vacant Holds the x and y coordinate of the vacant cell
2795 * @param spanX Horizontal cell span.
2796 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -07002797 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002798 * @return True if a vacant cell was found
2799 */
2800 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002801
Michael Jurka0280c3b2010-09-17 15:00:07 -07002802 return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002803 }
2804
2805 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
2806 int xCount, int yCount, boolean[][] occupied) {
2807
Adam Cohen2801caf2011-05-13 20:57:39 -07002808 for (int y = 0; y < yCount; y++) {
2809 for (int x = 0; x < xCount; x++) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002810 boolean available = !occupied[x][y];
2811out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
2812 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
2813 available = available && !occupied[i][j];
2814 if (!available) break out;
2815 }
2816 }
2817
2818 if (available) {
2819 vacant[0] = x;
2820 vacant[1] = y;
2821 return true;
2822 }
2823 }
2824 }
2825
2826 return false;
2827 }
2828
Michael Jurka0280c3b2010-09-17 15:00:07 -07002829 private void clearOccupiedCells() {
2830 for (int x = 0; x < mCountX; x++) {
2831 for (int y = 0; y < mCountY; y++) {
2832 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002833 }
2834 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002835 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002836
Adam Cohend41fbf52012-02-16 23:53:59 -08002837 public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002838 markCellsAsUnoccupiedForView(view);
Adam Cohen482ed822012-03-02 14:15:13 -08002839 markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002840 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002841
Adam Cohend4844c32011-02-18 19:25:06 -08002842 public void markCellsAsOccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08002843 markCellsAsOccupiedForView(view, mOccupied);
2844 }
2845 public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002846 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002847 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002848 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002849 }
2850
Adam Cohend4844c32011-02-18 19:25:06 -08002851 public void markCellsAsUnoccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08002852 markCellsAsUnoccupiedForView(view, mOccupied);
2853 }
2854 public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002855 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002856 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002857 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002858 }
2859
Adam Cohen482ed822012-03-02 14:15:13 -08002860 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2861 boolean value) {
2862 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002863 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2864 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002865 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002866 }
2867 }
2868 }
2869
Adam Cohen2801caf2011-05-13 20:57:39 -07002870 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002871 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002872 (Math.max((mCountX - 1), 0) * mWidthGap);
2873 }
2874
2875 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002876 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002877 (Math.max((mCountY - 1), 0) * mHeightGap);
2878 }
2879
Michael Jurka66d72172011-04-12 16:29:25 -07002880 public boolean isOccupied(int x, int y) {
2881 if (x < mCountX && y < mCountY) {
2882 return mOccupied[x][y];
2883 } else {
2884 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2885 }
2886 }
2887
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002888 @Override
2889 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2890 return new CellLayout.LayoutParams(getContext(), attrs);
2891 }
2892
2893 @Override
2894 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2895 return p instanceof CellLayout.LayoutParams;
2896 }
2897
2898 @Override
2899 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2900 return new CellLayout.LayoutParams(p);
2901 }
2902
Winson Chungaafa03c2010-06-11 17:34:16 -07002903 public static class CellLayoutAnimationController extends LayoutAnimationController {
2904 public CellLayoutAnimationController(Animation animation, float delay) {
2905 super(animation, delay);
2906 }
2907
2908 @Override
2909 protected long getDelayForView(View view) {
2910 return (int) (Math.random() * 150);
2911 }
2912 }
2913
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002914 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2915 /**
2916 * Horizontal location of the item in the grid.
2917 */
2918 @ViewDebug.ExportedProperty
2919 public int cellX;
2920
2921 /**
2922 * Vertical location of the item in the grid.
2923 */
2924 @ViewDebug.ExportedProperty
2925 public int cellY;
2926
2927 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002928 * Temporary horizontal location of the item in the grid during reorder
2929 */
2930 public int tmpCellX;
2931
2932 /**
2933 * Temporary vertical location of the item in the grid during reorder
2934 */
2935 public int tmpCellY;
2936
2937 /**
2938 * Indicates that the temporary coordinates should be used to layout the items
2939 */
2940 public boolean useTmpCoords;
2941
2942 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002943 * Number of cells spanned horizontally by the item.
2944 */
2945 @ViewDebug.ExportedProperty
2946 public int cellHSpan;
2947
2948 /**
2949 * Number of cells spanned vertically by the item.
2950 */
2951 @ViewDebug.ExportedProperty
2952 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002953
Adam Cohen1b607ed2011-03-03 17:26:50 -08002954 /**
2955 * Indicates whether the item will set its x, y, width and height parameters freely,
2956 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2957 */
Adam Cohend4844c32011-02-18 19:25:06 -08002958 public boolean isLockedToGrid = true;
2959
Adam Cohen482ed822012-03-02 14:15:13 -08002960 /**
2961 * Indicates whether this item can be reordered. Always true except in the case of the
2962 * the AllApps button.
2963 */
2964 public boolean canReorder = true;
2965
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002966 // X coordinate of the view in the layout.
2967 @ViewDebug.ExportedProperty
2968 int x;
2969 // Y coordinate of the view in the layout.
2970 @ViewDebug.ExportedProperty
2971 int y;
2972
Romain Guy84f296c2009-11-04 15:00:44 -08002973 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002974
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002975 public LayoutParams(Context c, AttributeSet attrs) {
2976 super(c, attrs);
2977 cellHSpan = 1;
2978 cellVSpan = 1;
2979 }
2980
2981 public LayoutParams(ViewGroup.LayoutParams source) {
2982 super(source);
2983 cellHSpan = 1;
2984 cellVSpan = 1;
2985 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002986
2987 public LayoutParams(LayoutParams source) {
2988 super(source);
2989 this.cellX = source.cellX;
2990 this.cellY = source.cellY;
2991 this.cellHSpan = source.cellHSpan;
2992 this.cellVSpan = source.cellVSpan;
2993 }
2994
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002995 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002996 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002997 this.cellX = cellX;
2998 this.cellY = cellY;
2999 this.cellHSpan = cellHSpan;
3000 this.cellVSpan = cellVSpan;
3001 }
3002
Adam Cohen7f4eabe2011-04-21 16:19:16 -07003003 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap) {
Adam Cohend4844c32011-02-18 19:25:06 -08003004 if (isLockedToGrid) {
3005 final int myCellHSpan = cellHSpan;
3006 final int myCellVSpan = cellVSpan;
Adam Cohen482ed822012-03-02 14:15:13 -08003007 final int myCellX = useTmpCoords ? tmpCellX : cellX;
3008 final int myCellY = useTmpCoords ? tmpCellY : cellY;
Adam Cohen1b607ed2011-03-03 17:26:50 -08003009
Adam Cohend4844c32011-02-18 19:25:06 -08003010 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
3011 leftMargin - rightMargin;
3012 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
3013 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08003014 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
3015 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08003016 }
3017 }
Winson Chungaafa03c2010-06-11 17:34:16 -07003018
Winson Chungaafa03c2010-06-11 17:34:16 -07003019 public String toString() {
3020 return "(" + this.cellX + ", " + this.cellY + ")";
3021 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07003022
3023 public void setWidth(int width) {
3024 this.width = width;
3025 }
3026
3027 public int getWidth() {
3028 return width;
3029 }
3030
3031 public void setHeight(int height) {
3032 this.height = height;
3033 }
3034
3035 public int getHeight() {
3036 return height;
3037 }
3038
3039 public void setX(int x) {
3040 this.x = x;
3041 }
3042
3043 public int getX() {
3044 return x;
3045 }
3046
3047 public void setY(int y) {
3048 this.y = y;
3049 }
3050
3051 public int getY() {
3052 return y;
3053 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003054 }
3055
Michael Jurka0280c3b2010-09-17 15:00:07 -07003056 // This class stores info for two purposes:
3057 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
3058 // its spanX, spanY, and the screen it is on
3059 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
3060 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
3061 // the CellLayout that was long clicked
Michael Jurkae5fb0f22011-04-11 13:27:46 -07003062 static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003063 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07003064 int cellX = -1;
3065 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003066 int spanX;
3067 int spanY;
3068 int screen;
Winson Chung3d503fb2011-07-13 17:25:49 -07003069 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003070
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003071 @Override
3072 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07003073 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
3074 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003075 }
3076 }
Michael Jurkad771c962011-08-09 15:00:48 -07003077
3078 public boolean lastDownOnOccupiedCell() {
3079 return mLastDownOnOccupiedCell;
3080 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003081}