blob: 69457eaece07f849dc56138b1f0e91debb55fb72 [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;
20import android.animation.AnimatorListenerAdapter;
Chet Haase00397b12010-10-07 11:13:10 -070021import android.animation.TimeInterpolator;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070022import android.animation.ValueAnimator;
23import android.animation.ValueAnimator.AnimatorUpdateListener;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080024import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040025import android.content.res.Resources;
Winson Chungaafa03c2010-06-11 17:34:16 -070026import android.content.res.TypedArray;
Joe Onorato4be866d2010-10-10 11:26:02 -070027import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070028import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080029import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070030import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070031import android.graphics.Point;
32import android.graphics.PointF;
Adam Cohenb5ba0972011-09-07 18:02:31 -070033import android.graphics.PorterDuff;
34import android.graphics.PorterDuffXfermode;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080035import android.graphics.Rect;
Adam Cohen482ed822012-03-02 14:15:13 -080036import android.graphics.drawable.ColorDrawable;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070037import android.graphics.drawable.Drawable;
Adam Cohenb5ba0972011-09-07 18:02:31 -070038import android.graphics.drawable.NinePatchDrawable;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080039import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070040import android.util.Log;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080041import android.view.MotionEvent;
42import android.view.View;
43import android.view.ViewDebug;
44import android.view.ViewGroup;
Winson Chungaafa03c2010-06-11 17:34:16 -070045import android.view.animation.Animation;
Winson Chung150fbab2010-09-29 17:14:26 -070046import android.view.animation.DecelerateInterpolator;
Winson Chungaafa03c2010-06-11 17:34:16 -070047import android.view.animation.LayoutAnimationController;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080048
Adam Cohen66396872011-04-15 17:50:36 -070049import com.android.launcher.R;
Adam Cohen69ce2e52011-07-03 19:25:21 -070050import com.android.launcher2.FolderIcon.FolderRingAnimator;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070051
Adam Cohen69ce2e52011-07-03 19:25:21 -070052import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070053import java.util.Arrays;
Adam Cohenbfbfd262011-06-13 16:55:12 -070054import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080055import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070056
Michael Jurkabdb5c532011-02-01 15:05:06 -080057public class CellLayout extends ViewGroup {
Winson Chungaafa03c2010-06-11 17:34:16 -070058 static final String TAG = "CellLayout";
59
Adam Cohen2acce882012-03-28 19:03:19 -070060 private Launcher mLauncher;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080061 private int mCellWidth;
62 private int mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070063
Adam Cohend22015c2010-07-26 22:02:18 -070064 private int mCountX;
65 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080066
Adam Cohen234c4cd2011-07-17 21:03:04 -070067 private int mOriginalWidthGap;
68 private int mOriginalHeightGap;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080069 private int mWidthGap;
70 private int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070071 private int mMaxGap;
Adam Cohenebea84d2011-11-09 17:20:41 -080072 private boolean mScrollingTransformsDirty = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080073
74 private final Rect mRect = new Rect();
75 private final CellInfo mCellInfo = new CellInfo();
Winson Chungaafa03c2010-06-11 17:34:16 -070076
Patrick Dubroyde7658b2010-09-27 11:15:43 -070077 // These are temporary variables to prevent having to allocate a new object just to
78 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Winson Chung0be025d2011-05-23 17:45:09 -070079 private final int[] mTmpXY = new int[2];
Patrick Dubroyde7658b2010-09-27 11:15:43 -070080 private final int[] mTmpPoint = new int[2];
81 private final PointF mTmpPointF = new PointF();
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 Cohen19f37922012-03-21 11:59:11 -0700150 private static final float REORDER_HINT_MAGNITUDE = 0.27f;
151 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);
428 cd.setBounds(0, 0, 80, 80);
429 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) {
1004 mBackgroundAlphaMultiplier = multiplier;
1005 }
1006
Adam Cohenddb82192010-11-10 16:32:54 -08001007 public float getBackgroundAlphaMultiplier() {
1008 return mBackgroundAlphaMultiplier;
1009 }
1010
Michael Jurka5f1c5092010-09-03 14:15:02 -07001011 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -08001012 if (mBackgroundAlpha != alpha) {
1013 mBackgroundAlpha = alpha;
1014 invalidate();
1015 }
Michael Jurkadee05892010-07-27 10:01:56 -07001016 }
1017
Michael Jurkaa52570f2012-03-20 03:18:20 -07001018 public void setShortcutAndWidgetAlpha(float alpha) {
Michael Jurka0142d492010-08-25 17:46:15 -07001019 final int childCount = getChildCount();
1020 for (int i = 0; i < childCount; i++) {
Michael Jurkadee05892010-07-27 10:01:56 -07001021 getChildAt(i).setAlpha(alpha);
1022 }
1023 }
1024
Michael Jurkaa52570f2012-03-20 03:18:20 -07001025 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
1026 if (getChildCount() > 0) {
1027 return (ShortcutAndWidgetContainer) getChildAt(0);
1028 }
1029 return null;
1030 }
1031
Patrick Dubroy440c3602010-07-13 17:50:32 -07001032 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001033 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -07001034 }
1035
Adam Cohen76fc0852011-06-17 13:26:23 -07001036 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -08001037 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001038 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -08001039 boolean[][] occupied = mOccupied;
1040 if (!permanent) {
1041 occupied = mTmpOccupied;
1042 }
1043
Adam Cohen19f37922012-03-21 11:59:11 -07001044 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001045 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1046 final ItemInfo info = (ItemInfo) child.getTag();
1047
1048 // We cancel any existing animations
1049 if (mReorderAnimators.containsKey(lp)) {
1050 mReorderAnimators.get(lp).cancel();
1051 mReorderAnimators.remove(lp);
1052 }
1053
Adam Cohen482ed822012-03-02 14:15:13 -08001054 final int oldX = lp.x;
1055 final int oldY = lp.y;
1056 if (adjustOccupied) {
1057 occupied[lp.cellX][lp.cellY] = false;
1058 occupied[cellX][cellY] = true;
1059 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001060 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001061 if (permanent) {
1062 lp.cellX = info.cellX = cellX;
1063 lp.cellY = info.cellY = cellY;
1064 } else {
1065 lp.tmpCellX = cellX;
1066 lp.tmpCellY = cellY;
1067 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001068 clc.setupLp(lp);
1069 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001070 final int newX = lp.x;
1071 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001072
Adam Cohen76fc0852011-06-17 13:26:23 -07001073 lp.x = oldX;
1074 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001075
Adam Cohen482ed822012-03-02 14:15:13 -08001076 // Exit early if we're not actually moving the view
1077 if (oldX == newX && oldY == newY) {
1078 lp.isLockedToGrid = true;
1079 return true;
1080 }
1081
1082 ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
1083 va.setDuration(duration);
1084 mReorderAnimators.put(lp, va);
1085
1086 va.addUpdateListener(new AnimatorUpdateListener() {
1087 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001088 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001089 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001090 lp.x = (int) ((1 - r) * oldX + r * newX);
1091 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001092 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001093 }
1094 });
Adam Cohen482ed822012-03-02 14:15:13 -08001095 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001096 boolean cancelled = false;
1097 public void onAnimationEnd(Animator animation) {
1098 // If the animation was cancelled, it means that another animation
1099 // has interrupted this one, and we don't want to lock the item into
1100 // place just yet.
1101 if (!cancelled) {
1102 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001103 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001104 }
1105 if (mReorderAnimators.containsKey(lp)) {
1106 mReorderAnimators.remove(lp);
1107 }
1108 }
1109 public void onAnimationCancel(Animator animation) {
1110 cancelled = true;
1111 }
1112 });
Adam Cohen482ed822012-03-02 14:15:13 -08001113 va.setStartDelay(delay);
1114 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001115 return true;
1116 }
1117 return false;
1118 }
1119
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001120 /**
1121 * Estimate where the top left cell of the dragged item will land if it is dropped.
1122 *
1123 * @param originX The X value of the top left corner of the item
1124 * @param originY The Y value of the top left corner of the item
1125 * @param spanX The number of horizontal cells that the item spans
1126 * @param spanY The number of vertical cells that the item spans
1127 * @param result The estimated drop cell X and Y.
1128 */
1129 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
Adam Cohend22015c2010-07-26 22:02:18 -07001130 final int countX = mCountX;
1131 final int countY = mCountY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001132
Michael Jurkaa63c4522010-08-19 13:52:27 -07001133 // pointToCellRounded takes the top left of a cell but will pad that with
1134 // cellWidth/2 and cellHeight/2 when finding the matching cell
1135 pointToCellRounded(originX, originY, result);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001136
1137 // If the item isn't fully on this screen, snap to the edges
1138 int rightOverhang = result[0] + spanX - countX;
1139 if (rightOverhang > 0) {
1140 result[0] -= rightOverhang; // Snap to right
1141 }
1142 result[0] = Math.max(0, result[0]); // Snap to left
1143 int bottomOverhang = result[1] + spanY - countY;
1144 if (bottomOverhang > 0) {
1145 result[1] -= bottomOverhang; // Snap to bottom
1146 }
1147 result[1] = Math.max(0, result[1]); // Snap to top
1148 }
1149
Adam Cohen482ed822012-03-02 14:15:13 -08001150 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1151 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001152 final int oldDragCellX = mDragCell[0];
1153 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001154
Winson Chungb8c69f32011-10-19 21:36:08 -07001155 if (v != null && dragOffset == null) {
Winson Chunga9abd0e2010-10-27 17:18:37 -07001156 mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
1157 } else {
1158 mDragCenter.set(originX, originY);
1159 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001160
Adam Cohen2801caf2011-05-13 20:57:39 -07001161 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001162 return;
1163 }
1164
Adam Cohen482ed822012-03-02 14:15:13 -08001165 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1166 mDragCell[0] = cellX;
1167 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001168 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001169 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001170 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001171
Joe Onorato4be866d2010-10-10 11:26:02 -07001172 int left = topLeft[0];
1173 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001174
Winson Chungb8c69f32011-10-19 21:36:08 -07001175 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001176 // When drawing the drag outline, it did not account for margin offsets
1177 // added by the view's parent.
1178 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1179 left += lp.leftMargin;
1180 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001181
Adam Cohen99e8b402011-03-25 19:23:43 -07001182 // Offsets due to the size difference between the View and the dragOutline.
1183 // There is a size difference to account for the outer blur, which may lie
1184 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001185 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001186 // We center about the x axis
1187 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1188 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001189 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001190 if (dragOffset != null && dragRegion != null) {
1191 // Center the drag region *horizontally* in the cell and apply a drag
1192 // outline offset
1193 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1194 - dragRegion.width()) / 2;
1195 top += dragOffset.y;
1196 } else {
1197 // Center the drag outline in the cell
1198 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1199 - dragOutline.getWidth()) / 2;
1200 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1201 - dragOutline.getHeight()) / 2;
1202 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001203 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001204 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001205 mDragOutlineAnims[oldIndex].animateOut();
1206 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001207 Rect r = mDragOutlines[mDragOutlineCurrent];
1208 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1209 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001210 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001211 }
Winson Chung150fbab2010-09-29 17:14:26 -07001212
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001213 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1214 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001215 }
1216 }
1217
Adam Cohene0310962011-04-18 16:15:31 -07001218 public void clearDragOutlines() {
1219 final int oldIndex = mDragOutlineCurrent;
1220 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001221 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001222 }
1223
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001224 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001225 * Find a vacant area that will fit the given bounds nearest the requested
1226 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001227 *
Romain Guy51afc022009-05-04 18:03:43 -07001228 * @param pixelX The X location at which you want to search for a vacant area.
1229 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001230 * @param spanX Horizontal span of the object.
1231 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001232 * @param result Array in which to place the result, or null (in which case a new array will
1233 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001234 * @return The X, Y cell of a vacant area that can contain this object,
1235 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001236 */
Adam Cohend41fbf52012-02-16 23:53:59 -08001237 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
1238 int[] result) {
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001239 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001240 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001241
Michael Jurka6a1435d2010-09-27 17:35:12 -07001242 /**
1243 * Find a vacant area that will fit the given bounds nearest the requested
1244 * cell location. Uses Euclidean distance to score multiple vacant areas.
1245 *
1246 * @param pixelX The X location at which you want to search for a vacant area.
1247 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001248 * @param minSpanX The minimum horizontal span required
1249 * @param minSpanY The minimum vertical span required
1250 * @param spanX Horizontal span of the object.
1251 * @param spanY Vertical span of the object.
1252 * @param result Array in which to place the result, or null (in which case a new array will
1253 * be allocated)
1254 * @return The X, Y cell of a vacant area that can contain this object,
1255 * nearest the requested location.
1256 */
1257 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1258 int spanY, int[] result, int[] resultSpan) {
1259 return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
1260 result, resultSpan);
1261 }
1262
1263 /**
1264 * Find a vacant area that will fit the given bounds nearest the requested
1265 * cell location. Uses Euclidean distance to score multiple vacant areas.
1266 *
1267 * @param pixelX The X location at which you want to search for a vacant area.
1268 * @param pixelY The Y location at which you want to search for a vacant area.
Michael Jurka6a1435d2010-09-27 17:35:12 -07001269 * @param spanX Horizontal span of the object.
1270 * @param spanY Vertical span of the object.
Adam Cohendf035382011-04-11 17:22:04 -07001271 * @param ignoreOccupied If true, the result can be an occupied cell
1272 * @param result Array in which to place the result, or null (in which case a new array will
1273 * be allocated)
Michael Jurka6a1435d2010-09-27 17:35:12 -07001274 * @return The X, Y cell of a vacant area that can contain this object,
1275 * nearest the requested location.
1276 */
Adam Cohendf035382011-04-11 17:22:04 -07001277 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
1278 boolean ignoreOccupied, int[] result) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001279 return findNearestArea(pixelX, pixelY, spanX, spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001280 spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08001281 }
1282
1283 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1284 private void lazyInitTempRectStack() {
1285 if (mTempRectStack.isEmpty()) {
1286 for (int i = 0; i < mCountX * mCountY; i++) {
1287 mTempRectStack.push(new Rect());
1288 }
1289 }
1290 }
Adam Cohen482ed822012-03-02 14:15:13 -08001291
Adam Cohend41fbf52012-02-16 23:53:59 -08001292 private void recycleTempRects(Stack<Rect> used) {
1293 while (!used.isEmpty()) {
1294 mTempRectStack.push(used.pop());
1295 }
1296 }
1297
1298 /**
1299 * Find a vacant area that will fit the given bounds nearest the requested
1300 * cell location. Uses Euclidean distance to score multiple vacant areas.
1301 *
1302 * @param pixelX The X location at which you want to search for a vacant area.
1303 * @param pixelY The Y location at which you want to search for a vacant area.
1304 * @param minSpanX The minimum horizontal span required
1305 * @param minSpanY The minimum vertical span required
1306 * @param spanX Horizontal span of the object.
1307 * @param spanY Vertical span of the object.
1308 * @param ignoreOccupied If true, the result can be an occupied cell
1309 * @param result Array in which to place the result, or null (in which case a new array will
1310 * be allocated)
1311 * @return The X, Y cell of a vacant area that can contain this object,
1312 * nearest the requested location.
1313 */
1314 int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001315 View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
1316 boolean[][] occupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001317 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001318 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08001319 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001320
Adam Cohene3e27a82011-04-15 12:07:39 -07001321 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1322 // to the center of the item, but we are searching based on the top-left cell, so
1323 // we translate the point over to correspond to the top-left.
1324 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1325 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1326
Jeff Sharkey70864282009-04-07 21:08:40 -07001327 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001328 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001329 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001330 final Rect bestRect = new Rect(-1, -1, -1, -1);
1331 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001332
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001333 final int countX = mCountX;
1334 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001335
Adam Cohend41fbf52012-02-16 23:53:59 -08001336 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1337 spanX < minSpanX || spanY < minSpanY) {
1338 return bestXY;
1339 }
1340
1341 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001342 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001343 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1344 int ySize = -1;
1345 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001346 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001347 // First, let's see if this thing fits anywhere
1348 for (int i = 0; i < minSpanX; i++) {
1349 for (int j = 0; j < minSpanY; j++) {
Adam Cohendf035382011-04-11 17:22:04 -07001350 if (occupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001351 continue inner;
1352 }
Michael Jurkac28de512010-08-13 11:27:44 -07001353 }
1354 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001355 xSize = minSpanX;
1356 ySize = minSpanY;
1357
1358 // We know that the item will fit at _some_ acceptable size, now let's see
1359 // how big we can make it. We'll alternate between incrementing x and y spans
1360 // until we hit a limit.
1361 boolean incX = true;
1362 boolean hitMaxX = xSize >= spanX;
1363 boolean hitMaxY = ySize >= spanY;
1364 while (!(hitMaxX && hitMaxY)) {
1365 if (incX && !hitMaxX) {
1366 for (int j = 0; j < ySize; j++) {
1367 if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
1368 // We can't move out horizontally
1369 hitMaxX = true;
1370 }
1371 }
1372 if (!hitMaxX) {
1373 xSize++;
1374 }
1375 } else if (!hitMaxY) {
1376 for (int i = 0; i < xSize; i++) {
1377 if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
1378 // We can't move out vertically
1379 hitMaxY = true;
1380 }
1381 }
1382 if (!hitMaxY) {
1383 ySize++;
1384 }
1385 }
1386 hitMaxX |= xSize >= spanX;
1387 hitMaxY |= ySize >= spanY;
1388 incX = !incX;
1389 }
1390 incX = true;
1391 hitMaxX = xSize >= spanX;
1392 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001393 }
Winson Chung0be025d2011-05-23 17:45:09 -07001394 final int[] cellXY = mTmpXY;
Adam Cohene3e27a82011-04-15 12:07:39 -07001395 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001396
Adam Cohend41fbf52012-02-16 23:53:59 -08001397 // We verify that the current rect is not a sub-rect of any of our previous
1398 // candidates. In this case, the current rect is disqualified in favour of the
1399 // containing rect.
1400 Rect currentRect = mTempRectStack.pop();
1401 currentRect.set(x, y, x + xSize, y + ySize);
1402 boolean contained = false;
1403 for (Rect r : validRegions) {
1404 if (r.contains(currentRect)) {
1405 contained = true;
1406 break;
1407 }
1408 }
1409 validRegions.push(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001410 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
1411 + Math.pow(cellXY[1] - pixelY, 2));
Adam Cohen482ed822012-03-02 14:15:13 -08001412
Adam Cohend41fbf52012-02-16 23:53:59 -08001413 if ((distance <= bestDistance && !contained) ||
1414 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001415 bestDistance = distance;
1416 bestXY[0] = x;
1417 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001418 if (resultSpan != null) {
1419 resultSpan[0] = xSize;
1420 resultSpan[1] = ySize;
1421 }
1422 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001423 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001424 }
1425 }
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001426 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08001427 markCellsAsOccupiedForView(ignoreView, occupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001428
Adam Cohenc0dcf592011-06-01 15:30:43 -07001429 // Return -1, -1 if no suitable location found
1430 if (bestDistance == Double.MAX_VALUE) {
1431 bestXY[0] = -1;
1432 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001433 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001434 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001435 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001436 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001437
Adam Cohen482ed822012-03-02 14:15:13 -08001438 /**
1439 * Find a vacant area that will fit the given bounds nearest the requested
1440 * cell location, and will also weigh in a suggested direction vector of the
1441 * desired location. This method computers distance based on unit grid distances,
1442 * not pixel distances.
1443 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001444 * @param cellX The X cell nearest to which you want to search for a vacant area.
1445 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001446 * @param spanX Horizontal span of the object.
1447 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001448 * @param direction The favored direction in which the views should move from x, y
1449 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1450 * matches exactly. Otherwise we find the best matching direction.
1451 * @param occoupied The array which represents which cells in the CellLayout are occupied
1452 * @param blockOccupied The array which represents which cells in the specified block (cellX,
1453 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001454 * @param result Array in which to place the result, or null (in which case a new array will
1455 * be allocated)
1456 * @return The X, Y cell of a vacant area that can contain this object,
1457 * nearest the requested location.
1458 */
1459 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001460 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001461 // Keep track of best-scoring drop area
1462 final int[] bestXY = result != null ? result : new int[2];
1463 float bestDistance = Float.MAX_VALUE;
1464 int bestDirectionScore = Integer.MIN_VALUE;
1465
1466 final int countX = mCountX;
1467 final int countY = mCountY;
1468
1469 for (int y = 0; y < countY - (spanY - 1); y++) {
1470 inner:
1471 for (int x = 0; x < countX - (spanX - 1); x++) {
1472 // First, let's see if this thing fits anywhere
1473 for (int i = 0; i < spanX; i++) {
1474 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001475 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001476 continue inner;
1477 }
1478 }
1479 }
1480
1481 float distance = (float)
1482 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1483 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001484 computeDirectionVector(x - cellX, y - cellY, curDirection);
1485 // The direction score is just the dot product of the two candidate direction
1486 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001487 int curDirectionScore = direction[0] * curDirection[0] +
1488 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001489 boolean exactDirectionOnly = false;
1490 boolean directionMatches = direction[0] == curDirection[0] &&
1491 direction[0] == curDirection[0];
1492 if ((directionMatches || !exactDirectionOnly) &&
1493 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001494 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1495 bestDistance = distance;
1496 bestDirectionScore = curDirectionScore;
1497 bestXY[0] = x;
1498 bestXY[1] = y;
1499 }
1500 }
1501 }
1502
1503 // Return -1, -1 if no suitable location found
1504 if (bestDistance == Float.MAX_VALUE) {
1505 bestXY[0] = -1;
1506 bestXY[1] = -1;
1507 }
1508 return bestXY;
1509 }
1510
Adam Cohen47a876d2012-03-19 13:21:41 -07001511 private int[] findNearestAreaInDirection(int cellX, int cellY, int spanX, int spanY,
1512 int[] direction,boolean[][] occupied,
1513 boolean blockOccupied[][], int[] result) {
1514 // Keep track of best-scoring drop area
1515 final int[] bestXY = result != null ? result : new int[2];
1516 bestXY[0] = -1;
1517 bestXY[1] = -1;
1518 float bestDistance = Float.MAX_VALUE;
1519
1520 // We use this to march in a single direction
Adam Cohen5b53f292012-03-29 14:30:35 -07001521 if ((direction[0] != 0 && direction[1] != 0) ||
1522 (direction[0] == 0 && direction[1] == 0)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001523 return bestXY;
1524 }
1525
1526 // This will only incrememnet one of x or y based on the assertion above
1527 int x = cellX + direction[0];
1528 int y = cellY + direction[1];
1529 while (x >= 0 && x + spanX <= mCountX && y >= 0 && y + spanY <= mCountY) {
1530
1531 boolean fail = false;
1532 for (int i = 0; i < spanX; i++) {
1533 for (int j = 0; j < spanY; j++) {
1534 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
1535 fail = true;
1536 }
1537 }
1538 }
1539 if (!fail) {
1540 float distance = (float)
1541 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1542 if (Float.compare(distance, bestDistance) < 0) {
1543 bestDistance = distance;
1544 bestXY[0] = x;
1545 bestXY[1] = y;
1546 }
1547 }
1548 x += direction[0];
1549 y += direction[1];
1550 }
1551 return bestXY;
1552 }
1553
Adam Cohen482ed822012-03-02 14:15:13 -08001554 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001555 int[] direction, ItemConfiguration currentState) {
1556 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001557 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001558 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001559 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1560
Adam Cohen8baab352012-03-20 17:39:21 -07001561 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001562
1563 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001564 c.x = mTempLocation[0];
1565 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001566 success = true;
1567
1568 }
Adam Cohen8baab352012-03-20 17:39:21 -07001569 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001570 return success;
1571 }
1572
Adam Cohen47a876d2012-03-19 13:21:41 -07001573 // This method looks in the specified direction to see if there is an additional view
1574 // immediately adjecent in that direction
1575 private boolean addViewInDirection(ArrayList<View> views, Rect boundingRect, int[] direction,
Adam Cohen19f37922012-03-21 11:59:11 -07001576 boolean[][] occupied, View dragView, ItemConfiguration currentState) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001577 boolean found = false;
1578
Michael Jurkaa52570f2012-03-20 03:18:20 -07001579 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen47a876d2012-03-19 13:21:41 -07001580 Rect r0 = new Rect(boundingRect);
1581 Rect r1 = new Rect();
1582
1583 int deltaX = 0;
1584 int deltaY = 0;
1585 if (direction[1] < 0) {
1586 r0.set(r0.left, r0.top - 1, r0.right, r0.bottom);
1587 deltaY = -1;
1588 } else if (direction[1] > 0) {
1589 r0.set(r0.left, r0.top, r0.right, r0.bottom + 1);
1590 deltaY = 1;
1591 } else if (direction[0] < 0) {
1592 r0.set(r0.left - 1, r0.top, r0.right, r0.bottom);
1593 deltaX = -1;
1594 } else if (direction[0] > 0) {
1595 r0.set(r0.left, r0.top, r0.right + 1, r0.bottom);
1596 deltaX = 1;
1597 }
1598
1599 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001600 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen19f37922012-03-21 11:59:11 -07001601 if (views.contains(child) || child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001602 CellAndSpan c = currentState.map.get(child);
Adam Cohen47a876d2012-03-19 13:21:41 -07001603
Adam Cohen8baab352012-03-20 17:39:21 -07001604 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1605 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen47a876d2012-03-19 13:21:41 -07001606 if (Rect.intersects(r0, r1)) {
1607 if (!lp.canReorder) {
1608 return false;
1609 }
1610 boolean pushed = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001611 for (int x = c.x; x < c.x + c.spanX; x++) {
1612 for (int y = c.y; y < c.y + c.spanY; y++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001613 boolean inBounds = x - deltaX >= 0 && x -deltaX < mCountX
1614 && y - deltaY >= 0 && y - deltaY < mCountY;
1615 if (inBounds && occupied[x - deltaX][y - deltaY]) {
1616 pushed = true;
1617 }
1618 }
1619 }
1620 if (pushed) {
1621 views.add(child);
Adam Cohen8baab352012-03-20 17:39:21 -07001622 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen47a876d2012-03-19 13:21:41 -07001623 found = true;
1624 }
1625 }
1626 }
1627 return found;
1628 }
1629
Adam Cohen482ed822012-03-02 14:15:13 -08001630 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohen19f37922012-03-21 11:59:11 -07001631 int[] direction, boolean push, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001632 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001633
Adam Cohen8baab352012-03-20 17:39:21 -07001634 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001635 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001636 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001637 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001638 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001639 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001640 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001641 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001642 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001643 }
1644 }
Adam Cohen8baab352012-03-20 17:39:21 -07001645
1646 @SuppressWarnings("unchecked")
1647 ArrayList<View> dup = (ArrayList<View>) views.clone();
1648 // We try and expand the group of views in the direction vector passed, based on
1649 // whether they are physically adjacent, ie. based on "push mechanics".
Adam Cohen19f37922012-03-21 11:59:11 -07001650 while (push && addViewInDirection(dup, boundingRect, direction, mTmpOccupied, dragView,
Adam Cohen8baab352012-03-20 17:39:21 -07001651 currentState)) {
1652 }
1653
1654 // Mark the occupied state as false for the group of views we want to move.
1655 for (View v: dup) {
1656 CellAndSpan c = currentState.map.get(v);
1657 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1658 }
1659
Adam Cohen47a876d2012-03-19 13:21:41 -07001660 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1661 int top = boundingRect.top;
1662 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001663 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
1664 // for tetris-style interlocking.
1665 for (View v: dup) {
1666 CellAndSpan c = currentState.map.get(v);
1667 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001668 }
1669
Adam Cohen482ed822012-03-02 14:15:13 -08001670 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1671
Adam Cohen8baab352012-03-20 17:39:21 -07001672 if (push) {
1673 findNearestAreaInDirection(boundingRect.left, boundingRect.top, boundingRect.width(),
1674 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
1675 } else {
1676 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1677 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
1678 }
Adam Cohen482ed822012-03-02 14:15:13 -08001679
Adam Cohen8baab352012-03-20 17:39:21 -07001680 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001681 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001682 int deltaX = mTempLocation[0] - boundingRect.left;
1683 int deltaY = mTempLocation[1] - boundingRect.top;
1684 for (View v: dup) {
1685 CellAndSpan c = currentState.map.get(v);
1686 c.x += deltaX;
1687 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001688 }
1689 success = true;
1690 }
Adam Cohen8baab352012-03-20 17:39:21 -07001691
1692 // In either case, we set the occupied array as marked for the location of the views
1693 for (View v: dup) {
1694 CellAndSpan c = currentState.map.get(v);
1695 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001696 }
1697 return success;
1698 }
1699
1700 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1701 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1702 }
1703
1704 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001705 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07001706 // Return early if get invalid cell positions
1707 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08001708
Adam Cohen8baab352012-03-20 17:39:21 -07001709 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001710 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001711
Adam Cohen8baab352012-03-20 17:39:21 -07001712 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001713 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001714 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001715 if (c != null) {
1716 c.x = cellX;
1717 c.y = cellY;
1718 }
Adam Cohen482ed822012-03-02 14:15:13 -08001719 }
Adam Cohen482ed822012-03-02 14:15:13 -08001720 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1721 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001722 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001723 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001724 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001725 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001726 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001727 if (Rect.intersects(r0, r1)) {
1728 if (!lp.canReorder) {
1729 return false;
1730 }
1731 mIntersectingViews.add(child);
1732 }
1733 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001734
Adam Cohen8baab352012-03-20 17:39:21 -07001735 // We try to move the intersecting views as a block using the push mechanic
Adam Cohen19f37922012-03-21 11:59:11 -07001736 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, true, ignoreView,
1737 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001738 return true;
1739 }
1740 // Try the opposite direction
1741 direction[0] *= -1;
1742 direction[1] *= -1;
Adam Cohen19f37922012-03-21 11:59:11 -07001743 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, true, ignoreView,
1744 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001745 return true;
1746 }
1747 // Switch the direction back
1748 direction[0] *= -1;
1749 direction[1] *= -1;
1750
Adam Cohen8baab352012-03-20 17:39:21 -07001751 // Next we try moving the views as a block , but without requiring the push mechanic
Adam Cohen19f37922012-03-21 11:59:11 -07001752 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, false, ignoreView,
1753 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001754 return true;
1755 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001756
Adam Cohen482ed822012-03-02 14:15:13 -08001757 // Ok, they couldn't move as a block, let's move them individually
1758 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001759 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001760 return false;
1761 }
1762 }
1763 return true;
1764 }
1765
1766 /*
1767 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1768 * the provided point and the provided cell
1769 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001770 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001771 double angle = Math.atan(((float) deltaY) / deltaX);
1772
1773 result[0] = 0;
1774 result[1] = 0;
1775 if (Math.abs(Math.cos(angle)) > 0.5f) {
1776 result[0] = (int) Math.signum(deltaX);
1777 }
1778 if (Math.abs(Math.sin(angle)) > 0.5f) {
1779 result[1] = (int) Math.signum(deltaY);
1780 }
1781 }
1782
Adam Cohen8baab352012-03-20 17:39:21 -07001783 private void copyOccupiedArray(boolean[][] occupied) {
1784 for (int i = 0; i < mCountX; i++) {
1785 for (int j = 0; j < mCountY; j++) {
1786 occupied[i][j] = mOccupied[i][j];
1787 }
1788 }
1789 }
1790
Adam Cohen482ed822012-03-02 14:15:13 -08001791 ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1792 int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001793 // Copy the current state into the solution. This solution will be manipulated as necessary.
1794 copyCurrentStateToSolution(solution, false);
1795 // Copy the current occupied array into the temporary occupied array. This array will be
1796 // manipulated as necessary to find a solution.
1797 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001798
1799 // We find the nearest cell into which we would place the dragged item, assuming there's
1800 // nothing in its way.
1801 int result[] = new int[2];
1802 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1803
1804 boolean success = false;
1805 // First we try the exact nearest position of the item being dragged,
1806 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001807 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1808 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001809
1810 if (!success) {
1811 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1812 // x, then 1 in y etc.
1813 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
1814 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
1815 dragView, false, solution);
1816 } else if (spanY > minSpanY) {
1817 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
1818 dragView, true, solution);
1819 }
1820 solution.isSolution = false;
1821 } else {
1822 solution.isSolution = true;
1823 solution.dragViewX = result[0];
1824 solution.dragViewY = result[1];
1825 solution.dragViewSpanX = spanX;
1826 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08001827 }
1828 return solution;
1829 }
1830
1831 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001832 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001833 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001834 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001835 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001836 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08001837 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07001838 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001839 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001840 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001841 }
Adam Cohen8baab352012-03-20 17:39:21 -07001842 solution.map.put(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08001843 }
1844 }
1845
1846 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
1847 for (int i = 0; i < mCountX; i++) {
1848 for (int j = 0; j < mCountY; j++) {
1849 mTmpOccupied[i][j] = false;
1850 }
1851 }
1852
Michael Jurkaa52570f2012-03-20 03:18:20 -07001853 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001854 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001855 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001856 if (child == dragView) continue;
1857 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001858 CellAndSpan c = solution.map.get(child);
1859 if (c != null) {
1860 lp.tmpCellX = c.x;
1861 lp.tmpCellY = c.y;
1862 lp.cellHSpan = c.spanX;
1863 lp.cellVSpan = c.spanY;
1864 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001865 }
1866 }
1867 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
1868 solution.dragViewSpanY, mTmpOccupied, true);
1869 }
1870
1871 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
1872 commitDragView) {
1873
1874 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
1875 for (int i = 0; i < mCountX; i++) {
1876 for (int j = 0; j < mCountY; j++) {
1877 occupied[i][j] = false;
1878 }
1879 }
1880
Michael Jurkaa52570f2012-03-20 03:18:20 -07001881 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001882 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001883 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001884 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001885 CellAndSpan c = solution.map.get(child);
1886 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07001887 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
1888 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07001889 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001890 }
1891 }
1892 if (commitDragView) {
1893 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
1894 solution.dragViewSpanY, occupied, true);
1895 }
1896 }
1897
Adam Cohen19f37922012-03-21 11:59:11 -07001898 // This method starts or changes the reorder hint animations
1899 private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
1900 int childCount = mShortcutsAndWidgets.getChildCount();
1901 int timeForPriorAnimationToComplete = getMaxCompletionTime();
1902 for (int i = 0; i < childCount; i++) {
1903 View child = mShortcutsAndWidgets.getChildAt(i);
1904 if (child == dragView) continue;
1905 CellAndSpan c = solution.map.get(child);
1906 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1907 if (c != null) {
1908 ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
1909 c.x, c.y, c.spanX, c.spanY);
1910 rha.animate(timeForPriorAnimationToComplete);
1911 }
1912 }
1913 }
1914
1915 // Class which represents the reorder hint animations. These animations show that an item is
1916 // in a temporary state, and hint at where the item will return to.
1917 class ReorderHintAnimation {
1918 View child;
1919 float deltaX;
1920 float deltaY;
1921 private static final int DURATION = 140;
1922 private int repeatCount;
1923 private boolean cancelOnCycleComplete = false;
1924 ValueAnimator va;
1925
1926 public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
1927 int spanX, int spanY) {
1928 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
1929 final int x0 = mTmpPoint[0];
1930 final int y0 = mTmpPoint[1];
1931 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
1932 final int x1 = mTmpPoint[0];
1933 final int y1 = mTmpPoint[1];
1934 final int dX = x1 - x0;
1935 final int dY = y1 - y0;
1936 deltaX = 0;
1937 deltaY = 0;
1938 if (dX == dY && dX == 0) {
1939 } else {
1940 if (dY == 0) {
1941 deltaX = mReorderHintAnimationMagnitude;
1942 } else if (dX == 0) {
1943 deltaY = mReorderHintAnimationMagnitude;
1944 } else {
1945 double angle = Math.atan( (float) (dY) / dX);
1946 deltaX = (int) (Math.cos(angle) * mReorderHintAnimationMagnitude);
1947 deltaY = (int) (Math.sin(angle) * mReorderHintAnimationMagnitude);
1948 }
1949 }
1950 this.child = child;
1951 }
1952
1953 void animate(int delay) {
1954 if (mShakeAnimators.containsKey(child)) {
1955 ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
1956 oldAnimation.completeAnimation();
1957 mShakeAnimators.remove(child);
1958 }
1959 if (deltaX == 0 && deltaY == 0) {
1960 return;
1961 }
1962 va = ValueAnimator.ofFloat(0f, 1f);
1963 va.setRepeatMode(ValueAnimator.REVERSE);
1964 va.setRepeatCount(ValueAnimator.INFINITE);
1965 va.setDuration(DURATION);
1966 va.addUpdateListener(new AnimatorUpdateListener() {
1967 @Override
1968 public void onAnimationUpdate(ValueAnimator animation) {
1969 float r = ((Float) animation.getAnimatedValue()).floatValue();
1970 float x = r * deltaX;
1971 float y = r * deltaY;
1972 child.setTranslationX(x);
1973 child.setTranslationY(y);
1974 }
1975 });
1976 va.addListener(new AnimatorListenerAdapter() {
1977 public void onAnimationRepeat(Animator animation) {
1978 repeatCount++;
1979 // We make sure to end only after a full period
1980 if (cancelOnCycleComplete && repeatCount % 2 == 0) {
1981 va.cancel();
1982 }
1983 }
1984 });
1985 va.setStartDelay(Math.max(REORDER_ANIMATION_DURATION, delay));
1986 mShakeAnimators.put(child, this);
1987 va.start();
1988 }
1989
1990
1991 private void completeAnimation() {
1992 cancelOnCycleComplete = true;
1993 }
1994
1995 // Returns the time required to complete the current oscillating animation
1996 private int completionTime() {
1997 if (repeatCount % 2 == 0) {
1998 return (int) (va.getDuration() - va.getCurrentPlayTime() + DURATION);
1999 } else {
2000 return (int) (va.getDuration() - va.getCurrentPlayTime());
2001 }
2002 }
2003 }
2004
2005 private void completeAndClearReorderHintAnimations() {
2006 for (ReorderHintAnimation a: mShakeAnimators.values()) {
2007 a.completeAnimation();
2008 }
2009 mShakeAnimators.clear();
2010 }
2011
2012 private int getMaxCompletionTime() {
2013 int maxTime = 0;
2014 for (ReorderHintAnimation a: mShakeAnimators.values()) {
2015 maxTime = Math.max(maxTime, a.completionTime());
2016 }
2017 return maxTime;
2018 }
2019
Adam Cohen482ed822012-03-02 14:15:13 -08002020 private void commitTempPlacement() {
2021 for (int i = 0; i < mCountX; i++) {
2022 for (int j = 0; j < mCountY; j++) {
2023 mOccupied[i][j] = mTmpOccupied[i][j];
2024 }
2025 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002026 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002027 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002028 View child = mShortcutsAndWidgets.getChildAt(i);
2029 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2030 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002031 // We do a null check here because the item info can be null in the case of the
2032 // AllApps button in the hotseat.
2033 if (info != null) {
2034 info.cellX = lp.cellX = lp.tmpCellX;
2035 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002036 info.spanX = lp.cellHSpan;
2037 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002038 }
Adam Cohen482ed822012-03-02 14:15:13 -08002039 }
Adam Cohen2acce882012-03-28 19:03:19 -07002040 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002041 }
2042
2043 public void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002044 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002045 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002046 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002047 lp.useTmpCoords = useTempCoords;
2048 }
2049 }
2050
Adam Cohen482ed822012-03-02 14:15:13 -08002051 ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
2052 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2053 int[] result = new int[2];
2054 int[] resultSpan = new int[2];
2055 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
2056 resultSpan);
2057 if (result[0] >= 0 && result[1] >= 0) {
2058 copyCurrentStateToSolution(solution, false);
2059 solution.dragViewX = result[0];
2060 solution.dragViewY = result[1];
2061 solution.dragViewSpanX = resultSpan[0];
2062 solution.dragViewSpanY = resultSpan[1];
2063 solution.isSolution = true;
2064 } else {
2065 solution.isSolution = false;
2066 }
2067 return solution;
2068 }
2069
2070 public void prepareChildForDrag(View child) {
2071 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002072 }
2073
Adam Cohen19f37922012-03-21 11:59:11 -07002074 /* This seems like it should be obvious and straight-forward, but when the direction vector
2075 needs to match with the notion of the dragView pushing other views, we have to employ
2076 a slightly more subtle notion of the direction vector. The question is what two points is
2077 the vector between? The center of the dragView and its desired destination? Not quite, as
2078 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2079 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2080 or right, which helps make pushing feel right.
2081 */
2082 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2083 int spanY, View dragView, int[] resultDirection) {
2084 int[] targetDestination = new int[2];
2085
2086 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2087 Rect dragRect = new Rect();
2088 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2089 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2090
2091 Rect dropRegionRect = new Rect();
2092 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2093 dragView, dropRegionRect, mIntersectingViews);
2094
2095 int dropRegionSpanX = dropRegionRect.width();
2096 int dropRegionSpanY = dropRegionRect.height();
2097
2098 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2099 dropRegionRect.height(), dropRegionRect);
2100
2101 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2102 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2103
2104 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2105 deltaX = 0;
2106 }
2107 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2108 deltaY = 0;
2109 }
2110
2111 if (deltaX == 0 && deltaY == 0) {
2112 // No idea what to do, give a random direction.
2113 resultDirection[0] = 1;
2114 resultDirection[1] = 0;
2115 } else {
2116 computeDirectionVector(deltaX, deltaY, resultDirection);
2117 }
2118 }
2119
2120 // For a given cell and span, fetch the set of views intersecting the region.
2121 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2122 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2123 if (boundingRect != null) {
2124 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2125 }
2126 intersectingViews.clear();
2127 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2128 Rect r1 = new Rect();
2129 final int count = mShortcutsAndWidgets.getChildCount();
2130 for (int i = 0; i < count; i++) {
2131 View child = mShortcutsAndWidgets.getChildAt(i);
2132 if (child == dragView) continue;
2133 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2134 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2135 if (Rect.intersects(r0, r1)) {
2136 mIntersectingViews.add(child);
2137 if (boundingRect != null) {
2138 boundingRect.union(r1);
2139 }
2140 }
2141 }
2142 }
2143
2144 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2145 View dragView, int[] result) {
2146 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2147 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2148 mIntersectingViews);
2149 return !mIntersectingViews.isEmpty();
2150 }
2151
2152 void revertTempState() {
2153 if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
2154 final int count = mShortcutsAndWidgets.getChildCount();
2155 for (int i = 0; i < count; i++) {
2156 View child = mShortcutsAndWidgets.getChildAt(i);
2157 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2158 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2159 lp.tmpCellX = lp.cellX;
2160 lp.tmpCellY = lp.cellY;
2161 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2162 0, false, false);
2163 }
2164 }
2165 completeAndClearReorderHintAnimations();
2166 setItemPlacementDirty(false);
2167 }
2168
Adam Cohenbebf0422012-04-11 18:06:28 -07002169 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2170 View dragView, int[] direction, boolean commit) {
2171 int[] pixelXY = new int[2];
2172 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2173
2174 // First we determine if things have moved enough to cause a different layout
2175 ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY,
2176 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2177
2178 setUseTempCoords(true);
2179 if (swapSolution != null && swapSolution.isSolution) {
2180 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2181 // committing anything or animating anything as we just want to determine if a solution
2182 // exists
2183 copySolutionToTempState(swapSolution, dragView);
2184 setItemPlacementDirty(true);
2185 animateItemsToSolution(swapSolution, dragView, commit);
2186
2187 if (commit) {
2188 commitTempPlacement();
2189 completeAndClearReorderHintAnimations();
2190 setItemPlacementDirty(false);
2191 } else {
2192 beginOrAdjustHintAnimations(swapSolution, dragView,
2193 REORDER_ANIMATION_DURATION);
2194 }
2195 mShortcutsAndWidgets.requestLayout();
2196 }
2197 return swapSolution.isSolution;
2198 }
2199
Adam Cohen482ed822012-03-02 14:15:13 -08002200 int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
2201 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002202 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002203 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002204
2205 if (resultSpan == null) {
2206 resultSpan = new int[2];
2207 }
2208
Adam Cohen19f37922012-03-21 11:59:11 -07002209 // When we are checking drop validity or actually dropping, we don't recompute the
2210 // direction vector, since we want the solution to match the preview, and it's possible
2211 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002212 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2213 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002214 mDirectionVector[0] = mPreviousReorderDirection[0];
2215 mDirectionVector[1] = mPreviousReorderDirection[1];
2216 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002217 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2218 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2219 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002220 }
2221 } else {
2222 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2223 mPreviousReorderDirection[0] = mDirectionVector[0];
2224 mPreviousReorderDirection[1] = mDirectionVector[1];
2225 }
2226
Adam Cohen482ed822012-03-02 14:15:13 -08002227 ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
2228 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2229
2230 // We attempt the approach which doesn't shuffle views at all
2231 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2232 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2233
2234 ItemConfiguration finalSolution = null;
2235 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2236 finalSolution = swapSolution;
2237 } else if (noShuffleSolution.isSolution) {
2238 finalSolution = noShuffleSolution;
2239 }
2240
2241 boolean foundSolution = true;
2242 if (!DESTRUCTIVE_REORDER) {
2243 setUseTempCoords(true);
2244 }
2245
2246 if (finalSolution != null) {
2247 result[0] = finalSolution.dragViewX;
2248 result[1] = finalSolution.dragViewY;
2249 resultSpan[0] = finalSolution.dragViewSpanX;
2250 resultSpan[1] = finalSolution.dragViewSpanY;
2251
2252 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2253 // committing anything or animating anything as we just want to determine if a solution
2254 // exists
2255 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2256 if (!DESTRUCTIVE_REORDER) {
2257 copySolutionToTempState(finalSolution, dragView);
2258 }
2259 setItemPlacementDirty(true);
2260 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2261
Adam Cohen19f37922012-03-21 11:59:11 -07002262 if (!DESTRUCTIVE_REORDER &&
2263 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002264 commitTempPlacement();
Adam Cohen19f37922012-03-21 11:59:11 -07002265 completeAndClearReorderHintAnimations();
2266 setItemPlacementDirty(false);
2267 } else {
2268 beginOrAdjustHintAnimations(finalSolution, dragView,
2269 REORDER_ANIMATION_DURATION);
Adam Cohen482ed822012-03-02 14:15:13 -08002270 }
2271 }
2272 } else {
2273 foundSolution = false;
2274 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2275 }
2276
2277 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2278 setUseTempCoords(false);
2279 }
Adam Cohen482ed822012-03-02 14:15:13 -08002280
Michael Jurkaa52570f2012-03-20 03:18:20 -07002281 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002282 return result;
2283 }
2284
Adam Cohen19f37922012-03-21 11:59:11 -07002285 void setItemPlacementDirty(boolean dirty) {
2286 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002287 }
Adam Cohen19f37922012-03-21 11:59:11 -07002288 boolean isItemPlacementDirty() {
2289 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002290 }
2291
2292 private class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002293 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohen482ed822012-03-02 14:15:13 -08002294 boolean isSolution = false;
2295 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2296
2297 int area() {
2298 return dragViewSpanX * dragViewSpanY;
2299 }
Adam Cohen8baab352012-03-20 17:39:21 -07002300 }
2301
2302 private class CellAndSpan {
2303 int x, y;
2304 int spanX, spanY;
2305
2306 public CellAndSpan(int x, int y, int spanX, int spanY) {
2307 this.x = x;
2308 this.y = y;
2309 this.spanX = spanX;
2310 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002311 }
2312 }
2313
Adam Cohendf035382011-04-11 17:22:04 -07002314 /**
2315 * Find a vacant area that will fit the given bounds nearest the requested
2316 * cell location. Uses Euclidean distance to score multiple vacant areas.
2317 *
2318 * @param pixelX The X location at which you want to search for a vacant area.
2319 * @param pixelY The Y location at which you want to search for a vacant area.
2320 * @param spanX Horizontal span of the object.
2321 * @param spanY Vertical span of the object.
2322 * @param ignoreView Considers space occupied by this view as unoccupied
2323 * @param result Previously returned value to possibly recycle.
2324 * @return The X, Y cell of a vacant area that can contain this object,
2325 * nearest the requested location.
2326 */
2327 int[] findNearestVacantArea(
2328 int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
2329 return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
2330 }
2331
2332 /**
Adam Cohend41fbf52012-02-16 23:53:59 -08002333 * Find a vacant area that will fit the given bounds nearest the requested
2334 * cell location. Uses Euclidean distance to score multiple vacant areas.
2335 *
2336 * @param pixelX The X location at which you want to search for a vacant area.
2337 * @param pixelY The Y location at which you want to search for a vacant area.
2338 * @param minSpanX The minimum horizontal span required
2339 * @param minSpanY The minimum vertical span required
2340 * @param spanX Horizontal span of the object.
2341 * @param spanY Vertical span of the object.
2342 * @param ignoreView Considers space occupied by this view as unoccupied
2343 * @param result Previously returned value to possibly recycle.
2344 * @return The X, Y cell of a vacant area that can contain this object,
2345 * nearest the requested location.
2346 */
2347 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
2348 int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
Adam Cohen482ed822012-03-02 14:15:13 -08002349 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
2350 result, resultSpan, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08002351 }
2352
2353 /**
Adam Cohendf035382011-04-11 17:22:04 -07002354 * Find a starting cell position that will fit the given bounds nearest the requested
2355 * cell location. Uses Euclidean distance to score multiple vacant areas.
2356 *
2357 * @param pixelX The X location at which you want to search for a vacant area.
2358 * @param pixelY The Y location at which you want to search for a vacant area.
2359 * @param spanX Horizontal span of the object.
2360 * @param spanY Vertical span of the object.
2361 * @param ignoreView Considers space occupied by this view as unoccupied
2362 * @param result Previously returned value to possibly recycle.
2363 * @return The X, Y cell of a vacant area that can contain this object,
2364 * nearest the requested location.
2365 */
2366 int[] findNearestArea(
2367 int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2368 return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
2369 }
2370
Michael Jurka0280c3b2010-09-17 15:00:07 -07002371 boolean existsEmptyCell() {
2372 return findCellForSpan(null, 1, 1);
2373 }
2374
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002375 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002376 * Finds the upper-left coordinate of the first rectangle in the grid that can
2377 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2378 * then this method will only return coordinates for rectangles that contain the cell
2379 * (intersectX, intersectY)
2380 *
2381 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2382 * can be found.
2383 * @param spanX The horizontal span of the cell we want to find.
2384 * @param spanY The vertical span of the cell we want to find.
2385 *
2386 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002387 */
Michael Jurka0280c3b2010-09-17 15:00:07 -07002388 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Adam Cohen482ed822012-03-02 14:15:13 -08002389 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002390 }
2391
2392 /**
2393 * Like above, but ignores any cells occupied by the item "ignoreView"
2394 *
2395 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2396 * can be found.
2397 * @param spanX The horizontal span of the cell we want to find.
2398 * @param spanY The vertical span of the cell we want to find.
2399 * @param ignoreView The home screen item we should treat as not occupying any space
2400 * @return
2401 */
2402 boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
Adam Cohen482ed822012-03-02 14:15:13 -08002403 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
2404 ignoreView, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002405 }
2406
2407 /**
2408 * Like above, but if intersectX and intersectY are not -1, then this method will try to
2409 * return coordinates for rectangles that contain the cell [intersectX, intersectY]
2410 *
2411 * @param spanX The horizontal span of the cell we want to find.
2412 * @param spanY The vertical span of the cell we want to find.
2413 * @param ignoreView The home screen item we should treat as not occupying any space
2414 * @param intersectX The X coordinate of the cell that we should try to overlap
2415 * @param intersectX The Y coordinate of the cell that we should try to overlap
2416 *
2417 * @return True if a vacant cell of the specified dimension was found, false otherwise.
2418 */
2419 boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
2420 int intersectX, int intersectY) {
2421 return findCellForSpanThatIntersectsIgnoring(
Adam Cohen482ed822012-03-02 14:15:13 -08002422 cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002423 }
2424
2425 /**
2426 * The superset of the above two methods
2427 */
2428 boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002429 int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002430 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08002431 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002432
Michael Jurka28750fb2010-09-24 17:43:49 -07002433 boolean foundCell = false;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002434 while (true) {
2435 int startX = 0;
2436 if (intersectX >= 0) {
2437 startX = Math.max(startX, intersectX - (spanX - 1));
2438 }
2439 int endX = mCountX - (spanX - 1);
2440 if (intersectX >= 0) {
2441 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
2442 }
2443 int startY = 0;
2444 if (intersectY >= 0) {
2445 startY = Math.max(startY, intersectY - (spanY - 1));
2446 }
2447 int endY = mCountY - (spanY - 1);
2448 if (intersectY >= 0) {
2449 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
2450 }
2451
Winson Chungbbc60d82010-11-11 16:34:41 -08002452 for (int y = startY; y < endY && !foundCell; y++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002453 inner:
Winson Chungbbc60d82010-11-11 16:34:41 -08002454 for (int x = startX; x < endX; x++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002455 for (int i = 0; i < spanX; i++) {
2456 for (int j = 0; j < spanY; j++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002457 if (occupied[x + i][y + j]) {
Winson Chungbbc60d82010-11-11 16:34:41 -08002458 // small optimization: we can skip to after the column we just found
Michael Jurka0280c3b2010-09-17 15:00:07 -07002459 // an occupied cell
Winson Chungbbc60d82010-11-11 16:34:41 -08002460 x += i;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002461 continue inner;
2462 }
2463 }
2464 }
2465 if (cellXY != null) {
2466 cellXY[0] = x;
2467 cellXY[1] = y;
2468 }
Michael Jurka28750fb2010-09-24 17:43:49 -07002469 foundCell = true;
2470 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002471 }
2472 }
2473 if (intersectX == -1 && intersectY == -1) {
2474 break;
2475 } else {
2476 // if we failed to find anything, try again but without any requirements of
2477 // intersecting
2478 intersectX = -1;
2479 intersectY = -1;
2480 continue;
2481 }
2482 }
2483
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002484 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08002485 markCellsAsOccupiedForView(ignoreView, occupied);
Michael Jurka28750fb2010-09-24 17:43:49 -07002486 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002487 }
2488
2489 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002490 * A drag event has begun over this layout.
2491 * It may have begun over this layout (in which case onDragChild is called first),
2492 * or it may have begun on another layout.
2493 */
2494 void onDragEnter() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002495 mDragEnforcer.onDragEnter();
Winson Chungc07918d2011-07-01 15:35:26 -07002496 mDragging = true;
2497 }
2498
2499 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002500 * Called when drag has left this CellLayout or has been completed (successfully or not)
2501 */
2502 void onDragExit() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002503 mDragEnforcer.onDragExit();
Joe Onorato4be866d2010-10-10 11:26:02 -07002504 // This can actually be called when we aren't in a drag, e.g. when adding a new
2505 // item to this layout via the customize drawer.
2506 // Guard against that case.
2507 if (mDragging) {
2508 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002509 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002510
2511 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002512 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002513 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2514 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002515 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002516 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002517 }
2518
2519 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002520 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002521 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002522 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002523 *
2524 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002525 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002526 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002527 if (child != null) {
2528 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002529 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002530 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002531 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002532 }
2533
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002534 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002535 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002536 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002537 * @param cellX X coordinate of upper left corner expressed as a cell position
2538 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002539 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002540 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002541 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002542 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002543 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002544 final int cellWidth = mCellWidth;
2545 final int cellHeight = mCellHeight;
2546 final int widthGap = mWidthGap;
2547 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002548
Winson Chung4b825dcd2011-06-19 12:41:22 -07002549 final int hStartPadding = getPaddingLeft();
2550 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002551
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002552 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2553 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2554
2555 int x = hStartPadding + cellX * (cellWidth + widthGap);
2556 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002557
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002558 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002559 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002560
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002561 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002562 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002563 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002564 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002565 * @param width Width in pixels
2566 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002567 * @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 -08002568 */
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002569 public int[] rectToCell(int width, int height, int[] result) {
Michael Jurka9987a5c2010-10-08 16:58:12 -07002570 return rectToCell(getResources(), width, height, result);
2571 }
2572
2573 public static int[] rectToCell(Resources resources, int width, int height, int[] result) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002574 // Always assume we're working with the smallest span to make sure we
2575 // reserve enough space in both orientations.
Joe Onorato79e56262009-09-21 15:23:04 -04002576 int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
2577 int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002578 int smallerSize = Math.min(actualWidth, actualHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002579
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002580 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002581 int spanX = (int) Math.ceil(width / (float) smallerSize);
2582 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002583
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002584 if (result == null) {
2585 return new int[] { spanX, spanY };
2586 }
2587 result[0] = spanX;
2588 result[1] = spanY;
2589 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002590 }
2591
Michael Jurkaf12c75c2011-01-25 22:41:40 -08002592 public int[] cellSpansToSize(int hSpans, int vSpans) {
2593 int[] size = new int[2];
2594 size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
2595 size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
2596 return size;
2597 }
2598
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002599 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08002600 * Calculate the grid spans needed to fit given item
2601 */
2602 public void calculateSpans(ItemInfo info) {
2603 final int minWidth;
2604 final int minHeight;
2605
2606 if (info instanceof LauncherAppWidgetInfo) {
2607 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
2608 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
2609 } else if (info instanceof PendingAddWidgetInfo) {
2610 minWidth = ((PendingAddWidgetInfo) info).minWidth;
2611 minHeight = ((PendingAddWidgetInfo) info).minHeight;
2612 } else {
2613 // It's not a widget, so it must be 1x1
2614 info.spanX = info.spanY = 1;
2615 return;
2616 }
2617 int[] spans = rectToCell(minWidth, minHeight, null);
2618 info.spanX = spans[0];
2619 info.spanY = spans[1];
2620 }
2621
2622 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002623 * Find the first vacant cell, if there is one.
2624 *
2625 * @param vacant Holds the x and y coordinate of the vacant cell
2626 * @param spanX Horizontal cell span.
2627 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -07002628 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002629 * @return True if a vacant cell was found
2630 */
2631 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002632
Michael Jurka0280c3b2010-09-17 15:00:07 -07002633 return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002634 }
2635
2636 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
2637 int xCount, int yCount, boolean[][] occupied) {
2638
Adam Cohen2801caf2011-05-13 20:57:39 -07002639 for (int y = 0; y < yCount; y++) {
2640 for (int x = 0; x < xCount; x++) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002641 boolean available = !occupied[x][y];
2642out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
2643 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
2644 available = available && !occupied[i][j];
2645 if (!available) break out;
2646 }
2647 }
2648
2649 if (available) {
2650 vacant[0] = x;
2651 vacant[1] = y;
2652 return true;
2653 }
2654 }
2655 }
2656
2657 return false;
2658 }
2659
Michael Jurka0280c3b2010-09-17 15:00:07 -07002660 private void clearOccupiedCells() {
2661 for (int x = 0; x < mCountX; x++) {
2662 for (int y = 0; y < mCountY; y++) {
2663 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002664 }
2665 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002666 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002667
Adam Cohend41fbf52012-02-16 23:53:59 -08002668 public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002669 markCellsAsUnoccupiedForView(view);
Adam Cohen482ed822012-03-02 14:15:13 -08002670 markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002671 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002672
Adam Cohend4844c32011-02-18 19:25:06 -08002673 public void markCellsAsOccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08002674 markCellsAsOccupiedForView(view, mOccupied);
2675 }
2676 public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002677 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002678 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002679 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002680 }
2681
Adam Cohend4844c32011-02-18 19:25:06 -08002682 public void markCellsAsUnoccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08002683 markCellsAsUnoccupiedForView(view, mOccupied);
2684 }
2685 public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002686 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002687 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002688 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002689 }
2690
Adam Cohen482ed822012-03-02 14:15:13 -08002691 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2692 boolean value) {
2693 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002694 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2695 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002696 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002697 }
2698 }
2699 }
2700
Adam Cohen2801caf2011-05-13 20:57:39 -07002701 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002702 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002703 (Math.max((mCountX - 1), 0) * mWidthGap);
2704 }
2705
2706 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07002707 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002708 (Math.max((mCountY - 1), 0) * mHeightGap);
2709 }
2710
Michael Jurka66d72172011-04-12 16:29:25 -07002711 public boolean isOccupied(int x, int y) {
2712 if (x < mCountX && y < mCountY) {
2713 return mOccupied[x][y];
2714 } else {
2715 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2716 }
2717 }
2718
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002719 @Override
2720 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2721 return new CellLayout.LayoutParams(getContext(), attrs);
2722 }
2723
2724 @Override
2725 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2726 return p instanceof CellLayout.LayoutParams;
2727 }
2728
2729 @Override
2730 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2731 return new CellLayout.LayoutParams(p);
2732 }
2733
Winson Chungaafa03c2010-06-11 17:34:16 -07002734 public static class CellLayoutAnimationController extends LayoutAnimationController {
2735 public CellLayoutAnimationController(Animation animation, float delay) {
2736 super(animation, delay);
2737 }
2738
2739 @Override
2740 protected long getDelayForView(View view) {
2741 return (int) (Math.random() * 150);
2742 }
2743 }
2744
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002745 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2746 /**
2747 * Horizontal location of the item in the grid.
2748 */
2749 @ViewDebug.ExportedProperty
2750 public int cellX;
2751
2752 /**
2753 * Vertical location of the item in the grid.
2754 */
2755 @ViewDebug.ExportedProperty
2756 public int cellY;
2757
2758 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002759 * Temporary horizontal location of the item in the grid during reorder
2760 */
2761 public int tmpCellX;
2762
2763 /**
2764 * Temporary vertical location of the item in the grid during reorder
2765 */
2766 public int tmpCellY;
2767
2768 /**
2769 * Indicates that the temporary coordinates should be used to layout the items
2770 */
2771 public boolean useTmpCoords;
2772
2773 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002774 * Number of cells spanned horizontally by the item.
2775 */
2776 @ViewDebug.ExportedProperty
2777 public int cellHSpan;
2778
2779 /**
2780 * Number of cells spanned vertically by the item.
2781 */
2782 @ViewDebug.ExportedProperty
2783 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002784
Adam Cohen1b607ed2011-03-03 17:26:50 -08002785 /**
2786 * Indicates whether the item will set its x, y, width and height parameters freely,
2787 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2788 */
Adam Cohend4844c32011-02-18 19:25:06 -08002789 public boolean isLockedToGrid = true;
2790
Adam Cohen482ed822012-03-02 14:15:13 -08002791 /**
2792 * Indicates whether this item can be reordered. Always true except in the case of the
2793 * the AllApps button.
2794 */
2795 public boolean canReorder = true;
2796
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002797 // X coordinate of the view in the layout.
2798 @ViewDebug.ExportedProperty
2799 int x;
2800 // Y coordinate of the view in the layout.
2801 @ViewDebug.ExportedProperty
2802 int y;
2803
Romain Guy84f296c2009-11-04 15:00:44 -08002804 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002805
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002806 public LayoutParams(Context c, AttributeSet attrs) {
2807 super(c, attrs);
2808 cellHSpan = 1;
2809 cellVSpan = 1;
2810 }
2811
2812 public LayoutParams(ViewGroup.LayoutParams source) {
2813 super(source);
2814 cellHSpan = 1;
2815 cellVSpan = 1;
2816 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002817
2818 public LayoutParams(LayoutParams source) {
2819 super(source);
2820 this.cellX = source.cellX;
2821 this.cellY = source.cellY;
2822 this.cellHSpan = source.cellHSpan;
2823 this.cellVSpan = source.cellVSpan;
2824 }
2825
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002826 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002827 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002828 this.cellX = cellX;
2829 this.cellY = cellY;
2830 this.cellHSpan = cellHSpan;
2831 this.cellVSpan = cellVSpan;
2832 }
2833
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002834 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap) {
Adam Cohend4844c32011-02-18 19:25:06 -08002835 if (isLockedToGrid) {
2836 final int myCellHSpan = cellHSpan;
2837 final int myCellVSpan = cellVSpan;
Adam Cohen482ed822012-03-02 14:15:13 -08002838 final int myCellX = useTmpCoords ? tmpCellX : cellX;
2839 final int myCellY = useTmpCoords ? tmpCellY : cellY;
Adam Cohen1b607ed2011-03-03 17:26:50 -08002840
Adam Cohend4844c32011-02-18 19:25:06 -08002841 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2842 leftMargin - rightMargin;
2843 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2844 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002845 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2846 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002847 }
2848 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002849
Winson Chungaafa03c2010-06-11 17:34:16 -07002850 public String toString() {
2851 return "(" + this.cellX + ", " + this.cellY + ")";
2852 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002853
2854 public void setWidth(int width) {
2855 this.width = width;
2856 }
2857
2858 public int getWidth() {
2859 return width;
2860 }
2861
2862 public void setHeight(int height) {
2863 this.height = height;
2864 }
2865
2866 public int getHeight() {
2867 return height;
2868 }
2869
2870 public void setX(int x) {
2871 this.x = x;
2872 }
2873
2874 public int getX() {
2875 return x;
2876 }
2877
2878 public void setY(int y) {
2879 this.y = y;
2880 }
2881
2882 public int getY() {
2883 return y;
2884 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002885 }
2886
Michael Jurka0280c3b2010-09-17 15:00:07 -07002887 // This class stores info for two purposes:
2888 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2889 // its spanX, spanY, and the screen it is on
2890 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2891 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2892 // the CellLayout that was long clicked
Michael Jurkae5fb0f22011-04-11 13:27:46 -07002893 static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002894 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07002895 int cellX = -1;
2896 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002897 int spanX;
2898 int spanY;
2899 int screen;
Winson Chung3d503fb2011-07-13 17:25:49 -07002900 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002901
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002902 @Override
2903 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002904 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2905 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002906 }
2907 }
Michael Jurkad771c962011-08-09 15:00:48 -07002908
2909 public boolean lastDownOnOccupiedCell() {
2910 return mLastDownOnOccupiedCell;
2911 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002912}