blob: a96b5b1688a2a75eb9da9aff0e97f5a67bfc9ba9 [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;
Brandon Keely50e6e562012-05-08 16:28:49 -070020import android.animation.AnimatorSet;
21import android.animation.ObjectAnimator;
Joe Onorato4be866d2010-10-10 11:26:02 -070022import android.animation.AnimatorListenerAdapter;
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;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080040import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070041import android.util.Log;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080042import android.view.MotionEvent;
43import android.view.View;
44import android.view.ViewDebug;
45import android.view.ViewGroup;
Winson Chungaafa03c2010-06-11 17:34:16 -070046import android.view.animation.Animation;
Winson Chung150fbab2010-09-29 17:14:26 -070047import android.view.animation.DecelerateInterpolator;
Winson Chungaafa03c2010-06-11 17:34:16 -070048import android.view.animation.LayoutAnimationController;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080049
Adam Cohen66396872011-04-15 17:50:36 -070050import com.android.launcher.R;
Adam Cohen69ce2e52011-07-03 19:25:21 -070051import com.android.launcher2.FolderIcon.FolderRingAnimator;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070052
Adam Cohen69ce2e52011-07-03 19:25:21 -070053import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070054import java.util.Arrays;
Adam Cohenbfbfd262011-06-13 16:55:12 -070055import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080056import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070057
Michael Jurkabdb5c532011-02-01 15:05:06 -080058public class CellLayout extends ViewGroup {
Winson Chungaafa03c2010-06-11 17:34:16 -070059 static final String TAG = "CellLayout";
60
Adam Cohen2acce882012-03-28 19:03:19 -070061 private Launcher mLauncher;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080062 private int mCellWidth;
63 private int mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070064
Adam Cohend22015c2010-07-26 22:02:18 -070065 private int mCountX;
66 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080067
Adam Cohen234c4cd2011-07-17 21:03:04 -070068 private int mOriginalWidthGap;
69 private int mOriginalHeightGap;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080070 private int mWidthGap;
71 private int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070072 private int mMaxGap;
Adam Cohenebea84d2011-11-09 17:20:41 -080073 private boolean mScrollingTransformsDirty = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080074
75 private final Rect mRect = new Rect();
76 private final CellInfo mCellInfo = new CellInfo();
Winson Chungaafa03c2010-06-11 17:34:16 -070077
Patrick Dubroyde7658b2010-09-27 11:15:43 -070078 // These are temporary variables to prevent having to allocate a new object just to
79 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Winson Chung0be025d2011-05-23 17:45:09 -070080 private final int[] mTmpXY = new int[2];
Patrick Dubroyde7658b2010-09-27 11:15:43 -070081 private final int[] mTmpPoint = new int[2];
Adam Cohen69ce2e52011-07-03 19:25:21 -070082 int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070083
The Android Open Source Project31dd5032009-03-03 19:32:27 -080084 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -080085 boolean[][] mTmpOccupied;
Michael Jurkad771c962011-08-09 15:00:48 -070086 private boolean mLastDownOnOccupiedCell = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080087
Michael Jurkadee05892010-07-27 10:01:56 -070088 private OnTouchListener mInterceptTouchListener;
89
Adam Cohen69ce2e52011-07-03 19:25:21 -070090 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -070091 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -070092
Adam Cohenb5ba0972011-09-07 18:02:31 -070093 private int mForegroundAlpha = 0;
Michael Jurka5f1c5092010-09-03 14:15:02 -070094 private float mBackgroundAlpha;
Adam Cohen1b0aaac2010-10-28 11:11:18 -070095 private float mBackgroundAlphaMultiplier = 1.0f;
Adam Cohenf34bab52010-09-30 14:11:56 -070096
Michael Jurka33945b22010-12-21 18:19:38 -080097 private Drawable mNormalBackground;
Michael Jurka33945b22010-12-21 18:19:38 -080098 private Drawable mActiveGlowBackground;
Adam Cohenb5ba0972011-09-07 18:02:31 -070099 private Drawable mOverScrollForegroundDrawable;
100 private Drawable mOverScrollLeft;
101 private Drawable mOverScrollRight;
Michael Jurka18014792010-10-14 09:01:34 -0700102 private Rect mBackgroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700103 private Rect mForegroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700104 private int mForegroundPadding;
Patrick Dubroy1262e362010-10-06 15:49:50 -0700105
Michael Jurka33945b22010-12-21 18:19:38 -0800106 // If we're actively dragging something over this screen, mIsDragOverlapping is true
107 private boolean mIsDragOverlapping = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700108 private final Point mDragCenter = new Point();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700109
Winson Chung150fbab2010-09-29 17:14:26 -0700110 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700111 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohend41fbf52012-02-16 23:53:59 -0800112 private Rect[] mDragOutlines = new Rect[4];
Chet Haase472b2812010-10-14 07:02:04 -0700113 private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700114 private InterruptibleInOutAnimator[] mDragOutlineAnims =
115 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700116
117 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700118 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700119 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700120
Patrick Dubroy96864c32011-03-10 17:17:23 -0800121 private BubbleTextView mPressedOrFocusedIcon;
122
Adam Cohen482ed822012-03-02 14:15:13 -0800123 private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
124 HashMap<CellLayout.LayoutParams, Animator>();
Adam Cohen19f37922012-03-21 11:59:11 -0700125 private HashMap<View, ReorderHintAnimation>
126 mShakeAnimators = new HashMap<View, ReorderHintAnimation>();
127
128 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700129
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700130 // When a drag operation is in progress, holds the nearest cell to the touch point
131 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800132
Joe Onorato4be866d2010-10-10 11:26:02 -0700133 private boolean mDragging = false;
134
Patrick Dubroyce34a972010-10-19 10:34:32 -0700135 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700136 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700137
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800138 private boolean mIsHotseat = false;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800139
Adam Cohen482ed822012-03-02 14:15:13 -0800140 public static final int MODE_DRAG_OVER = 0;
141 public static final int MODE_ON_DROP = 1;
142 public static final int MODE_ON_DROP_EXTERNAL = 2;
143 public static final int MODE_ACCEPT_DROP = 3;
Adam Cohen19f37922012-03-21 11:59:11 -0700144 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800145 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
146
Adam Cohena897f392012-04-27 18:12:05 -0700147 static final int LANDSCAPE = 0;
148 static final int PORTRAIT = 1;
149
Adam Cohen7bdfc972012-05-22 16:50:35 -0700150 private static final float REORDER_HINT_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700151 private static final int REORDER_ANIMATION_DURATION = 150;
152 private float mReorderHintAnimationMagnitude;
153
Adam Cohen482ed822012-03-02 14:15:13 -0800154 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
155 private Rect mOccupiedRect = new Rect();
156 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700157 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700158 private static final int INVALID_DIRECTION = -100;
Adam Cohenc6cc61d2012-04-04 12:47:08 -0700159 private DropTarget.DragEnforcer mDragEnforcer;
Adam Cohen482ed822012-03-02 14:15:13 -0800160
Romain Guy8a0bff52012-05-06 13:14:33 -0700161 private final static PorterDuffXfermode sAddBlendMode =
162 new PorterDuffXfermode(PorterDuff.Mode.ADD);
163
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800164 public CellLayout(Context context) {
165 this(context, null);
166 }
167
168 public CellLayout(Context context, AttributeSet attrs) {
169 this(context, attrs, 0);
170 }
171
172 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
173 super(context, attrs, defStyle);
Michael Jurka8b805b12012-04-18 14:23:14 -0700174 mDragEnforcer = new DropTarget.DragEnforcer(context);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700175
176 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
177 // the user where a dragged item will land when dropped.
178 setWillNotDraw(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700179 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700180
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800181 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
182
Adam Cohenf4bd5792012-04-27 11:35:29 -0700183 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
184 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700185 mWidthGap = mOriginalWidthGap = a.getDimensionPixelSize(R.styleable.CellLayout_widthGap, 0);
186 mHeightGap = mOriginalHeightGap = a.getDimensionPixelSize(R.styleable.CellLayout_heightGap, 0);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700187 mMaxGap = a.getDimensionPixelSize(R.styleable.CellLayout_maxGap, 0);
Adam Cohend22015c2010-07-26 22:02:18 -0700188 mCountX = LauncherModel.getCellCountX();
189 mCountY = LauncherModel.getCellCountY();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700190 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800191 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700192 mPreviousReorderDirection[0] = INVALID_DIRECTION;
193 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800194
195 a.recycle();
196
197 setAlwaysDrawnWithCacheEnabled(false);
198
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700199 final Resources res = getResources();
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700200
Winson Chung967289b2011-06-30 18:09:30 -0700201 mNormalBackground = res.getDrawable(R.drawable.homescreen_blue_normal_holo);
Winson Chungdea74b72011-09-13 18:06:43 -0700202 mActiveGlowBackground = res.getDrawable(R.drawable.homescreen_blue_strong_holo);
Michael Jurka33945b22010-12-21 18:19:38 -0800203
Adam Cohenb5ba0972011-09-07 18:02:31 -0700204 mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
205 mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
206 mForegroundPadding =
207 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
Michael Jurka33945b22010-12-21 18:19:38 -0800208
Adam Cohen19f37922012-03-21 11:59:11 -0700209 mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE *
210 res.getDimensionPixelSize(R.dimen.app_icon_size));
211
Winson Chungb26f3d62011-06-02 10:49:29 -0700212 mNormalBackground.setFilterBitmap(true);
Winson Chungb26f3d62011-06-02 10:49:29 -0700213 mActiveGlowBackground.setFilterBitmap(true);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700214
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700215 // Initialize the data structures used for the drag visualization.
Winson Chung150fbab2010-09-29 17:14:26 -0700216
Patrick Dubroyce34a972010-10-19 10:34:32 -0700217 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700218
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700219
Winson Chungb8c69f32011-10-19 21:36:08 -0700220 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700221 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800222 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700223 }
224
225 // When dragging things around the home screens, we show a green outline of
226 // where the item will land. The outlines gradually fade out, leaving a trail
227 // behind the drag path.
228 // Set up all the animations that are used to implement this fading.
229 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700230 final float fromAlphaValue = 0;
231 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700232
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700233 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700234
235 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700236 final InterruptibleInOutAnimator anim =
237 new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700238 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700239 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700240 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700241 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700242 final Bitmap outline = (Bitmap)anim.getTag();
243
244 // If an animation is started and then stopped very quickly, we can still
245 // get spurious updates we've cleared the tag. Guard against this.
246 if (outline == null) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700247 @SuppressWarnings("all") // suppress dead code warning
248 final boolean debug = false;
249 if (debug) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700250 Object val = animation.getAnimatedValue();
251 Log.d(TAG, "anim " + thisIndex + " update: " + val +
252 ", isStopped " + anim.isStopped());
253 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700254 // Try to prevent it from continuing to run
255 animation.cancel();
256 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700257 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800258 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700259 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700260 }
261 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700262 // The animation holds a reference to the drag outline bitmap as long is it's
263 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700264 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700265 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700266 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700267 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700268 anim.setTag(null);
269 }
270 }
271 });
272 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700273 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700274
Michael Jurka18014792010-10-14 09:01:34 -0700275 mBackgroundRect = new Rect();
Adam Cohenb5ba0972011-09-07 18:02:31 -0700276 mForegroundRect = new Rect();
Michael Jurkabea15192010-11-17 12:33:46 -0800277
Michael Jurkaa52570f2012-03-20 03:18:20 -0700278 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
279 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
280 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700281 }
282
Michael Jurkaf6440da2011-04-05 14:50:34 -0700283 static int widthInPortrait(Resources r, int numCells) {
284 // We use this method from Workspace to figure out how many rows/columns Launcher should
285 // have. We ignore the left/right padding on CellLayout because it turns out in our design
286 // the padding extends outside the visible screen size, but it looked fine anyway.
Michael Jurkaf6440da2011-04-05 14:50:34 -0700287 int cellWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700288 int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
289 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
Michael Jurkaf6440da2011-04-05 14:50:34 -0700290
Winson Chung4b825dcd2011-06-19 12:41:22 -0700291 return minGap * (numCells - 1) + cellWidth * numCells;
Michael Jurkaf6440da2011-04-05 14:50:34 -0700292 }
293
Michael Jurkaf6440da2011-04-05 14:50:34 -0700294 static int heightInLandscape(Resources r, int numCells) {
295 // We use this method from Workspace to figure out how many rows/columns Launcher should
296 // have. We ignore the left/right padding on CellLayout because it turns out in our design
297 // the padding extends outside the visible screen size, but it looked fine anyway.
Michael Jurkaf6440da2011-04-05 14:50:34 -0700298 int cellHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700299 int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
300 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
Michael Jurkaf6440da2011-04-05 14:50:34 -0700301
Winson Chung4b825dcd2011-06-19 12:41:22 -0700302 return minGap * (numCells - 1) + cellHeight * numCells;
Michael Jurkaf6440da2011-04-05 14:50:34 -0700303 }
304
Adam Cohen2801caf2011-05-13 20:57:39 -0700305 public void enableHardwareLayers() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700306 mShortcutsAndWidgets.enableHardwareLayers();
Adam Cohen2801caf2011-05-13 20:57:39 -0700307 }
308
309 public void setGridSize(int x, int y) {
310 mCountX = x;
311 mCountY = y;
312 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800313 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700314 mTempRectStack.clear();
Adam Cohen76fc0852011-06-17 13:26:23 -0700315 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700316 }
317
Patrick Dubroy96864c32011-03-10 17:17:23 -0800318 private void invalidateBubbleTextView(BubbleTextView icon) {
319 final int padding = icon.getPressedOrFocusedBackgroundPadding();
Winson Chung4b825dcd2011-06-19 12:41:22 -0700320 invalidate(icon.getLeft() + getPaddingLeft() - padding,
321 icon.getTop() + getPaddingTop() - padding,
322 icon.getRight() + getPaddingLeft() + padding,
323 icon.getBottom() + getPaddingTop() + padding);
Patrick Dubroy96864c32011-03-10 17:17:23 -0800324 }
325
Adam Cohenb5ba0972011-09-07 18:02:31 -0700326 void setOverScrollAmount(float r, boolean left) {
327 if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
328 mOverScrollForegroundDrawable = mOverScrollLeft;
329 } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
330 mOverScrollForegroundDrawable = mOverScrollRight;
331 }
332
333 mForegroundAlpha = (int) Math.round((r * 255));
334 mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
335 invalidate();
336 }
337
Patrick Dubroy96864c32011-03-10 17:17:23 -0800338 void setPressedOrFocusedIcon(BubbleTextView icon) {
339 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
340 // requires an expanded clip rect (due to the glow's blur radius)
341 BubbleTextView oldIcon = mPressedOrFocusedIcon;
342 mPressedOrFocusedIcon = icon;
343 if (oldIcon != null) {
344 invalidateBubbleTextView(oldIcon);
345 }
346 if (mPressedOrFocusedIcon != null) {
347 invalidateBubbleTextView(mPressedOrFocusedIcon);
348 }
349 }
350
Michael Jurka33945b22010-12-21 18:19:38 -0800351 void setIsDragOverlapping(boolean isDragOverlapping) {
352 if (mIsDragOverlapping != isDragOverlapping) {
353 mIsDragOverlapping = isDragOverlapping;
354 invalidate();
355 }
356 }
357
358 boolean getIsDragOverlapping() {
359 return mIsDragOverlapping;
360 }
361
Adam Cohenebea84d2011-11-09 17:20:41 -0800362 protected void setOverscrollTransformsDirty(boolean dirty) {
363 mScrollingTransformsDirty = dirty;
364 }
365
366 protected void resetOverscrollTransforms() {
367 if (mScrollingTransformsDirty) {
368 setOverscrollTransformsDirty(false);
369 setTranslationX(0);
370 setRotationY(0);
371 // It doesn't matter if we pass true or false here, the important thing is that we
372 // pass 0, which results in the overscroll drawable not being drawn any more.
373 setOverScrollAmount(0, false);
374 setPivotX(getMeasuredWidth() / 2);
375 setPivotY(getMeasuredHeight() / 2);
376 }
377 }
378
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700379 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700380 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700381 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
382 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
383 // When we're small, we are either drawn normally or in the "accepts drops" state (during
384 // a drag). However, we also drag the mini hover background *over* one of those two
385 // backgrounds
Winson Chungb26f3d62011-06-02 10:49:29 -0700386 if (mBackgroundAlpha > 0.0f) {
Adam Cohenf34bab52010-09-30 14:11:56 -0700387 Drawable bg;
Michael Jurka33945b22010-12-21 18:19:38 -0800388
389 if (mIsDragOverlapping) {
390 // In the mini case, we draw the active_glow bg *over* the active background
Michael Jurkabdf78552011-10-31 14:34:25 -0700391 bg = mActiveGlowBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700392 } else {
Michael Jurkabdf78552011-10-31 14:34:25 -0700393 bg = mNormalBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700394 }
Michael Jurka33945b22010-12-21 18:19:38 -0800395
396 bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
397 bg.setBounds(mBackgroundRect);
398 bg.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700399 }
Romain Guya6abce82009-11-10 02:54:41 -0800400
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700401 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700402 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700403 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700404 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800405 final Rect r = mDragOutlines[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700406 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700407 paint.setAlpha((int)(alpha + .5f));
Adam Cohend41fbf52012-02-16 23:53:59 -0800408 canvas.drawBitmap(b, null, r, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700409 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700410 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800411
412 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
413 // requires an expanded clip rect (due to the glow's blur radius)
414 if (mPressedOrFocusedIcon != null) {
415 final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
416 final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
417 if (b != null) {
418 canvas.drawBitmap(b,
Winson Chung4b825dcd2011-06-19 12:41:22 -0700419 mPressedOrFocusedIcon.getLeft() + getPaddingLeft() - padding,
420 mPressedOrFocusedIcon.getTop() + getPaddingTop() - padding,
Patrick Dubroy96864c32011-03-10 17:17:23 -0800421 null);
422 }
423 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700424
Adam Cohen482ed822012-03-02 14:15:13 -0800425 if (DEBUG_VISUALIZE_OCCUPIED) {
426 int[] pt = new int[2];
427 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700428 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800429 for (int i = 0; i < mCountX; i++) {
430 for (int j = 0; j < mCountY; j++) {
431 if (mOccupied[i][j]) {
432 cellToPoint(i, j, pt);
433 canvas.save();
434 canvas.translate(pt[0], pt[1]);
435 cd.draw(canvas);
436 canvas.restore();
437 }
438 }
439 }
440 }
441
Andrew Flynn850d2e72012-04-26 16:51:20 -0700442 int previewOffset = FolderRingAnimator.sPreviewSize;
443
Adam Cohen69ce2e52011-07-03 19:25:21 -0700444 // The folder outer / inner ring image(s)
445 for (int i = 0; i < mFolderOuterRings.size(); i++) {
446 FolderRingAnimator fra = mFolderOuterRings.get(i);
447
448 // Draw outer ring
449 Drawable d = FolderRingAnimator.sSharedOuterRingDrawable;
450 int width = (int) fra.getOuterRingSize();
451 int height = width;
452 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
453
454 int centerX = mTempLocation[0] + mCellWidth / 2;
Andrew Flynn850d2e72012-04-26 16:51:20 -0700455 int centerY = mTempLocation[1] + previewOffset / 2;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700456
457 canvas.save();
458 canvas.translate(centerX - width / 2, centerY - height / 2);
459 d.setBounds(0, 0, width, height);
460 d.draw(canvas);
461 canvas.restore();
462
463 // Draw inner ring
464 d = FolderRingAnimator.sSharedInnerRingDrawable;
465 width = (int) fra.getInnerRingSize();
466 height = width;
467 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
468
469 centerX = mTempLocation[0] + mCellWidth / 2;
Andrew Flynn850d2e72012-04-26 16:51:20 -0700470 centerY = mTempLocation[1] + previewOffset / 2;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700471 canvas.save();
472 canvas.translate(centerX - width / 2, centerY - width / 2);
473 d.setBounds(0, 0, width, height);
474 d.draw(canvas);
475 canvas.restore();
476 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700477
478 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
479 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
480 int width = d.getIntrinsicWidth();
481 int height = d.getIntrinsicHeight();
482
483 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
484 int centerX = mTempLocation[0] + mCellWidth / 2;
Andrew Flynn850d2e72012-04-26 16:51:20 -0700485 int centerY = mTempLocation[1] + previewOffset / 2;
Adam Cohenc51934b2011-07-26 21:07:43 -0700486
487 canvas.save();
488 canvas.translate(centerX - width / 2, centerY - width / 2);
489 d.setBounds(0, 0, width, height);
490 d.draw(canvas);
491 canvas.restore();
492 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700493 }
494
Adam Cohenb5ba0972011-09-07 18:02:31 -0700495 @Override
496 protected void dispatchDraw(Canvas canvas) {
497 super.dispatchDraw(canvas);
498 if (mForegroundAlpha > 0) {
499 mOverScrollForegroundDrawable.setBounds(mForegroundRect);
500 Paint p = ((NinePatchDrawable) mOverScrollForegroundDrawable).getPaint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700501 p.setXfermode(sAddBlendMode);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700502 mOverScrollForegroundDrawable.draw(canvas);
503 p.setXfermode(null);
504 }
505 }
506
Adam Cohen69ce2e52011-07-03 19:25:21 -0700507 public void showFolderAccept(FolderRingAnimator fra) {
508 mFolderOuterRings.add(fra);
509 }
510
511 public void hideFolderAccept(FolderRingAnimator fra) {
512 if (mFolderOuterRings.contains(fra)) {
513 mFolderOuterRings.remove(fra);
514 }
515 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700516 }
517
Adam Cohenc51934b2011-07-26 21:07:43 -0700518 public void setFolderLeaveBehindCell(int x, int y) {
519 mFolderLeaveBehindCell[0] = x;
520 mFolderLeaveBehindCell[1] = y;
521 invalidate();
522 }
523
524 public void clearFolderLeaveBehind() {
525 mFolderLeaveBehindCell[0] = -1;
526 mFolderLeaveBehindCell[1] = -1;
527 invalidate();
528 }
529
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700530 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700531 public boolean shouldDelayChildPressedState() {
532 return false;
533 }
534
535 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700536 public void cancelLongPress() {
537 super.cancelLongPress();
538
539 // Cancel long press for all children
540 final int count = getChildCount();
541 for (int i = 0; i < count; i++) {
542 final View child = getChildAt(i);
543 child.cancelLongPress();
544 }
545 }
546
Michael Jurkadee05892010-07-27 10:01:56 -0700547 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
548 mInterceptTouchListener = listener;
549 }
550
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800551 int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700552 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800553 }
554
555 int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700556 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800557 }
558
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800559 public void setIsHotseat(boolean isHotseat) {
560 mIsHotseat = isHotseat;
561 }
562
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800563 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Andrew Flynn850d2e72012-04-26 16:51:20 -0700564 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700565 final LayoutParams lp = params;
566
Andrew Flynnde38e422012-05-08 11:22:15 -0700567 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800568 if (child instanceof BubbleTextView) {
569 BubbleTextView bubbleChild = (BubbleTextView) child;
570
Andrew Flynnde38e422012-05-08 11:22:15 -0700571 Resources res = getResources();
572 if (mIsHotseat) {
573 bubbleChild.setTextColor(res.getColor(android.R.color.transparent));
574 } else {
575 bubbleChild.setTextColor(res.getColor(R.color.workspace_icon_text_color));
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800576 }
577 }
578
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800579 // Generate an id for each view, this assumes we have at most 256x256 cells
580 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700581 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700582 // If the horizontal or vertical span is set to -1, it is taken to
583 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700584 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
585 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800586
Winson Chungaafa03c2010-06-11 17:34:16 -0700587 child.setId(childId);
588
Michael Jurkaa52570f2012-03-20 03:18:20 -0700589 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700590
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700591 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700592
Winson Chungaafa03c2010-06-11 17:34:16 -0700593 return true;
594 }
595 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800596 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700597
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800598 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700599 public void removeAllViews() {
600 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700601 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700602 }
603
604 @Override
605 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700606 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700607 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700608 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700609 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700610 }
611
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700612 public void removeViewWithoutMarkingCells(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700613 mShortcutsAndWidgets.removeView(view);
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700614 }
615
Michael Jurka0280c3b2010-09-17 15:00:07 -0700616 @Override
617 public void removeView(View view) {
618 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700619 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700620 }
621
622 @Override
623 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700624 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
625 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700626 }
627
628 @Override
629 public void removeViewInLayout(View view) {
630 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700631 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700632 }
633
634 @Override
635 public void removeViews(int start, int count) {
636 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700637 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700638 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700639 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700640 }
641
642 @Override
643 public void removeViewsInLayout(int start, int count) {
644 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700645 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700646 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700647 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800648 }
649
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800650 @Override
651 protected void onAttachedToWindow() {
652 super.onAttachedToWindow();
653 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
654 }
655
Michael Jurkaaf442092010-06-10 17:01:57 -0700656 public void setTagToCellInfoForPoint(int touchX, int touchY) {
657 final CellInfo cellInfo = mCellInfo;
Winson Chungeecf02d2012-03-02 17:14:58 -0800658 Rect frame = mRect;
Michael Jurka8b805b12012-04-18 14:23:14 -0700659 final int x = touchX + getScrollX();
660 final int y = touchY + getScrollY();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700661 final int count = mShortcutsAndWidgets.getChildCount();
Michael Jurkaaf442092010-06-10 17:01:57 -0700662
663 boolean found = false;
664 for (int i = count - 1; i >= 0; i--) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700665 final View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohend4844c32011-02-18 19:25:06 -0800666 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Michael Jurkaaf442092010-06-10 17:01:57 -0700667
Adam Cohen1b607ed2011-03-03 17:26:50 -0800668 if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
669 lp.isLockedToGrid) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700670 child.getHitRect(frame);
Winson Chung0be025d2011-05-23 17:45:09 -0700671
Winson Chungeecf02d2012-03-02 17:14:58 -0800672 float scale = child.getScaleX();
673 frame = new Rect(child.getLeft(), child.getTop(), child.getRight(),
674 child.getBottom());
Winson Chung0be025d2011-05-23 17:45:09 -0700675 // The child hit rect is relative to the CellLayoutChildren parent, so we need to
676 // offset that by this CellLayout's padding to test an (x,y) point that is relative
677 // to this view.
Michael Jurka8b805b12012-04-18 14:23:14 -0700678 frame.offset(getPaddingLeft(), getPaddingTop());
Winson Chungeecf02d2012-03-02 17:14:58 -0800679 frame.inset((int) (frame.width() * (1f - scale) / 2),
680 (int) (frame.height() * (1f - scale) / 2));
Winson Chung0be025d2011-05-23 17:45:09 -0700681
Michael Jurkaaf442092010-06-10 17:01:57 -0700682 if (frame.contains(x, y)) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700683 cellInfo.cell = child;
684 cellInfo.cellX = lp.cellX;
685 cellInfo.cellY = lp.cellY;
686 cellInfo.spanX = lp.cellHSpan;
687 cellInfo.spanY = lp.cellVSpan;
Michael Jurkaaf442092010-06-10 17:01:57 -0700688 found = true;
Michael Jurkaaf442092010-06-10 17:01:57 -0700689 break;
690 }
691 }
692 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700693
Michael Jurkad771c962011-08-09 15:00:48 -0700694 mLastDownOnOccupiedCell = found;
695
Michael Jurkaaf442092010-06-10 17:01:57 -0700696 if (!found) {
Winson Chung0be025d2011-05-23 17:45:09 -0700697 final int cellXY[] = mTmpXY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700698 pointToCellExact(x, y, cellXY);
699
Michael Jurkaaf442092010-06-10 17:01:57 -0700700 cellInfo.cell = null;
701 cellInfo.cellX = cellXY[0];
702 cellInfo.cellY = cellXY[1];
703 cellInfo.spanX = 1;
704 cellInfo.spanY = 1;
Michael Jurkaaf442092010-06-10 17:01:57 -0700705 }
706 setTag(cellInfo);
707 }
708
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800709 @Override
710 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Cohenc1997fd2011-08-15 18:26:39 -0700711 // First we clear the tag to ensure that on every touch down we start with a fresh slate,
712 // even in the case where we return early. Not clearing here was causing bugs whereby on
713 // long-press we'd end up picking up an item from a previous drag operation.
714 final int action = ev.getAction();
715
716 if (action == MotionEvent.ACTION_DOWN) {
717 clearTagCellInfo();
718 }
719
Michael Jurkadee05892010-07-27 10:01:56 -0700720 if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
721 return true;
722 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800723
724 if (action == MotionEvent.ACTION_DOWN) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700725 setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800726 }
Winson Chungeecf02d2012-03-02 17:14:58 -0800727
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800728 return false;
729 }
730
Adam Cohenc1997fd2011-08-15 18:26:39 -0700731 private void clearTagCellInfo() {
732 final CellInfo cellInfo = mCellInfo;
733 cellInfo.cell = null;
734 cellInfo.cellX = -1;
735 cellInfo.cellY = -1;
736 cellInfo.spanX = 0;
737 cellInfo.spanY = 0;
738 setTag(cellInfo);
739 }
740
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800741 public CellInfo getTag() {
Michael Jurka0280c3b2010-09-17 15:00:07 -0700742 return (CellInfo) super.getTag();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800743 }
744
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700745 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700746 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800747 * @param x X coordinate of the point
748 * @param y Y coordinate of the point
749 * @param result Array of 2 ints to hold the x and y coordinate of the cell
750 */
751 void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700752 final int hStartPadding = getPaddingLeft();
753 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800754
755 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
756 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
757
Adam Cohend22015c2010-07-26 22:02:18 -0700758 final int xAxis = mCountX;
759 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800760
761 if (result[0] < 0) result[0] = 0;
762 if (result[0] >= xAxis) result[0] = xAxis - 1;
763 if (result[1] < 0) result[1] = 0;
764 if (result[1] >= yAxis) result[1] = yAxis - 1;
765 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700766
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800767 /**
768 * Given a point, return the cell that most closely encloses that point
769 * @param x X coordinate of the point
770 * @param y Y coordinate of the point
771 * @param result Array of 2 ints to hold the x and y coordinate of the cell
772 */
773 void pointToCellRounded(int x, int y, int[] result) {
774 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
775 }
776
777 /**
778 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700779 *
780 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800781 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700782 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800783 * @param result Array of 2 ints to hold the x and y coordinate of the point
784 */
785 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700786 final int hStartPadding = getPaddingLeft();
787 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800788
789 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
790 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
791 }
792
Adam Cohene3e27a82011-04-15 12:07:39 -0700793 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800794 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700795 *
796 * @param cellX X coordinate of the cell
797 * @param cellY Y coordinate of the cell
798 *
799 * @param result Array of 2 ints to hold the x and y coordinate of the point
800 */
801 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700802 regionToCenterPoint(cellX, cellY, 1, 1, result);
803 }
804
805 /**
806 * Given a cell coordinate and span return the point that represents the center of the regio
807 *
808 * @param cellX X coordinate of the cell
809 * @param cellY Y coordinate of the cell
810 *
811 * @param result Array of 2 ints to hold the x and y coordinate of the point
812 */
813 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700814 final int hStartPadding = getPaddingLeft();
815 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700816 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
817 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
818 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
819 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700820 }
821
Adam Cohen19f37922012-03-21 11:59:11 -0700822 /**
823 * Given a cell coordinate and span fills out a corresponding pixel rect
824 *
825 * @param cellX X coordinate of the cell
826 * @param cellY Y coordinate of the cell
827 * @param result Rect in which to write the result
828 */
829 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
830 final int hStartPadding = getPaddingLeft();
831 final int vStartPadding = getPaddingTop();
832 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
833 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
834 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
835 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
836 }
837
Adam Cohen482ed822012-03-02 14:15:13 -0800838 public float getDistanceFromCell(float x, float y, int[] cell) {
839 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
840 float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) +
841 Math.pow(y - mTmpPoint[1], 2));
842 return distance;
843 }
844
Romain Guy84f296c2009-11-04 15:00:44 -0800845 int getCellWidth() {
846 return mCellWidth;
847 }
848
849 int getCellHeight() {
850 return mCellHeight;
851 }
852
Adam Cohend4844c32011-02-18 19:25:06 -0800853 int getWidthGap() {
854 return mWidthGap;
855 }
856
857 int getHeightGap() {
858 return mHeightGap;
859 }
860
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700861 Rect getContentRect(Rect r) {
862 if (r == null) {
863 r = new Rect();
864 }
865 int left = getPaddingLeft();
866 int top = getPaddingTop();
Michael Jurka8b805b12012-04-18 14:23:14 -0700867 int right = left + getWidth() - getPaddingLeft() - getPaddingRight();
868 int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom();
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700869 r.set(left, top, right, bottom);
870 return r;
871 }
872
Adam Cohena897f392012-04-27 18:12:05 -0700873 static void getMetrics(Rect metrics, Resources res, int measureWidth, int measureHeight,
874 int countX, int countY, int orientation) {
875 int numWidthGaps = countX - 1;
876 int numHeightGaps = countY - 1;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700877
878 int widthGap;
879 int heightGap;
880 int cellWidth;
881 int cellHeight;
882 int paddingLeft;
883 int paddingRight;
884 int paddingTop;
885 int paddingBottom;
886
Adam Cohena897f392012-04-27 18:12:05 -0700887 int maxGap = res.getDimensionPixelSize(R.dimen.workspace_max_gap);
Adam Cohenf4bd5792012-04-27 11:35:29 -0700888 if (orientation == LANDSCAPE) {
889 cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_land);
890 cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_land);
891 widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_land);
892 heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_land);
893 paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_land);
894 paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_land);
895 paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_land);
896 paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_land);
897 } else {
898 // PORTRAIT
899 cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_port);
900 cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_port);
901 widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_port);
902 heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_port);
903 paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_port);
904 paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_port);
905 paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_port);
906 paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_port);
907 }
908
909 if (widthGap < 0 || heightGap < 0) {
910 int hSpace = measureWidth - paddingLeft - paddingRight;
911 int vSpace = measureHeight - paddingTop - paddingBottom;
Adam Cohena897f392012-04-27 18:12:05 -0700912 int hFreeSpace = hSpace - (countX * cellWidth);
913 int vFreeSpace = vSpace - (countY * cellHeight);
914 widthGap = Math.min(maxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
915 heightGap = Math.min(maxGap, numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Adam Cohenf4bd5792012-04-27 11:35:29 -0700916 }
917 metrics.set(cellWidth, cellHeight, widthGap, heightGap);
918 }
919
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800920 @Override
921 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800922 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700923 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
924
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800925 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
926 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700927
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800928 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
929 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
930 }
931
Adam Cohend22015c2010-07-26 22:02:18 -0700932 int numWidthGaps = mCountX - 1;
933 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800934
Adam Cohen234c4cd2011-07-17 21:03:04 -0700935 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Michael Jurkadd13e3d2012-05-01 12:38:17 -0700936 int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight();
937 int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom();
Adam Cohenf4bd5792012-04-27 11:35:29 -0700938 int hFreeSpace = hSpace - (mCountX * mCellWidth);
939 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700940 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
941 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700942 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700943 } else {
944 mWidthGap = mOriginalWidthGap;
945 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700946 }
Michael Jurka5f1c5092010-09-03 14:15:02 -0700947
Michael Jurka8c920dd2011-01-20 14:16:56 -0800948 // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
949 int newWidth = widthSpecSize;
950 int newHeight = heightSpecSize;
Michael Jurka5f1c5092010-09-03 14:15:02 -0700951 if (widthSpecMode == MeasureSpec.AT_MOST) {
Michael Jurka8b805b12012-04-18 14:23:14 -0700952 newWidth = getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Winson Chungece7f5b2010-10-22 14:54:12 -0700953 ((mCountX - 1) * mWidthGap);
Michael Jurka8b805b12012-04-18 14:23:14 -0700954 newHeight = getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Winson Chungece7f5b2010-10-22 14:54:12 -0700955 ((mCountY - 1) * mHeightGap);
Michael Jurka5f1c5092010-09-03 14:15:02 -0700956 setMeasuredDimension(newWidth, newHeight);
Michael Jurka5f1c5092010-09-03 14:15:02 -0700957 }
Michael Jurka8c920dd2011-01-20 14:16:56 -0800958
959 int count = getChildCount();
960 for (int i = 0; i < count; i++) {
961 View child = getChildAt(i);
Michael Jurka8b805b12012-04-18 14:23:14 -0700962 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() -
963 getPaddingRight(), MeasureSpec.EXACTLY);
964 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() -
965 getPaddingBottom(), MeasureSpec.EXACTLY);
Michael Jurka8c920dd2011-01-20 14:16:56 -0800966 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
967 }
968 setMeasuredDimension(newWidth, newHeight);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800969 }
970
971 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -0700972 protected void onLayout(boolean changed, int l, int t, int r, int b) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800973 int count = getChildCount();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800974 for (int i = 0; i < count; i++) {
Michael Jurka8c920dd2011-01-20 14:16:56 -0800975 View child = getChildAt(i);
Michael Jurka8b805b12012-04-18 14:23:14 -0700976 child.layout(getPaddingLeft(), getPaddingTop(),
977 r - l - getPaddingRight(), b - t - getPaddingBottom());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800978 }
979 }
980
981 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700982 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
983 super.onSizeChanged(w, h, oldw, oldh);
Michael Jurka18014792010-10-14 09:01:34 -0700984 mBackgroundRect.set(0, 0, w, h);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700985 mForegroundRect.set(mForegroundPadding, mForegroundPadding,
986 w - 2 * mForegroundPadding, h - 2 * mForegroundPadding);
Michael Jurkadee05892010-07-27 10:01:56 -0700987 }
988
989 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800990 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700991 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800992 }
993
994 @Override
995 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700996 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800997 }
998
Michael Jurka5f1c5092010-09-03 14:15:02 -0700999 public float getBackgroundAlpha() {
1000 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -07001001 }
1002
Adam Cohen1b0aaac2010-10-28 11:11:18 -07001003 public void setBackgroundAlphaMultiplier(float multiplier) {
Michael Jurkaa3d30ad2012-05-08 13:43:43 -07001004 if (mBackgroundAlphaMultiplier != multiplier) {
1005 mBackgroundAlphaMultiplier = multiplier;
1006 invalidate();
1007 }
Adam Cohen1b0aaac2010-10-28 11:11:18 -07001008 }
1009
Adam Cohenddb82192010-11-10 16:32:54 -08001010 public float getBackgroundAlphaMultiplier() {
1011 return mBackgroundAlphaMultiplier;
1012 }
1013
Michael Jurka5f1c5092010-09-03 14:15:02 -07001014 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -08001015 if (mBackgroundAlpha != alpha) {
1016 mBackgroundAlpha = alpha;
1017 invalidate();
1018 }
Michael Jurkadee05892010-07-27 10:01:56 -07001019 }
1020
Michael Jurkaa52570f2012-03-20 03:18:20 -07001021 public void setShortcutAndWidgetAlpha(float alpha) {
Michael Jurka0142d492010-08-25 17:46:15 -07001022 final int childCount = getChildCount();
1023 for (int i = 0; i < childCount; i++) {
Michael Jurkadee05892010-07-27 10:01:56 -07001024 getChildAt(i).setAlpha(alpha);
1025 }
1026 }
1027
Michael Jurkaa52570f2012-03-20 03:18:20 -07001028 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
1029 if (getChildCount() > 0) {
1030 return (ShortcutAndWidgetContainer) getChildAt(0);
1031 }
1032 return null;
1033 }
1034
Patrick Dubroy440c3602010-07-13 17:50:32 -07001035 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001036 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -07001037 }
1038
Adam Cohen76fc0852011-06-17 13:26:23 -07001039 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -08001040 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001041 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -08001042 boolean[][] occupied = mOccupied;
1043 if (!permanent) {
1044 occupied = mTmpOccupied;
1045 }
1046
Adam Cohen19f37922012-03-21 11:59:11 -07001047 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001048 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1049 final ItemInfo info = (ItemInfo) child.getTag();
1050
1051 // We cancel any existing animations
1052 if (mReorderAnimators.containsKey(lp)) {
1053 mReorderAnimators.get(lp).cancel();
1054 mReorderAnimators.remove(lp);
1055 }
1056
Adam Cohen482ed822012-03-02 14:15:13 -08001057 final int oldX = lp.x;
1058 final int oldY = lp.y;
1059 if (adjustOccupied) {
1060 occupied[lp.cellX][lp.cellY] = false;
1061 occupied[cellX][cellY] = true;
1062 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001063 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001064 if (permanent) {
1065 lp.cellX = info.cellX = cellX;
1066 lp.cellY = info.cellY = cellY;
1067 } else {
1068 lp.tmpCellX = cellX;
1069 lp.tmpCellY = cellY;
1070 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001071 clc.setupLp(lp);
1072 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001073 final int newX = lp.x;
1074 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001075
Adam Cohen76fc0852011-06-17 13:26:23 -07001076 lp.x = oldX;
1077 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001078
Adam Cohen482ed822012-03-02 14:15:13 -08001079 // Exit early if we're not actually moving the view
1080 if (oldX == newX && oldY == newY) {
1081 lp.isLockedToGrid = true;
1082 return true;
1083 }
1084
1085 ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
1086 va.setDuration(duration);
1087 mReorderAnimators.put(lp, va);
1088
1089 va.addUpdateListener(new AnimatorUpdateListener() {
1090 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001091 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001092 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001093 lp.x = (int) ((1 - r) * oldX + r * newX);
1094 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001095 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001096 }
1097 });
Adam Cohen482ed822012-03-02 14:15:13 -08001098 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001099 boolean cancelled = false;
1100 public void onAnimationEnd(Animator animation) {
1101 // If the animation was cancelled, it means that another animation
1102 // has interrupted this one, and we don't want to lock the item into
1103 // place just yet.
1104 if (!cancelled) {
1105 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001106 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001107 }
1108 if (mReorderAnimators.containsKey(lp)) {
1109 mReorderAnimators.remove(lp);
1110 }
1111 }
1112 public void onAnimationCancel(Animator animation) {
1113 cancelled = true;
1114 }
1115 });
Adam Cohen482ed822012-03-02 14:15:13 -08001116 va.setStartDelay(delay);
1117 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001118 return true;
1119 }
1120 return false;
1121 }
1122
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001123 /**
1124 * Estimate where the top left cell of the dragged item will land if it is dropped.
1125 *
1126 * @param originX The X value of the top left corner of the item
1127 * @param originY The Y value of the top left corner of the item
1128 * @param spanX The number of horizontal cells that the item spans
1129 * @param spanY The number of vertical cells that the item spans
1130 * @param result The estimated drop cell X and Y.
1131 */
1132 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
Adam Cohend22015c2010-07-26 22:02:18 -07001133 final int countX = mCountX;
1134 final int countY = mCountY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001135
Michael Jurkaa63c4522010-08-19 13:52:27 -07001136 // pointToCellRounded takes the top left of a cell but will pad that with
1137 // cellWidth/2 and cellHeight/2 when finding the matching cell
1138 pointToCellRounded(originX, originY, result);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001139
1140 // If the item isn't fully on this screen, snap to the edges
1141 int rightOverhang = result[0] + spanX - countX;
1142 if (rightOverhang > 0) {
1143 result[0] -= rightOverhang; // Snap to right
1144 }
1145 result[0] = Math.max(0, result[0]); // Snap to left
1146 int bottomOverhang = result[1] + spanY - countY;
1147 if (bottomOverhang > 0) {
1148 result[1] -= bottomOverhang; // Snap to bottom
1149 }
1150 result[1] = Math.max(0, result[1]); // Snap to top
1151 }
1152
Adam Cohen482ed822012-03-02 14:15:13 -08001153 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1154 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001155 final int oldDragCellX = mDragCell[0];
1156 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001157
Winson Chungb8c69f32011-10-19 21:36:08 -07001158 if (v != null && dragOffset == null) {
Winson Chunga9abd0e2010-10-27 17:18:37 -07001159 mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
1160 } else {
1161 mDragCenter.set(originX, originY);
1162 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001163
Adam Cohen2801caf2011-05-13 20:57:39 -07001164 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001165 return;
1166 }
1167
Adam Cohen482ed822012-03-02 14:15:13 -08001168 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1169 mDragCell[0] = cellX;
1170 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001171 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001172 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001173 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001174
Joe Onorato4be866d2010-10-10 11:26:02 -07001175 int left = topLeft[0];
1176 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001177
Winson Chungb8c69f32011-10-19 21:36:08 -07001178 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001179 // When drawing the drag outline, it did not account for margin offsets
1180 // added by the view's parent.
1181 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1182 left += lp.leftMargin;
1183 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001184
Adam Cohen99e8b402011-03-25 19:23:43 -07001185 // Offsets due to the size difference between the View and the dragOutline.
1186 // There is a size difference to account for the outer blur, which may lie
1187 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001188 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001189 // We center about the x axis
1190 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1191 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001192 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001193 if (dragOffset != null && dragRegion != null) {
1194 // Center the drag region *horizontally* in the cell and apply a drag
1195 // outline offset
1196 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1197 - dragRegion.width()) / 2;
1198 top += dragOffset.y;
1199 } else {
1200 // Center the drag outline in the cell
1201 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1202 - dragOutline.getWidth()) / 2;
1203 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1204 - dragOutline.getHeight()) / 2;
1205 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001206 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001207 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001208 mDragOutlineAnims[oldIndex].animateOut();
1209 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001210 Rect r = mDragOutlines[mDragOutlineCurrent];
1211 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1212 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001213 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001214 }
Winson Chung150fbab2010-09-29 17:14:26 -07001215
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001216 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1217 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001218 }
1219 }
1220
Adam Cohene0310962011-04-18 16:15:31 -07001221 public void clearDragOutlines() {
1222 final int oldIndex = mDragOutlineCurrent;
1223 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001224 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001225 }
1226
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001227 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001228 * Find a vacant area that will fit the given bounds nearest the requested
1229 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001230 *
Romain Guy51afc022009-05-04 18:03:43 -07001231 * @param pixelX The X location at which you want to search for a vacant area.
1232 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001233 * @param spanX Horizontal span of the object.
1234 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001235 * @param result Array in which to place the result, or null (in which case a new array will
1236 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001237 * @return The X, Y cell of a vacant area that can contain this object,
1238 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001239 */
Adam Cohend41fbf52012-02-16 23:53:59 -08001240 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
1241 int[] result) {
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001242 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001243 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001244
Michael Jurka6a1435d2010-09-27 17:35:12 -07001245 /**
1246 * Find a vacant area that will fit the given bounds nearest the requested
1247 * cell location. Uses Euclidean distance to score multiple vacant areas.
1248 *
1249 * @param pixelX The X location at which you want to search for a vacant area.
1250 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001251 * @param minSpanX The minimum horizontal span required
1252 * @param minSpanY The minimum vertical span required
1253 * @param spanX Horizontal span of the object.
1254 * @param spanY Vertical span of the object.
1255 * @param result Array in which to place the result, or null (in which case a new array will
1256 * be allocated)
1257 * @return The X, Y cell of a vacant area that can contain this object,
1258 * nearest the requested location.
1259 */
1260 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1261 int spanY, int[] result, int[] resultSpan) {
1262 return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
1263 result, resultSpan);
1264 }
1265
1266 /**
1267 * Find a vacant area that will fit the given bounds nearest the requested
1268 * cell location. Uses Euclidean distance to score multiple vacant areas.
1269 *
1270 * @param pixelX The X location at which you want to search for a vacant area.
1271 * @param pixelY The Y location at which you want to search for a vacant area.
Michael Jurka6a1435d2010-09-27 17:35:12 -07001272 * @param spanX Horizontal span of the object.
1273 * @param spanY Vertical span of the object.
Adam Cohendf035382011-04-11 17:22:04 -07001274 * @param ignoreOccupied If true, the result can be an occupied cell
1275 * @param result Array in which to place the result, or null (in which case a new array will
1276 * be allocated)
Michael Jurka6a1435d2010-09-27 17:35:12 -07001277 * @return The X, Y cell of a vacant area that can contain this object,
1278 * nearest the requested location.
1279 */
Adam Cohendf035382011-04-11 17:22:04 -07001280 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
1281 boolean ignoreOccupied, int[] result) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001282 return findNearestArea(pixelX, pixelY, spanX, spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001283 spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08001284 }
1285
1286 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1287 private void lazyInitTempRectStack() {
1288 if (mTempRectStack.isEmpty()) {
1289 for (int i = 0; i < mCountX * mCountY; i++) {
1290 mTempRectStack.push(new Rect());
1291 }
1292 }
1293 }
Adam Cohen482ed822012-03-02 14:15:13 -08001294
Adam Cohend41fbf52012-02-16 23:53:59 -08001295 private void recycleTempRects(Stack<Rect> used) {
1296 while (!used.isEmpty()) {
1297 mTempRectStack.push(used.pop());
1298 }
1299 }
1300
1301 /**
1302 * Find a vacant area that will fit the given bounds nearest the requested
1303 * cell location. Uses Euclidean distance to score multiple vacant areas.
1304 *
1305 * @param pixelX The X location at which you want to search for a vacant area.
1306 * @param pixelY The Y location at which you want to search for a vacant area.
1307 * @param minSpanX The minimum horizontal span required
1308 * @param minSpanY The minimum vertical span required
1309 * @param spanX Horizontal span of the object.
1310 * @param spanY Vertical span of the object.
1311 * @param ignoreOccupied If true, the result can be an occupied cell
1312 * @param result Array in which to place the result, or null (in which case a new array will
1313 * be allocated)
1314 * @return The X, Y cell of a vacant area that can contain this object,
1315 * nearest the requested location.
1316 */
1317 int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001318 View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
1319 boolean[][] occupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001320 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001321 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08001322 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001323
Adam Cohene3e27a82011-04-15 12:07:39 -07001324 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1325 // to the center of the item, but we are searching based on the top-left cell, so
1326 // we translate the point over to correspond to the top-left.
1327 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1328 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1329
Jeff Sharkey70864282009-04-07 21:08:40 -07001330 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001331 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001332 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001333 final Rect bestRect = new Rect(-1, -1, -1, -1);
1334 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001335
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001336 final int countX = mCountX;
1337 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001338
Adam Cohend41fbf52012-02-16 23:53:59 -08001339 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1340 spanX < minSpanX || spanY < minSpanY) {
1341 return bestXY;
1342 }
1343
1344 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001345 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001346 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1347 int ySize = -1;
1348 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001349 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001350 // First, let's see if this thing fits anywhere
1351 for (int i = 0; i < minSpanX; i++) {
1352 for (int j = 0; j < minSpanY; j++) {
Adam Cohendf035382011-04-11 17:22:04 -07001353 if (occupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001354 continue inner;
1355 }
Michael Jurkac28de512010-08-13 11:27:44 -07001356 }
1357 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001358 xSize = minSpanX;
1359 ySize = minSpanY;
1360
1361 // We know that the item will fit at _some_ acceptable size, now let's see
1362 // how big we can make it. We'll alternate between incrementing x and y spans
1363 // until we hit a limit.
1364 boolean incX = true;
1365 boolean hitMaxX = xSize >= spanX;
1366 boolean hitMaxY = ySize >= spanY;
1367 while (!(hitMaxX && hitMaxY)) {
1368 if (incX && !hitMaxX) {
1369 for (int j = 0; j < ySize; j++) {
1370 if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
1371 // We can't move out horizontally
1372 hitMaxX = true;
1373 }
1374 }
1375 if (!hitMaxX) {
1376 xSize++;
1377 }
1378 } else if (!hitMaxY) {
1379 for (int i = 0; i < xSize; i++) {
1380 if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
1381 // We can't move out vertically
1382 hitMaxY = true;
1383 }
1384 }
1385 if (!hitMaxY) {
1386 ySize++;
1387 }
1388 }
1389 hitMaxX |= xSize >= spanX;
1390 hitMaxY |= ySize >= spanY;
1391 incX = !incX;
1392 }
1393 incX = true;
1394 hitMaxX = xSize >= spanX;
1395 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001396 }
Winson Chung0be025d2011-05-23 17:45:09 -07001397 final int[] cellXY = mTmpXY;
Adam Cohene3e27a82011-04-15 12:07:39 -07001398 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001399
Adam Cohend41fbf52012-02-16 23:53:59 -08001400 // We verify that the current rect is not a sub-rect of any of our previous
1401 // candidates. In this case, the current rect is disqualified in favour of the
1402 // containing rect.
1403 Rect currentRect = mTempRectStack.pop();
1404 currentRect.set(x, y, x + xSize, y + ySize);
1405 boolean contained = false;
1406 for (Rect r : validRegions) {
1407 if (r.contains(currentRect)) {
1408 contained = true;
1409 break;
1410 }
1411 }
1412 validRegions.push(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001413 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
1414 + Math.pow(cellXY[1] - pixelY, 2));
Adam Cohen482ed822012-03-02 14:15:13 -08001415
Adam Cohend41fbf52012-02-16 23:53:59 -08001416 if ((distance <= bestDistance && !contained) ||
1417 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001418 bestDistance = distance;
1419 bestXY[0] = x;
1420 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001421 if (resultSpan != null) {
1422 resultSpan[0] = xSize;
1423 resultSpan[1] = ySize;
1424 }
1425 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001426 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001427 }
1428 }
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001429 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08001430 markCellsAsOccupiedForView(ignoreView, occupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001431
Adam Cohenc0dcf592011-06-01 15:30:43 -07001432 // Return -1, -1 if no suitable location found
1433 if (bestDistance == Double.MAX_VALUE) {
1434 bestXY[0] = -1;
1435 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001436 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001437 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001438 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001439 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001440
Adam Cohen482ed822012-03-02 14:15:13 -08001441 /**
1442 * Find a vacant area that will fit the given bounds nearest the requested
1443 * cell location, and will also weigh in a suggested direction vector of the
1444 * desired location. This method computers distance based on unit grid distances,
1445 * not pixel distances.
1446 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001447 * @param cellX The X cell nearest to which you want to search for a vacant area.
1448 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001449 * @param spanX Horizontal span of the object.
1450 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001451 * @param direction The favored direction in which the views should move from x, y
1452 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1453 * matches exactly. Otherwise we find the best matching direction.
1454 * @param occoupied The array which represents which cells in the CellLayout are occupied
1455 * @param blockOccupied The array which represents which cells in the specified block (cellX,
1456 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001457 * @param result Array in which to place the result, or null (in which case a new array will
1458 * be allocated)
1459 * @return The X, Y cell of a vacant area that can contain this object,
1460 * nearest the requested location.
1461 */
1462 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001463 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001464 // Keep track of best-scoring drop area
1465 final int[] bestXY = result != null ? result : new int[2];
1466 float bestDistance = Float.MAX_VALUE;
1467 int bestDirectionScore = Integer.MIN_VALUE;
1468
1469 final int countX = mCountX;
1470 final int countY = mCountY;
1471
1472 for (int y = 0; y < countY - (spanY - 1); y++) {
1473 inner:
1474 for (int x = 0; x < countX - (spanX - 1); x++) {
1475 // First, let's see if this thing fits anywhere
1476 for (int i = 0; i < spanX; i++) {
1477 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001478 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001479 continue inner;
1480 }
1481 }
1482 }
1483
1484 float distance = (float)
1485 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1486 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001487 computeDirectionVector(x - cellX, y - cellY, curDirection);
1488 // The direction score is just the dot product of the two candidate direction
1489 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001490 int curDirectionScore = direction[0] * curDirection[0] +
1491 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001492 boolean exactDirectionOnly = false;
1493 boolean directionMatches = direction[0] == curDirection[0] &&
1494 direction[0] == curDirection[0];
1495 if ((directionMatches || !exactDirectionOnly) &&
1496 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001497 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1498 bestDistance = distance;
1499 bestDirectionScore = curDirectionScore;
1500 bestXY[0] = x;
1501 bestXY[1] = y;
1502 }
1503 }
1504 }
1505
1506 // Return -1, -1 if no suitable location found
1507 if (bestDistance == Float.MAX_VALUE) {
1508 bestXY[0] = -1;
1509 bestXY[1] = -1;
1510 }
1511 return bestXY;
1512 }
1513
Adam Cohen47a876d2012-03-19 13:21:41 -07001514 private int[] findNearestAreaInDirection(int cellX, int cellY, int spanX, int spanY,
1515 int[] direction,boolean[][] occupied,
1516 boolean blockOccupied[][], int[] result) {
1517 // Keep track of best-scoring drop area
1518 final int[] bestXY = result != null ? result : new int[2];
1519 bestXY[0] = -1;
1520 bestXY[1] = -1;
1521 float bestDistance = Float.MAX_VALUE;
1522
1523 // We use this to march in a single direction
Adam Cohen5b53f292012-03-29 14:30:35 -07001524 if ((direction[0] != 0 && direction[1] != 0) ||
1525 (direction[0] == 0 && direction[1] == 0)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001526 return bestXY;
1527 }
1528
1529 // This will only incrememnet one of x or y based on the assertion above
1530 int x = cellX + direction[0];
1531 int y = cellY + direction[1];
1532 while (x >= 0 && x + spanX <= mCountX && y >= 0 && y + spanY <= mCountY) {
1533
1534 boolean fail = false;
1535 for (int i = 0; i < spanX; i++) {
1536 for (int j = 0; j < spanY; j++) {
1537 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
1538 fail = true;
1539 }
1540 }
1541 }
1542 if (!fail) {
1543 float distance = (float)
1544 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1545 if (Float.compare(distance, bestDistance) < 0) {
1546 bestDistance = distance;
1547 bestXY[0] = x;
1548 bestXY[1] = y;
1549 }
1550 }
1551 x += direction[0];
1552 y += direction[1];
1553 }
1554 return bestXY;
1555 }
1556
Adam Cohen482ed822012-03-02 14:15:13 -08001557 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001558 int[] direction, ItemConfiguration currentState) {
1559 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001560 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001561 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001562 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1563
Adam Cohen8baab352012-03-20 17:39:21 -07001564 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001565
1566 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001567 c.x = mTempLocation[0];
1568 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001569 success = true;
1570
1571 }
Adam Cohen8baab352012-03-20 17:39:21 -07001572 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001573 return success;
1574 }
1575
Adam Cohen47a876d2012-03-19 13:21:41 -07001576 // This method looks in the specified direction to see if there is an additional view
1577 // immediately adjecent in that direction
1578 private boolean addViewInDirection(ArrayList<View> views, Rect boundingRect, int[] direction,
Adam Cohen19f37922012-03-21 11:59:11 -07001579 boolean[][] occupied, View dragView, ItemConfiguration currentState) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001580 boolean found = false;
1581
Michael Jurkaa52570f2012-03-20 03:18:20 -07001582 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen47a876d2012-03-19 13:21:41 -07001583 Rect r0 = new Rect(boundingRect);
1584 Rect r1 = new Rect();
1585
1586 int deltaX = 0;
1587 int deltaY = 0;
1588 if (direction[1] < 0) {
1589 r0.set(r0.left, r0.top - 1, r0.right, r0.bottom);
1590 deltaY = -1;
1591 } else if (direction[1] > 0) {
1592 r0.set(r0.left, r0.top, r0.right, r0.bottom + 1);
1593 deltaY = 1;
1594 } else if (direction[0] < 0) {
1595 r0.set(r0.left - 1, r0.top, r0.right, r0.bottom);
1596 deltaX = -1;
1597 } else if (direction[0] > 0) {
1598 r0.set(r0.left, r0.top, r0.right + 1, r0.bottom);
1599 deltaX = 1;
1600 }
1601
1602 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001603 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen19f37922012-03-21 11:59:11 -07001604 if (views.contains(child) || child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001605 CellAndSpan c = currentState.map.get(child);
Adam Cohen47a876d2012-03-19 13:21:41 -07001606
Adam Cohen8baab352012-03-20 17:39:21 -07001607 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1608 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen47a876d2012-03-19 13:21:41 -07001609 if (Rect.intersects(r0, r1)) {
1610 if (!lp.canReorder) {
1611 return false;
1612 }
1613 boolean pushed = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001614 for (int x = c.x; x < c.x + c.spanX; x++) {
1615 for (int y = c.y; y < c.y + c.spanY; y++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001616 boolean inBounds = x - deltaX >= 0 && x -deltaX < mCountX
1617 && y - deltaY >= 0 && y - deltaY < mCountY;
1618 if (inBounds && occupied[x - deltaX][y - deltaY]) {
1619 pushed = true;
1620 }
1621 }
1622 }
1623 if (pushed) {
1624 views.add(child);
Adam Cohen8baab352012-03-20 17:39:21 -07001625 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen47a876d2012-03-19 13:21:41 -07001626 found = true;
1627 }
1628 }
1629 }
1630 return found;
1631 }
1632
Adam Cohen482ed822012-03-02 14:15:13 -08001633 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohen19f37922012-03-21 11:59:11 -07001634 int[] direction, boolean push, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001635 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001636
Adam Cohen8baab352012-03-20 17:39:21 -07001637 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001638 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001639 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001640 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001641 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001642 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001643 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001644 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001645 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001646 }
1647 }
Adam Cohen8baab352012-03-20 17:39:21 -07001648
1649 @SuppressWarnings("unchecked")
1650 ArrayList<View> dup = (ArrayList<View>) views.clone();
1651 // We try and expand the group of views in the direction vector passed, based on
1652 // whether they are physically adjacent, ie. based on "push mechanics".
Adam Cohen19f37922012-03-21 11:59:11 -07001653 while (push && addViewInDirection(dup, boundingRect, direction, mTmpOccupied, dragView,
Adam Cohen8baab352012-03-20 17:39:21 -07001654 currentState)) {
1655 }
1656
1657 // Mark the occupied state as false for the group of views we want to move.
1658 for (View v: dup) {
1659 CellAndSpan c = currentState.map.get(v);
1660 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1661 }
1662
Adam Cohen47a876d2012-03-19 13:21:41 -07001663 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1664 int top = boundingRect.top;
1665 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001666 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
1667 // for tetris-style interlocking.
1668 for (View v: dup) {
1669 CellAndSpan c = currentState.map.get(v);
1670 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001671 }
1672
Adam Cohen482ed822012-03-02 14:15:13 -08001673 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1674
Adam Cohen8baab352012-03-20 17:39:21 -07001675 if (push) {
1676 findNearestAreaInDirection(boundingRect.left, boundingRect.top, boundingRect.width(),
1677 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
1678 } else {
1679 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1680 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
1681 }
Adam Cohen482ed822012-03-02 14:15:13 -08001682
Adam Cohen8baab352012-03-20 17:39:21 -07001683 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001684 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001685 int deltaX = mTempLocation[0] - boundingRect.left;
1686 int deltaY = mTempLocation[1] - boundingRect.top;
1687 for (View v: dup) {
1688 CellAndSpan c = currentState.map.get(v);
1689 c.x += deltaX;
1690 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001691 }
1692 success = true;
1693 }
Adam Cohen8baab352012-03-20 17:39:21 -07001694
1695 // In either case, we set the occupied array as marked for the location of the views
1696 for (View v: dup) {
1697 CellAndSpan c = currentState.map.get(v);
1698 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001699 }
1700 return success;
1701 }
1702
1703 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1704 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1705 }
1706
1707 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001708 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001709 // Return early if get invalid cell positions
1710 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001711
Adam Cohen8baab352012-03-20 17:39:21 -07001712 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001713 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001714
Adam Cohen8baab352012-03-20 17:39:21 -07001715 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001716 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001717 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001718 if (c != null) {
1719 c.x = cellX;
1720 c.y = cellY;
1721 }
Adam Cohen482ed822012-03-02 14:15:13 -08001722 }
Adam Cohen482ed822012-03-02 14:15:13 -08001723 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1724 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001725 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001726 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001727 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001728 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001729 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001730 if (Rect.intersects(r0, r1)) {
1731 if (!lp.canReorder) {
1732 return false;
1733 }
1734 mIntersectingViews.add(child);
1735 }
1736 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001737
Adam Cohen8baab352012-03-20 17:39:21 -07001738 // We try to move the intersecting views as a block using the push mechanic
Adam Cohen19f37922012-03-21 11:59:11 -07001739 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, true, ignoreView,
1740 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001741 return true;
1742 }
1743 // Try the opposite direction
1744 direction[0] *= -1;
1745 direction[1] *= -1;
Adam Cohen19f37922012-03-21 11:59:11 -07001746 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, true, ignoreView,
1747 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001748 return true;
1749 }
1750 // Switch the direction back
1751 direction[0] *= -1;
1752 direction[1] *= -1;
1753
Adam Cohen8baab352012-03-20 17:39:21 -07001754 // Next we try moving the views as a block , but without requiring the push mechanic
Adam Cohen19f37922012-03-21 11:59:11 -07001755 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, false, ignoreView,
1756 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001757 return true;
1758 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001759
Adam Cohen482ed822012-03-02 14:15:13 -08001760 // Ok, they couldn't move as a block, let's move them individually
1761 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001762 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001763 return false;
1764 }
1765 }
1766 return true;
1767 }
1768
1769 /*
1770 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1771 * the provided point and the provided cell
1772 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001773 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001774 double angle = Math.atan(((float) deltaY) / deltaX);
1775
1776 result[0] = 0;
1777 result[1] = 0;
1778 if (Math.abs(Math.cos(angle)) > 0.5f) {
1779 result[0] = (int) Math.signum(deltaX);
1780 }
1781 if (Math.abs(Math.sin(angle)) > 0.5f) {
1782 result[1] = (int) Math.signum(deltaY);
1783 }
1784 }
1785
Adam Cohen8baab352012-03-20 17:39:21 -07001786 private void copyOccupiedArray(boolean[][] occupied) {
1787 for (int i = 0; i < mCountX; i++) {
1788 for (int j = 0; j < mCountY; j++) {
1789 occupied[i][j] = mOccupied[i][j];
1790 }
1791 }
1792 }
1793
Adam Cohen482ed822012-03-02 14:15:13 -08001794 ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1795 int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001796 // Copy the current state into the solution. This solution will be manipulated as necessary.
1797 copyCurrentStateToSolution(solution, false);
1798 // Copy the current occupied array into the temporary occupied array. This array will be
1799 // manipulated as necessary to find a solution.
1800 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001801
1802 // We find the nearest cell into which we would place the dragged item, assuming there's
1803 // nothing in its way.
1804 int result[] = new int[2];
1805 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1806
1807 boolean success = false;
1808 // First we try the exact nearest position of the item being dragged,
1809 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001810 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1811 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001812
1813 if (!success) {
1814 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1815 // x, then 1 in y etc.
1816 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
1817 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
1818 dragView, false, solution);
1819 } else if (spanY > minSpanY) {
1820 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
1821 dragView, true, solution);
1822 }
1823 solution.isSolution = false;
1824 } else {
1825 solution.isSolution = true;
1826 solution.dragViewX = result[0];
1827 solution.dragViewY = result[1];
1828 solution.dragViewSpanX = spanX;
1829 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08001830 }
1831 return solution;
1832 }
1833
1834 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001835 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001836 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001837 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001838 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001839 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08001840 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07001841 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001842 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001843 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001844 }
Adam Cohen8baab352012-03-20 17:39:21 -07001845 solution.map.put(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08001846 }
1847 }
1848
1849 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
1850 for (int i = 0; i < mCountX; i++) {
1851 for (int j = 0; j < mCountY; j++) {
1852 mTmpOccupied[i][j] = false;
1853 }
1854 }
1855
Michael Jurkaa52570f2012-03-20 03:18:20 -07001856 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001857 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001858 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001859 if (child == dragView) continue;
1860 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001861 CellAndSpan c = solution.map.get(child);
1862 if (c != null) {
1863 lp.tmpCellX = c.x;
1864 lp.tmpCellY = c.y;
1865 lp.cellHSpan = c.spanX;
1866 lp.cellVSpan = c.spanY;
1867 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001868 }
1869 }
1870 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
1871 solution.dragViewSpanY, mTmpOccupied, true);
1872 }
1873
1874 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
1875 commitDragView) {
1876
1877 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
1878 for (int i = 0; i < mCountX; i++) {
1879 for (int j = 0; j < mCountY; j++) {
1880 occupied[i][j] = false;
1881 }
1882 }
1883
Michael Jurkaa52570f2012-03-20 03:18:20 -07001884 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001885 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001886 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001887 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001888 CellAndSpan c = solution.map.get(child);
1889 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07001890 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
1891 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07001892 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001893 }
1894 }
1895 if (commitDragView) {
1896 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
1897 solution.dragViewSpanY, occupied, true);
1898 }
1899 }
1900
Adam Cohen19f37922012-03-21 11:59:11 -07001901 // This method starts or changes the reorder hint animations
1902 private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
1903 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07001904 for (int i = 0; i < childCount; i++) {
1905 View child = mShortcutsAndWidgets.getChildAt(i);
1906 if (child == dragView) continue;
1907 CellAndSpan c = solution.map.get(child);
1908 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1909 if (c != null) {
1910 ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
1911 c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07001912 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07001913 }
1914 }
1915 }
1916
1917 // Class which represents the reorder hint animations. These animations show that an item is
1918 // in a temporary state, and hint at where the item will return to.
1919 class ReorderHintAnimation {
1920 View child;
Adam Cohend024f982012-05-23 18:26:45 -07001921 float finalDeltaX;
1922 float finalDeltaY;
1923 float initDeltaX;
1924 float initDeltaY;
1925 float finalScale;
1926 float initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07001927 private static final int DURATION = 300;
Adam Cohene7587d22012-05-24 18:50:02 -07001928 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07001929
1930 public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
1931 int spanX, int spanY) {
1932 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
1933 final int x0 = mTmpPoint[0];
1934 final int y0 = mTmpPoint[1];
1935 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
1936 final int x1 = mTmpPoint[0];
1937 final int y1 = mTmpPoint[1];
1938 final int dX = x1 - x0;
1939 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07001940 finalDeltaX = 0;
1941 finalDeltaY = 0;
Adam Cohen19f37922012-03-21 11:59:11 -07001942 if (dX == dY && dX == 0) {
1943 } else {
1944 if (dY == 0) {
Adam Cohend024f982012-05-23 18:26:45 -07001945 finalDeltaX = - Math.signum(dX) * mReorderHintAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07001946 } else if (dX == 0) {
Adam Cohend024f982012-05-23 18:26:45 -07001947 finalDeltaY = - Math.signum(dY) * mReorderHintAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07001948 } else {
1949 double angle = Math.atan( (float) (dY) / dX);
Adam Cohend024f982012-05-23 18:26:45 -07001950 finalDeltaX = (int) (- Math.signum(dX) *
Adam Cohenfe41ac62012-05-23 14:00:37 -07001951 Math.abs(Math.cos(angle) * mReorderHintAnimationMagnitude));
Adam Cohend024f982012-05-23 18:26:45 -07001952 finalDeltaY = (int) (- Math.signum(dY) *
Adam Cohenfe41ac62012-05-23 14:00:37 -07001953 Math.abs(Math.sin(angle) * mReorderHintAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07001954 }
1955 }
Adam Cohend024f982012-05-23 18:26:45 -07001956 initDeltaX = child.getTranslationX();
1957 initDeltaY = child.getTranslationY();
1958 finalScale = 1.0f - 4.0f / child.getWidth();
1959 initScale = child.getScaleX();
1960
Brandon Keely50e6e562012-05-08 16:28:49 -07001961 child.setPivotY(child.getMeasuredHeight() * 0.5f);
1962 child.setPivotX(child.getMeasuredWidth() * 0.5f);
Adam Cohen19f37922012-03-21 11:59:11 -07001963 this.child = child;
1964 }
1965
Adam Cohend024f982012-05-23 18:26:45 -07001966 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07001967 if (mShakeAnimators.containsKey(child)) {
1968 ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07001969 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07001970 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07001971 if (finalDeltaX == 0 && finalDeltaY == 0) {
1972 completeAnimationImmediately();
1973 return;
1974 }
Adam Cohen19f37922012-03-21 11:59:11 -07001975 }
Adam Cohend024f982012-05-23 18:26:45 -07001976 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07001977 return;
1978 }
Adam Cohene7587d22012-05-24 18:50:02 -07001979 ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
1980 a = va;
Adam Cohen19f37922012-03-21 11:59:11 -07001981 va.setRepeatMode(ValueAnimator.REVERSE);
1982 va.setRepeatCount(ValueAnimator.INFINITE);
Adam Cohen7bdfc972012-05-22 16:50:35 -07001983 va.setDuration(DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07001984 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07001985 va.addUpdateListener(new AnimatorUpdateListener() {
1986 @Override
1987 public void onAnimationUpdate(ValueAnimator animation) {
1988 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohend024f982012-05-23 18:26:45 -07001989 float x = r * finalDeltaX + (1 - r) * initDeltaX;
1990 float y = r * finalDeltaY + (1 - r) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07001991 child.setTranslationX(x);
1992 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07001993 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07001994 child.setScaleX(s);
1995 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07001996 }
1997 });
1998 va.addListener(new AnimatorListenerAdapter() {
1999 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002000 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002001 initDeltaX = 0;
2002 initDeltaY = 0;
2003 initScale = 1.0f;
Adam Cohen19f37922012-03-21 11:59:11 -07002004 }
2005 });
Adam Cohen19f37922012-03-21 11:59:11 -07002006 mShakeAnimators.put(child, this);
2007 va.start();
2008 }
2009
Adam Cohend024f982012-05-23 18:26:45 -07002010 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002011 if (a != null) {
2012 a.cancel();
2013 }
Adam Cohen19f37922012-03-21 11:59:11 -07002014 }
Adam Cohene7587d22012-05-24 18:50:02 -07002015
Brandon Keely50e6e562012-05-08 16:28:49 -07002016 private void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002017 if (a != null) {
2018 a.cancel();
2019 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002020
2021 AnimatorSet s = new AnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002022 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002023 s.playTogether(
2024 ObjectAnimator.ofFloat(child, "scaleX", 1f),
2025 ObjectAnimator.ofFloat(child, "scaleY", 1f),
2026 ObjectAnimator.ofFloat(child, "translationX", 0f),
Brandon Keelyfaf22e12012-05-10 16:48:50 -07002027 ObjectAnimator.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002028 );
2029 s.setDuration(REORDER_ANIMATION_DURATION);
2030 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2031 s.start();
2032 }
Adam Cohen19f37922012-03-21 11:59:11 -07002033 }
2034
2035 private void completeAndClearReorderHintAnimations() {
2036 for (ReorderHintAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002037 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002038 }
2039 mShakeAnimators.clear();
2040 }
2041
Adam Cohen482ed822012-03-02 14:15:13 -08002042 private void commitTempPlacement() {
2043 for (int i = 0; i < mCountX; i++) {
2044 for (int j = 0; j < mCountY; j++) {
2045 mOccupied[i][j] = mTmpOccupied[i][j];
2046 }
2047 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002048 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002049 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002050 View child = mShortcutsAndWidgets.getChildAt(i);
2051 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2052 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002053 // We do a null check here because the item info can be null in the case of the
2054 // AllApps button in the hotseat.
2055 if (info != null) {
2056 info.cellX = lp.cellX = lp.tmpCellX;
2057 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002058 info.spanX = lp.cellHSpan;
2059 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002060 }
Adam Cohen482ed822012-03-02 14:15:13 -08002061 }
Adam Cohen2acce882012-03-28 19:03:19 -07002062 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002063 }
2064
2065 public void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002066 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002067 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002068 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002069 lp.useTmpCoords = useTempCoords;
2070 }
2071 }
2072
Adam Cohen482ed822012-03-02 14:15:13 -08002073 ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
2074 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2075 int[] result = new int[2];
2076 int[] resultSpan = new int[2];
2077 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
2078 resultSpan);
2079 if (result[0] >= 0 && result[1] >= 0) {
2080 copyCurrentStateToSolution(solution, false);
2081 solution.dragViewX = result[0];
2082 solution.dragViewY = result[1];
2083 solution.dragViewSpanX = resultSpan[0];
2084 solution.dragViewSpanY = resultSpan[1];
2085 solution.isSolution = true;
2086 } else {
2087 solution.isSolution = false;
2088 }
2089 return solution;
2090 }
2091
2092 public void prepareChildForDrag(View child) {
2093 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002094 }
2095
Adam Cohen19f37922012-03-21 11:59:11 -07002096 /* This seems like it should be obvious and straight-forward, but when the direction vector
2097 needs to match with the notion of the dragView pushing other views, we have to employ
2098 a slightly more subtle notion of the direction vector. The question is what two points is
2099 the vector between? The center of the dragView and its desired destination? Not quite, as
2100 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2101 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2102 or right, which helps make pushing feel right.
2103 */
2104 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2105 int spanY, View dragView, int[] resultDirection) {
2106 int[] targetDestination = new int[2];
2107
2108 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2109 Rect dragRect = new Rect();
2110 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2111 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2112
2113 Rect dropRegionRect = new Rect();
2114 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2115 dragView, dropRegionRect, mIntersectingViews);
2116
2117 int dropRegionSpanX = dropRegionRect.width();
2118 int dropRegionSpanY = dropRegionRect.height();
2119
2120 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2121 dropRegionRect.height(), dropRegionRect);
2122
2123 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2124 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2125
2126 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2127 deltaX = 0;
2128 }
2129 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2130 deltaY = 0;
2131 }
2132
2133 if (deltaX == 0 && deltaY == 0) {
2134 // No idea what to do, give a random direction.
2135 resultDirection[0] = 1;
2136 resultDirection[1] = 0;
2137 } else {
2138 computeDirectionVector(deltaX, deltaY, resultDirection);
2139 }
2140 }
2141
2142 // For a given cell and span, fetch the set of views intersecting the region.
2143 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2144 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2145 if (boundingRect != null) {
2146 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2147 }
2148 intersectingViews.clear();
2149 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2150 Rect r1 = new Rect();
2151 final int count = mShortcutsAndWidgets.getChildCount();
2152 for (int i = 0; i < count; i++) {
2153 View child = mShortcutsAndWidgets.getChildAt(i);
2154 if (child == dragView) continue;
2155 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2156 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2157 if (Rect.intersects(r0, r1)) {
2158 mIntersectingViews.add(child);
2159 if (boundingRect != null) {
2160 boundingRect.union(r1);
2161 }
2162 }
2163 }
2164 }
2165
2166 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2167 View dragView, int[] result) {
2168 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2169 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2170 mIntersectingViews);
2171 return !mIntersectingViews.isEmpty();
2172 }
2173
2174 void revertTempState() {
2175 if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
2176 final int count = mShortcutsAndWidgets.getChildCount();
2177 for (int i = 0; i < count; i++) {
2178 View child = mShortcutsAndWidgets.getChildAt(i);
2179 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2180 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2181 lp.tmpCellX = lp.cellX;
2182 lp.tmpCellY = lp.cellY;
2183 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2184 0, false, false);
2185 }
2186 }
2187 completeAndClearReorderHintAnimations();
2188 setItemPlacementDirty(false);
2189 }
2190
Adam Cohenbebf0422012-04-11 18:06:28 -07002191 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2192 View dragView, int[] direction, boolean commit) {
2193 int[] pixelXY = new int[2];
2194 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2195
2196 // First we determine if things have moved enough to cause a different layout
2197 ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY,
2198 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2199
2200 setUseTempCoords(true);
2201 if (swapSolution != null && swapSolution.isSolution) {
2202 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2203 // committing anything or animating anything as we just want to determine if a solution
2204 // exists
2205 copySolutionToTempState(swapSolution, dragView);
2206 setItemPlacementDirty(true);
2207 animateItemsToSolution(swapSolution, dragView, commit);
2208
2209 if (commit) {
2210 commitTempPlacement();
2211 completeAndClearReorderHintAnimations();
2212 setItemPlacementDirty(false);
2213 } else {
2214 beginOrAdjustHintAnimations(swapSolution, dragView,
2215 REORDER_ANIMATION_DURATION);
2216 }
2217 mShortcutsAndWidgets.requestLayout();
2218 }
2219 return swapSolution.isSolution;
2220 }
2221
Adam Cohen482ed822012-03-02 14:15:13 -08002222 int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
2223 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002224 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002225 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002226
2227 if (resultSpan == null) {
2228 resultSpan = new int[2];
2229 }
2230
Adam Cohen19f37922012-03-21 11:59:11 -07002231 // When we are checking drop validity or actually dropping, we don't recompute the
2232 // direction vector, since we want the solution to match the preview, and it's possible
2233 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002234 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2235 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002236 mDirectionVector[0] = mPreviousReorderDirection[0];
2237 mDirectionVector[1] = mPreviousReorderDirection[1];
2238 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002239 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2240 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2241 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002242 }
2243 } else {
2244 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2245 mPreviousReorderDirection[0] = mDirectionVector[0];
2246 mPreviousReorderDirection[1] = mDirectionVector[1];
2247 }
2248
Adam Cohen482ed822012-03-02 14:15:13 -08002249 ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
2250 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2251
2252 // We attempt the approach which doesn't shuffle views at all
2253 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2254 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2255
2256 ItemConfiguration finalSolution = null;
2257 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2258 finalSolution = swapSolution;
2259 } else if (noShuffleSolution.isSolution) {
2260 finalSolution = noShuffleSolution;
2261 }
2262
2263 boolean foundSolution = true;
2264 if (!DESTRUCTIVE_REORDER) {
2265 setUseTempCoords(true);
2266 }
2267
2268 if (finalSolution != null) {
2269 result[0] = finalSolution.dragViewX;
2270 result[1] = finalSolution.dragViewY;
2271 resultSpan[0] = finalSolution.dragViewSpanX;
2272 resultSpan[1] = finalSolution.dragViewSpanY;
2273
2274 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2275 // committing anything or animating anything as we just want to determine if a solution
2276 // exists
2277 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2278 if (!DESTRUCTIVE_REORDER) {
2279 copySolutionToTempState(finalSolution, dragView);
2280 }
2281 setItemPlacementDirty(true);
2282 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2283
Adam Cohen19f37922012-03-21 11:59:11 -07002284 if (!DESTRUCTIVE_REORDER &&
2285 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002286 commitTempPlacement();
Adam Cohen19f37922012-03-21 11:59:11 -07002287 completeAndClearReorderHintAnimations();
2288 setItemPlacementDirty(false);
2289 } else {
2290 beginOrAdjustHintAnimations(finalSolution, dragView,
2291 REORDER_ANIMATION_DURATION);
Adam Cohen482ed822012-03-02 14:15:13 -08002292 }
2293 }
2294 } else {
2295 foundSolution = false;
2296 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2297 }
2298
2299 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2300 setUseTempCoords(false);
2301 }
Adam Cohen482ed822012-03-02 14:15:13 -08002302
Michael Jurkaa52570f2012-03-20 03:18:20 -07002303 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002304 return result;
2305 }
2306
Adam Cohen19f37922012-03-21 11:59:11 -07002307 void setItemPlacementDirty(boolean dirty) {
2308 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002309 }
Adam Cohen19f37922012-03-21 11:59:11 -07002310 boolean isItemPlacementDirty() {
2311 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002312 }
2313
2314 private class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002315 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohen482ed822012-03-02 14:15:13 -08002316 boolean isSolution = false;
2317 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2318
2319 int area() {
2320 return dragViewSpanX * dragViewSpanY;
2321 }
Adam Cohen8baab352012-03-20 17:39:21 -07002322 }
2323
2324 private class CellAndSpan {
2325 int x, y;
2326 int spanX, spanY;
2327
2328 public CellAndSpan(int x, int y, int spanX, int spanY) {
2329 this.x = x;
2330 this.y = y;
2331 this.spanX = spanX;
2332 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002333 }
2334 }
2335
Adam Cohendf035382011-04-11 17:22:04 -07002336 /**
2337 * Find a vacant area that will fit the given bounds nearest the requested
2338 * cell location. Uses Euclidean distance to score multiple vacant areas.
2339 *
2340 * @param pixelX The X location at which you want to search for a vacant area.
2341 * @param pixelY The Y location at which you want to search for a vacant area.
2342 * @param spanX Horizontal span of the object.
2343 * @param spanY Vertical span of the object.
2344 * @param ignoreView Considers space occupied by this view as unoccupied
2345 * @param result Previously returned value to possibly recycle.
2346 * @return The X, Y cell of a vacant area that can contain this object,
2347 * nearest the requested location.
2348 */
2349 int[] findNearestVacantArea(
2350 int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
2351 return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
2352 }
2353
2354 /**
Adam Cohend41fbf52012-02-16 23:53:59 -08002355 * Find a vacant area that will fit the given bounds nearest the requested
2356 * cell location. Uses Euclidean distance to score multiple vacant areas.
2357 *
2358 * @param pixelX The X location at which you want to search for a vacant area.
2359 * @param pixelY The Y location at which you want to search for a vacant area.
2360 * @param minSpanX The minimum horizontal span required
2361 * @param minSpanY The minimum vertical span required
2362 * @param spanX Horizontal span of the object.
2363 * @param spanY Vertical span of the object.
2364 * @param ignoreView Considers space occupied by this view as unoccupied
2365 * @param result Previously returned value to possibly recycle.
2366 * @return The X, Y cell of a vacant area that can contain this object,
2367 * nearest the requested location.
2368 */
2369 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
2370 int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
Adam Cohen482ed822012-03-02 14:15:13 -08002371 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
2372 result, resultSpan, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08002373 }
2374
2375 /**
Adam Cohendf035382011-04-11 17:22:04 -07002376 * Find a starting cell position that will fit the given bounds nearest the requested
2377 * cell location. Uses Euclidean distance to score multiple vacant areas.
2378 *
2379 * @param pixelX The X location at which you want to search for a vacant area.
2380 * @param pixelY The Y location at which you want to search for a vacant area.
2381 * @param spanX Horizontal span of the object.
2382 * @param spanY Vertical span of the object.
2383 * @param ignoreView Considers space occupied by this view as unoccupied
2384 * @param result Previously returned value to possibly recycle.
2385 * @return The X, Y cell of a vacant area that can contain this object,
2386 * nearest the requested location.
2387 */
2388 int[] findNearestArea(
2389 int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2390 return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
2391 }
2392
Michael Jurka0280c3b2010-09-17 15:00:07 -07002393 boolean existsEmptyCell() {
2394 return findCellForSpan(null, 1, 1);
2395 }
2396
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002397 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002398 * Finds the upper-left coordinate of the first rectangle in the grid that can
2399 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2400 * then this method will only return coordinates for rectangles that contain the cell
2401 * (intersectX, intersectY)
2402 *
2403 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2404 * can be found.
2405 * @param spanX The horizontal span of the cell we want to find.
2406 * @param spanY The vertical span of the cell we want to find.
2407 *
2408 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002409 */
Michael Jurka0280c3b2010-09-17 15:00:07 -07002410 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Adam Cohen482ed822012-03-02 14:15:13 -08002411 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002412 }
2413
2414 /**
2415 * Like above, but ignores any cells occupied by the item "ignoreView"
2416 *
2417 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2418 * can be found.
2419 * @param spanX The horizontal span of the cell we want to find.
2420 * @param spanY The vertical span of the cell we want to find.
2421 * @param ignoreView The home screen item we should treat as not occupying any space
2422 * @return
2423 */
2424 boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
Adam Cohen482ed822012-03-02 14:15:13 -08002425 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
2426 ignoreView, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002427 }
2428
2429 /**
2430 * Like above, but if intersectX and intersectY are not -1, then this method will try to
2431 * return coordinates for rectangles that contain the cell [intersectX, intersectY]
2432 *
2433 * @param spanX The horizontal span of the cell we want to find.
2434 * @param spanY The vertical span of the cell we want to find.
2435 * @param ignoreView The home screen item we should treat as not occupying any space
2436 * @param intersectX The X coordinate of the cell that we should try to overlap
2437 * @param intersectX The Y coordinate of the cell that we should try to overlap
2438 *
2439 * @return True if a vacant cell of the specified dimension was found, false otherwise.
2440 */
2441 boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
2442 int intersectX, int intersectY) {
2443 return findCellForSpanThatIntersectsIgnoring(
Adam Cohen482ed822012-03-02 14:15:13 -08002444 cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002445 }
2446
2447 /**
2448 * The superset of the above two methods
2449 */
2450 boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002451 int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002452 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08002453 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002454
Michael Jurka28750fb2010-09-24 17:43:49 -07002455 boolean foundCell = false;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002456 while (true) {
2457 int startX = 0;
2458 if (intersectX >= 0) {
2459 startX = Math.max(startX, intersectX - (spanX - 1));
2460 }
2461 int endX = mCountX - (spanX - 1);
2462 if (intersectX >= 0) {
2463 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
2464 }
2465 int startY = 0;
2466 if (intersectY >= 0) {
2467 startY = Math.max(startY, intersectY - (spanY - 1));
2468 }
2469 int endY = mCountY - (spanY - 1);
2470 if (intersectY >= 0) {
2471 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
2472 }
2473
Winson Chungbbc60d82010-11-11 16:34:41 -08002474 for (int y = startY; y < endY && !foundCell; y++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002475 inner:
Winson Chungbbc60d82010-11-11 16:34:41 -08002476 for (int x = startX; x < endX; x++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002477 for (int i = 0; i < spanX; i++) {
2478 for (int j = 0; j < spanY; j++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002479 if (occupied[x + i][y + j]) {
Winson Chungbbc60d82010-11-11 16:34:41 -08002480 // small optimization: we can skip to after the column we just found
Michael Jurka0280c3b2010-09-17 15:00:07 -07002481 // an occupied cell
Winson Chungbbc60d82010-11-11 16:34:41 -08002482 x += i;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002483 continue inner;
2484 }
2485 }
2486 }
2487 if (cellXY != null) {
2488 cellXY[0] = x;
2489 cellXY[1] = y;
2490 }
Michael Jurka28750fb2010-09-24 17:43:49 -07002491 foundCell = true;
2492 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002493 }
2494 }
2495 if (intersectX == -1 && intersectY == -1) {
2496 break;
2497 } else {
2498 // if we failed to find anything, try again but without any requirements of
2499 // intersecting
2500 intersectX = -1;
2501 intersectY = -1;
2502 continue;
2503 }
2504 }
2505
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002506 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08002507 markCellsAsOccupiedForView(ignoreView, occupied);
Michael Jurka28750fb2010-09-24 17:43:49 -07002508 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002509 }
2510
2511 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002512 * A drag event has begun over this layout.
2513 * It may have begun over this layout (in which case onDragChild is called first),
2514 * or it may have begun on another layout.
2515 */
2516 void onDragEnter() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002517 mDragEnforcer.onDragEnter();
Winson Chungc07918d2011-07-01 15:35:26 -07002518 mDragging = true;
2519 }
2520
2521 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002522 * Called when drag has left this CellLayout or has been completed (successfully or not)
2523 */
2524 void onDragExit() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002525 mDragEnforcer.onDragExit();
Joe Onorato4be866d2010-10-10 11:26:02 -07002526 // This can actually be called when we aren't in a drag, e.g. when adding a new
2527 // item to this layout via the customize drawer.
2528 // Guard against that case.
2529 if (mDragging) {
2530 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002531 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002532
2533 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002534 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002535 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2536 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002537 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002538 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002539 }
2540
2541 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002542 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002543 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002544 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002545 *
2546 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002547 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002548 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002549 if (child != null) {
2550 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002551 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002552 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002553 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002554 }
2555
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002556 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002557 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002558 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002559 * @param cellX X coordinate of upper left corner expressed as a cell position
2560 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002561 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002562 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002563 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002564 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002565 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002566 final int cellWidth = mCellWidth;
2567 final int cellHeight = mCellHeight;
2568 final int widthGap = mWidthGap;
2569 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002570
Winson Chung4b825dcd2011-06-19 12:41:22 -07002571 final int hStartPadding = getPaddingLeft();
2572 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002573
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002574 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2575 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2576
2577 int x = hStartPadding + cellX * (cellWidth + widthGap);
2578 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002579
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002580 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002581 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002582
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002583 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002584 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002585 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002586 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002587 * @param width Width in pixels
2588 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002589 * @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 -08002590 */
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002591 public int[] rectToCell(int width, int height, int[] result) {
Michael Jurka9987a5c2010-10-08 16:58:12 -07002592 return rectToCell(getResources(), width, height, result);
2593 }
2594
2595 public static int[] rectToCell(Resources resources, int width, int height, int[] result) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002596 // Always assume we're working with the smallest span to make sure we
2597 // reserve enough space in both orientations.
Joe Onorato79e56262009-09-21 15:23:04 -04002598 int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
2599 int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002600 int smallerSize = Math.min(actualWidth, actualHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002601
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002602 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002603 int spanX = (int) Math.ceil(width / (float) smallerSize);
2604 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002605
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002606 if (result == null) {
2607 return new int[] { spanX, spanY };
2608 }
2609 result[0] = spanX;
2610 result[1] = spanY;
2611 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002612 }
2613
Michael Jurkaf12c75c2011-01-25 22:41:40 -08002614 public int[] cellSpansToSize(int hSpans, int vSpans) {
2615 int[] size = new int[2];
2616 size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
2617 size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
2618 return size;
2619 }
2620
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002621 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08002622 * Calculate the grid spans needed to fit given item
2623 */
2624 public void calculateSpans(ItemInfo info) {
2625 final int minWidth;
2626 final int minHeight;
2627
2628 if (info instanceof LauncherAppWidgetInfo) {
2629 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
2630 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
2631 } else if (info instanceof PendingAddWidgetInfo) {
2632 minWidth = ((PendingAddWidgetInfo) info).minWidth;
2633 minHeight = ((PendingAddWidgetInfo) info).minHeight;
2634 } else {
2635 // It's not a widget, so it must be 1x1
2636 info.spanX = info.spanY = 1;
2637 return;
2638 }
2639 int[] spans = rectToCell(minWidth, minHeight, null);
2640 info.spanX = spans[0];
2641 info.spanY = spans[1];
2642 }
2643
2644 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002645 * Find the first vacant cell, if there is one.
2646 *
2647 * @param vacant Holds the x and y coordinate of the vacant cell
2648 * @param spanX Horizontal cell span.
2649 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -07002650 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002651 * @return True if a vacant cell was found
2652 */
2653 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002654
Michael Jurka0280c3b2010-09-17 15:00:07 -07002655 return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002656 }
2657
2658 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
2659 int xCount, int yCount, boolean[][] occupied) {
2660
Adam Cohen2801caf2011-05-13 20:57:39 -07002661 for (int y = 0; y < yCount; y++) {
2662 for (int x = 0; x < xCount; x++) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002663 boolean available = !occupied[x][y];
2664out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
2665 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
2666 available = available && !occupied[i][j];
2667 if (!available) break out;
2668 }
2669 }
2670
2671 if (available) {
2672 vacant[0] = x;
2673 vacant[1] = y;
2674 return true;
2675 }
2676 }
2677 }
2678
2679 return false;
2680 }
2681
Michael Jurka0280c3b2010-09-17 15:00:07 -07002682 private void clearOccupiedCells() {
2683 for (int x = 0; x < mCountX; x++) {
2684 for (int y = 0; y < mCountY; y++) {
2685 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002686 }
2687 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002688 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002689
Adam Cohend41fbf52012-02-16 23:53:59 -08002690 public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002691 markCellsAsUnoccupiedForView(view);
Adam Cohen482ed822012-03-02 14:15:13 -08002692 markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002693 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002694
Adam Cohend4844c32011-02-18 19:25:06 -08002695 public void markCellsAsOccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08002696 markCellsAsOccupiedForView(view, mOccupied);
2697 }
2698 public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002699 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002700 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002701 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002702 }
2703
Adam Cohend4844c32011-02-18 19:25:06 -08002704 public void markCellsAsUnoccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08002705 markCellsAsUnoccupiedForView(view, mOccupied);
2706 }
2707 public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002708 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002709 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002710 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002711 }
2712
Adam Cohen482ed822012-03-02 14:15:13 -08002713 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2714 boolean value) {
2715 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002716 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2717 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002718 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002719 }
2720 }
2721 }
2722
Adam Cohen2801caf2011-05-13 20:57:39 -07002723 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002724 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002725 (Math.max((mCountX - 1), 0) * mWidthGap);
2726 }
2727
2728 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002729 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002730 (Math.max((mCountY - 1), 0) * mHeightGap);
2731 }
2732
Michael Jurka66d72172011-04-12 16:29:25 -07002733 public boolean isOccupied(int x, int y) {
2734 if (x < mCountX && y < mCountY) {
2735 return mOccupied[x][y];
2736 } else {
2737 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2738 }
2739 }
2740
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002741 @Override
2742 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2743 return new CellLayout.LayoutParams(getContext(), attrs);
2744 }
2745
2746 @Override
2747 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2748 return p instanceof CellLayout.LayoutParams;
2749 }
2750
2751 @Override
2752 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2753 return new CellLayout.LayoutParams(p);
2754 }
2755
Winson Chungaafa03c2010-06-11 17:34:16 -07002756 public static class CellLayoutAnimationController extends LayoutAnimationController {
2757 public CellLayoutAnimationController(Animation animation, float delay) {
2758 super(animation, delay);
2759 }
2760
2761 @Override
2762 protected long getDelayForView(View view) {
2763 return (int) (Math.random() * 150);
2764 }
2765 }
2766
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002767 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2768 /**
2769 * Horizontal location of the item in the grid.
2770 */
2771 @ViewDebug.ExportedProperty
2772 public int cellX;
2773
2774 /**
2775 * Vertical location of the item in the grid.
2776 */
2777 @ViewDebug.ExportedProperty
2778 public int cellY;
2779
2780 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002781 * Temporary horizontal location of the item in the grid during reorder
2782 */
2783 public int tmpCellX;
2784
2785 /**
2786 * Temporary vertical location of the item in the grid during reorder
2787 */
2788 public int tmpCellY;
2789
2790 /**
2791 * Indicates that the temporary coordinates should be used to layout the items
2792 */
2793 public boolean useTmpCoords;
2794
2795 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002796 * Number of cells spanned horizontally by the item.
2797 */
2798 @ViewDebug.ExportedProperty
2799 public int cellHSpan;
2800
2801 /**
2802 * Number of cells spanned vertically by the item.
2803 */
2804 @ViewDebug.ExportedProperty
2805 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002806
Adam Cohen1b607ed2011-03-03 17:26:50 -08002807 /**
2808 * Indicates whether the item will set its x, y, width and height parameters freely,
2809 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2810 */
Adam Cohend4844c32011-02-18 19:25:06 -08002811 public boolean isLockedToGrid = true;
2812
Adam Cohen482ed822012-03-02 14:15:13 -08002813 /**
2814 * Indicates whether this item can be reordered. Always true except in the case of the
2815 * the AllApps button.
2816 */
2817 public boolean canReorder = true;
2818
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002819 // X coordinate of the view in the layout.
2820 @ViewDebug.ExportedProperty
2821 int x;
2822 // Y coordinate of the view in the layout.
2823 @ViewDebug.ExportedProperty
2824 int y;
2825
Romain Guy84f296c2009-11-04 15:00:44 -08002826 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002827
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002828 public LayoutParams(Context c, AttributeSet attrs) {
2829 super(c, attrs);
2830 cellHSpan = 1;
2831 cellVSpan = 1;
2832 }
2833
2834 public LayoutParams(ViewGroup.LayoutParams source) {
2835 super(source);
2836 cellHSpan = 1;
2837 cellVSpan = 1;
2838 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002839
2840 public LayoutParams(LayoutParams source) {
2841 super(source);
2842 this.cellX = source.cellX;
2843 this.cellY = source.cellY;
2844 this.cellHSpan = source.cellHSpan;
2845 this.cellVSpan = source.cellVSpan;
2846 }
2847
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002848 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002849 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002850 this.cellX = cellX;
2851 this.cellY = cellY;
2852 this.cellHSpan = cellHSpan;
2853 this.cellVSpan = cellVSpan;
2854 }
2855
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002856 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap) {
Adam Cohend4844c32011-02-18 19:25:06 -08002857 if (isLockedToGrid) {
2858 final int myCellHSpan = cellHSpan;
2859 final int myCellVSpan = cellVSpan;
Adam Cohen482ed822012-03-02 14:15:13 -08002860 final int myCellX = useTmpCoords ? tmpCellX : cellX;
2861 final int myCellY = useTmpCoords ? tmpCellY : cellY;
Adam Cohen1b607ed2011-03-03 17:26:50 -08002862
Adam Cohend4844c32011-02-18 19:25:06 -08002863 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2864 leftMargin - rightMargin;
2865 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2866 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002867 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2868 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002869 }
2870 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002871
Winson Chungaafa03c2010-06-11 17:34:16 -07002872 public String toString() {
2873 return "(" + this.cellX + ", " + this.cellY + ")";
2874 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002875
2876 public void setWidth(int width) {
2877 this.width = width;
2878 }
2879
2880 public int getWidth() {
2881 return width;
2882 }
2883
2884 public void setHeight(int height) {
2885 this.height = height;
2886 }
2887
2888 public int getHeight() {
2889 return height;
2890 }
2891
2892 public void setX(int x) {
2893 this.x = x;
2894 }
2895
2896 public int getX() {
2897 return x;
2898 }
2899
2900 public void setY(int y) {
2901 this.y = y;
2902 }
2903
2904 public int getY() {
2905 return y;
2906 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002907 }
2908
Michael Jurka0280c3b2010-09-17 15:00:07 -07002909 // This class stores info for two purposes:
2910 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2911 // its spanX, spanY, and the screen it is on
2912 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2913 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2914 // the CellLayout that was long clicked
Michael Jurkae5fb0f22011-04-11 13:27:46 -07002915 static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002916 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07002917 int cellX = -1;
2918 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002919 int spanX;
2920 int spanY;
2921 int screen;
Winson Chung3d503fb2011-07-13 17:25:49 -07002922 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002923
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002924 @Override
2925 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002926 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2927 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002928 }
2929 }
Michael Jurkad771c962011-08-09 15:00:48 -07002930
2931 public boolean lastDownOnOccupiedCell() {
2932 return mLastDownOnOccupiedCell;
2933 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002934}