blob: e8d5c19c61e7c6cbe1edd719595e516c350c3e3e [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
Winson Chung4b825dcd2011-06-19 12:41:22 -070060 private int mOriginalCellWidth;
61 private int mOriginalCellHeight;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080062 private int mCellWidth;
63 private int mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070064
Adam Cohend22015c2010-07-26 22:02:18 -070065 private int mCountX;
66 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080067
Adam Cohen234c4cd2011-07-17 21:03:04 -070068 private int mOriginalWidthGap;
69 private int mOriginalHeightGap;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080070 private int mWidthGap;
71 private int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070072 private int mMaxGap;
Adam Cohenebea84d2011-11-09 17:20:41 -080073 private boolean mScrollingTransformsDirty = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080074
75 private final Rect mRect = new Rect();
76 private final CellInfo mCellInfo = new CellInfo();
Winson Chungaafa03c2010-06-11 17:34:16 -070077
Patrick Dubroyde7658b2010-09-27 11:15:43 -070078 // These are temporary variables to prevent having to allocate a new object just to
79 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Winson Chung0be025d2011-05-23 17:45:09 -070080 private final int[] mTmpXY = new int[2];
Patrick Dubroyde7658b2010-09-27 11:15:43 -070081 private final int[] mTmpPoint = new int[2];
82 private final PointF mTmpPointF = new PointF();
Adam Cohen69ce2e52011-07-03 19:25:21 -070083 int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070084
The Android Open Source Project31dd5032009-03-03 19:32:27 -080085 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -080086 boolean[][] mTmpOccupied;
Michael Jurkad771c962011-08-09 15:00:48 -070087 private boolean mLastDownOnOccupiedCell = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080088
Michael Jurkadee05892010-07-27 10:01:56 -070089 private OnTouchListener mInterceptTouchListener;
90
Adam Cohen69ce2e52011-07-03 19:25:21 -070091 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -070092 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -070093
Adam Cohenb5ba0972011-09-07 18:02:31 -070094 private int mForegroundAlpha = 0;
Michael Jurka5f1c5092010-09-03 14:15:02 -070095 private float mBackgroundAlpha;
Adam Cohen1b0aaac2010-10-28 11:11:18 -070096 private float mBackgroundAlphaMultiplier = 1.0f;
Adam Cohenf34bab52010-09-30 14:11:56 -070097
Michael Jurka33945b22010-12-21 18:19:38 -080098 private Drawable mNormalBackground;
Michael Jurka33945b22010-12-21 18:19:38 -080099 private Drawable mActiveGlowBackground;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700100 private Drawable mOverScrollForegroundDrawable;
101 private Drawable mOverScrollLeft;
102 private Drawable mOverScrollRight;
Michael Jurka18014792010-10-14 09:01:34 -0700103 private Rect mBackgroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700104 private Rect mForegroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700105 private int mForegroundPadding;
Patrick Dubroy1262e362010-10-06 15:49:50 -0700106
Michael Jurka33945b22010-12-21 18:19:38 -0800107 // If we're actively dragging something over this screen, mIsDragOverlapping is true
108 private boolean mIsDragOverlapping = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700109 private final Point mDragCenter = new Point();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700110
Winson Chung150fbab2010-09-29 17:14:26 -0700111 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700112 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohend41fbf52012-02-16 23:53:59 -0800113 private Rect[] mDragOutlines = new Rect[4];
Chet Haase472b2812010-10-14 07:02:04 -0700114 private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700115 private InterruptibleInOutAnimator[] mDragOutlineAnims =
116 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700117
118 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700119 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700120 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700121
Patrick Dubroy96864c32011-03-10 17:17:23 -0800122 private BubbleTextView mPressedOrFocusedIcon;
123
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700124 private Drawable mCrosshairsDrawable = null;
Patrick Dubroy49250ad2010-10-08 15:33:52 -0700125 private InterruptibleInOutAnimator mCrosshairsAnimator = null;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700126 private float mCrosshairsVisibility = 0.0f;
127
Adam Cohen482ed822012-03-02 14:15:13 -0800128 private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
129 HashMap<CellLayout.LayoutParams, Animator>();
Adam Cohen19f37922012-03-21 11:59:11 -0700130 private HashMap<View, ReorderHintAnimation>
131 mShakeAnimators = new HashMap<View, ReorderHintAnimation>();
132
133 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700134
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700135 // When a drag operation is in progress, holds the nearest cell to the touch point
136 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800137
Joe Onorato4be866d2010-10-10 11:26:02 -0700138 private boolean mDragging = false;
139
Patrick Dubroyce34a972010-10-19 10:34:32 -0700140 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700141 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700142
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800143 private boolean mIsHotseat = false;
Winson Chungeecf02d2012-03-02 17:14:58 -0800144 private float mChildScale = 1f;
145 private float mHotseatChildScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800146
Adam Cohen482ed822012-03-02 14:15:13 -0800147 public static final int MODE_DRAG_OVER = 0;
148 public static final int MODE_ON_DROP = 1;
149 public static final int MODE_ON_DROP_EXTERNAL = 2;
150 public static final int MODE_ACCEPT_DROP = 3;
Adam Cohen19f37922012-03-21 11:59:11 -0700151 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800152 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
153
Adam Cohen19f37922012-03-21 11:59:11 -0700154 private static final float REORDER_HINT_MAGNITUDE = 0.27f;
155 private static final int REORDER_ANIMATION_DURATION = 150;
156 private float mReorderHintAnimationMagnitude;
157
Adam Cohen482ed822012-03-02 14:15:13 -0800158 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
159 private Rect mOccupiedRect = new Rect();
160 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700161 int[] mPreviousReorderDirection = new int[2];
Adam Cohen482ed822012-03-02 14:15:13 -0800162
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800163 public CellLayout(Context context) {
164 this(context, null);
165 }
166
167 public CellLayout(Context context, AttributeSet attrs) {
168 this(context, attrs, 0);
169 }
170
171 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
172 super(context, attrs, defStyle);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700173
174 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
175 // the user where a dragged item will land when dropped.
176 setWillNotDraw(false);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700177
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800178 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
179
Winson Chung4b825dcd2011-06-19 12:41:22 -0700180 mOriginalCellWidth =
181 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
182 mOriginalCellHeight =
183 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700184 mWidthGap = mOriginalWidthGap = a.getDimensionPixelSize(R.styleable.CellLayout_widthGap, 0);
185 mHeightGap = mOriginalHeightGap = a.getDimensionPixelSize(R.styleable.CellLayout_heightGap, 0);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700186 mMaxGap = a.getDimensionPixelSize(R.styleable.CellLayout_maxGap, 0);
Adam Cohend22015c2010-07-26 22:02:18 -0700187 mCountX = LauncherModel.getCellCountX();
188 mCountY = LauncherModel.getCellCountY();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700189 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800190 mTmpOccupied = new boolean[mCountX][mCountY];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800191
192 a.recycle();
193
194 setAlwaysDrawnWithCacheEnabled(false);
195
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700196 final Resources res = getResources();
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700197
Winson Chung967289b2011-06-30 18:09:30 -0700198 mNormalBackground = res.getDrawable(R.drawable.homescreen_blue_normal_holo);
Winson Chungdea74b72011-09-13 18:06:43 -0700199 mActiveGlowBackground = res.getDrawable(R.drawable.homescreen_blue_strong_holo);
Michael Jurka33945b22010-12-21 18:19:38 -0800200
Adam Cohenb5ba0972011-09-07 18:02:31 -0700201 mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
202 mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
203 mForegroundPadding =
204 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
Michael Jurka33945b22010-12-21 18:19:38 -0800205
Adam Cohen19f37922012-03-21 11:59:11 -0700206 mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE *
207 res.getDimensionPixelSize(R.dimen.app_icon_size));
208
Winson Chungb26f3d62011-06-02 10:49:29 -0700209 mNormalBackground.setFilterBitmap(true);
Winson Chungb26f3d62011-06-02 10:49:29 -0700210 mActiveGlowBackground.setFilterBitmap(true);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700211
Winson Chungeecf02d2012-03-02 17:14:58 -0800212 int iconScale = res.getInteger(R.integer.app_icon_scale_percent);
213 if (iconScale >= 0) {
214 mChildScale = iconScale / 100f;
215 }
216 int hotseatIconScale = res.getInteger(R.integer.app_icon_hotseat_scale_percent);
217 if (hotseatIconScale >= 0) {
218 mHotseatChildScale = hotseatIconScale / 100f;
219 }
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800220
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700221 // Initialize the data structures used for the drag visualization.
Winson Chung150fbab2010-09-29 17:14:26 -0700222
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700223 mCrosshairsDrawable = res.getDrawable(R.drawable.gardening_crosshairs);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700224 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700225
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700226 // Set up the animation for fading the crosshairs in and out
227 int animDuration = res.getInteger(R.integer.config_crosshairsFadeInTime);
Patrick Dubroy49250ad2010-10-08 15:33:52 -0700228 mCrosshairsAnimator = new InterruptibleInOutAnimator(animDuration, 0.0f, 1.0f);
Chet Haase472b2812010-10-14 07:02:04 -0700229 mCrosshairsAnimator.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700230 public void onAnimationUpdate(ValueAnimator animation) {
231 mCrosshairsVisibility = ((Float) animation.getAnimatedValue()).floatValue();
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700232 invalidate();
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700233 }
234 });
Patrick Dubroyce34a972010-10-19 10:34:32 -0700235 mCrosshairsAnimator.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700236
Winson Chungb8c69f32011-10-19 21:36:08 -0700237 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700238 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800239 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700240 }
241
242 // When dragging things around the home screens, we show a green outline of
243 // where the item will land. The outlines gradually fade out, leaving a trail
244 // behind the drag path.
245 // Set up all the animations that are used to implement this fading.
246 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700247 final float fromAlphaValue = 0;
248 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700249
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700250 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700251
252 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700253 final InterruptibleInOutAnimator anim =
254 new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700255 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700256 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700257 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700258 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700259 final Bitmap outline = (Bitmap)anim.getTag();
260
261 // If an animation is started and then stopped very quickly, we can still
262 // get spurious updates we've cleared the tag. Guard against this.
263 if (outline == null) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700264 if (false) {
265 Object val = animation.getAnimatedValue();
266 Log.d(TAG, "anim " + thisIndex + " update: " + val +
267 ", isStopped " + anim.isStopped());
268 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700269 // Try to prevent it from continuing to run
270 animation.cancel();
271 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700272 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800273 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700274 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700275 }
276 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700277 // The animation holds a reference to the drag outline bitmap as long is it's
278 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700279 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700280 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700281 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700282 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700283 anim.setTag(null);
284 }
285 }
286 });
287 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700288 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700289
Michael Jurka18014792010-10-14 09:01:34 -0700290 mBackgroundRect = new Rect();
Adam Cohenb5ba0972011-09-07 18:02:31 -0700291 mForegroundRect = new Rect();
Michael Jurkabea15192010-11-17 12:33:46 -0800292
Michael Jurkaa52570f2012-03-20 03:18:20 -0700293 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
294 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
295 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700296 }
297
Michael Jurkaf6440da2011-04-05 14:50:34 -0700298 static int widthInPortrait(Resources r, int numCells) {
299 // We use this method from Workspace to figure out how many rows/columns Launcher should
300 // have. We ignore the left/right padding on CellLayout because it turns out in our design
301 // the padding extends outside the visible screen size, but it looked fine anyway.
Michael Jurkaf6440da2011-04-05 14:50:34 -0700302 int cellWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700303 int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
304 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
Michael Jurkaf6440da2011-04-05 14:50:34 -0700305
Winson Chung4b825dcd2011-06-19 12:41:22 -0700306 return minGap * (numCells - 1) + cellWidth * numCells;
Michael Jurkaf6440da2011-04-05 14:50:34 -0700307 }
308
Michael Jurkaf6440da2011-04-05 14:50:34 -0700309 static int heightInLandscape(Resources r, int numCells) {
310 // We use this method from Workspace to figure out how many rows/columns Launcher should
311 // have. We ignore the left/right padding on CellLayout because it turns out in our design
312 // the padding extends outside the visible screen size, but it looked fine anyway.
Michael Jurkaf6440da2011-04-05 14:50:34 -0700313 int cellHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700314 int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
315 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
Michael Jurkaf6440da2011-04-05 14:50:34 -0700316
Winson Chung4b825dcd2011-06-19 12:41:22 -0700317 return minGap * (numCells - 1) + cellHeight * numCells;
Michael Jurkaf6440da2011-04-05 14:50:34 -0700318 }
319
Adam Cohen2801caf2011-05-13 20:57:39 -0700320 public void enableHardwareLayers() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700321 mShortcutsAndWidgets.enableHardwareLayers();
Adam Cohen2801caf2011-05-13 20:57:39 -0700322 }
323
324 public void setGridSize(int x, int y) {
325 mCountX = x;
326 mCountY = y;
327 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800328 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen76fc0852011-06-17 13:26:23 -0700329 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700330 }
331
Patrick Dubroy96864c32011-03-10 17:17:23 -0800332 private void invalidateBubbleTextView(BubbleTextView icon) {
333 final int padding = icon.getPressedOrFocusedBackgroundPadding();
Winson Chung4b825dcd2011-06-19 12:41:22 -0700334 invalidate(icon.getLeft() + getPaddingLeft() - padding,
335 icon.getTop() + getPaddingTop() - padding,
336 icon.getRight() + getPaddingLeft() + padding,
337 icon.getBottom() + getPaddingTop() + padding);
Patrick Dubroy96864c32011-03-10 17:17:23 -0800338 }
339
Adam Cohenb5ba0972011-09-07 18:02:31 -0700340 void setOverScrollAmount(float r, boolean left) {
341 if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
342 mOverScrollForegroundDrawable = mOverScrollLeft;
343 } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
344 mOverScrollForegroundDrawable = mOverScrollRight;
345 }
346
347 mForegroundAlpha = (int) Math.round((r * 255));
348 mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
349 invalidate();
350 }
351
Patrick Dubroy96864c32011-03-10 17:17:23 -0800352 void setPressedOrFocusedIcon(BubbleTextView icon) {
353 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
354 // requires an expanded clip rect (due to the glow's blur radius)
355 BubbleTextView oldIcon = mPressedOrFocusedIcon;
356 mPressedOrFocusedIcon = icon;
357 if (oldIcon != null) {
358 invalidateBubbleTextView(oldIcon);
359 }
360 if (mPressedOrFocusedIcon != null) {
361 invalidateBubbleTextView(mPressedOrFocusedIcon);
362 }
363 }
364
Michael Jurka33945b22010-12-21 18:19:38 -0800365 void setIsDragOverlapping(boolean isDragOverlapping) {
366 if (mIsDragOverlapping != isDragOverlapping) {
367 mIsDragOverlapping = isDragOverlapping;
368 invalidate();
369 }
370 }
371
372 boolean getIsDragOverlapping() {
373 return mIsDragOverlapping;
374 }
375
Adam Cohenebea84d2011-11-09 17:20:41 -0800376 protected void setOverscrollTransformsDirty(boolean dirty) {
377 mScrollingTransformsDirty = dirty;
378 }
379
380 protected void resetOverscrollTransforms() {
381 if (mScrollingTransformsDirty) {
382 setOverscrollTransformsDirty(false);
383 setTranslationX(0);
384 setRotationY(0);
385 // It doesn't matter if we pass true or false here, the important thing is that we
386 // pass 0, which results in the overscroll drawable not being drawn any more.
387 setOverScrollAmount(0, false);
388 setPivotX(getMeasuredWidth() / 2);
389 setPivotY(getMeasuredHeight() / 2);
390 }
391 }
392
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700393 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700394 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700395 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
396 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
397 // When we're small, we are either drawn normally or in the "accepts drops" state (during
398 // a drag). However, we also drag the mini hover background *over* one of those two
399 // backgrounds
Winson Chungb26f3d62011-06-02 10:49:29 -0700400 if (mBackgroundAlpha > 0.0f) {
Adam Cohenf34bab52010-09-30 14:11:56 -0700401 Drawable bg;
Michael Jurka33945b22010-12-21 18:19:38 -0800402
403 if (mIsDragOverlapping) {
404 // In the mini case, we draw the active_glow bg *over* the active background
Michael Jurkabdf78552011-10-31 14:34:25 -0700405 bg = mActiveGlowBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700406 } else {
Michael Jurkabdf78552011-10-31 14:34:25 -0700407 bg = mNormalBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700408 }
Michael Jurka33945b22010-12-21 18:19:38 -0800409
410 bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
411 bg.setBounds(mBackgroundRect);
412 bg.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700413 }
Romain Guya6abce82009-11-10 02:54:41 -0800414
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700415 if (mCrosshairsVisibility > 0.0f) {
416 final int countX = mCountX;
417 final int countY = mCountY;
418
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700419 final float MAX_ALPHA = 0.4f;
420 final int MAX_VISIBLE_DISTANCE = 600;
421 final float DISTANCE_MULTIPLIER = 0.002f;
422
423 final Drawable d = mCrosshairsDrawable;
424 final int width = d.getIntrinsicWidth();
425 final int height = d.getIntrinsicHeight();
426
Winson Chung4b825dcd2011-06-19 12:41:22 -0700427 int x = getPaddingLeft() - (mWidthGap / 2) - (width / 2);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700428 for (int col = 0; col <= countX; col++) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700429 int y = getPaddingTop() - (mHeightGap / 2) - (height / 2);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700430 for (int row = 0; row <= countY; row++) {
431 mTmpPointF.set(x - mDragCenter.x, y - mDragCenter.y);
432 float dist = mTmpPointF.length();
433 // Crosshairs further from the drag point are more faint
434 float alpha = Math.min(MAX_ALPHA,
435 DISTANCE_MULTIPLIER * (MAX_VISIBLE_DISTANCE - dist));
436 if (alpha > 0.0f) {
437 d.setBounds(x, y, x + width, y + height);
438 d.setAlpha((int) (alpha * 255 * mCrosshairsVisibility));
439 d.draw(canvas);
440 }
441 y += mCellHeight + mHeightGap;
442 }
443 x += mCellWidth + mWidthGap;
444 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700445 }
Winson Chung150fbab2010-09-29 17:14:26 -0700446
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700447 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700448 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700449 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700450 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800451 final Rect r = mDragOutlines[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700452 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700453 paint.setAlpha((int)(alpha + .5f));
Adam Cohend41fbf52012-02-16 23:53:59 -0800454 canvas.drawBitmap(b, null, r, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700455 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700456 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800457
458 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
459 // requires an expanded clip rect (due to the glow's blur radius)
460 if (mPressedOrFocusedIcon != null) {
461 final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
462 final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
463 if (b != null) {
464 canvas.drawBitmap(b,
Winson Chung4b825dcd2011-06-19 12:41:22 -0700465 mPressedOrFocusedIcon.getLeft() + getPaddingLeft() - padding,
466 mPressedOrFocusedIcon.getTop() + getPaddingTop() - padding,
Patrick Dubroy96864c32011-03-10 17:17:23 -0800467 null);
468 }
469 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700470
Adam Cohen482ed822012-03-02 14:15:13 -0800471 if (DEBUG_VISUALIZE_OCCUPIED) {
472 int[] pt = new int[2];
473 ColorDrawable cd = new ColorDrawable(Color.RED);
474 cd.setBounds(0, 0, 80, 80);
475 for (int i = 0; i < mCountX; i++) {
476 for (int j = 0; j < mCountY; j++) {
477 if (mOccupied[i][j]) {
478 cellToPoint(i, j, pt);
479 canvas.save();
480 canvas.translate(pt[0], pt[1]);
481 cd.draw(canvas);
482 canvas.restore();
483 }
484 }
485 }
486 }
487
Adam Cohen69ce2e52011-07-03 19:25:21 -0700488 // The folder outer / inner ring image(s)
489 for (int i = 0; i < mFolderOuterRings.size(); i++) {
490 FolderRingAnimator fra = mFolderOuterRings.get(i);
491
492 // Draw outer ring
493 Drawable d = FolderRingAnimator.sSharedOuterRingDrawable;
494 int width = (int) fra.getOuterRingSize();
495 int height = width;
496 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
497
498 int centerX = mTempLocation[0] + mCellWidth / 2;
499 int centerY = mTempLocation[1] + FolderRingAnimator.sPreviewSize / 2;
500
501 canvas.save();
502 canvas.translate(centerX - width / 2, centerY - height / 2);
503 d.setBounds(0, 0, width, height);
504 d.draw(canvas);
505 canvas.restore();
506
507 // Draw inner ring
508 d = FolderRingAnimator.sSharedInnerRingDrawable;
509 width = (int) fra.getInnerRingSize();
510 height = width;
511 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
512
513 centerX = mTempLocation[0] + mCellWidth / 2;
514 centerY = mTempLocation[1] + FolderRingAnimator.sPreviewSize / 2;
515 canvas.save();
516 canvas.translate(centerX - width / 2, centerY - width / 2);
517 d.setBounds(0, 0, width, height);
518 d.draw(canvas);
519 canvas.restore();
520 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700521
522 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
523 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
524 int width = d.getIntrinsicWidth();
525 int height = d.getIntrinsicHeight();
526
527 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
528 int centerX = mTempLocation[0] + mCellWidth / 2;
529 int centerY = mTempLocation[1] + FolderRingAnimator.sPreviewSize / 2;
530
531 canvas.save();
532 canvas.translate(centerX - width / 2, centerY - width / 2);
533 d.setBounds(0, 0, width, height);
534 d.draw(canvas);
535 canvas.restore();
536 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700537 }
538
Adam Cohenb5ba0972011-09-07 18:02:31 -0700539 @Override
540 protected void dispatchDraw(Canvas canvas) {
541 super.dispatchDraw(canvas);
542 if (mForegroundAlpha > 0) {
543 mOverScrollForegroundDrawable.setBounds(mForegroundRect);
544 Paint p = ((NinePatchDrawable) mOverScrollForegroundDrawable).getPaint();
545 p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
546 mOverScrollForegroundDrawable.draw(canvas);
547 p.setXfermode(null);
548 }
549 }
550
Adam Cohen69ce2e52011-07-03 19:25:21 -0700551 public void showFolderAccept(FolderRingAnimator fra) {
552 mFolderOuterRings.add(fra);
553 }
554
555 public void hideFolderAccept(FolderRingAnimator fra) {
556 if (mFolderOuterRings.contains(fra)) {
557 mFolderOuterRings.remove(fra);
558 }
559 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700560 }
561
Adam Cohenc51934b2011-07-26 21:07:43 -0700562 public void setFolderLeaveBehindCell(int x, int y) {
563 mFolderLeaveBehindCell[0] = x;
564 mFolderLeaveBehindCell[1] = y;
565 invalidate();
566 }
567
568 public void clearFolderLeaveBehind() {
569 mFolderLeaveBehindCell[0] = -1;
570 mFolderLeaveBehindCell[1] = -1;
571 invalidate();
572 }
573
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700574 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700575 public boolean shouldDelayChildPressedState() {
576 return false;
577 }
578
579 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700580 public void cancelLongPress() {
581 super.cancelLongPress();
582
583 // Cancel long press for all children
584 final int count = getChildCount();
585 for (int i = 0; i < count; i++) {
586 final View child = getChildAt(i);
587 child.cancelLongPress();
588 }
589 }
590
Michael Jurkadee05892010-07-27 10:01:56 -0700591 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
592 mInterceptTouchListener = listener;
593 }
594
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800595 int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700596 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800597 }
598
599 int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700600 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800601 }
602
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800603 public void setIsHotseat(boolean isHotseat) {
604 mIsHotseat = isHotseat;
605 }
606
Winson Chungeecf02d2012-03-02 17:14:58 -0800607 public float getChildrenScale() {
608 return mIsHotseat ? mHotseatChildScale : mChildScale;
609 }
610
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700611 public boolean addViewToCellLayout(
612 View child, int index, int childId, LayoutParams params, boolean markCells) {
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800613 return addViewToCellLayout(child, index, childId, params, markCells, false);
614 }
615
Winson Chungeecf02d2012-03-02 17:14:58 -0800616 private void scaleChild(BubbleTextView bubbleChild, float pivot, float scale) {
Andrew Flynnbc239a12012-03-06 11:39:49 -0800617 // If we haven't measured the child yet, do it now
618 // (this happens if we're being dropped from all-apps
619 if (bubbleChild.getLayoutParams() instanceof LayoutParams &&
620 (bubbleChild.getMeasuredWidth() | bubbleChild.getMeasuredHeight()) == 0) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700621 getShortcutsAndWidgets().measureChild(bubbleChild);
Andrew Flynnbc239a12012-03-06 11:39:49 -0800622 }
Andrew Flynnbc239a12012-03-06 11:39:49 -0800623
Andrew Flynnbc239a12012-03-06 11:39:49 -0800624 bubbleChild.setScaleX(scale);
625 bubbleChild.setScaleY(scale);
Andrew Flynnbc239a12012-03-06 11:39:49 -0800626 }
627
628 private void resetChild(BubbleTextView bubbleChild) {
629 bubbleChild.setScaleX(1f);
630 bubbleChild.setScaleY(1f);
Andrew Flynnbc239a12012-03-06 11:39:49 -0800631
632 bubbleChild.setTextColor(getResources().getColor(R.color.workspace_icon_text_color));
633 }
634
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800635 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
636 boolean markCells, boolean allApps) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700637 final LayoutParams lp = params;
638
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800639 // Hotseat icons - scale down and remove text
640 // Don't scale the all apps button
641 // scale percent set to -1 means do not scale
642 // Only scale BubbleTextViews
643 if (child instanceof BubbleTextView) {
644 BubbleTextView bubbleChild = (BubbleTextView) child;
645
Andrew Flynnbc239a12012-03-06 11:39:49 -0800646 // Start the child with 100% scale and visible text
647 resetChild(bubbleChild);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800648
Winson Chungeecf02d2012-03-02 17:14:58 -0800649 if (mIsHotseat && !allApps && mHotseatChildScale >= 0) {
Andrew Flynnbc239a12012-03-06 11:39:49 -0800650 // Scale/make transparent for a hotseat
Winson Chungeecf02d2012-03-02 17:14:58 -0800651 scaleChild(bubbleChild, 0f, mHotseatChildScale);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800652
Andrew Flynnbc239a12012-03-06 11:39:49 -0800653 bubbleChild.setTextColor(getResources().getColor(android.R.color.transparent));
Winson Chungeecf02d2012-03-02 17:14:58 -0800654 } else if (mChildScale >= 0) {
Andrew Flynnbc239a12012-03-06 11:39:49 -0800655 // Else possibly still scale it if we need to for smaller icons
Winson Chungeecf02d2012-03-02 17:14:58 -0800656 scaleChild(bubbleChild, 0f, mChildScale);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800657 }
658 }
659
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800660 // Generate an id for each view, this assumes we have at most 256x256 cells
661 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700662 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700663 // If the horizontal or vertical span is set to -1, it is taken to
664 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700665 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
666 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800667
Winson Chungaafa03c2010-06-11 17:34:16 -0700668 child.setId(childId);
669
Michael Jurkaa52570f2012-03-20 03:18:20 -0700670 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700671
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700672 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700673
Winson Chungaafa03c2010-06-11 17:34:16 -0700674 return true;
675 }
676 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800677 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700678
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800679 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700680 public void removeAllViews() {
681 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700682 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700683 }
684
685 @Override
686 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700687 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700688 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700689 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700690 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700691 }
692
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700693 public void removeViewWithoutMarkingCells(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700694 mShortcutsAndWidgets.removeView(view);
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700695 }
696
Michael Jurka0280c3b2010-09-17 15:00:07 -0700697 @Override
698 public void removeView(View view) {
699 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700700 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700701 }
702
703 @Override
704 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700705 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
706 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700707 }
708
709 @Override
710 public void removeViewInLayout(View view) {
711 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700712 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700713 }
714
715 @Override
716 public void removeViews(int start, int count) {
717 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700718 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700719 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700720 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700721 }
722
723 @Override
724 public void removeViewsInLayout(int start, int count) {
725 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700726 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700727 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700728 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800729 }
730
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800731 @Override
732 protected void onAttachedToWindow() {
733 super.onAttachedToWindow();
734 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
735 }
736
Michael Jurkaaf442092010-06-10 17:01:57 -0700737 public void setTagToCellInfoForPoint(int touchX, int touchY) {
738 final CellInfo cellInfo = mCellInfo;
Winson Chungeecf02d2012-03-02 17:14:58 -0800739 Rect frame = mRect;
Michael Jurkaaf442092010-06-10 17:01:57 -0700740 final int x = touchX + mScrollX;
741 final int y = touchY + mScrollY;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700742 final int count = mShortcutsAndWidgets.getChildCount();
Michael Jurkaaf442092010-06-10 17:01:57 -0700743
744 boolean found = false;
745 for (int i = count - 1; i >= 0; i--) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700746 final View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohend4844c32011-02-18 19:25:06 -0800747 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Michael Jurkaaf442092010-06-10 17:01:57 -0700748
Adam Cohen1b607ed2011-03-03 17:26:50 -0800749 if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
750 lp.isLockedToGrid) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700751 child.getHitRect(frame);
Winson Chung0be025d2011-05-23 17:45:09 -0700752
Winson Chungeecf02d2012-03-02 17:14:58 -0800753 float scale = child.getScaleX();
754 frame = new Rect(child.getLeft(), child.getTop(), child.getRight(),
755 child.getBottom());
Winson Chung0be025d2011-05-23 17:45:09 -0700756 // The child hit rect is relative to the CellLayoutChildren parent, so we need to
757 // offset that by this CellLayout's padding to test an (x,y) point that is relative
758 // to this view.
Winson Chung4b825dcd2011-06-19 12:41:22 -0700759 frame.offset(mPaddingLeft, mPaddingTop);
Winson Chungeecf02d2012-03-02 17:14:58 -0800760 frame.inset((int) (frame.width() * (1f - scale) / 2),
761 (int) (frame.height() * (1f - scale) / 2));
Winson Chung0be025d2011-05-23 17:45:09 -0700762
Michael Jurkaaf442092010-06-10 17:01:57 -0700763 if (frame.contains(x, y)) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700764 cellInfo.cell = child;
765 cellInfo.cellX = lp.cellX;
766 cellInfo.cellY = lp.cellY;
767 cellInfo.spanX = lp.cellHSpan;
768 cellInfo.spanY = lp.cellVSpan;
Michael Jurkaaf442092010-06-10 17:01:57 -0700769 found = true;
Michael Jurkaaf442092010-06-10 17:01:57 -0700770 break;
771 }
772 }
773 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700774
Michael Jurkad771c962011-08-09 15:00:48 -0700775 mLastDownOnOccupiedCell = found;
776
Michael Jurkaaf442092010-06-10 17:01:57 -0700777 if (!found) {
Winson Chung0be025d2011-05-23 17:45:09 -0700778 final int cellXY[] = mTmpXY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700779 pointToCellExact(x, y, cellXY);
780
Michael Jurkaaf442092010-06-10 17:01:57 -0700781 cellInfo.cell = null;
782 cellInfo.cellX = cellXY[0];
783 cellInfo.cellY = cellXY[1];
784 cellInfo.spanX = 1;
785 cellInfo.spanY = 1;
Michael Jurkaaf442092010-06-10 17:01:57 -0700786 }
787 setTag(cellInfo);
788 }
789
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800790 @Override
791 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Cohenc1997fd2011-08-15 18:26:39 -0700792 // First we clear the tag to ensure that on every touch down we start with a fresh slate,
793 // even in the case where we return early. Not clearing here was causing bugs whereby on
794 // long-press we'd end up picking up an item from a previous drag operation.
795 final int action = ev.getAction();
796
797 if (action == MotionEvent.ACTION_DOWN) {
798 clearTagCellInfo();
799 }
800
Michael Jurkadee05892010-07-27 10:01:56 -0700801 if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
802 return true;
803 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800804
805 if (action == MotionEvent.ACTION_DOWN) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700806 setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800807 }
Winson Chungeecf02d2012-03-02 17:14:58 -0800808
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800809 return false;
810 }
811
Adam Cohenc1997fd2011-08-15 18:26:39 -0700812 private void clearTagCellInfo() {
813 final CellInfo cellInfo = mCellInfo;
814 cellInfo.cell = null;
815 cellInfo.cellX = -1;
816 cellInfo.cellY = -1;
817 cellInfo.spanX = 0;
818 cellInfo.spanY = 0;
819 setTag(cellInfo);
820 }
821
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800822 public CellInfo getTag() {
Michael Jurka0280c3b2010-09-17 15:00:07 -0700823 return (CellInfo) super.getTag();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800824 }
825
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700826 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700827 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800828 * @param x X coordinate of the point
829 * @param y Y coordinate of the point
830 * @param result Array of 2 ints to hold the x and y coordinate of the cell
831 */
832 void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700833 final int hStartPadding = getPaddingLeft();
834 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800835
836 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
837 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
838
Adam Cohend22015c2010-07-26 22:02:18 -0700839 final int xAxis = mCountX;
840 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800841
842 if (result[0] < 0) result[0] = 0;
843 if (result[0] >= xAxis) result[0] = xAxis - 1;
844 if (result[1] < 0) result[1] = 0;
845 if (result[1] >= yAxis) result[1] = yAxis - 1;
846 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700847
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800848 /**
849 * Given a point, return the cell that most closely encloses that point
850 * @param x X coordinate of the point
851 * @param y Y coordinate of the point
852 * @param result Array of 2 ints to hold the x and y coordinate of the cell
853 */
854 void pointToCellRounded(int x, int y, int[] result) {
855 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
856 }
857
858 /**
859 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700860 *
861 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800862 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700863 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800864 * @param result Array of 2 ints to hold the x and y coordinate of the point
865 */
866 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700867 final int hStartPadding = getPaddingLeft();
868 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800869
870 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
871 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
872 }
873
Adam Cohene3e27a82011-04-15 12:07:39 -0700874 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800875 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700876 *
877 * @param cellX X coordinate of the cell
878 * @param cellY Y coordinate of the cell
879 *
880 * @param result Array of 2 ints to hold the x and y coordinate of the point
881 */
882 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700883 regionToCenterPoint(cellX, cellY, 1, 1, result);
884 }
885
886 /**
887 * Given a cell coordinate and span return the point that represents the center of the regio
888 *
889 * @param cellX X coordinate of the cell
890 * @param cellY Y coordinate of the cell
891 *
892 * @param result Array of 2 ints to hold the x and y coordinate of the point
893 */
894 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700895 final int hStartPadding = getPaddingLeft();
896 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700897 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
898 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
899 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
900 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700901 }
902
Adam Cohen19f37922012-03-21 11:59:11 -0700903 /**
904 * Given a cell coordinate and span fills out a corresponding pixel rect
905 *
906 * @param cellX X coordinate of the cell
907 * @param cellY Y coordinate of the cell
908 * @param result Rect in which to write the result
909 */
910 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
911 final int hStartPadding = getPaddingLeft();
912 final int vStartPadding = getPaddingTop();
913 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
914 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
915 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
916 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
917 }
918
Adam Cohen482ed822012-03-02 14:15:13 -0800919 public float getDistanceFromCell(float x, float y, int[] cell) {
920 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
921 float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) +
922 Math.pow(y - mTmpPoint[1], 2));
923 return distance;
924 }
925
Romain Guy84f296c2009-11-04 15:00:44 -0800926 int getCellWidth() {
927 return mCellWidth;
928 }
929
930 int getCellHeight() {
931 return mCellHeight;
932 }
933
Adam Cohend4844c32011-02-18 19:25:06 -0800934 int getWidthGap() {
935 return mWidthGap;
936 }
937
938 int getHeightGap() {
939 return mHeightGap;
940 }
941
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700942 Rect getContentRect(Rect r) {
943 if (r == null) {
944 r = new Rect();
945 }
946 int left = getPaddingLeft();
947 int top = getPaddingTop();
Winson Chung4b825dcd2011-06-19 12:41:22 -0700948 int right = left + getWidth() - mPaddingLeft - mPaddingRight;
949 int bottom = top + getHeight() - mPaddingTop - mPaddingBottom;
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700950 r.set(left, top, right, bottom);
951 return r;
952 }
953
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800954 @Override
955 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
956 // TODO: currently ignoring padding
Winson Chungaafa03c2010-06-11 17:34:16 -0700957
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800958 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700959 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
960
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800961 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
962 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700963
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800964 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
965 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
966 }
967
Adam Cohend22015c2010-07-26 22:02:18 -0700968 int numWidthGaps = mCountX - 1;
969 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800970
Adam Cohen234c4cd2011-07-17 21:03:04 -0700971 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700972 int hSpace = widthSpecSize - mPaddingLeft - mPaddingRight;
973 int vSpace = heightSpecSize - mPaddingTop - mPaddingBottom;
974 int hFreeSpace = hSpace - (mCountX * mOriginalCellWidth);
975 int vFreeSpace = vSpace - (mCountY * mOriginalCellHeight);
976 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
977 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700978 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700979 } else {
980 mWidthGap = mOriginalWidthGap;
981 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700982 }
Michael Jurka5f1c5092010-09-03 14:15:02 -0700983
Michael Jurka8c920dd2011-01-20 14:16:56 -0800984 // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
985 int newWidth = widthSpecSize;
986 int newHeight = heightSpecSize;
Michael Jurka5f1c5092010-09-03 14:15:02 -0700987 if (widthSpecMode == MeasureSpec.AT_MOST) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700988 newWidth = mPaddingLeft + mPaddingRight + (mCountX * mCellWidth) +
Winson Chungece7f5b2010-10-22 14:54:12 -0700989 ((mCountX - 1) * mWidthGap);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700990 newHeight = mPaddingTop + mPaddingBottom + (mCountY * mCellHeight) +
Winson Chungece7f5b2010-10-22 14:54:12 -0700991 ((mCountY - 1) * mHeightGap);
Michael Jurka5f1c5092010-09-03 14:15:02 -0700992 setMeasuredDimension(newWidth, newHeight);
Michael Jurka5f1c5092010-09-03 14:15:02 -0700993 }
Michael Jurka8c920dd2011-01-20 14:16:56 -0800994
995 int count = getChildCount();
996 for (int i = 0; i < count; i++) {
997 View child = getChildAt(i);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700998 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - mPaddingLeft -
999 mPaddingRight, MeasureSpec.EXACTLY);
1000 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - mPaddingTop -
1001 mPaddingBottom, MeasureSpec.EXACTLY);
Michael Jurka8c920dd2011-01-20 14:16:56 -08001002 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
1003 }
1004 setMeasuredDimension(newWidth, newHeight);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001005 }
1006
1007 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -07001008 protected void onLayout(boolean changed, int l, int t, int r, int b) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001009 int count = getChildCount();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001010 for (int i = 0; i < count; i++) {
Michael Jurka8c920dd2011-01-20 14:16:56 -08001011 View child = getChildAt(i);
Winson Chung4b825dcd2011-06-19 12:41:22 -07001012 child.layout(mPaddingLeft, mPaddingTop,
1013 r - l - mPaddingRight, b - t - mPaddingBottom);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001014 }
1015 }
1016
1017 @Override
Michael Jurkadee05892010-07-27 10:01:56 -07001018 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1019 super.onSizeChanged(w, h, oldw, oldh);
Michael Jurka18014792010-10-14 09:01:34 -07001020 mBackgroundRect.set(0, 0, w, h);
Adam Cohenb5ba0972011-09-07 18:02:31 -07001021 mForegroundRect.set(mForegroundPadding, mForegroundPadding,
1022 w - 2 * mForegroundPadding, h - 2 * mForegroundPadding);
Michael Jurkadee05892010-07-27 10:01:56 -07001023 }
1024
1025 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001026 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001027 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001028 }
1029
1030 @Override
1031 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001032 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001033 }
1034
Michael Jurka5f1c5092010-09-03 14:15:02 -07001035 public float getBackgroundAlpha() {
1036 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -07001037 }
1038
Adam Cohen1b0aaac2010-10-28 11:11:18 -07001039 public void setBackgroundAlphaMultiplier(float multiplier) {
1040 mBackgroundAlphaMultiplier = multiplier;
1041 }
1042
Adam Cohenddb82192010-11-10 16:32:54 -08001043 public float getBackgroundAlphaMultiplier() {
1044 return mBackgroundAlphaMultiplier;
1045 }
1046
Michael Jurka5f1c5092010-09-03 14:15:02 -07001047 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -08001048 if (mBackgroundAlpha != alpha) {
1049 mBackgroundAlpha = alpha;
1050 invalidate();
1051 }
Michael Jurkadee05892010-07-27 10:01:56 -07001052 }
1053
Michael Jurkaa52570f2012-03-20 03:18:20 -07001054 public void setShortcutAndWidgetAlpha(float alpha) {
Michael Jurka0142d492010-08-25 17:46:15 -07001055 final int childCount = getChildCount();
1056 for (int i = 0; i < childCount; i++) {
Michael Jurkadee05892010-07-27 10:01:56 -07001057 getChildAt(i).setAlpha(alpha);
1058 }
1059 }
1060
Michael Jurkaa52570f2012-03-20 03:18:20 -07001061 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
1062 if (getChildCount() > 0) {
1063 return (ShortcutAndWidgetContainer) getChildAt(0);
1064 }
1065 return null;
1066 }
1067
Patrick Dubroy440c3602010-07-13 17:50:32 -07001068 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001069 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -07001070 }
1071
Adam Cohen76fc0852011-06-17 13:26:23 -07001072 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -08001073 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001074 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -08001075 boolean[][] occupied = mOccupied;
1076 if (!permanent) {
1077 occupied = mTmpOccupied;
1078 }
1079
Adam Cohen19f37922012-03-21 11:59:11 -07001080 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001081 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1082 final ItemInfo info = (ItemInfo) child.getTag();
1083
1084 // We cancel any existing animations
1085 if (mReorderAnimators.containsKey(lp)) {
1086 mReorderAnimators.get(lp).cancel();
1087 mReorderAnimators.remove(lp);
1088 }
1089
Adam Cohen482ed822012-03-02 14:15:13 -08001090 final int oldX = lp.x;
1091 final int oldY = lp.y;
1092 if (adjustOccupied) {
1093 occupied[lp.cellX][lp.cellY] = false;
1094 occupied[cellX][cellY] = true;
1095 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001096 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001097 if (permanent) {
1098 lp.cellX = info.cellX = cellX;
1099 lp.cellY = info.cellY = cellY;
1100 } else {
1101 lp.tmpCellX = cellX;
1102 lp.tmpCellY = cellY;
1103 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001104 clc.setupLp(lp);
1105 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001106 final int newX = lp.x;
1107 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001108
Adam Cohen76fc0852011-06-17 13:26:23 -07001109 lp.x = oldX;
1110 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001111
Adam Cohen482ed822012-03-02 14:15:13 -08001112 // Exit early if we're not actually moving the view
1113 if (oldX == newX && oldY == newY) {
1114 lp.isLockedToGrid = true;
1115 return true;
1116 }
1117
1118 ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
1119 va.setDuration(duration);
1120 mReorderAnimators.put(lp, va);
1121
1122 va.addUpdateListener(new AnimatorUpdateListener() {
1123 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001124 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001125 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001126 lp.x = (int) ((1 - r) * oldX + r * newX);
1127 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001128 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001129 }
1130 });
Adam Cohen482ed822012-03-02 14:15:13 -08001131 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001132 boolean cancelled = false;
1133 public void onAnimationEnd(Animator animation) {
1134 // If the animation was cancelled, it means that another animation
1135 // has interrupted this one, and we don't want to lock the item into
1136 // place just yet.
1137 if (!cancelled) {
1138 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001139 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001140 }
1141 if (mReorderAnimators.containsKey(lp)) {
1142 mReorderAnimators.remove(lp);
1143 }
1144 }
1145 public void onAnimationCancel(Animator animation) {
1146 cancelled = true;
1147 }
1148 });
Adam Cohen482ed822012-03-02 14:15:13 -08001149 va.setStartDelay(delay);
1150 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001151 return true;
1152 }
1153 return false;
1154 }
1155
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001156 /**
1157 * Estimate where the top left cell of the dragged item will land if it is dropped.
1158 *
1159 * @param originX The X value of the top left corner of the item
1160 * @param originY The Y value of the top left corner of the item
1161 * @param spanX The number of horizontal cells that the item spans
1162 * @param spanY The number of vertical cells that the item spans
1163 * @param result The estimated drop cell X and Y.
1164 */
1165 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
Adam Cohend22015c2010-07-26 22:02:18 -07001166 final int countX = mCountX;
1167 final int countY = mCountY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001168
Michael Jurkaa63c4522010-08-19 13:52:27 -07001169 // pointToCellRounded takes the top left of a cell but will pad that with
1170 // cellWidth/2 and cellHeight/2 when finding the matching cell
1171 pointToCellRounded(originX, originY, result);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001172
1173 // If the item isn't fully on this screen, snap to the edges
1174 int rightOverhang = result[0] + spanX - countX;
1175 if (rightOverhang > 0) {
1176 result[0] -= rightOverhang; // Snap to right
1177 }
1178 result[0] = Math.max(0, result[0]); // Snap to left
1179 int bottomOverhang = result[1] + spanY - countY;
1180 if (bottomOverhang > 0) {
1181 result[1] -= bottomOverhang; // Snap to bottom
1182 }
1183 result[1] = Math.max(0, result[1]); // Snap to top
1184 }
1185
Adam Cohen482ed822012-03-02 14:15:13 -08001186 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1187 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001188 final int oldDragCellX = mDragCell[0];
1189 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001190
Winson Chungb8c69f32011-10-19 21:36:08 -07001191 if (v != null && dragOffset == null) {
Winson Chunga9abd0e2010-10-27 17:18:37 -07001192 mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
1193 } else {
1194 mDragCenter.set(originX, originY);
1195 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001196
Adam Cohen2801caf2011-05-13 20:57:39 -07001197 if (dragOutline == null && v == null) {
1198 if (mCrosshairsDrawable != null) {
1199 invalidate();
1200 }
1201 return;
1202 }
1203
Adam Cohen482ed822012-03-02 14:15:13 -08001204 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1205 mDragCell[0] = cellX;
1206 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001207 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001208 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001209 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001210
Joe Onorato4be866d2010-10-10 11:26:02 -07001211 int left = topLeft[0];
1212 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001213
Winson Chungb8c69f32011-10-19 21:36:08 -07001214 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001215 // When drawing the drag outline, it did not account for margin offsets
1216 // added by the view's parent.
1217 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1218 left += lp.leftMargin;
1219 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001220
Adam Cohen99e8b402011-03-25 19:23:43 -07001221 // Offsets due to the size difference between the View and the dragOutline.
1222 // There is a size difference to account for the outer blur, which may lie
1223 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001224 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001225 // We center about the x axis
1226 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1227 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001228 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001229 if (dragOffset != null && dragRegion != null) {
1230 // Center the drag region *horizontally* in the cell and apply a drag
1231 // outline offset
1232 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1233 - dragRegion.width()) / 2;
1234 top += dragOffset.y;
1235 } else {
1236 // Center the drag outline in the cell
1237 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1238 - dragOutline.getWidth()) / 2;
1239 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1240 - dragOutline.getHeight()) / 2;
1241 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001242 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001243 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001244 mDragOutlineAnims[oldIndex].animateOut();
1245 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001246 Rect r = mDragOutlines[mDragOutlineCurrent];
1247 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1248 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001249 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001250 }
Winson Chung150fbab2010-09-29 17:14:26 -07001251
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001252 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1253 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001254 }
Patrick Dubroy49250ad2010-10-08 15:33:52 -07001255
1256 // If we are drawing crosshairs, the entire CellLayout needs to be invalidated
1257 if (mCrosshairsDrawable != null) {
1258 invalidate();
1259 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001260 }
1261
Adam Cohene0310962011-04-18 16:15:31 -07001262 public void clearDragOutlines() {
1263 final int oldIndex = mDragOutlineCurrent;
1264 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001265 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001266 }
1267
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001268 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001269 * Find a vacant area that will fit the given bounds nearest the requested
1270 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001271 *
Romain Guy51afc022009-05-04 18:03:43 -07001272 * @param pixelX The X location at which you want to search for a vacant area.
1273 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001274 * @param spanX Horizontal span of the object.
1275 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001276 * @param result Array in which to place the result, or null (in which case a new array will
1277 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001278 * @return The X, Y cell of a vacant area that can contain this object,
1279 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001280 */
Adam Cohend41fbf52012-02-16 23:53:59 -08001281 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
1282 int[] result) {
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001283 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001284 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001285
Michael Jurka6a1435d2010-09-27 17:35:12 -07001286 /**
1287 * Find a vacant area that will fit the given bounds nearest the requested
1288 * cell location. Uses Euclidean distance to score multiple vacant areas.
1289 *
1290 * @param pixelX The X location at which you want to search for a vacant area.
1291 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001292 * @param minSpanX The minimum horizontal span required
1293 * @param minSpanY The minimum vertical span required
1294 * @param spanX Horizontal span of the object.
1295 * @param spanY Vertical span of the object.
1296 * @param result Array in which to place the result, or null (in which case a new array will
1297 * be allocated)
1298 * @return The X, Y cell of a vacant area that can contain this object,
1299 * nearest the requested location.
1300 */
1301 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1302 int spanY, int[] result, int[] resultSpan) {
1303 return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
1304 result, resultSpan);
1305 }
1306
1307 /**
1308 * Find a vacant area that will fit the given bounds nearest the requested
1309 * cell location. Uses Euclidean distance to score multiple vacant areas.
1310 *
1311 * @param pixelX The X location at which you want to search for a vacant area.
1312 * @param pixelY The Y location at which you want to search for a vacant area.
Michael Jurka6a1435d2010-09-27 17:35:12 -07001313 * @param spanX Horizontal span of the object.
1314 * @param spanY Vertical span of the object.
Adam Cohendf035382011-04-11 17:22:04 -07001315 * @param ignoreOccupied If true, the result can be an occupied cell
1316 * @param result Array in which to place the result, or null (in which case a new array will
1317 * be allocated)
Michael Jurka6a1435d2010-09-27 17:35:12 -07001318 * @return The X, Y cell of a vacant area that can contain this object,
1319 * nearest the requested location.
1320 */
Adam Cohendf035382011-04-11 17:22:04 -07001321 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
1322 boolean ignoreOccupied, int[] result) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001323 return findNearestArea(pixelX, pixelY, spanX, spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001324 spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08001325 }
1326
1327 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1328 private void lazyInitTempRectStack() {
1329 if (mTempRectStack.isEmpty()) {
1330 for (int i = 0; i < mCountX * mCountY; i++) {
1331 mTempRectStack.push(new Rect());
1332 }
1333 }
1334 }
Adam Cohen482ed822012-03-02 14:15:13 -08001335
Adam Cohend41fbf52012-02-16 23:53:59 -08001336 private void recycleTempRects(Stack<Rect> used) {
1337 while (!used.isEmpty()) {
1338 mTempRectStack.push(used.pop());
1339 }
1340 }
1341
1342 /**
1343 * Find a vacant area that will fit the given bounds nearest the requested
1344 * cell location. Uses Euclidean distance to score multiple vacant areas.
1345 *
1346 * @param pixelX The X location at which you want to search for a vacant area.
1347 * @param pixelY The Y location at which you want to search for a vacant area.
1348 * @param minSpanX The minimum horizontal span required
1349 * @param minSpanY The minimum vertical span required
1350 * @param spanX Horizontal span of the object.
1351 * @param spanY Vertical span of the object.
1352 * @param ignoreOccupied If true, the result can be an occupied cell
1353 * @param result Array in which to place the result, or null (in which case a new array will
1354 * be allocated)
1355 * @return The X, Y cell of a vacant area that can contain this object,
1356 * nearest the requested location.
1357 */
1358 int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001359 View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
1360 boolean[][] occupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001361 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001362 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08001363 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001364
Adam Cohene3e27a82011-04-15 12:07:39 -07001365 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1366 // to the center of the item, but we are searching based on the top-left cell, so
1367 // we translate the point over to correspond to the top-left.
1368 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1369 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1370
Jeff Sharkey70864282009-04-07 21:08:40 -07001371 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001372 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001373 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001374 final Rect bestRect = new Rect(-1, -1, -1, -1);
1375 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001376
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001377 final int countX = mCountX;
1378 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001379
Adam Cohend41fbf52012-02-16 23:53:59 -08001380 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1381 spanX < minSpanX || spanY < minSpanY) {
1382 return bestXY;
1383 }
1384
1385 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001386 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001387 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1388 int ySize = -1;
1389 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001390 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001391 // First, let's see if this thing fits anywhere
1392 for (int i = 0; i < minSpanX; i++) {
1393 for (int j = 0; j < minSpanY; j++) {
Adam Cohendf035382011-04-11 17:22:04 -07001394 if (occupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001395 continue inner;
1396 }
Michael Jurkac28de512010-08-13 11:27:44 -07001397 }
1398 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001399 xSize = minSpanX;
1400 ySize = minSpanY;
1401
1402 // We know that the item will fit at _some_ acceptable size, now let's see
1403 // how big we can make it. We'll alternate between incrementing x and y spans
1404 // until we hit a limit.
1405 boolean incX = true;
1406 boolean hitMaxX = xSize >= spanX;
1407 boolean hitMaxY = ySize >= spanY;
1408 while (!(hitMaxX && hitMaxY)) {
1409 if (incX && !hitMaxX) {
1410 for (int j = 0; j < ySize; j++) {
1411 if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
1412 // We can't move out horizontally
1413 hitMaxX = true;
1414 }
1415 }
1416 if (!hitMaxX) {
1417 xSize++;
1418 }
1419 } else if (!hitMaxY) {
1420 for (int i = 0; i < xSize; i++) {
1421 if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
1422 // We can't move out vertically
1423 hitMaxY = true;
1424 }
1425 }
1426 if (!hitMaxY) {
1427 ySize++;
1428 }
1429 }
1430 hitMaxX |= xSize >= spanX;
1431 hitMaxY |= ySize >= spanY;
1432 incX = !incX;
1433 }
1434 incX = true;
1435 hitMaxX = xSize >= spanX;
1436 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001437 }
Winson Chung0be025d2011-05-23 17:45:09 -07001438 final int[] cellXY = mTmpXY;
Adam Cohene3e27a82011-04-15 12:07:39 -07001439 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001440
Adam Cohend41fbf52012-02-16 23:53:59 -08001441 // We verify that the current rect is not a sub-rect of any of our previous
1442 // candidates. In this case, the current rect is disqualified in favour of the
1443 // containing rect.
1444 Rect currentRect = mTempRectStack.pop();
1445 currentRect.set(x, y, x + xSize, y + ySize);
1446 boolean contained = false;
1447 for (Rect r : validRegions) {
1448 if (r.contains(currentRect)) {
1449 contained = true;
1450 break;
1451 }
1452 }
1453 validRegions.push(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001454 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
1455 + Math.pow(cellXY[1] - pixelY, 2));
Adam Cohen482ed822012-03-02 14:15:13 -08001456
Adam Cohend41fbf52012-02-16 23:53:59 -08001457 if ((distance <= bestDistance && !contained) ||
1458 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001459 bestDistance = distance;
1460 bestXY[0] = x;
1461 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001462 if (resultSpan != null) {
1463 resultSpan[0] = xSize;
1464 resultSpan[1] = ySize;
1465 }
1466 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001467 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001468 }
1469 }
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001470 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08001471 markCellsAsOccupiedForView(ignoreView, occupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001472
Adam Cohenc0dcf592011-06-01 15:30:43 -07001473 // Return -1, -1 if no suitable location found
1474 if (bestDistance == Double.MAX_VALUE) {
1475 bestXY[0] = -1;
1476 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001477 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001478 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001479 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001480 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001481
Adam Cohen482ed822012-03-02 14:15:13 -08001482 /**
1483 * Find a vacant area that will fit the given bounds nearest the requested
1484 * cell location, and will also weigh in a suggested direction vector of the
1485 * desired location. This method computers distance based on unit grid distances,
1486 * not pixel distances.
1487 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001488 * @param cellX The X cell nearest to which you want to search for a vacant area.
1489 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001490 * @param spanX Horizontal span of the object.
1491 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001492 * @param direction The favored direction in which the views should move from x, y
1493 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1494 * matches exactly. Otherwise we find the best matching direction.
1495 * @param occoupied The array which represents which cells in the CellLayout are occupied
1496 * @param blockOccupied The array which represents which cells in the specified block (cellX,
1497 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001498 * @param result Array in which to place the result, or null (in which case a new array will
1499 * be allocated)
1500 * @return The X, Y cell of a vacant area that can contain this object,
1501 * nearest the requested location.
1502 */
1503 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001504 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001505 // Keep track of best-scoring drop area
1506 final int[] bestXY = result != null ? result : new int[2];
1507 float bestDistance = Float.MAX_VALUE;
1508 int bestDirectionScore = Integer.MIN_VALUE;
1509
1510 final int countX = mCountX;
1511 final int countY = mCountY;
1512
1513 for (int y = 0; y < countY - (spanY - 1); y++) {
1514 inner:
1515 for (int x = 0; x < countX - (spanX - 1); x++) {
1516 // First, let's see if this thing fits anywhere
1517 for (int i = 0; i < spanX; i++) {
1518 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001519 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001520 continue inner;
1521 }
1522 }
1523 }
1524
1525 float distance = (float)
1526 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1527 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001528 computeDirectionVector(x - cellX, y - cellY, curDirection);
1529 // The direction score is just the dot product of the two candidate direction
1530 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001531 int curDirectionScore = direction[0] * curDirection[0] +
1532 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001533 boolean exactDirectionOnly = false;
1534 boolean directionMatches = direction[0] == curDirection[0] &&
1535 direction[0] == curDirection[0];
1536 if ((directionMatches || !exactDirectionOnly) &&
1537 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001538 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1539 bestDistance = distance;
1540 bestDirectionScore = curDirectionScore;
1541 bestXY[0] = x;
1542 bestXY[1] = y;
1543 }
1544 }
1545 }
1546
1547 // Return -1, -1 if no suitable location found
1548 if (bestDistance == Float.MAX_VALUE) {
1549 bestXY[0] = -1;
1550 bestXY[1] = -1;
1551 }
1552 return bestXY;
1553 }
1554
Adam Cohen47a876d2012-03-19 13:21:41 -07001555 private int[] findNearestAreaInDirection(int cellX, int cellY, int spanX, int spanY,
1556 int[] direction,boolean[][] occupied,
1557 boolean blockOccupied[][], int[] result) {
1558 // Keep track of best-scoring drop area
1559 final int[] bestXY = result != null ? result : new int[2];
1560 bestXY[0] = -1;
1561 bestXY[1] = -1;
1562 float bestDistance = Float.MAX_VALUE;
1563
1564 // We use this to march in a single direction
1565 if (direction[0] != 0 && direction[1] != 0) {
1566 return bestXY;
1567 }
1568
1569 // This will only incrememnet one of x or y based on the assertion above
1570 int x = cellX + direction[0];
1571 int y = cellY + direction[1];
1572 while (x >= 0 && x + spanX <= mCountX && y >= 0 && y + spanY <= mCountY) {
1573
1574 boolean fail = false;
1575 for (int i = 0; i < spanX; i++) {
1576 for (int j = 0; j < spanY; j++) {
1577 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
1578 fail = true;
1579 }
1580 }
1581 }
1582 if (!fail) {
1583 float distance = (float)
1584 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1585 if (Float.compare(distance, bestDistance) < 0) {
1586 bestDistance = distance;
1587 bestXY[0] = x;
1588 bestXY[1] = y;
1589 }
1590 }
1591 x += direction[0];
1592 y += direction[1];
1593 }
1594 return bestXY;
1595 }
1596
Adam Cohen482ed822012-03-02 14:15:13 -08001597 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001598 int[] direction, ItemConfiguration currentState) {
1599 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001600 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001601 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001602 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1603
Adam Cohen8baab352012-03-20 17:39:21 -07001604 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001605
1606 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001607 c.x = mTempLocation[0];
1608 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001609 success = true;
1610
1611 }
Adam Cohen8baab352012-03-20 17:39:21 -07001612 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001613 return success;
1614 }
1615
Adam Cohen47a876d2012-03-19 13:21:41 -07001616 // This method looks in the specified direction to see if there is an additional view
1617 // immediately adjecent in that direction
1618 private boolean addViewInDirection(ArrayList<View> views, Rect boundingRect, int[] direction,
Adam Cohen19f37922012-03-21 11:59:11 -07001619 boolean[][] occupied, View dragView, ItemConfiguration currentState) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001620 boolean found = false;
1621
Michael Jurkaa52570f2012-03-20 03:18:20 -07001622 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen47a876d2012-03-19 13:21:41 -07001623 Rect r0 = new Rect(boundingRect);
1624 Rect r1 = new Rect();
1625
1626 int deltaX = 0;
1627 int deltaY = 0;
1628 if (direction[1] < 0) {
1629 r0.set(r0.left, r0.top - 1, r0.right, r0.bottom);
1630 deltaY = -1;
1631 } else if (direction[1] > 0) {
1632 r0.set(r0.left, r0.top, r0.right, r0.bottom + 1);
1633 deltaY = 1;
1634 } else if (direction[0] < 0) {
1635 r0.set(r0.left - 1, r0.top, r0.right, r0.bottom);
1636 deltaX = -1;
1637 } else if (direction[0] > 0) {
1638 r0.set(r0.left, r0.top, r0.right + 1, r0.bottom);
1639 deltaX = 1;
1640 }
1641
1642 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001643 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen19f37922012-03-21 11:59:11 -07001644 if (views.contains(child) || child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001645 CellAndSpan c = currentState.map.get(child);
Adam Cohen47a876d2012-03-19 13:21:41 -07001646
Adam Cohen8baab352012-03-20 17:39:21 -07001647 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1648 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen47a876d2012-03-19 13:21:41 -07001649 if (Rect.intersects(r0, r1)) {
1650 if (!lp.canReorder) {
1651 return false;
1652 }
1653 boolean pushed = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001654 for (int x = c.x; x < c.x + c.spanX; x++) {
1655 for (int y = c.y; y < c.y + c.spanY; y++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001656 boolean inBounds = x - deltaX >= 0 && x -deltaX < mCountX
1657 && y - deltaY >= 0 && y - deltaY < mCountY;
1658 if (inBounds && occupied[x - deltaX][y - deltaY]) {
1659 pushed = true;
1660 }
1661 }
1662 }
1663 if (pushed) {
1664 views.add(child);
Adam Cohen8baab352012-03-20 17:39:21 -07001665 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen47a876d2012-03-19 13:21:41 -07001666 found = true;
1667 }
1668 }
1669 }
1670 return found;
1671 }
1672
Adam Cohen482ed822012-03-02 14:15:13 -08001673 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohen19f37922012-03-21 11:59:11 -07001674 int[] direction, boolean push, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001675 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001676
Adam Cohen8baab352012-03-20 17:39:21 -07001677 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001678 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001679 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001680 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001681 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001682 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001683 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001684 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001685 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001686 }
1687 }
Adam Cohen8baab352012-03-20 17:39:21 -07001688
1689 @SuppressWarnings("unchecked")
1690 ArrayList<View> dup = (ArrayList<View>) views.clone();
1691 // We try and expand the group of views in the direction vector passed, based on
1692 // whether they are physically adjacent, ie. based on "push mechanics".
Adam Cohen19f37922012-03-21 11:59:11 -07001693 while (push && addViewInDirection(dup, boundingRect, direction, mTmpOccupied, dragView,
Adam Cohen8baab352012-03-20 17:39:21 -07001694 currentState)) {
1695 }
1696
1697 // Mark the occupied state as false for the group of views we want to move.
1698 for (View v: dup) {
1699 CellAndSpan c = currentState.map.get(v);
1700 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1701 }
1702
Adam Cohen47a876d2012-03-19 13:21:41 -07001703 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1704 int top = boundingRect.top;
1705 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001706 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
1707 // for tetris-style interlocking.
1708 for (View v: dup) {
1709 CellAndSpan c = currentState.map.get(v);
1710 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001711 }
1712
Adam Cohen482ed822012-03-02 14:15:13 -08001713 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1714
Adam Cohen8baab352012-03-20 17:39:21 -07001715 if (push) {
1716 findNearestAreaInDirection(boundingRect.left, boundingRect.top, boundingRect.width(),
1717 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
1718 } else {
1719 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1720 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
1721 }
Adam Cohen482ed822012-03-02 14:15:13 -08001722
Adam Cohen8baab352012-03-20 17:39:21 -07001723 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001724 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001725 int deltaX = mTempLocation[0] - boundingRect.left;
1726 int deltaY = mTempLocation[1] - boundingRect.top;
1727 for (View v: dup) {
1728 CellAndSpan c = currentState.map.get(v);
1729 c.x += deltaX;
1730 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001731 }
1732 success = true;
1733 }
Adam Cohen8baab352012-03-20 17:39:21 -07001734
1735 // In either case, we set the occupied array as marked for the location of the views
1736 for (View v: dup) {
1737 CellAndSpan c = currentState.map.get(v);
1738 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001739 }
1740 return success;
1741 }
1742
1743 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1744 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1745 }
1746
1747 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001748 View ignoreView, ItemConfiguration solution) {
Adam Cohen482ed822012-03-02 14:15:13 -08001749
Adam Cohen8baab352012-03-20 17:39:21 -07001750 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001751 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001752
Adam Cohen8baab352012-03-20 17:39:21 -07001753 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001754 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001755 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001756 if (c != null) {
1757 c.x = cellX;
1758 c.y = cellY;
1759 }
Adam Cohen482ed822012-03-02 14:15:13 -08001760 }
Adam Cohen482ed822012-03-02 14:15:13 -08001761 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1762 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001763 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001764 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001765 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001766 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001767 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001768 if (Rect.intersects(r0, r1)) {
1769 if (!lp.canReorder) {
1770 return false;
1771 }
1772 mIntersectingViews.add(child);
1773 }
1774 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001775
Adam Cohen8baab352012-03-20 17:39:21 -07001776 // We try to move the intersecting views as a block using the push mechanic
Adam Cohen19f37922012-03-21 11:59:11 -07001777 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, true, ignoreView,
1778 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001779 return true;
1780 }
1781 // Try the opposite direction
1782 direction[0] *= -1;
1783 direction[1] *= -1;
Adam Cohen19f37922012-03-21 11:59:11 -07001784 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, true, ignoreView,
1785 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001786 return true;
1787 }
1788 // Switch the direction back
1789 direction[0] *= -1;
1790 direction[1] *= -1;
1791
Adam Cohen8baab352012-03-20 17:39:21 -07001792 // Next we try moving the views as a block , but without requiring the push mechanic
Adam Cohen19f37922012-03-21 11:59:11 -07001793 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, false, ignoreView,
1794 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001795 return true;
1796 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001797
Adam Cohen482ed822012-03-02 14:15:13 -08001798 // Ok, they couldn't move as a block, let's move them individually
1799 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001800 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001801 return false;
1802 }
1803 }
1804 return true;
1805 }
1806
1807 /*
1808 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1809 * the provided point and the provided cell
1810 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001811 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001812 double angle = Math.atan(((float) deltaY) / deltaX);
1813
1814 result[0] = 0;
1815 result[1] = 0;
1816 if (Math.abs(Math.cos(angle)) > 0.5f) {
1817 result[0] = (int) Math.signum(deltaX);
1818 }
1819 if (Math.abs(Math.sin(angle)) > 0.5f) {
1820 result[1] = (int) Math.signum(deltaY);
1821 }
1822 }
1823
Adam Cohen8baab352012-03-20 17:39:21 -07001824 private void copyOccupiedArray(boolean[][] occupied) {
1825 for (int i = 0; i < mCountX; i++) {
1826 for (int j = 0; j < mCountY; j++) {
1827 occupied[i][j] = mOccupied[i][j];
1828 }
1829 }
1830 }
1831
Adam Cohen482ed822012-03-02 14:15:13 -08001832 ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1833 int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001834 // Copy the current state into the solution. This solution will be manipulated as necessary.
1835 copyCurrentStateToSolution(solution, false);
1836 // Copy the current occupied array into the temporary occupied array. This array will be
1837 // manipulated as necessary to find a solution.
1838 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001839
1840 // We find the nearest cell into which we would place the dragged item, assuming there's
1841 // nothing in its way.
1842 int result[] = new int[2];
1843 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1844
1845 boolean success = false;
1846 // First we try the exact nearest position of the item being dragged,
1847 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001848 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1849 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001850
1851 if (!success) {
1852 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1853 // x, then 1 in y etc.
1854 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
1855 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
1856 dragView, false, solution);
1857 } else if (spanY > minSpanY) {
1858 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
1859 dragView, true, solution);
1860 }
1861 solution.isSolution = false;
1862 } else {
1863 solution.isSolution = true;
1864 solution.dragViewX = result[0];
1865 solution.dragViewY = result[1];
1866 solution.dragViewSpanX = spanX;
1867 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08001868 }
1869 return solution;
1870 }
1871
1872 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001873 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001874 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001875 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001876 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001877 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08001878 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07001879 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001880 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001881 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001882 }
Adam Cohen8baab352012-03-20 17:39:21 -07001883 solution.map.put(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08001884 }
1885 }
1886
1887 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
1888 for (int i = 0; i < mCountX; i++) {
1889 for (int j = 0; j < mCountY; j++) {
1890 mTmpOccupied[i][j] = false;
1891 }
1892 }
1893
Michael Jurkaa52570f2012-03-20 03:18:20 -07001894 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001895 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001896 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001897 if (child == dragView) continue;
1898 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001899 CellAndSpan c = solution.map.get(child);
1900 if (c != null) {
1901 lp.tmpCellX = c.x;
1902 lp.tmpCellY = c.y;
1903 lp.cellHSpan = c.spanX;
1904 lp.cellVSpan = c.spanY;
1905 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001906 }
1907 }
1908 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
1909 solution.dragViewSpanY, mTmpOccupied, true);
1910 }
1911
1912 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
1913 commitDragView) {
1914
1915 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
1916 for (int i = 0; i < mCountX; i++) {
1917 for (int j = 0; j < mCountY; j++) {
1918 occupied[i][j] = false;
1919 }
1920 }
1921
Michael Jurkaa52570f2012-03-20 03:18:20 -07001922 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001923 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001924 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001925 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001926 CellAndSpan c = solution.map.get(child);
1927 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07001928 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
1929 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07001930 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001931 }
1932 }
1933 if (commitDragView) {
1934 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
1935 solution.dragViewSpanY, occupied, true);
1936 }
1937 }
1938
Adam Cohen19f37922012-03-21 11:59:11 -07001939 // This method starts or changes the reorder hint animations
1940 private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
1941 int childCount = mShortcutsAndWidgets.getChildCount();
1942 int timeForPriorAnimationToComplete = getMaxCompletionTime();
1943 for (int i = 0; i < childCount; i++) {
1944 View child = mShortcutsAndWidgets.getChildAt(i);
1945 if (child == dragView) continue;
1946 CellAndSpan c = solution.map.get(child);
1947 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1948 if (c != null) {
1949 ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
1950 c.x, c.y, c.spanX, c.spanY);
1951 rha.animate(timeForPriorAnimationToComplete);
1952 }
1953 }
1954 }
1955
1956 // Class which represents the reorder hint animations. These animations show that an item is
1957 // in a temporary state, and hint at where the item will return to.
1958 class ReorderHintAnimation {
1959 View child;
1960 float deltaX;
1961 float deltaY;
1962 private static final int DURATION = 140;
1963 private int repeatCount;
1964 private boolean cancelOnCycleComplete = false;
1965 ValueAnimator va;
1966
1967 public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
1968 int spanX, int spanY) {
1969 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
1970 final int x0 = mTmpPoint[0];
1971 final int y0 = mTmpPoint[1];
1972 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
1973 final int x1 = mTmpPoint[0];
1974 final int y1 = mTmpPoint[1];
1975 final int dX = x1 - x0;
1976 final int dY = y1 - y0;
1977 deltaX = 0;
1978 deltaY = 0;
1979 if (dX == dY && dX == 0) {
1980 } else {
1981 if (dY == 0) {
1982 deltaX = mReorderHintAnimationMagnitude;
1983 } else if (dX == 0) {
1984 deltaY = mReorderHintAnimationMagnitude;
1985 } else {
1986 double angle = Math.atan( (float) (dY) / dX);
1987 deltaX = (int) (Math.cos(angle) * mReorderHintAnimationMagnitude);
1988 deltaY = (int) (Math.sin(angle) * mReorderHintAnimationMagnitude);
1989 }
1990 }
1991 this.child = child;
1992 }
1993
1994 void animate(int delay) {
1995 if (mShakeAnimators.containsKey(child)) {
1996 ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
1997 oldAnimation.completeAnimation();
1998 mShakeAnimators.remove(child);
1999 }
2000 if (deltaX == 0 && deltaY == 0) {
2001 return;
2002 }
2003 va = ValueAnimator.ofFloat(0f, 1f);
2004 va.setRepeatMode(ValueAnimator.REVERSE);
2005 va.setRepeatCount(ValueAnimator.INFINITE);
2006 va.setDuration(DURATION);
2007 va.addUpdateListener(new AnimatorUpdateListener() {
2008 @Override
2009 public void onAnimationUpdate(ValueAnimator animation) {
2010 float r = ((Float) animation.getAnimatedValue()).floatValue();
2011 float x = r * deltaX;
2012 float y = r * deltaY;
2013 child.setTranslationX(x);
2014 child.setTranslationY(y);
2015 }
2016 });
2017 va.addListener(new AnimatorListenerAdapter() {
2018 public void onAnimationRepeat(Animator animation) {
2019 repeatCount++;
2020 // We make sure to end only after a full period
2021 if (cancelOnCycleComplete && repeatCount % 2 == 0) {
2022 va.cancel();
2023 }
2024 }
2025 });
2026 va.setStartDelay(Math.max(REORDER_ANIMATION_DURATION, delay));
2027 mShakeAnimators.put(child, this);
2028 va.start();
2029 }
2030
2031
2032 private void completeAnimation() {
2033 cancelOnCycleComplete = true;
2034 }
2035
2036 // Returns the time required to complete the current oscillating animation
2037 private int completionTime() {
2038 if (repeatCount % 2 == 0) {
2039 return (int) (va.getDuration() - va.getCurrentPlayTime() + DURATION);
2040 } else {
2041 return (int) (va.getDuration() - va.getCurrentPlayTime());
2042 }
2043 }
2044 }
2045
2046 private void completeAndClearReorderHintAnimations() {
2047 for (ReorderHintAnimation a: mShakeAnimators.values()) {
2048 a.completeAnimation();
2049 }
2050 mShakeAnimators.clear();
2051 }
2052
2053 private int getMaxCompletionTime() {
2054 int maxTime = 0;
2055 for (ReorderHintAnimation a: mShakeAnimators.values()) {
2056 maxTime = Math.max(maxTime, a.completionTime());
2057 }
2058 return maxTime;
2059 }
2060
Adam Cohen482ed822012-03-02 14:15:13 -08002061 private void commitTempPlacement() {
2062 for (int i = 0; i < mCountX; i++) {
2063 for (int j = 0; j < mCountY; j++) {
2064 mOccupied[i][j] = mTmpOccupied[i][j];
2065 }
2066 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002067 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002068 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002069 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002070 lp.cellX = lp.tmpCellX;
2071 lp.cellY = lp.tmpCellY;
2072 }
2073 }
2074
2075 public void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002076 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002077 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002078 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002079 lp.useTmpCoords = useTempCoords;
2080 }
2081 }
2082
Adam Cohen482ed822012-03-02 14:15:13 -08002083 ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
2084 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2085 int[] result = new int[2];
2086 int[] resultSpan = new int[2];
2087 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
2088 resultSpan);
2089 if (result[0] >= 0 && result[1] >= 0) {
2090 copyCurrentStateToSolution(solution, false);
2091 solution.dragViewX = result[0];
2092 solution.dragViewY = result[1];
2093 solution.dragViewSpanX = resultSpan[0];
2094 solution.dragViewSpanY = resultSpan[1];
2095 solution.isSolution = true;
2096 } else {
2097 solution.isSolution = false;
2098 }
2099 return solution;
2100 }
2101
2102 public void prepareChildForDrag(View child) {
2103 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002104 }
2105
Adam Cohen19f37922012-03-21 11:59:11 -07002106 /* This seems like it should be obvious and straight-forward, but when the direction vector
2107 needs to match with the notion of the dragView pushing other views, we have to employ
2108 a slightly more subtle notion of the direction vector. The question is what two points is
2109 the vector between? The center of the dragView and its desired destination? Not quite, as
2110 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2111 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2112 or right, which helps make pushing feel right.
2113 */
2114 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2115 int spanY, View dragView, int[] resultDirection) {
2116 int[] targetDestination = new int[2];
2117
2118 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2119 Rect dragRect = new Rect();
2120 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2121 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2122
2123 Rect dropRegionRect = new Rect();
2124 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2125 dragView, dropRegionRect, mIntersectingViews);
2126
2127 int dropRegionSpanX = dropRegionRect.width();
2128 int dropRegionSpanY = dropRegionRect.height();
2129
2130 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2131 dropRegionRect.height(), dropRegionRect);
2132
2133 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2134 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2135
2136 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2137 deltaX = 0;
2138 }
2139 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2140 deltaY = 0;
2141 }
2142
2143 if (deltaX == 0 && deltaY == 0) {
2144 // No idea what to do, give a random direction.
2145 resultDirection[0] = 1;
2146 resultDirection[1] = 0;
2147 } else {
2148 computeDirectionVector(deltaX, deltaY, resultDirection);
2149 }
2150 }
2151
2152 // For a given cell and span, fetch the set of views intersecting the region.
2153 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2154 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2155 if (boundingRect != null) {
2156 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2157 }
2158 intersectingViews.clear();
2159 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2160 Rect r1 = new Rect();
2161 final int count = mShortcutsAndWidgets.getChildCount();
2162 for (int i = 0; i < count; i++) {
2163 View child = mShortcutsAndWidgets.getChildAt(i);
2164 if (child == dragView) continue;
2165 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2166 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2167 if (Rect.intersects(r0, r1)) {
2168 mIntersectingViews.add(child);
2169 if (boundingRect != null) {
2170 boundingRect.union(r1);
2171 }
2172 }
2173 }
2174 }
2175
2176 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2177 View dragView, int[] result) {
2178 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2179 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2180 mIntersectingViews);
2181 return !mIntersectingViews.isEmpty();
2182 }
2183
2184 void revertTempState() {
2185 if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
2186 final int count = mShortcutsAndWidgets.getChildCount();
2187 for (int i = 0; i < count; i++) {
2188 View child = mShortcutsAndWidgets.getChildAt(i);
2189 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2190 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2191 lp.tmpCellX = lp.cellX;
2192 lp.tmpCellY = lp.cellY;
2193 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2194 0, false, false);
2195 }
2196 }
2197 completeAndClearReorderHintAnimations();
2198 setItemPlacementDirty(false);
2199 }
2200
Adam Cohen482ed822012-03-02 14:15:13 -08002201 int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
2202 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002203 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002204 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002205
2206 if (resultSpan == null) {
2207 resultSpan = new int[2];
2208 }
2209
Adam Cohen19f37922012-03-21 11:59:11 -07002210 // When we are checking drop validity or actually dropping, we don't recompute the
2211 // direction vector, since we want the solution to match the preview, and it's possible
2212 // that the exact position of the item has changed to result in a new reordering outcome.
2213 if ((mode == MODE_ON_DROP || mode == MODE_ACCEPT_DROP)
2214 && mPreviousReorderDirection[0] != -1) {
2215 mDirectionVector[0] = mPreviousReorderDirection[0];
2216 mDirectionVector[1] = mPreviousReorderDirection[1];
2217 // We reset this vector after drop
2218 if (mode == MODE_ON_DROP) {
2219 mPreviousReorderDirection[0] = -1;
2220 mPreviousReorderDirection[1] = -1;
2221 }
2222 } else {
2223 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2224 mPreviousReorderDirection[0] = mDirectionVector[0];
2225 mPreviousReorderDirection[1] = mDirectionVector[1];
2226 }
2227
Adam Cohen482ed822012-03-02 14:15:13 -08002228 ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
2229 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2230
2231 // We attempt the approach which doesn't shuffle views at all
2232 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2233 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2234
2235 ItemConfiguration finalSolution = null;
2236 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2237 finalSolution = swapSolution;
2238 } else if (noShuffleSolution.isSolution) {
2239 finalSolution = noShuffleSolution;
2240 }
2241
2242 boolean foundSolution = true;
2243 if (!DESTRUCTIVE_REORDER) {
2244 setUseTempCoords(true);
2245 }
2246
2247 if (finalSolution != null) {
2248 result[0] = finalSolution.dragViewX;
2249 result[1] = finalSolution.dragViewY;
2250 resultSpan[0] = finalSolution.dragViewSpanX;
2251 resultSpan[1] = finalSolution.dragViewSpanY;
2252
2253 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2254 // committing anything or animating anything as we just want to determine if a solution
2255 // exists
2256 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2257 if (!DESTRUCTIVE_REORDER) {
2258 copySolutionToTempState(finalSolution, dragView);
2259 }
2260 setItemPlacementDirty(true);
2261 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2262
Adam Cohen19f37922012-03-21 11:59:11 -07002263 if (!DESTRUCTIVE_REORDER &&
2264 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002265 commitTempPlacement();
Adam Cohen19f37922012-03-21 11:59:11 -07002266 completeAndClearReorderHintAnimations();
2267 setItemPlacementDirty(false);
2268 } else {
2269 beginOrAdjustHintAnimations(finalSolution, dragView,
2270 REORDER_ANIMATION_DURATION);
Adam Cohen482ed822012-03-02 14:15:13 -08002271 }
2272 }
2273 } else {
2274 foundSolution = false;
2275 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2276 }
2277
2278 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2279 setUseTempCoords(false);
2280 }
Adam Cohen482ed822012-03-02 14:15:13 -08002281
Michael Jurkaa52570f2012-03-20 03:18:20 -07002282 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002283 return result;
2284 }
2285
Adam Cohen19f37922012-03-21 11:59:11 -07002286 void setItemPlacementDirty(boolean dirty) {
2287 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002288 }
Adam Cohen19f37922012-03-21 11:59:11 -07002289 boolean isItemPlacementDirty() {
2290 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002291 }
2292
2293 private class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002294 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohen482ed822012-03-02 14:15:13 -08002295 boolean isSolution = false;
2296 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2297
2298 int area() {
2299 return dragViewSpanX * dragViewSpanY;
2300 }
Adam Cohen8baab352012-03-20 17:39:21 -07002301 }
2302
2303 private class CellAndSpan {
2304 int x, y;
2305 int spanX, spanY;
2306
2307 public CellAndSpan(int x, int y, int spanX, int spanY) {
2308 this.x = x;
2309 this.y = y;
2310 this.spanX = spanX;
2311 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002312 }
2313 }
2314
Adam Cohendf035382011-04-11 17:22:04 -07002315 /**
2316 * Find a vacant area that will fit the given bounds nearest the requested
2317 * cell location. Uses Euclidean distance to score multiple vacant areas.
2318 *
2319 * @param pixelX The X location at which you want to search for a vacant area.
2320 * @param pixelY The Y location at which you want to search for a vacant area.
2321 * @param spanX Horizontal span of the object.
2322 * @param spanY Vertical span of the object.
2323 * @param ignoreView Considers space occupied by this view as unoccupied
2324 * @param result Previously returned value to possibly recycle.
2325 * @return The X, Y cell of a vacant area that can contain this object,
2326 * nearest the requested location.
2327 */
2328 int[] findNearestVacantArea(
2329 int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
2330 return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
2331 }
2332
2333 /**
Adam Cohend41fbf52012-02-16 23:53:59 -08002334 * Find a vacant area that will fit the given bounds nearest the requested
2335 * cell location. Uses Euclidean distance to score multiple vacant areas.
2336 *
2337 * @param pixelX The X location at which you want to search for a vacant area.
2338 * @param pixelY The Y location at which you want to search for a vacant area.
2339 * @param minSpanX The minimum horizontal span required
2340 * @param minSpanY The minimum vertical span required
2341 * @param spanX Horizontal span of the object.
2342 * @param spanY Vertical span of the object.
2343 * @param ignoreView Considers space occupied by this view as unoccupied
2344 * @param result Previously returned value to possibly recycle.
2345 * @return The X, Y cell of a vacant area that can contain this object,
2346 * nearest the requested location.
2347 */
2348 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
2349 int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
Adam Cohen482ed822012-03-02 14:15:13 -08002350 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
2351 result, resultSpan, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08002352 }
2353
2354 /**
Adam Cohendf035382011-04-11 17:22:04 -07002355 * Find a starting cell position that will fit the given bounds nearest the requested
2356 * cell location. Uses Euclidean distance to score multiple vacant areas.
2357 *
2358 * @param pixelX The X location at which you want to search for a vacant area.
2359 * @param pixelY The Y location at which you want to search for a vacant area.
2360 * @param spanX Horizontal span of the object.
2361 * @param spanY Vertical span of the object.
2362 * @param ignoreView Considers space occupied by this view as unoccupied
2363 * @param result Previously returned value to possibly recycle.
2364 * @return The X, Y cell of a vacant area that can contain this object,
2365 * nearest the requested location.
2366 */
2367 int[] findNearestArea(
2368 int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2369 return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
2370 }
2371
Michael Jurka0280c3b2010-09-17 15:00:07 -07002372 boolean existsEmptyCell() {
2373 return findCellForSpan(null, 1, 1);
2374 }
2375
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002376 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002377 * Finds the upper-left coordinate of the first rectangle in the grid that can
2378 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2379 * then this method will only return coordinates for rectangles that contain the cell
2380 * (intersectX, intersectY)
2381 *
2382 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2383 * can be found.
2384 * @param spanX The horizontal span of the cell we want to find.
2385 * @param spanY The vertical span of the cell we want to find.
2386 *
2387 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002388 */
Michael Jurka0280c3b2010-09-17 15:00:07 -07002389 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Adam Cohen482ed822012-03-02 14:15:13 -08002390 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002391 }
2392
2393 /**
2394 * Like above, but ignores any cells occupied by the item "ignoreView"
2395 *
2396 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2397 * can be found.
2398 * @param spanX The horizontal span of the cell we want to find.
2399 * @param spanY The vertical span of the cell we want to find.
2400 * @param ignoreView The home screen item we should treat as not occupying any space
2401 * @return
2402 */
2403 boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
Adam Cohen482ed822012-03-02 14:15:13 -08002404 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
2405 ignoreView, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002406 }
2407
2408 /**
2409 * Like above, but if intersectX and intersectY are not -1, then this method will try to
2410 * return coordinates for rectangles that contain the cell [intersectX, intersectY]
2411 *
2412 * @param spanX The horizontal span of the cell we want to find.
2413 * @param spanY The vertical span of the cell we want to find.
2414 * @param ignoreView The home screen item we should treat as not occupying any space
2415 * @param intersectX The X coordinate of the cell that we should try to overlap
2416 * @param intersectX The Y coordinate of the cell that we should try to overlap
2417 *
2418 * @return True if a vacant cell of the specified dimension was found, false otherwise.
2419 */
2420 boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
2421 int intersectX, int intersectY) {
2422 return findCellForSpanThatIntersectsIgnoring(
Adam Cohen482ed822012-03-02 14:15:13 -08002423 cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002424 }
2425
2426 /**
2427 * The superset of the above two methods
2428 */
2429 boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002430 int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002431 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08002432 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002433
Michael Jurka28750fb2010-09-24 17:43:49 -07002434 boolean foundCell = false;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002435 while (true) {
2436 int startX = 0;
2437 if (intersectX >= 0) {
2438 startX = Math.max(startX, intersectX - (spanX - 1));
2439 }
2440 int endX = mCountX - (spanX - 1);
2441 if (intersectX >= 0) {
2442 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
2443 }
2444 int startY = 0;
2445 if (intersectY >= 0) {
2446 startY = Math.max(startY, intersectY - (spanY - 1));
2447 }
2448 int endY = mCountY - (spanY - 1);
2449 if (intersectY >= 0) {
2450 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
2451 }
2452
Winson Chungbbc60d82010-11-11 16:34:41 -08002453 for (int y = startY; y < endY && !foundCell; y++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002454 inner:
Winson Chungbbc60d82010-11-11 16:34:41 -08002455 for (int x = startX; x < endX; x++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002456 for (int i = 0; i < spanX; i++) {
2457 for (int j = 0; j < spanY; j++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002458 if (occupied[x + i][y + j]) {
Winson Chungbbc60d82010-11-11 16:34:41 -08002459 // small optimization: we can skip to after the column we just found
Michael Jurka0280c3b2010-09-17 15:00:07 -07002460 // an occupied cell
Winson Chungbbc60d82010-11-11 16:34:41 -08002461 x += i;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002462 continue inner;
2463 }
2464 }
2465 }
2466 if (cellXY != null) {
2467 cellXY[0] = x;
2468 cellXY[1] = y;
2469 }
Michael Jurka28750fb2010-09-24 17:43:49 -07002470 foundCell = true;
2471 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002472 }
2473 }
2474 if (intersectX == -1 && intersectY == -1) {
2475 break;
2476 } else {
2477 // if we failed to find anything, try again but without any requirements of
2478 // intersecting
2479 intersectX = -1;
2480 intersectY = -1;
2481 continue;
2482 }
2483 }
2484
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002485 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08002486 markCellsAsOccupiedForView(ignoreView, occupied);
Michael Jurka28750fb2010-09-24 17:43:49 -07002487 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002488 }
2489
2490 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002491 * A drag event has begun over this layout.
2492 * It may have begun over this layout (in which case onDragChild is called first),
2493 * or it may have begun on another layout.
2494 */
2495 void onDragEnter() {
2496 if (!mDragging) {
2497 // Fade in the drag indicators
2498 if (mCrosshairsAnimator != null) {
2499 mCrosshairsAnimator.animateIn();
2500 }
2501 }
2502 mDragging = true;
2503 }
2504
2505 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002506 * Called when drag has left this CellLayout or has been completed (successfully or not)
2507 */
2508 void onDragExit() {
Joe Onorato4be866d2010-10-10 11:26:02 -07002509 // This can actually be called when we aren't in a drag, e.g. when adding a new
2510 // item to this layout via the customize drawer.
2511 // Guard against that case.
2512 if (mDragging) {
2513 mDragging = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002514
Joe Onorato4be866d2010-10-10 11:26:02 -07002515 // Fade out the drag indicators
2516 if (mCrosshairsAnimator != null) {
2517 mCrosshairsAnimator.animateOut();
2518 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002519 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002520
2521 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002522 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002523 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2524 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002525 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002526 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002527 }
2528
2529 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002530 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002531 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002532 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002533 *
2534 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002535 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002536 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002537 if (child != null) {
2538 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002539 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002540 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002541 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002542 }
2543
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002544 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002545 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002546 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002547 * @param cellX X coordinate of upper left corner expressed as a cell position
2548 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002549 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002550 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002551 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002552 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002553 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002554 final int cellWidth = mCellWidth;
2555 final int cellHeight = mCellHeight;
2556 final int widthGap = mWidthGap;
2557 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002558
Winson Chung4b825dcd2011-06-19 12:41:22 -07002559 final int hStartPadding = getPaddingLeft();
2560 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002561
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002562 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2563 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2564
2565 int x = hStartPadding + cellX * (cellWidth + widthGap);
2566 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002567
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002568 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002569 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002570
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002571 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002572 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002573 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002574 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002575 * @param width Width in pixels
2576 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002577 * @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 -08002578 */
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002579 public int[] rectToCell(int width, int height, int[] result) {
Michael Jurka9987a5c2010-10-08 16:58:12 -07002580 return rectToCell(getResources(), width, height, result);
2581 }
2582
2583 public static int[] rectToCell(Resources resources, int width, int height, int[] result) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002584 // Always assume we're working with the smallest span to make sure we
2585 // reserve enough space in both orientations.
Joe Onorato79e56262009-09-21 15:23:04 -04002586 int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
2587 int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002588 int smallerSize = Math.min(actualWidth, actualHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002589
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002590 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002591 int spanX = (int) Math.ceil(width / (float) smallerSize);
2592 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002593
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002594 if (result == null) {
2595 return new int[] { spanX, spanY };
2596 }
2597 result[0] = spanX;
2598 result[1] = spanY;
2599 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002600 }
2601
Michael Jurkaf12c75c2011-01-25 22:41:40 -08002602 public int[] cellSpansToSize(int hSpans, int vSpans) {
2603 int[] size = new int[2];
2604 size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
2605 size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
2606 return size;
2607 }
2608
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002609 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08002610 * Calculate the grid spans needed to fit given item
2611 */
2612 public void calculateSpans(ItemInfo info) {
2613 final int minWidth;
2614 final int minHeight;
2615
2616 if (info instanceof LauncherAppWidgetInfo) {
2617 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
2618 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
2619 } else if (info instanceof PendingAddWidgetInfo) {
2620 minWidth = ((PendingAddWidgetInfo) info).minWidth;
2621 minHeight = ((PendingAddWidgetInfo) info).minHeight;
2622 } else {
2623 // It's not a widget, so it must be 1x1
2624 info.spanX = info.spanY = 1;
2625 return;
2626 }
2627 int[] spans = rectToCell(minWidth, minHeight, null);
2628 info.spanX = spans[0];
2629 info.spanY = spans[1];
2630 }
2631
2632 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002633 * Find the first vacant cell, if there is one.
2634 *
2635 * @param vacant Holds the x and y coordinate of the vacant cell
2636 * @param spanX Horizontal cell span.
2637 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -07002638 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002639 * @return True if a vacant cell was found
2640 */
2641 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002642
Michael Jurka0280c3b2010-09-17 15:00:07 -07002643 return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002644 }
2645
2646 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
2647 int xCount, int yCount, boolean[][] occupied) {
2648
Adam Cohen2801caf2011-05-13 20:57:39 -07002649 for (int y = 0; y < yCount; y++) {
2650 for (int x = 0; x < xCount; x++) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002651 boolean available = !occupied[x][y];
2652out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
2653 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
2654 available = available && !occupied[i][j];
2655 if (!available) break out;
2656 }
2657 }
2658
2659 if (available) {
2660 vacant[0] = x;
2661 vacant[1] = y;
2662 return true;
2663 }
2664 }
2665 }
2666
2667 return false;
2668 }
2669
Michael Jurka0280c3b2010-09-17 15:00:07 -07002670 private void clearOccupiedCells() {
2671 for (int x = 0; x < mCountX; x++) {
2672 for (int y = 0; y < mCountY; y++) {
2673 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002674 }
2675 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002676 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002677
Adam Cohen1b607ed2011-03-03 17:26:50 -08002678 /**
2679 * Given a view, determines how much that view can be expanded in all directions, in terms of
2680 * whether or not there are other items occupying adjacent cells. Used by the
2681 * AppWidgetResizeFrame to determine how the widget can be resized.
2682 */
Adam Cohend4844c32011-02-18 19:25:06 -08002683 public void getExpandabilityArrayForView(View view, int[] expandability) {
Adam Cohen1b607ed2011-03-03 17:26:50 -08002684 final LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohend4844c32011-02-18 19:25:06 -08002685 boolean flag;
2686
Adam Cohen1b607ed2011-03-03 17:26:50 -08002687 expandability[AppWidgetResizeFrame.LEFT] = 0;
Adam Cohend4844c32011-02-18 19:25:06 -08002688 for (int x = lp.cellX - 1; x >= 0; x--) {
2689 flag = false;
2690 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan; y++) {
2691 if (mOccupied[x][y]) flag = true;
2692 }
2693 if (flag) break;
Adam Cohen1b607ed2011-03-03 17:26:50 -08002694 expandability[AppWidgetResizeFrame.LEFT]++;
Adam Cohend4844c32011-02-18 19:25:06 -08002695 }
2696
Adam Cohen1b607ed2011-03-03 17:26:50 -08002697 expandability[AppWidgetResizeFrame.TOP] = 0;
Adam Cohend4844c32011-02-18 19:25:06 -08002698 for (int y = lp.cellY - 1; y >= 0; y--) {
2699 flag = false;
2700 for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan; x++) {
2701 if (mOccupied[x][y]) flag = true;
2702 }
2703 if (flag) break;
Adam Cohen1b607ed2011-03-03 17:26:50 -08002704 expandability[AppWidgetResizeFrame.TOP]++;
2705 }
Adam Cohend4844c32011-02-18 19:25:06 -08002706
Adam Cohen1b607ed2011-03-03 17:26:50 -08002707 expandability[AppWidgetResizeFrame.RIGHT] = 0;
Adam Cohend4844c32011-02-18 19:25:06 -08002708 for (int x = lp.cellX + lp.cellHSpan; x < mCountX; x++) {
2709 flag = false;
2710 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan; y++) {
2711 if (mOccupied[x][y]) flag = true;
2712 }
2713 if (flag) break;
Adam Cohen1b607ed2011-03-03 17:26:50 -08002714 expandability[AppWidgetResizeFrame.RIGHT]++;
2715 }
Adam Cohend4844c32011-02-18 19:25:06 -08002716
Adam Cohen1b607ed2011-03-03 17:26:50 -08002717 expandability[AppWidgetResizeFrame.BOTTOM] = 0;
Adam Cohend4844c32011-02-18 19:25:06 -08002718 for (int y = lp.cellY + lp.cellVSpan; y < mCountY; y++) {
2719 flag = false;
2720 for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan; x++) {
2721 if (mOccupied[x][y]) flag = true;
2722 }
2723 if (flag) break;
Adam Cohen1b607ed2011-03-03 17:26:50 -08002724 expandability[AppWidgetResizeFrame.BOTTOM]++;
2725 }
Adam Cohend4844c32011-02-18 19:25:06 -08002726 }
2727
Adam Cohend41fbf52012-02-16 23:53:59 -08002728 public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002729 markCellsAsUnoccupiedForView(view);
Adam Cohen482ed822012-03-02 14:15:13 -08002730 markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002731 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002732
Adam Cohend4844c32011-02-18 19:25:06 -08002733 public void markCellsAsOccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08002734 markCellsAsOccupiedForView(view, mOccupied);
2735 }
2736 public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002737 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002738 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002739 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002740 }
2741
Adam Cohend4844c32011-02-18 19:25:06 -08002742 public void markCellsAsUnoccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08002743 markCellsAsUnoccupiedForView(view, mOccupied);
2744 }
2745 public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002746 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002747 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002748 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002749 }
2750
Adam Cohen482ed822012-03-02 14:15:13 -08002751 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2752 boolean value) {
2753 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002754 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2755 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002756 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002757 }
2758 }
2759 }
2760
Adam Cohen2801caf2011-05-13 20:57:39 -07002761 public int getDesiredWidth() {
Winson Chung4b825dcd2011-06-19 12:41:22 -07002762 return mPaddingLeft + mPaddingRight + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002763 (Math.max((mCountX - 1), 0) * mWidthGap);
2764 }
2765
2766 public int getDesiredHeight() {
Winson Chung4b825dcd2011-06-19 12:41:22 -07002767 return mPaddingTop + mPaddingBottom + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002768 (Math.max((mCountY - 1), 0) * mHeightGap);
2769 }
2770
Michael Jurka66d72172011-04-12 16:29:25 -07002771 public boolean isOccupied(int x, int y) {
2772 if (x < mCountX && y < mCountY) {
2773 return mOccupied[x][y];
2774 } else {
2775 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2776 }
2777 }
2778
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002779 @Override
2780 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2781 return new CellLayout.LayoutParams(getContext(), attrs);
2782 }
2783
2784 @Override
2785 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2786 return p instanceof CellLayout.LayoutParams;
2787 }
2788
2789 @Override
2790 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2791 return new CellLayout.LayoutParams(p);
2792 }
2793
Winson Chungaafa03c2010-06-11 17:34:16 -07002794 public static class CellLayoutAnimationController extends LayoutAnimationController {
2795 public CellLayoutAnimationController(Animation animation, float delay) {
2796 super(animation, delay);
2797 }
2798
2799 @Override
2800 protected long getDelayForView(View view) {
2801 return (int) (Math.random() * 150);
2802 }
2803 }
2804
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002805 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2806 /**
2807 * Horizontal location of the item in the grid.
2808 */
2809 @ViewDebug.ExportedProperty
2810 public int cellX;
2811
2812 /**
2813 * Vertical location of the item in the grid.
2814 */
2815 @ViewDebug.ExportedProperty
2816 public int cellY;
2817
2818 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002819 * Temporary horizontal location of the item in the grid during reorder
2820 */
2821 public int tmpCellX;
2822
2823 /**
2824 * Temporary vertical location of the item in the grid during reorder
2825 */
2826 public int tmpCellY;
2827
2828 /**
2829 * Indicates that the temporary coordinates should be used to layout the items
2830 */
2831 public boolean useTmpCoords;
2832
2833 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002834 * Number of cells spanned horizontally by the item.
2835 */
2836 @ViewDebug.ExportedProperty
2837 public int cellHSpan;
2838
2839 /**
2840 * Number of cells spanned vertically by the item.
2841 */
2842 @ViewDebug.ExportedProperty
2843 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002844
Adam Cohen1b607ed2011-03-03 17:26:50 -08002845 /**
2846 * Indicates whether the item will set its x, y, width and height parameters freely,
2847 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2848 */
Adam Cohend4844c32011-02-18 19:25:06 -08002849 public boolean isLockedToGrid = true;
2850
Adam Cohen482ed822012-03-02 14:15:13 -08002851 /**
2852 * Indicates whether this item can be reordered. Always true except in the case of the
2853 * the AllApps button.
2854 */
2855 public boolean canReorder = true;
2856
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002857 // X coordinate of the view in the layout.
2858 @ViewDebug.ExportedProperty
2859 int x;
2860 // Y coordinate of the view in the layout.
2861 @ViewDebug.ExportedProperty
2862 int y;
2863
Romain Guy84f296c2009-11-04 15:00:44 -08002864 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002865
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002866 public LayoutParams(Context c, AttributeSet attrs) {
2867 super(c, attrs);
2868 cellHSpan = 1;
2869 cellVSpan = 1;
2870 }
2871
2872 public LayoutParams(ViewGroup.LayoutParams source) {
2873 super(source);
2874 cellHSpan = 1;
2875 cellVSpan = 1;
2876 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002877
2878 public LayoutParams(LayoutParams source) {
2879 super(source);
2880 this.cellX = source.cellX;
2881 this.cellY = source.cellY;
2882 this.cellHSpan = source.cellHSpan;
2883 this.cellVSpan = source.cellVSpan;
2884 }
2885
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002886 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002887 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002888 this.cellX = cellX;
2889 this.cellY = cellY;
2890 this.cellHSpan = cellHSpan;
2891 this.cellVSpan = cellVSpan;
2892 }
2893
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002894 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap) {
Adam Cohend4844c32011-02-18 19:25:06 -08002895 if (isLockedToGrid) {
2896 final int myCellHSpan = cellHSpan;
2897 final int myCellVSpan = cellVSpan;
Adam Cohen482ed822012-03-02 14:15:13 -08002898 final int myCellX = useTmpCoords ? tmpCellX : cellX;
2899 final int myCellY = useTmpCoords ? tmpCellY : cellY;
Adam Cohen1b607ed2011-03-03 17:26:50 -08002900
Adam Cohend4844c32011-02-18 19:25:06 -08002901 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2902 leftMargin - rightMargin;
2903 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2904 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002905 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2906 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002907 }
2908 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002909
Winson Chungaafa03c2010-06-11 17:34:16 -07002910 public String toString() {
2911 return "(" + this.cellX + ", " + this.cellY + ")";
2912 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002913
2914 public void setWidth(int width) {
2915 this.width = width;
2916 }
2917
2918 public int getWidth() {
2919 return width;
2920 }
2921
2922 public void setHeight(int height) {
2923 this.height = height;
2924 }
2925
2926 public int getHeight() {
2927 return height;
2928 }
2929
2930 public void setX(int x) {
2931 this.x = x;
2932 }
2933
2934 public int getX() {
2935 return x;
2936 }
2937
2938 public void setY(int y) {
2939 this.y = y;
2940 }
2941
2942 public int getY() {
2943 return y;
2944 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002945 }
2946
Michael Jurka0280c3b2010-09-17 15:00:07 -07002947 // This class stores info for two purposes:
2948 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2949 // its spanX, spanY, and the screen it is on
2950 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2951 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2952 // the CellLayout that was long clicked
Michael Jurkae5fb0f22011-04-11 13:27:46 -07002953 static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002954 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07002955 int cellX = -1;
2956 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002957 int spanX;
2958 int spanY;
2959 int screen;
Winson Chung3d503fb2011-07-13 17:25:49 -07002960 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002961
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002962 @Override
2963 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002964 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2965 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002966 }
2967 }
Michael Jurkad771c962011-08-09 15:00:48 -07002968
2969 public boolean lastDownOnOccupiedCell() {
2970 return mLastDownOnOccupiedCell;
2971 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002972}