blob: 0760ee25d7cf623f145aac20494eb42cd4f694cf [file] [log] [blame]
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Joe Onoratoa5902522009-07-30 13:37:37 -070017package com.android.launcher2;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080018
Joe Onorato4be866d2010-10-10 11:26:02 -070019import android.animation.Animator;
Michael Jurka629758f2012-06-14 16:18:21 -070020import android.animation.AnimatorListenerAdapter;
Brandon Keely50e6e562012-05-08 16:28:49 -070021import android.animation.AnimatorSet;
22import android.animation.ObjectAnimator;
Chet Haase00397b12010-10-07 11:13:10 -070023import android.animation.TimeInterpolator;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070024import android.animation.ValueAnimator;
25import android.animation.ValueAnimator.AnimatorUpdateListener;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080026import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040027import android.content.res.Resources;
Winson Chungaafa03c2010-06-11 17:34:16 -070028import android.content.res.TypedArray;
Joe Onorato4be866d2010-10-10 11:26:02 -070029import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070030import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080031import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070032import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070033import android.graphics.Point;
Adam Cohenb5ba0972011-09-07 18:02:31 -070034import android.graphics.PorterDuff;
35import android.graphics.PorterDuffXfermode;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080036import android.graphics.Rect;
Adam Cohen482ed822012-03-02 14:15:13 -080037import android.graphics.drawable.ColorDrawable;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070038import android.graphics.drawable.Drawable;
Adam Cohenb5ba0972011-09-07 18:02:31 -070039import android.graphics.drawable.NinePatchDrawable;
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
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001707 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1708 // to push items in each of the cardinal directions, in an order based on the direction vector
1709 // passed.
1710 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1711 int[] direction, View ignoreView, ItemConfiguration solution) {
1712 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
1713 // If the direction vector has two non-zero components, we try pushing
1714 // separately in each of the components.
1715 int temp = direction[1];
1716 direction[1] = 0;
1717 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1718 ignoreView, solution)) {
1719 return true;
1720 }
1721 direction[1] = temp;
1722 temp = direction[0];
1723 direction[0] = 0;
1724 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1725 ignoreView, solution)) {
1726 return true;
1727 }
1728 // Revert the direction
1729 direction[0] = temp;
1730
1731 // Now we try pushing in each component of the opposite direction
1732 direction[0] *= -1;
1733 direction[1] *= -1;
1734 temp = direction[1];
1735 direction[1] = 0;
1736 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1737 ignoreView, solution)) {
1738 return true;
1739 }
1740
1741 direction[1] = temp;
1742 temp = direction[0];
1743 direction[0] = 0;
1744 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1745 ignoreView, solution)) {
1746 return true;
1747 }
1748 // revert the direction
1749 direction[0] = temp;
1750 direction[0] *= -1;
1751 direction[1] *= -1;
1752
1753 } else {
1754 // If the direction vector has a single non-zero component, we push first in the
1755 // direction of the vector
1756 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1757 ignoreView, solution)) {
1758 return true;
1759 }
1760
1761 // Then we try the opposite direction
1762 direction[0] *= -1;
1763 direction[1] *= -1;
1764 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1765 ignoreView, solution)) {
1766 return true;
1767 }
1768 // Switch the direction back
1769 direction[0] *= -1;
1770 direction[1] *= -1;
1771
1772 // If we have failed to find a push solution with the above, then we try
1773 // to find a solution by pushing along the perpendicular axis.
1774
1775 // Swap the components
1776 int temp = direction[1];
1777 direction[1] = direction[0];
1778 direction[0] = temp;
1779 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1780 ignoreView, solution)) {
1781 return true;
1782 }
1783
1784 // Then we try the opposite direction
1785 direction[0] *= -1;
1786 direction[1] *= -1;
1787 if (addViewsToTempLocation(intersectingViews, occupied, direction, true,
1788 ignoreView, solution)) {
1789 return true;
1790 }
1791 // Switch the direction back
1792 direction[0] *= -1;
1793 direction[1] *= -1;
1794
1795 // Swap the components back
1796 temp = direction[1];
1797 direction[1] = direction[0];
1798 direction[0] = temp;
1799 }
1800 return false;
1801 }
1802
Adam Cohen482ed822012-03-02 14:15:13 -08001803 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001804 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001805 // Return early if get invalid cell positions
1806 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001807
Adam Cohen8baab352012-03-20 17:39:21 -07001808 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001809 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001810
Adam Cohen8baab352012-03-20 17:39:21 -07001811 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001812 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001813 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001814 if (c != null) {
1815 c.x = cellX;
1816 c.y = cellY;
1817 }
Adam Cohen482ed822012-03-02 14:15:13 -08001818 }
Adam Cohen482ed822012-03-02 14:15:13 -08001819 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1820 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001821 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001822 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001823 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001824 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001825 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001826 if (Rect.intersects(r0, r1)) {
1827 if (!lp.canReorder) {
1828 return false;
1829 }
1830 mIntersectingViews.add(child);
1831 }
1832 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001833
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001834 // First we try to find a solution which respects the push mechanic. That is,
1835 // we try to find a solution such that no displaced item travels through another item
1836 // without also displacing that item.
1837 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07001838 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001839 return true;
1840 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001841
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001842 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohen19f37922012-03-21 11:59:11 -07001843 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, false, ignoreView,
1844 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001845 return true;
1846 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001847
Adam Cohen482ed822012-03-02 14:15:13 -08001848 // Ok, they couldn't move as a block, let's move them individually
1849 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001850 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001851 return false;
1852 }
1853 }
1854 return true;
1855 }
1856
1857 /*
1858 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1859 * the provided point and the provided cell
1860 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001861 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001862 double angle = Math.atan(((float) deltaY) / deltaX);
1863
1864 result[0] = 0;
1865 result[1] = 0;
1866 if (Math.abs(Math.cos(angle)) > 0.5f) {
1867 result[0] = (int) Math.signum(deltaX);
1868 }
1869 if (Math.abs(Math.sin(angle)) > 0.5f) {
1870 result[1] = (int) Math.signum(deltaY);
1871 }
1872 }
1873
Adam Cohen8baab352012-03-20 17:39:21 -07001874 private void copyOccupiedArray(boolean[][] occupied) {
1875 for (int i = 0; i < mCountX; i++) {
1876 for (int j = 0; j < mCountY; j++) {
1877 occupied[i][j] = mOccupied[i][j];
1878 }
1879 }
1880 }
1881
Adam Cohen482ed822012-03-02 14:15:13 -08001882 ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1883 int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001884 // Copy the current state into the solution. This solution will be manipulated as necessary.
1885 copyCurrentStateToSolution(solution, false);
1886 // Copy the current occupied array into the temporary occupied array. This array will be
1887 // manipulated as necessary to find a solution.
1888 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001889
1890 // We find the nearest cell into which we would place the dragged item, assuming there's
1891 // nothing in its way.
1892 int result[] = new int[2];
1893 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1894
1895 boolean success = false;
1896 // First we try the exact nearest position of the item being dragged,
1897 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001898 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1899 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001900
1901 if (!success) {
1902 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1903 // x, then 1 in y etc.
1904 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
1905 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
1906 dragView, false, solution);
1907 } else if (spanY > minSpanY) {
1908 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
1909 dragView, true, solution);
1910 }
1911 solution.isSolution = false;
1912 } else {
1913 solution.isSolution = true;
1914 solution.dragViewX = result[0];
1915 solution.dragViewY = result[1];
1916 solution.dragViewSpanX = spanX;
1917 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08001918 }
1919 return solution;
1920 }
1921
1922 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001923 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001924 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001925 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001926 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001927 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08001928 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07001929 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001930 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001931 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001932 }
Adam Cohen8baab352012-03-20 17:39:21 -07001933 solution.map.put(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08001934 }
1935 }
1936
1937 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
1938 for (int i = 0; i < mCountX; i++) {
1939 for (int j = 0; j < mCountY; j++) {
1940 mTmpOccupied[i][j] = false;
1941 }
1942 }
1943
Michael Jurkaa52570f2012-03-20 03:18:20 -07001944 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001945 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001946 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001947 if (child == dragView) continue;
1948 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001949 CellAndSpan c = solution.map.get(child);
1950 if (c != null) {
1951 lp.tmpCellX = c.x;
1952 lp.tmpCellY = c.y;
1953 lp.cellHSpan = c.spanX;
1954 lp.cellVSpan = c.spanY;
1955 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001956 }
1957 }
1958 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
1959 solution.dragViewSpanY, mTmpOccupied, true);
1960 }
1961
1962 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
1963 commitDragView) {
1964
1965 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
1966 for (int i = 0; i < mCountX; i++) {
1967 for (int j = 0; j < mCountY; j++) {
1968 occupied[i][j] = false;
1969 }
1970 }
1971
Michael Jurkaa52570f2012-03-20 03:18:20 -07001972 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001973 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001974 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001975 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001976 CellAndSpan c = solution.map.get(child);
1977 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07001978 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
1979 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07001980 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001981 }
1982 }
1983 if (commitDragView) {
1984 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
1985 solution.dragViewSpanY, occupied, true);
1986 }
1987 }
1988
Adam Cohen19f37922012-03-21 11:59:11 -07001989 // This method starts or changes the reorder hint animations
1990 private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
1991 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07001992 for (int i = 0; i < childCount; i++) {
1993 View child = mShortcutsAndWidgets.getChildAt(i);
1994 if (child == dragView) continue;
1995 CellAndSpan c = solution.map.get(child);
1996 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1997 if (c != null) {
1998 ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
1999 c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002000 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002001 }
2002 }
2003 }
2004
2005 // Class which represents the reorder hint animations. These animations show that an item is
2006 // in a temporary state, and hint at where the item will return to.
2007 class ReorderHintAnimation {
2008 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002009 float finalDeltaX;
2010 float finalDeltaY;
2011 float initDeltaX;
2012 float initDeltaY;
2013 float finalScale;
2014 float initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002015 private static final int DURATION = 300;
Adam Cohene7587d22012-05-24 18:50:02 -07002016 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002017
2018 public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
2019 int spanX, int spanY) {
2020 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2021 final int x0 = mTmpPoint[0];
2022 final int y0 = mTmpPoint[1];
2023 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2024 final int x1 = mTmpPoint[0];
2025 final int y1 = mTmpPoint[1];
2026 final int dX = x1 - x0;
2027 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002028 finalDeltaX = 0;
2029 finalDeltaY = 0;
Adam Cohen19f37922012-03-21 11:59:11 -07002030 if (dX == dY && dX == 0) {
2031 } else {
2032 if (dY == 0) {
Adam Cohend024f982012-05-23 18:26:45 -07002033 finalDeltaX = - Math.signum(dX) * mReorderHintAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002034 } else if (dX == 0) {
Adam Cohend024f982012-05-23 18:26:45 -07002035 finalDeltaY = - Math.signum(dY) * mReorderHintAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002036 } else {
2037 double angle = Math.atan( (float) (dY) / dX);
Adam Cohend024f982012-05-23 18:26:45 -07002038 finalDeltaX = (int) (- Math.signum(dX) *
Adam Cohenfe41ac62012-05-23 14:00:37 -07002039 Math.abs(Math.cos(angle) * mReorderHintAnimationMagnitude));
Adam Cohend024f982012-05-23 18:26:45 -07002040 finalDeltaY = (int) (- Math.signum(dY) *
Adam Cohenfe41ac62012-05-23 14:00:37 -07002041 Math.abs(Math.sin(angle) * mReorderHintAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002042 }
2043 }
Adam Cohend024f982012-05-23 18:26:45 -07002044 initDeltaX = child.getTranslationX();
2045 initDeltaY = child.getTranslationY();
2046 finalScale = 1.0f - 4.0f / child.getWidth();
2047 initScale = child.getScaleX();
2048
Brandon Keely50e6e562012-05-08 16:28:49 -07002049 child.setPivotY(child.getMeasuredHeight() * 0.5f);
2050 child.setPivotX(child.getMeasuredWidth() * 0.5f);
Adam Cohen19f37922012-03-21 11:59:11 -07002051 this.child = child;
2052 }
2053
Adam Cohend024f982012-05-23 18:26:45 -07002054 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002055 if (mShakeAnimators.containsKey(child)) {
2056 ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002057 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002058 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002059 if (finalDeltaX == 0 && finalDeltaY == 0) {
2060 completeAnimationImmediately();
2061 return;
2062 }
Adam Cohen19f37922012-03-21 11:59:11 -07002063 }
Adam Cohend024f982012-05-23 18:26:45 -07002064 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002065 return;
2066 }
Adam Cohene7587d22012-05-24 18:50:02 -07002067 ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
2068 a = va;
Adam Cohen19f37922012-03-21 11:59:11 -07002069 va.setRepeatMode(ValueAnimator.REVERSE);
2070 va.setRepeatCount(ValueAnimator.INFINITE);
Adam Cohen7bdfc972012-05-22 16:50:35 -07002071 va.setDuration(DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002072 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002073 va.addUpdateListener(new AnimatorUpdateListener() {
2074 @Override
2075 public void onAnimationUpdate(ValueAnimator animation) {
2076 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohend024f982012-05-23 18:26:45 -07002077 float x = r * finalDeltaX + (1 - r) * initDeltaX;
2078 float y = r * finalDeltaY + (1 - r) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002079 child.setTranslationX(x);
2080 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002081 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002082 child.setScaleX(s);
2083 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002084 }
2085 });
2086 va.addListener(new AnimatorListenerAdapter() {
2087 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002088 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002089 initDeltaX = 0;
2090 initDeltaY = 0;
2091 initScale = 1.0f;
Adam Cohen19f37922012-03-21 11:59:11 -07002092 }
2093 });
Adam Cohen19f37922012-03-21 11:59:11 -07002094 mShakeAnimators.put(child, this);
2095 va.start();
2096 }
2097
Adam Cohend024f982012-05-23 18:26:45 -07002098 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002099 if (a != null) {
2100 a.cancel();
2101 }
Adam Cohen19f37922012-03-21 11:59:11 -07002102 }
Adam Cohene7587d22012-05-24 18:50:02 -07002103
Brandon Keely50e6e562012-05-08 16:28:49 -07002104 private void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002105 if (a != null) {
2106 a.cancel();
2107 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002108
2109 AnimatorSet s = new AnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002110 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002111 s.playTogether(
2112 ObjectAnimator.ofFloat(child, "scaleX", 1f),
2113 ObjectAnimator.ofFloat(child, "scaleY", 1f),
2114 ObjectAnimator.ofFloat(child, "translationX", 0f),
Brandon Keelyfaf22e12012-05-10 16:48:50 -07002115 ObjectAnimator.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002116 );
2117 s.setDuration(REORDER_ANIMATION_DURATION);
2118 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2119 s.start();
2120 }
Adam Cohen19f37922012-03-21 11:59:11 -07002121 }
2122
2123 private void completeAndClearReorderHintAnimations() {
2124 for (ReorderHintAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002125 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002126 }
2127 mShakeAnimators.clear();
2128 }
2129
Adam Cohen482ed822012-03-02 14:15:13 -08002130 private void commitTempPlacement() {
2131 for (int i = 0; i < mCountX; i++) {
2132 for (int j = 0; j < mCountY; j++) {
2133 mOccupied[i][j] = mTmpOccupied[i][j];
2134 }
2135 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002136 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002137 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002138 View child = mShortcutsAndWidgets.getChildAt(i);
2139 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2140 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002141 // We do a null check here because the item info can be null in the case of the
2142 // AllApps button in the hotseat.
2143 if (info != null) {
2144 info.cellX = lp.cellX = lp.tmpCellX;
2145 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002146 info.spanX = lp.cellHSpan;
2147 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002148 }
Adam Cohen482ed822012-03-02 14:15:13 -08002149 }
Adam Cohen2acce882012-03-28 19:03:19 -07002150 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002151 }
2152
2153 public void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002154 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002155 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002156 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002157 lp.useTmpCoords = useTempCoords;
2158 }
2159 }
2160
Adam Cohen482ed822012-03-02 14:15:13 -08002161 ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
2162 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2163 int[] result = new int[2];
2164 int[] resultSpan = new int[2];
2165 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
2166 resultSpan);
2167 if (result[0] >= 0 && result[1] >= 0) {
2168 copyCurrentStateToSolution(solution, false);
2169 solution.dragViewX = result[0];
2170 solution.dragViewY = result[1];
2171 solution.dragViewSpanX = resultSpan[0];
2172 solution.dragViewSpanY = resultSpan[1];
2173 solution.isSolution = true;
2174 } else {
2175 solution.isSolution = false;
2176 }
2177 return solution;
2178 }
2179
2180 public void prepareChildForDrag(View child) {
2181 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002182 }
2183
Adam Cohen19f37922012-03-21 11:59:11 -07002184 /* This seems like it should be obvious and straight-forward, but when the direction vector
2185 needs to match with the notion of the dragView pushing other views, we have to employ
2186 a slightly more subtle notion of the direction vector. The question is what two points is
2187 the vector between? The center of the dragView and its desired destination? Not quite, as
2188 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2189 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2190 or right, which helps make pushing feel right.
2191 */
2192 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2193 int spanY, View dragView, int[] resultDirection) {
2194 int[] targetDestination = new int[2];
2195
2196 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2197 Rect dragRect = new Rect();
2198 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2199 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2200
2201 Rect dropRegionRect = new Rect();
2202 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2203 dragView, dropRegionRect, mIntersectingViews);
2204
2205 int dropRegionSpanX = dropRegionRect.width();
2206 int dropRegionSpanY = dropRegionRect.height();
2207
2208 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2209 dropRegionRect.height(), dropRegionRect);
2210
2211 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2212 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2213
2214 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2215 deltaX = 0;
2216 }
2217 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2218 deltaY = 0;
2219 }
2220
2221 if (deltaX == 0 && deltaY == 0) {
2222 // No idea what to do, give a random direction.
2223 resultDirection[0] = 1;
2224 resultDirection[1] = 0;
2225 } else {
2226 computeDirectionVector(deltaX, deltaY, resultDirection);
2227 }
2228 }
2229
2230 // For a given cell and span, fetch the set of views intersecting the region.
2231 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2232 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2233 if (boundingRect != null) {
2234 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2235 }
2236 intersectingViews.clear();
2237 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2238 Rect r1 = new Rect();
2239 final int count = mShortcutsAndWidgets.getChildCount();
2240 for (int i = 0; i < count; i++) {
2241 View child = mShortcutsAndWidgets.getChildAt(i);
2242 if (child == dragView) continue;
2243 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2244 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2245 if (Rect.intersects(r0, r1)) {
2246 mIntersectingViews.add(child);
2247 if (boundingRect != null) {
2248 boundingRect.union(r1);
2249 }
2250 }
2251 }
2252 }
2253
2254 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2255 View dragView, int[] result) {
2256 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2257 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2258 mIntersectingViews);
2259 return !mIntersectingViews.isEmpty();
2260 }
2261
2262 void revertTempState() {
2263 if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
2264 final int count = mShortcutsAndWidgets.getChildCount();
2265 for (int i = 0; i < count; i++) {
2266 View child = mShortcutsAndWidgets.getChildAt(i);
2267 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2268 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2269 lp.tmpCellX = lp.cellX;
2270 lp.tmpCellY = lp.cellY;
2271 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2272 0, false, false);
2273 }
2274 }
2275 completeAndClearReorderHintAnimations();
2276 setItemPlacementDirty(false);
2277 }
2278
Adam Cohenbebf0422012-04-11 18:06:28 -07002279 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2280 View dragView, int[] direction, boolean commit) {
2281 int[] pixelXY = new int[2];
2282 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2283
2284 // First we determine if things have moved enough to cause a different layout
2285 ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY,
2286 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2287
2288 setUseTempCoords(true);
2289 if (swapSolution != null && swapSolution.isSolution) {
2290 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2291 // committing anything or animating anything as we just want to determine if a solution
2292 // exists
2293 copySolutionToTempState(swapSolution, dragView);
2294 setItemPlacementDirty(true);
2295 animateItemsToSolution(swapSolution, dragView, commit);
2296
2297 if (commit) {
2298 commitTempPlacement();
2299 completeAndClearReorderHintAnimations();
2300 setItemPlacementDirty(false);
2301 } else {
2302 beginOrAdjustHintAnimations(swapSolution, dragView,
2303 REORDER_ANIMATION_DURATION);
2304 }
2305 mShortcutsAndWidgets.requestLayout();
2306 }
2307 return swapSolution.isSolution;
2308 }
2309
Adam Cohen482ed822012-03-02 14:15:13 -08002310 int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
2311 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002312 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002313 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002314
2315 if (resultSpan == null) {
2316 resultSpan = new int[2];
2317 }
2318
Adam Cohen19f37922012-03-21 11:59:11 -07002319 // When we are checking drop validity or actually dropping, we don't recompute the
2320 // direction vector, since we want the solution to match the preview, and it's possible
2321 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002322 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2323 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002324 mDirectionVector[0] = mPreviousReorderDirection[0];
2325 mDirectionVector[1] = mPreviousReorderDirection[1];
2326 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002327 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2328 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2329 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002330 }
2331 } else {
2332 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2333 mPreviousReorderDirection[0] = mDirectionVector[0];
2334 mPreviousReorderDirection[1] = mDirectionVector[1];
2335 }
2336
Adam Cohen482ed822012-03-02 14:15:13 -08002337 ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
2338 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2339
2340 // We attempt the approach which doesn't shuffle views at all
2341 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2342 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2343
2344 ItemConfiguration finalSolution = null;
2345 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2346 finalSolution = swapSolution;
2347 } else if (noShuffleSolution.isSolution) {
2348 finalSolution = noShuffleSolution;
2349 }
2350
2351 boolean foundSolution = true;
2352 if (!DESTRUCTIVE_REORDER) {
2353 setUseTempCoords(true);
2354 }
2355
2356 if (finalSolution != null) {
2357 result[0] = finalSolution.dragViewX;
2358 result[1] = finalSolution.dragViewY;
2359 resultSpan[0] = finalSolution.dragViewSpanX;
2360 resultSpan[1] = finalSolution.dragViewSpanY;
2361
2362 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2363 // committing anything or animating anything as we just want to determine if a solution
2364 // exists
2365 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2366 if (!DESTRUCTIVE_REORDER) {
2367 copySolutionToTempState(finalSolution, dragView);
2368 }
2369 setItemPlacementDirty(true);
2370 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2371
Adam Cohen19f37922012-03-21 11:59:11 -07002372 if (!DESTRUCTIVE_REORDER &&
2373 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002374 commitTempPlacement();
Adam Cohen19f37922012-03-21 11:59:11 -07002375 completeAndClearReorderHintAnimations();
2376 setItemPlacementDirty(false);
2377 } else {
2378 beginOrAdjustHintAnimations(finalSolution, dragView,
2379 REORDER_ANIMATION_DURATION);
Adam Cohen482ed822012-03-02 14:15:13 -08002380 }
2381 }
2382 } else {
2383 foundSolution = false;
2384 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2385 }
2386
2387 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2388 setUseTempCoords(false);
2389 }
Adam Cohen482ed822012-03-02 14:15:13 -08002390
Michael Jurkaa52570f2012-03-20 03:18:20 -07002391 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002392 return result;
2393 }
2394
Adam Cohen19f37922012-03-21 11:59:11 -07002395 void setItemPlacementDirty(boolean dirty) {
2396 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002397 }
Adam Cohen19f37922012-03-21 11:59:11 -07002398 boolean isItemPlacementDirty() {
2399 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002400 }
2401
2402 private class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002403 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohen482ed822012-03-02 14:15:13 -08002404 boolean isSolution = false;
2405 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2406
2407 int area() {
2408 return dragViewSpanX * dragViewSpanY;
2409 }
Adam Cohen8baab352012-03-20 17:39:21 -07002410 }
2411
2412 private class CellAndSpan {
2413 int x, y;
2414 int spanX, spanY;
2415
2416 public CellAndSpan(int x, int y, int spanX, int spanY) {
2417 this.x = x;
2418 this.y = y;
2419 this.spanX = spanX;
2420 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002421 }
2422 }
2423
Adam Cohendf035382011-04-11 17:22:04 -07002424 /**
2425 * Find a vacant area that will fit the given bounds nearest the requested
2426 * cell location. Uses Euclidean distance to score multiple vacant areas.
2427 *
2428 * @param pixelX The X location at which you want to search for a vacant area.
2429 * @param pixelY The Y location at which you want to search for a vacant area.
2430 * @param spanX Horizontal span of the object.
2431 * @param spanY Vertical span of the object.
2432 * @param ignoreView Considers space occupied by this view as unoccupied
2433 * @param result Previously returned value to possibly recycle.
2434 * @return The X, Y cell of a vacant area that can contain this object,
2435 * nearest the requested location.
2436 */
2437 int[] findNearestVacantArea(
2438 int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
2439 return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
2440 }
2441
2442 /**
Adam Cohend41fbf52012-02-16 23:53:59 -08002443 * Find a vacant area that will fit the given bounds nearest the requested
2444 * cell location. Uses Euclidean distance to score multiple vacant areas.
2445 *
2446 * @param pixelX The X location at which you want to search for a vacant area.
2447 * @param pixelY The Y location at which you want to search for a vacant area.
2448 * @param minSpanX The minimum horizontal span required
2449 * @param minSpanY The minimum vertical span required
2450 * @param spanX Horizontal span of the object.
2451 * @param spanY Vertical span of the object.
2452 * @param ignoreView Considers space occupied by this view as unoccupied
2453 * @param result Previously returned value to possibly recycle.
2454 * @return The X, Y cell of a vacant area that can contain this object,
2455 * nearest the requested location.
2456 */
2457 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
2458 int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
Adam Cohen482ed822012-03-02 14:15:13 -08002459 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
2460 result, resultSpan, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08002461 }
2462
2463 /**
Adam Cohendf035382011-04-11 17:22:04 -07002464 * Find a starting cell position that will fit the given bounds nearest the requested
2465 * cell location. Uses Euclidean distance to score multiple vacant areas.
2466 *
2467 * @param pixelX The X location at which you want to search for a vacant area.
2468 * @param pixelY The Y location at which you want to search for a vacant area.
2469 * @param spanX Horizontal span of the object.
2470 * @param spanY Vertical span of the object.
2471 * @param ignoreView Considers space occupied by this view as unoccupied
2472 * @param result Previously returned value to possibly recycle.
2473 * @return The X, Y cell of a vacant area that can contain this object,
2474 * nearest the requested location.
2475 */
2476 int[] findNearestArea(
2477 int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2478 return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
2479 }
2480
Michael Jurka0280c3b2010-09-17 15:00:07 -07002481 boolean existsEmptyCell() {
2482 return findCellForSpan(null, 1, 1);
2483 }
2484
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002485 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002486 * Finds the upper-left coordinate of the first rectangle in the grid that can
2487 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2488 * then this method will only return coordinates for rectangles that contain the cell
2489 * (intersectX, intersectY)
2490 *
2491 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2492 * can be found.
2493 * @param spanX The horizontal span of the cell we want to find.
2494 * @param spanY The vertical span of the cell we want to find.
2495 *
2496 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002497 */
Michael Jurka0280c3b2010-09-17 15:00:07 -07002498 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Adam Cohen482ed822012-03-02 14:15:13 -08002499 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002500 }
2501
2502 /**
2503 * Like above, but ignores any cells occupied by the item "ignoreView"
2504 *
2505 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2506 * can be found.
2507 * @param spanX The horizontal span of the cell we want to find.
2508 * @param spanY The vertical span of the cell we want to find.
2509 * @param ignoreView The home screen item we should treat as not occupying any space
2510 * @return
2511 */
2512 boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
Adam Cohen482ed822012-03-02 14:15:13 -08002513 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
2514 ignoreView, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002515 }
2516
2517 /**
2518 * Like above, but if intersectX and intersectY are not -1, then this method will try to
2519 * return coordinates for rectangles that contain the cell [intersectX, intersectY]
2520 *
2521 * @param spanX The horizontal span of the cell we want to find.
2522 * @param spanY The vertical span of the cell we want to find.
2523 * @param ignoreView The home screen item we should treat as not occupying any space
2524 * @param intersectX The X coordinate of the cell that we should try to overlap
2525 * @param intersectX The Y coordinate of the cell that we should try to overlap
2526 *
2527 * @return True if a vacant cell of the specified dimension was found, false otherwise.
2528 */
2529 boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
2530 int intersectX, int intersectY) {
2531 return findCellForSpanThatIntersectsIgnoring(
Adam Cohen482ed822012-03-02 14:15:13 -08002532 cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002533 }
2534
2535 /**
2536 * The superset of the above two methods
2537 */
2538 boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002539 int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002540 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08002541 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002542
Michael Jurka28750fb2010-09-24 17:43:49 -07002543 boolean foundCell = false;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002544 while (true) {
2545 int startX = 0;
2546 if (intersectX >= 0) {
2547 startX = Math.max(startX, intersectX - (spanX - 1));
2548 }
2549 int endX = mCountX - (spanX - 1);
2550 if (intersectX >= 0) {
2551 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
2552 }
2553 int startY = 0;
2554 if (intersectY >= 0) {
2555 startY = Math.max(startY, intersectY - (spanY - 1));
2556 }
2557 int endY = mCountY - (spanY - 1);
2558 if (intersectY >= 0) {
2559 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
2560 }
2561
Winson Chungbbc60d82010-11-11 16:34:41 -08002562 for (int y = startY; y < endY && !foundCell; y++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002563 inner:
Winson Chungbbc60d82010-11-11 16:34:41 -08002564 for (int x = startX; x < endX; x++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002565 for (int i = 0; i < spanX; i++) {
2566 for (int j = 0; j < spanY; j++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002567 if (occupied[x + i][y + j]) {
Winson Chungbbc60d82010-11-11 16:34:41 -08002568 // small optimization: we can skip to after the column we just found
Michael Jurka0280c3b2010-09-17 15:00:07 -07002569 // an occupied cell
Winson Chungbbc60d82010-11-11 16:34:41 -08002570 x += i;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002571 continue inner;
2572 }
2573 }
2574 }
2575 if (cellXY != null) {
2576 cellXY[0] = x;
2577 cellXY[1] = y;
2578 }
Michael Jurka28750fb2010-09-24 17:43:49 -07002579 foundCell = true;
2580 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002581 }
2582 }
2583 if (intersectX == -1 && intersectY == -1) {
2584 break;
2585 } else {
2586 // if we failed to find anything, try again but without any requirements of
2587 // intersecting
2588 intersectX = -1;
2589 intersectY = -1;
2590 continue;
2591 }
2592 }
2593
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002594 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08002595 markCellsAsOccupiedForView(ignoreView, occupied);
Michael Jurka28750fb2010-09-24 17:43:49 -07002596 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002597 }
2598
2599 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002600 * A drag event has begun over this layout.
2601 * It may have begun over this layout (in which case onDragChild is called first),
2602 * or it may have begun on another layout.
2603 */
2604 void onDragEnter() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002605 mDragEnforcer.onDragEnter();
Winson Chungc07918d2011-07-01 15:35:26 -07002606 mDragging = true;
2607 }
2608
2609 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002610 * Called when drag has left this CellLayout or has been completed (successfully or not)
2611 */
2612 void onDragExit() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002613 mDragEnforcer.onDragExit();
Joe Onorato4be866d2010-10-10 11:26:02 -07002614 // This can actually be called when we aren't in a drag, e.g. when adding a new
2615 // item to this layout via the customize drawer.
2616 // Guard against that case.
2617 if (mDragging) {
2618 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002619 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002620
2621 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002622 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002623 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2624 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002625 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002626 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002627 }
2628
2629 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002630 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002631 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002632 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002633 *
2634 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002635 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002636 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002637 if (child != null) {
2638 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002639 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002640 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002641 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002642 }
2643
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002644 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002645 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002646 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002647 * @param cellX X coordinate of upper left corner expressed as a cell position
2648 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002649 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002650 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002651 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002652 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002653 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002654 final int cellWidth = mCellWidth;
2655 final int cellHeight = mCellHeight;
2656 final int widthGap = mWidthGap;
2657 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002658
Winson Chung4b825dcd2011-06-19 12:41:22 -07002659 final int hStartPadding = getPaddingLeft();
2660 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002661
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002662 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2663 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2664
2665 int x = hStartPadding + cellX * (cellWidth + widthGap);
2666 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002667
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002668 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002669 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002670
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002671 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002672 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002673 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002674 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002675 * @param width Width in pixels
2676 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002677 * @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 -08002678 */
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002679 public int[] rectToCell(int width, int height, int[] result) {
Michael Jurka9987a5c2010-10-08 16:58:12 -07002680 return rectToCell(getResources(), width, height, result);
2681 }
2682
2683 public static int[] rectToCell(Resources resources, int width, int height, int[] result) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002684 // Always assume we're working with the smallest span to make sure we
2685 // reserve enough space in both orientations.
Joe Onorato79e56262009-09-21 15:23:04 -04002686 int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
2687 int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002688 int smallerSize = Math.min(actualWidth, actualHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002689
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002690 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002691 int spanX = (int) Math.ceil(width / (float) smallerSize);
2692 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002693
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002694 if (result == null) {
2695 return new int[] { spanX, spanY };
2696 }
2697 result[0] = spanX;
2698 result[1] = spanY;
2699 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002700 }
2701
Michael Jurkaf12c75c2011-01-25 22:41:40 -08002702 public int[] cellSpansToSize(int hSpans, int vSpans) {
2703 int[] size = new int[2];
2704 size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
2705 size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
2706 return size;
2707 }
2708
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002709 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08002710 * Calculate the grid spans needed to fit given item
2711 */
2712 public void calculateSpans(ItemInfo info) {
2713 final int minWidth;
2714 final int minHeight;
2715
2716 if (info instanceof LauncherAppWidgetInfo) {
2717 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
2718 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
2719 } else if (info instanceof PendingAddWidgetInfo) {
2720 minWidth = ((PendingAddWidgetInfo) info).minWidth;
2721 minHeight = ((PendingAddWidgetInfo) info).minHeight;
2722 } else {
2723 // It's not a widget, so it must be 1x1
2724 info.spanX = info.spanY = 1;
2725 return;
2726 }
2727 int[] spans = rectToCell(minWidth, minHeight, null);
2728 info.spanX = spans[0];
2729 info.spanY = spans[1];
2730 }
2731
2732 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002733 * Find the first vacant cell, if there is one.
2734 *
2735 * @param vacant Holds the x and y coordinate of the vacant cell
2736 * @param spanX Horizontal cell span.
2737 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -07002738 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002739 * @return True if a vacant cell was found
2740 */
2741 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002742
Michael Jurka0280c3b2010-09-17 15:00:07 -07002743 return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002744 }
2745
2746 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
2747 int xCount, int yCount, boolean[][] occupied) {
2748
Adam Cohen2801caf2011-05-13 20:57:39 -07002749 for (int y = 0; y < yCount; y++) {
2750 for (int x = 0; x < xCount; x++) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002751 boolean available = !occupied[x][y];
2752out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
2753 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
2754 available = available && !occupied[i][j];
2755 if (!available) break out;
2756 }
2757 }
2758
2759 if (available) {
2760 vacant[0] = x;
2761 vacant[1] = y;
2762 return true;
2763 }
2764 }
2765 }
2766
2767 return false;
2768 }
2769
Michael Jurka0280c3b2010-09-17 15:00:07 -07002770 private void clearOccupiedCells() {
2771 for (int x = 0; x < mCountX; x++) {
2772 for (int y = 0; y < mCountY; y++) {
2773 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002774 }
2775 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002776 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002777
Adam Cohend41fbf52012-02-16 23:53:59 -08002778 public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002779 markCellsAsUnoccupiedForView(view);
Adam Cohen482ed822012-03-02 14:15:13 -08002780 markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002781 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002782
Adam Cohend4844c32011-02-18 19:25:06 -08002783 public void markCellsAsOccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08002784 markCellsAsOccupiedForView(view, mOccupied);
2785 }
2786 public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002787 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002788 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002789 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002790 }
2791
Adam Cohend4844c32011-02-18 19:25:06 -08002792 public void markCellsAsUnoccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08002793 markCellsAsUnoccupiedForView(view, mOccupied);
2794 }
2795 public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002796 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002797 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002798 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002799 }
2800
Adam Cohen482ed822012-03-02 14:15:13 -08002801 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2802 boolean value) {
2803 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002804 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2805 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002806 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002807 }
2808 }
2809 }
2810
Adam Cohen2801caf2011-05-13 20:57:39 -07002811 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002812 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002813 (Math.max((mCountX - 1), 0) * mWidthGap);
2814 }
2815
2816 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002817 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002818 (Math.max((mCountY - 1), 0) * mHeightGap);
2819 }
2820
Michael Jurka66d72172011-04-12 16:29:25 -07002821 public boolean isOccupied(int x, int y) {
2822 if (x < mCountX && y < mCountY) {
2823 return mOccupied[x][y];
2824 } else {
2825 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2826 }
2827 }
2828
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002829 @Override
2830 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2831 return new CellLayout.LayoutParams(getContext(), attrs);
2832 }
2833
2834 @Override
2835 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2836 return p instanceof CellLayout.LayoutParams;
2837 }
2838
2839 @Override
2840 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2841 return new CellLayout.LayoutParams(p);
2842 }
2843
Winson Chungaafa03c2010-06-11 17:34:16 -07002844 public static class CellLayoutAnimationController extends LayoutAnimationController {
2845 public CellLayoutAnimationController(Animation animation, float delay) {
2846 super(animation, delay);
2847 }
2848
2849 @Override
2850 protected long getDelayForView(View view) {
2851 return (int) (Math.random() * 150);
2852 }
2853 }
2854
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002855 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2856 /**
2857 * Horizontal location of the item in the grid.
2858 */
2859 @ViewDebug.ExportedProperty
2860 public int cellX;
2861
2862 /**
2863 * Vertical location of the item in the grid.
2864 */
2865 @ViewDebug.ExportedProperty
2866 public int cellY;
2867
2868 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002869 * Temporary horizontal location of the item in the grid during reorder
2870 */
2871 public int tmpCellX;
2872
2873 /**
2874 * Temporary vertical location of the item in the grid during reorder
2875 */
2876 public int tmpCellY;
2877
2878 /**
2879 * Indicates that the temporary coordinates should be used to layout the items
2880 */
2881 public boolean useTmpCoords;
2882
2883 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002884 * Number of cells spanned horizontally by the item.
2885 */
2886 @ViewDebug.ExportedProperty
2887 public int cellHSpan;
2888
2889 /**
2890 * Number of cells spanned vertically by the item.
2891 */
2892 @ViewDebug.ExportedProperty
2893 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002894
Adam Cohen1b607ed2011-03-03 17:26:50 -08002895 /**
2896 * Indicates whether the item will set its x, y, width and height parameters freely,
2897 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2898 */
Adam Cohend4844c32011-02-18 19:25:06 -08002899 public boolean isLockedToGrid = true;
2900
Adam Cohen482ed822012-03-02 14:15:13 -08002901 /**
2902 * Indicates whether this item can be reordered. Always true except in the case of the
2903 * the AllApps button.
2904 */
2905 public boolean canReorder = true;
2906
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002907 // X coordinate of the view in the layout.
2908 @ViewDebug.ExportedProperty
2909 int x;
2910 // Y coordinate of the view in the layout.
2911 @ViewDebug.ExportedProperty
2912 int y;
2913
Romain Guy84f296c2009-11-04 15:00:44 -08002914 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002915
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002916 public LayoutParams(Context c, AttributeSet attrs) {
2917 super(c, attrs);
2918 cellHSpan = 1;
2919 cellVSpan = 1;
2920 }
2921
2922 public LayoutParams(ViewGroup.LayoutParams source) {
2923 super(source);
2924 cellHSpan = 1;
2925 cellVSpan = 1;
2926 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002927
2928 public LayoutParams(LayoutParams source) {
2929 super(source);
2930 this.cellX = source.cellX;
2931 this.cellY = source.cellY;
2932 this.cellHSpan = source.cellHSpan;
2933 this.cellVSpan = source.cellVSpan;
2934 }
2935
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002936 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002937 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002938 this.cellX = cellX;
2939 this.cellY = cellY;
2940 this.cellHSpan = cellHSpan;
2941 this.cellVSpan = cellVSpan;
2942 }
2943
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002944 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap) {
Adam Cohend4844c32011-02-18 19:25:06 -08002945 if (isLockedToGrid) {
2946 final int myCellHSpan = cellHSpan;
2947 final int myCellVSpan = cellVSpan;
Adam Cohen482ed822012-03-02 14:15:13 -08002948 final int myCellX = useTmpCoords ? tmpCellX : cellX;
2949 final int myCellY = useTmpCoords ? tmpCellY : cellY;
Adam Cohen1b607ed2011-03-03 17:26:50 -08002950
Adam Cohend4844c32011-02-18 19:25:06 -08002951 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2952 leftMargin - rightMargin;
2953 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2954 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002955 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2956 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002957 }
2958 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002959
Winson Chungaafa03c2010-06-11 17:34:16 -07002960 public String toString() {
2961 return "(" + this.cellX + ", " + this.cellY + ")";
2962 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002963
2964 public void setWidth(int width) {
2965 this.width = width;
2966 }
2967
2968 public int getWidth() {
2969 return width;
2970 }
2971
2972 public void setHeight(int height) {
2973 this.height = height;
2974 }
2975
2976 public int getHeight() {
2977 return height;
2978 }
2979
2980 public void setX(int x) {
2981 this.x = x;
2982 }
2983
2984 public int getX() {
2985 return x;
2986 }
2987
2988 public void setY(int y) {
2989 this.y = y;
2990 }
2991
2992 public int getY() {
2993 return y;
2994 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002995 }
2996
Michael Jurka0280c3b2010-09-17 15:00:07 -07002997 // This class stores info for two purposes:
2998 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2999 // its spanX, spanY, and the screen it is on
3000 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
3001 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
3002 // the CellLayout that was long clicked
Michael Jurkae5fb0f22011-04-11 13:27:46 -07003003 static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003004 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07003005 int cellX = -1;
3006 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003007 int spanX;
3008 int spanY;
3009 int screen;
Winson Chung3d503fb2011-07-13 17:25:49 -07003010 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003011
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003012 @Override
3013 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07003014 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
3015 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003016 }
3017 }
Michael Jurkad771c962011-08-09 15:00:48 -07003018
3019 public boolean lastDownOnOccupiedCell() {
3020 return mLastDownOnOccupiedCell;
3021 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003022}