blob: 5aa39c425fb3674f2ab3e5aa5c7c565eedb314d2 [file] [log] [blame]
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Joe Onoratoa5902522009-07-30 13:37:37 -070017package com.android.launcher2;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080018
Joe Onorato4be866d2010-10-10 11:26:02 -070019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
Chet Haase00397b12010-10-07 11:13:10 -070021import android.animation.TimeInterpolator;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070022import android.animation.ValueAnimator;
23import android.animation.ValueAnimator.AnimatorUpdateListener;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080024import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040025import android.content.res.Resources;
Winson Chungaafa03c2010-06-11 17:34:16 -070026import android.content.res.TypedArray;
Joe Onorato4be866d2010-10-10 11:26:02 -070027import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070028import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080029import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070030import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070031import android.graphics.Point;
32import android.graphics.PointF;
Adam Cohenb5ba0972011-09-07 18:02:31 -070033import android.graphics.PorterDuff;
34import android.graphics.PorterDuffXfermode;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080035import android.graphics.Rect;
Adam Cohen482ed822012-03-02 14:15:13 -080036import android.graphics.drawable.ColorDrawable;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070037import android.graphics.drawable.Drawable;
Adam Cohenb5ba0972011-09-07 18:02:31 -070038import android.graphics.drawable.NinePatchDrawable;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080039import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070040import android.util.Log;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080041import android.view.MotionEvent;
42import android.view.View;
43import android.view.ViewDebug;
44import android.view.ViewGroup;
Winson Chungaafa03c2010-06-11 17:34:16 -070045import android.view.animation.Animation;
Winson Chung150fbab2010-09-29 17:14:26 -070046import android.view.animation.DecelerateInterpolator;
Winson Chungaafa03c2010-06-11 17:34:16 -070047import android.view.animation.LayoutAnimationController;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080048
Adam Cohen66396872011-04-15 17:50:36 -070049import com.android.launcher.R;
Adam Cohen69ce2e52011-07-03 19:25:21 -070050import com.android.launcher2.FolderIcon.FolderRingAnimator;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070051
Adam Cohen69ce2e52011-07-03 19:25:21 -070052import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070053import java.util.Arrays;
Adam Cohenbfbfd262011-06-13 16:55:12 -070054import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080055import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070056
Michael Jurkabdb5c532011-02-01 15:05:06 -080057public class CellLayout extends ViewGroup {
Winson Chungaafa03c2010-06-11 17:34:16 -070058 static final String TAG = "CellLayout";
59
Adam Cohen2acce882012-03-28 19:03:19 -070060 private Launcher mLauncher;
Winson Chung4b825dcd2011-06-19 12:41:22 -070061 private int mOriginalCellWidth;
62 private int mOriginalCellHeight;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080063 private int mCellWidth;
64 private int mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070065
Adam Cohend22015c2010-07-26 22:02:18 -070066 private int mCountX;
67 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080068
Adam Cohen234c4cd2011-07-17 21:03:04 -070069 private int mOriginalWidthGap;
70 private int mOriginalHeightGap;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080071 private int mWidthGap;
72 private int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070073 private int mMaxGap;
Adam Cohenebea84d2011-11-09 17:20:41 -080074 private boolean mScrollingTransformsDirty = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080075
76 private final Rect mRect = new Rect();
77 private final CellInfo mCellInfo = new CellInfo();
Winson Chungaafa03c2010-06-11 17:34:16 -070078
Patrick Dubroyde7658b2010-09-27 11:15:43 -070079 // These are temporary variables to prevent having to allocate a new object just to
80 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Winson Chung0be025d2011-05-23 17:45:09 -070081 private final int[] mTmpXY = new int[2];
Patrick Dubroyde7658b2010-09-27 11:15:43 -070082 private final int[] mTmpPoint = new int[2];
83 private final PointF mTmpPointF = new PointF();
Adam Cohen69ce2e52011-07-03 19:25:21 -070084 int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070085
The Android Open Source Project31dd5032009-03-03 19:32:27 -080086 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -080087 boolean[][] mTmpOccupied;
Michael Jurkad771c962011-08-09 15:00:48 -070088 private boolean mLastDownOnOccupiedCell = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080089
Michael Jurkadee05892010-07-27 10:01:56 -070090 private OnTouchListener mInterceptTouchListener;
91
Adam Cohen69ce2e52011-07-03 19:25:21 -070092 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -070093 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -070094
Adam Cohenb5ba0972011-09-07 18:02:31 -070095 private int mForegroundAlpha = 0;
Michael Jurka5f1c5092010-09-03 14:15:02 -070096 private float mBackgroundAlpha;
Adam Cohen1b0aaac2010-10-28 11:11:18 -070097 private float mBackgroundAlphaMultiplier = 1.0f;
Adam Cohenf34bab52010-09-30 14:11:56 -070098
Michael Jurka33945b22010-12-21 18:19:38 -080099 private Drawable mNormalBackground;
Michael Jurka33945b22010-12-21 18:19:38 -0800100 private Drawable mActiveGlowBackground;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700101 private Drawable mOverScrollForegroundDrawable;
102 private Drawable mOverScrollLeft;
103 private Drawable mOverScrollRight;
Michael Jurka18014792010-10-14 09:01:34 -0700104 private Rect mBackgroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700105 private Rect mForegroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700106 private int mForegroundPadding;
Patrick Dubroy1262e362010-10-06 15:49:50 -0700107
Michael Jurka33945b22010-12-21 18:19:38 -0800108 // If we're actively dragging something over this screen, mIsDragOverlapping is true
109 private boolean mIsDragOverlapping = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700110 private final Point mDragCenter = new Point();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700111
Winson Chung150fbab2010-09-29 17:14:26 -0700112 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700113 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohend41fbf52012-02-16 23:53:59 -0800114 private Rect[] mDragOutlines = new Rect[4];
Chet Haase472b2812010-10-14 07:02:04 -0700115 private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700116 private InterruptibleInOutAnimator[] mDragOutlineAnims =
117 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700118
119 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700120 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700121 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700122
Patrick Dubroy96864c32011-03-10 17:17:23 -0800123 private BubbleTextView mPressedOrFocusedIcon;
124
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700125 private Drawable mCrosshairsDrawable = null;
Patrick Dubroy49250ad2010-10-08 15:33:52 -0700126 private InterruptibleInOutAnimator mCrosshairsAnimator = null;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700127 private float mCrosshairsVisibility = 0.0f;
128
Adam Cohen482ed822012-03-02 14:15:13 -0800129 private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
130 HashMap<CellLayout.LayoutParams, Animator>();
Adam Cohen19f37922012-03-21 11:59:11 -0700131 private HashMap<View, ReorderHintAnimation>
132 mShakeAnimators = new HashMap<View, ReorderHintAnimation>();
133
134 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700135
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700136 // When a drag operation is in progress, holds the nearest cell to the touch point
137 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800138
Joe Onorato4be866d2010-10-10 11:26:02 -0700139 private boolean mDragging = false;
140
Patrick Dubroyce34a972010-10-19 10:34:32 -0700141 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700142 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700143
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800144 private boolean mIsHotseat = false;
Winson Chungeecf02d2012-03-02 17:14:58 -0800145 private float mChildScale = 1f;
146 private float mHotseatChildScale = 1f;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800147
Adam Cohen482ed822012-03-02 14:15:13 -0800148 public static final int MODE_DRAG_OVER = 0;
149 public static final int MODE_ON_DROP = 1;
150 public static final int MODE_ON_DROP_EXTERNAL = 2;
151 public static final int MODE_ACCEPT_DROP = 3;
Adam Cohen19f37922012-03-21 11:59:11 -0700152 private static final boolean DESTRUCTIVE_REORDER = false;
Adam Cohen482ed822012-03-02 14:15:13 -0800153 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
154
Adam Cohen19f37922012-03-21 11:59:11 -0700155 private static final float REORDER_HINT_MAGNITUDE = 0.27f;
156 private static final int REORDER_ANIMATION_DURATION = 150;
157 private float mReorderHintAnimationMagnitude;
158
Adam Cohen482ed822012-03-02 14:15:13 -0800159 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
160 private Rect mOccupiedRect = new Rect();
161 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700162 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700163 private static final int INVALID_DIRECTION = -100;
Adam Cohen482ed822012-03-02 14:15:13 -0800164
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800165 public CellLayout(Context context) {
166 this(context, null);
167 }
168
169 public CellLayout(Context context, AttributeSet attrs) {
170 this(context, attrs, 0);
171 }
172
173 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
174 super(context, attrs, defStyle);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700175
176 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
177 // the user where a dragged item will land when dropped.
178 setWillNotDraw(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700179 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700180
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800181 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
182
Winson Chung4b825dcd2011-06-19 12:41:22 -0700183 mOriginalCellWidth =
184 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
185 mOriginalCellHeight =
186 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700187 mWidthGap = mOriginalWidthGap = a.getDimensionPixelSize(R.styleable.CellLayout_widthGap, 0);
188 mHeightGap = mOriginalHeightGap = a.getDimensionPixelSize(R.styleable.CellLayout_heightGap, 0);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700189 mMaxGap = a.getDimensionPixelSize(R.styleable.CellLayout_maxGap, 0);
Adam Cohend22015c2010-07-26 22:02:18 -0700190 mCountX = LauncherModel.getCellCountX();
191 mCountY = LauncherModel.getCellCountY();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700192 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800193 mTmpOccupied = new boolean[mCountX][mCountY];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800194
195 a.recycle();
196
197 setAlwaysDrawnWithCacheEnabled(false);
198
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700199 final Resources res = getResources();
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700200
Winson Chung967289b2011-06-30 18:09:30 -0700201 mNormalBackground = res.getDrawable(R.drawable.homescreen_blue_normal_holo);
Winson Chungdea74b72011-09-13 18:06:43 -0700202 mActiveGlowBackground = res.getDrawable(R.drawable.homescreen_blue_strong_holo);
Michael Jurka33945b22010-12-21 18:19:38 -0800203
Adam Cohenb5ba0972011-09-07 18:02:31 -0700204 mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
205 mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
206 mForegroundPadding =
207 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
Michael Jurka33945b22010-12-21 18:19:38 -0800208
Adam Cohen19f37922012-03-21 11:59:11 -0700209 mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE *
210 res.getDimensionPixelSize(R.dimen.app_icon_size));
211
Winson Chungb26f3d62011-06-02 10:49:29 -0700212 mNormalBackground.setFilterBitmap(true);
Winson Chungb26f3d62011-06-02 10:49:29 -0700213 mActiveGlowBackground.setFilterBitmap(true);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700214
Winson Chungeecf02d2012-03-02 17:14:58 -0800215 int iconScale = res.getInteger(R.integer.app_icon_scale_percent);
216 if (iconScale >= 0) {
217 mChildScale = iconScale / 100f;
218 }
219 int hotseatIconScale = res.getInteger(R.integer.app_icon_hotseat_scale_percent);
220 if (hotseatIconScale >= 0) {
221 mHotseatChildScale = hotseatIconScale / 100f;
222 }
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800223
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700224 // Initialize the data structures used for the drag visualization.
Winson Chung150fbab2010-09-29 17:14:26 -0700225
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700226 mCrosshairsDrawable = res.getDrawable(R.drawable.gardening_crosshairs);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700227 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700228
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700229 // Set up the animation for fading the crosshairs in and out
230 int animDuration = res.getInteger(R.integer.config_crosshairsFadeInTime);
Patrick Dubroy49250ad2010-10-08 15:33:52 -0700231 mCrosshairsAnimator = new InterruptibleInOutAnimator(animDuration, 0.0f, 1.0f);
Chet Haase472b2812010-10-14 07:02:04 -0700232 mCrosshairsAnimator.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700233 public void onAnimationUpdate(ValueAnimator animation) {
234 mCrosshairsVisibility = ((Float) animation.getAnimatedValue()).floatValue();
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700235 invalidate();
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700236 }
237 });
Patrick Dubroyce34a972010-10-19 10:34:32 -0700238 mCrosshairsAnimator.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700239
Winson Chungb8c69f32011-10-19 21:36:08 -0700240 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700241 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800242 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700243 }
244
245 // When dragging things around the home screens, we show a green outline of
246 // where the item will land. The outlines gradually fade out, leaving a trail
247 // behind the drag path.
248 // Set up all the animations that are used to implement this fading.
249 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700250 final float fromAlphaValue = 0;
251 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700252
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700253 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700254
255 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700256 final InterruptibleInOutAnimator anim =
257 new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700258 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700259 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700260 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700261 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700262 final Bitmap outline = (Bitmap)anim.getTag();
263
264 // If an animation is started and then stopped very quickly, we can still
265 // get spurious updates we've cleared the tag. Guard against this.
266 if (outline == null) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700267 if (false) {
268 Object val = animation.getAnimatedValue();
269 Log.d(TAG, "anim " + thisIndex + " update: " + val +
270 ", isStopped " + anim.isStopped());
271 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700272 // Try to prevent it from continuing to run
273 animation.cancel();
274 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700275 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800276 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700277 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700278 }
279 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700280 // The animation holds a reference to the drag outline bitmap as long is it's
281 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700282 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700283 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700284 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700285 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700286 anim.setTag(null);
287 }
288 }
289 });
290 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700291 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700292
Michael Jurka18014792010-10-14 09:01:34 -0700293 mBackgroundRect = new Rect();
Adam Cohenb5ba0972011-09-07 18:02:31 -0700294 mForegroundRect = new Rect();
Michael Jurkabea15192010-11-17 12:33:46 -0800295
Michael Jurkaa52570f2012-03-20 03:18:20 -0700296 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
297 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
298 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700299 }
300
Michael Jurkaf6440da2011-04-05 14:50:34 -0700301 static int widthInPortrait(Resources r, int numCells) {
302 // We use this method from Workspace to figure out how many rows/columns Launcher should
303 // have. We ignore the left/right padding on CellLayout because it turns out in our design
304 // the padding extends outside the visible screen size, but it looked fine anyway.
Michael Jurkaf6440da2011-04-05 14:50:34 -0700305 int cellWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700306 int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
307 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
Michael Jurkaf6440da2011-04-05 14:50:34 -0700308
Winson Chung4b825dcd2011-06-19 12:41:22 -0700309 return minGap * (numCells - 1) + cellWidth * numCells;
Michael Jurkaf6440da2011-04-05 14:50:34 -0700310 }
311
Michael Jurkaf6440da2011-04-05 14:50:34 -0700312 static int heightInLandscape(Resources r, int numCells) {
313 // We use this method from Workspace to figure out how many rows/columns Launcher should
314 // have. We ignore the left/right padding on CellLayout because it turns out in our design
315 // the padding extends outside the visible screen size, but it looked fine anyway.
Michael Jurkaf6440da2011-04-05 14:50:34 -0700316 int cellHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700317 int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
318 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
Michael Jurkaf6440da2011-04-05 14:50:34 -0700319
Winson Chung4b825dcd2011-06-19 12:41:22 -0700320 return minGap * (numCells - 1) + cellHeight * numCells;
Michael Jurkaf6440da2011-04-05 14:50:34 -0700321 }
322
Adam Cohen2801caf2011-05-13 20:57:39 -0700323 public void enableHardwareLayers() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700324 mShortcutsAndWidgets.enableHardwareLayers();
Adam Cohen2801caf2011-05-13 20:57:39 -0700325 }
326
327 public void setGridSize(int x, int y) {
328 mCountX = x;
329 mCountY = y;
330 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800331 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700332 mTempRectStack.clear();
Adam Cohen76fc0852011-06-17 13:26:23 -0700333 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700334 }
335
Patrick Dubroy96864c32011-03-10 17:17:23 -0800336 private void invalidateBubbleTextView(BubbleTextView icon) {
337 final int padding = icon.getPressedOrFocusedBackgroundPadding();
Winson Chung4b825dcd2011-06-19 12:41:22 -0700338 invalidate(icon.getLeft() + getPaddingLeft() - padding,
339 icon.getTop() + getPaddingTop() - padding,
340 icon.getRight() + getPaddingLeft() + padding,
341 icon.getBottom() + getPaddingTop() + padding);
Patrick Dubroy96864c32011-03-10 17:17:23 -0800342 }
343
Adam Cohenb5ba0972011-09-07 18:02:31 -0700344 void setOverScrollAmount(float r, boolean left) {
345 if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
346 mOverScrollForegroundDrawable = mOverScrollLeft;
347 } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
348 mOverScrollForegroundDrawable = mOverScrollRight;
349 }
350
351 mForegroundAlpha = (int) Math.round((r * 255));
352 mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
353 invalidate();
354 }
355
Patrick Dubroy96864c32011-03-10 17:17:23 -0800356 void setPressedOrFocusedIcon(BubbleTextView icon) {
357 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
358 // requires an expanded clip rect (due to the glow's blur radius)
359 BubbleTextView oldIcon = mPressedOrFocusedIcon;
360 mPressedOrFocusedIcon = icon;
361 if (oldIcon != null) {
362 invalidateBubbleTextView(oldIcon);
363 }
364 if (mPressedOrFocusedIcon != null) {
365 invalidateBubbleTextView(mPressedOrFocusedIcon);
366 }
367 }
368
Michael Jurka33945b22010-12-21 18:19:38 -0800369 void setIsDragOverlapping(boolean isDragOverlapping) {
370 if (mIsDragOverlapping != isDragOverlapping) {
371 mIsDragOverlapping = isDragOverlapping;
372 invalidate();
373 }
374 }
375
376 boolean getIsDragOverlapping() {
377 return mIsDragOverlapping;
378 }
379
Adam Cohenebea84d2011-11-09 17:20:41 -0800380 protected void setOverscrollTransformsDirty(boolean dirty) {
381 mScrollingTransformsDirty = dirty;
382 }
383
384 protected void resetOverscrollTransforms() {
385 if (mScrollingTransformsDirty) {
386 setOverscrollTransformsDirty(false);
387 setTranslationX(0);
388 setRotationY(0);
389 // It doesn't matter if we pass true or false here, the important thing is that we
390 // pass 0, which results in the overscroll drawable not being drawn any more.
391 setOverScrollAmount(0, false);
392 setPivotX(getMeasuredWidth() / 2);
393 setPivotY(getMeasuredHeight() / 2);
394 }
395 }
396
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700397 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700398 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700399 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
400 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
401 // When we're small, we are either drawn normally or in the "accepts drops" state (during
402 // a drag). However, we also drag the mini hover background *over* one of those two
403 // backgrounds
Winson Chungb26f3d62011-06-02 10:49:29 -0700404 if (mBackgroundAlpha > 0.0f) {
Adam Cohenf34bab52010-09-30 14:11:56 -0700405 Drawable bg;
Michael Jurka33945b22010-12-21 18:19:38 -0800406
407 if (mIsDragOverlapping) {
408 // In the mini case, we draw the active_glow bg *over* the active background
Michael Jurkabdf78552011-10-31 14:34:25 -0700409 bg = mActiveGlowBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700410 } else {
Michael Jurkabdf78552011-10-31 14:34:25 -0700411 bg = mNormalBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700412 }
Michael Jurka33945b22010-12-21 18:19:38 -0800413
414 bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
415 bg.setBounds(mBackgroundRect);
416 bg.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700417 }
Romain Guya6abce82009-11-10 02:54:41 -0800418
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700419 if (mCrosshairsVisibility > 0.0f) {
420 final int countX = mCountX;
421 final int countY = mCountY;
422
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700423 final float MAX_ALPHA = 0.4f;
424 final int MAX_VISIBLE_DISTANCE = 600;
425 final float DISTANCE_MULTIPLIER = 0.002f;
426
427 final Drawable d = mCrosshairsDrawable;
428 final int width = d.getIntrinsicWidth();
429 final int height = d.getIntrinsicHeight();
430
Winson Chung4b825dcd2011-06-19 12:41:22 -0700431 int x = getPaddingLeft() - (mWidthGap / 2) - (width / 2);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700432 for (int col = 0; col <= countX; col++) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700433 int y = getPaddingTop() - (mHeightGap / 2) - (height / 2);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700434 for (int row = 0; row <= countY; row++) {
435 mTmpPointF.set(x - mDragCenter.x, y - mDragCenter.y);
436 float dist = mTmpPointF.length();
437 // Crosshairs further from the drag point are more faint
438 float alpha = Math.min(MAX_ALPHA,
439 DISTANCE_MULTIPLIER * (MAX_VISIBLE_DISTANCE - dist));
440 if (alpha > 0.0f) {
441 d.setBounds(x, y, x + width, y + height);
442 d.setAlpha((int) (alpha * 255 * mCrosshairsVisibility));
443 d.draw(canvas);
444 }
445 y += mCellHeight + mHeightGap;
446 }
447 x += mCellWidth + mWidthGap;
448 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700449 }
Winson Chung150fbab2010-09-29 17:14:26 -0700450
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700451 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700452 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700453 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700454 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800455 final Rect r = mDragOutlines[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700456 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700457 paint.setAlpha((int)(alpha + .5f));
Adam Cohend41fbf52012-02-16 23:53:59 -0800458 canvas.drawBitmap(b, null, r, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700459 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700460 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800461
462 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
463 // requires an expanded clip rect (due to the glow's blur radius)
464 if (mPressedOrFocusedIcon != null) {
465 final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
466 final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
467 if (b != null) {
468 canvas.drawBitmap(b,
Winson Chung4b825dcd2011-06-19 12:41:22 -0700469 mPressedOrFocusedIcon.getLeft() + getPaddingLeft() - padding,
470 mPressedOrFocusedIcon.getTop() + getPaddingTop() - padding,
Patrick Dubroy96864c32011-03-10 17:17:23 -0800471 null);
472 }
473 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700474
Adam Cohen482ed822012-03-02 14:15:13 -0800475 if (DEBUG_VISUALIZE_OCCUPIED) {
476 int[] pt = new int[2];
477 ColorDrawable cd = new ColorDrawable(Color.RED);
478 cd.setBounds(0, 0, 80, 80);
479 for (int i = 0; i < mCountX; i++) {
480 for (int j = 0; j < mCountY; j++) {
481 if (mOccupied[i][j]) {
482 cellToPoint(i, j, pt);
483 canvas.save();
484 canvas.translate(pt[0], pt[1]);
485 cd.draw(canvas);
486 canvas.restore();
487 }
488 }
489 }
490 }
491
Adam Cohen69ce2e52011-07-03 19:25:21 -0700492 // The folder outer / inner ring image(s)
493 for (int i = 0; i < mFolderOuterRings.size(); i++) {
494 FolderRingAnimator fra = mFolderOuterRings.get(i);
495
496 // Draw outer ring
497 Drawable d = FolderRingAnimator.sSharedOuterRingDrawable;
498 int width = (int) fra.getOuterRingSize();
499 int height = width;
500 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
501
502 int centerX = mTempLocation[0] + mCellWidth / 2;
503 int centerY = mTempLocation[1] + FolderRingAnimator.sPreviewSize / 2;
504
505 canvas.save();
506 canvas.translate(centerX - width / 2, centerY - height / 2);
507 d.setBounds(0, 0, width, height);
508 d.draw(canvas);
509 canvas.restore();
510
511 // Draw inner ring
512 d = FolderRingAnimator.sSharedInnerRingDrawable;
513 width = (int) fra.getInnerRingSize();
514 height = width;
515 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
516
517 centerX = mTempLocation[0] + mCellWidth / 2;
518 centerY = mTempLocation[1] + FolderRingAnimator.sPreviewSize / 2;
519 canvas.save();
520 canvas.translate(centerX - width / 2, centerY - width / 2);
521 d.setBounds(0, 0, width, height);
522 d.draw(canvas);
523 canvas.restore();
524 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700525
526 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
527 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
528 int width = d.getIntrinsicWidth();
529 int height = d.getIntrinsicHeight();
530
531 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
532 int centerX = mTempLocation[0] + mCellWidth / 2;
533 int centerY = mTempLocation[1] + FolderRingAnimator.sPreviewSize / 2;
534
535 canvas.save();
536 canvas.translate(centerX - width / 2, centerY - width / 2);
537 d.setBounds(0, 0, width, height);
538 d.draw(canvas);
539 canvas.restore();
540 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700541 }
542
Adam Cohenb5ba0972011-09-07 18:02:31 -0700543 @Override
544 protected void dispatchDraw(Canvas canvas) {
545 super.dispatchDraw(canvas);
546 if (mForegroundAlpha > 0) {
547 mOverScrollForegroundDrawable.setBounds(mForegroundRect);
548 Paint p = ((NinePatchDrawable) mOverScrollForegroundDrawable).getPaint();
549 p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
550 mOverScrollForegroundDrawable.draw(canvas);
551 p.setXfermode(null);
552 }
553 }
554
Adam Cohen69ce2e52011-07-03 19:25:21 -0700555 public void showFolderAccept(FolderRingAnimator fra) {
556 mFolderOuterRings.add(fra);
557 }
558
559 public void hideFolderAccept(FolderRingAnimator fra) {
560 if (mFolderOuterRings.contains(fra)) {
561 mFolderOuterRings.remove(fra);
562 }
563 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700564 }
565
Adam Cohenc51934b2011-07-26 21:07:43 -0700566 public void setFolderLeaveBehindCell(int x, int y) {
567 mFolderLeaveBehindCell[0] = x;
568 mFolderLeaveBehindCell[1] = y;
569 invalidate();
570 }
571
572 public void clearFolderLeaveBehind() {
573 mFolderLeaveBehindCell[0] = -1;
574 mFolderLeaveBehindCell[1] = -1;
575 invalidate();
576 }
577
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700578 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700579 public boolean shouldDelayChildPressedState() {
580 return false;
581 }
582
583 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700584 public void cancelLongPress() {
585 super.cancelLongPress();
586
587 // Cancel long press for all children
588 final int count = getChildCount();
589 for (int i = 0; i < count; i++) {
590 final View child = getChildAt(i);
591 child.cancelLongPress();
592 }
593 }
594
Michael Jurkadee05892010-07-27 10:01:56 -0700595 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
596 mInterceptTouchListener = listener;
597 }
598
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800599 int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700600 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800601 }
602
603 int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700604 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800605 }
606
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800607 public void setIsHotseat(boolean isHotseat) {
608 mIsHotseat = isHotseat;
609 }
610
Winson Chungeecf02d2012-03-02 17:14:58 -0800611 public float getChildrenScale() {
612 return mIsHotseat ? mHotseatChildScale : mChildScale;
613 }
614
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700615 public boolean addViewToCellLayout(
616 View child, int index, int childId, LayoutParams params, boolean markCells) {
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800617 return addViewToCellLayout(child, index, childId, params, markCells, false);
618 }
619
Winson Chungeecf02d2012-03-02 17:14:58 -0800620 private void scaleChild(BubbleTextView bubbleChild, float pivot, float scale) {
Andrew Flynnbc239a12012-03-06 11:39:49 -0800621 // If we haven't measured the child yet, do it now
622 // (this happens if we're being dropped from all-apps
623 if (bubbleChild.getLayoutParams() instanceof LayoutParams &&
624 (bubbleChild.getMeasuredWidth() | bubbleChild.getMeasuredHeight()) == 0) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700625 getShortcutsAndWidgets().measureChild(bubbleChild);
Andrew Flynnbc239a12012-03-06 11:39:49 -0800626 }
Andrew Flynnbc239a12012-03-06 11:39:49 -0800627
Andrew Flynnbc239a12012-03-06 11:39:49 -0800628 bubbleChild.setScaleX(scale);
629 bubbleChild.setScaleY(scale);
Andrew Flynnbc239a12012-03-06 11:39:49 -0800630 }
631
632 private void resetChild(BubbleTextView bubbleChild) {
633 bubbleChild.setScaleX(1f);
634 bubbleChild.setScaleY(1f);
Andrew Flynnbc239a12012-03-06 11:39:49 -0800635
636 bubbleChild.setTextColor(getResources().getColor(R.color.workspace_icon_text_color));
637 }
638
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800639 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
640 boolean markCells, boolean allApps) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700641 final LayoutParams lp = params;
642
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800643 // Hotseat icons - scale down and remove text
644 // Don't scale the all apps button
645 // scale percent set to -1 means do not scale
646 // Only scale BubbleTextViews
647 if (child instanceof BubbleTextView) {
648 BubbleTextView bubbleChild = (BubbleTextView) child;
649
Andrew Flynnbc239a12012-03-06 11:39:49 -0800650 // Start the child with 100% scale and visible text
651 resetChild(bubbleChild);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800652
Winson Chungeecf02d2012-03-02 17:14:58 -0800653 if (mIsHotseat && !allApps && mHotseatChildScale >= 0) {
Andrew Flynnbc239a12012-03-06 11:39:49 -0800654 // Scale/make transparent for a hotseat
Winson Chungeecf02d2012-03-02 17:14:58 -0800655 scaleChild(bubbleChild, 0f, mHotseatChildScale);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800656
Andrew Flynnbc239a12012-03-06 11:39:49 -0800657 bubbleChild.setTextColor(getResources().getColor(android.R.color.transparent));
Winson Chungeecf02d2012-03-02 17:14:58 -0800658 } else if (mChildScale >= 0) {
Andrew Flynnbc239a12012-03-06 11:39:49 -0800659 // Else possibly still scale it if we need to for smaller icons
Winson Chungeecf02d2012-03-02 17:14:58 -0800660 scaleChild(bubbleChild, 0f, mChildScale);
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800661 }
662 }
663
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800664 // Generate an id for each view, this assumes we have at most 256x256 cells
665 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700666 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700667 // If the horizontal or vertical span is set to -1, it is taken to
668 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700669 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
670 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800671
Winson Chungaafa03c2010-06-11 17:34:16 -0700672 child.setId(childId);
673
Michael Jurkaa52570f2012-03-20 03:18:20 -0700674 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700675
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700676 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700677
Winson Chungaafa03c2010-06-11 17:34:16 -0700678 return true;
679 }
680 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800681 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700682
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800683 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700684 public void removeAllViews() {
685 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700686 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700687 }
688
689 @Override
690 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700691 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700692 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700693 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700694 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700695 }
696
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700697 public void removeViewWithoutMarkingCells(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700698 mShortcutsAndWidgets.removeView(view);
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700699 }
700
Michael Jurka0280c3b2010-09-17 15:00:07 -0700701 @Override
702 public void removeView(View view) {
703 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700704 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700705 }
706
707 @Override
708 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700709 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
710 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700711 }
712
713 @Override
714 public void removeViewInLayout(View view) {
715 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700716 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700717 }
718
719 @Override
720 public void removeViews(int start, int count) {
721 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700722 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700723 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700724 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700725 }
726
727 @Override
728 public void removeViewsInLayout(int start, int count) {
729 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700730 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700731 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700732 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800733 }
734
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800735 @Override
736 protected void onAttachedToWindow() {
737 super.onAttachedToWindow();
738 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
739 }
740
Michael Jurkaaf442092010-06-10 17:01:57 -0700741 public void setTagToCellInfoForPoint(int touchX, int touchY) {
742 final CellInfo cellInfo = mCellInfo;
Winson Chungeecf02d2012-03-02 17:14:58 -0800743 Rect frame = mRect;
Michael Jurkaaf442092010-06-10 17:01:57 -0700744 final int x = touchX + mScrollX;
745 final int y = touchY + mScrollY;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700746 final int count = mShortcutsAndWidgets.getChildCount();
Michael Jurkaaf442092010-06-10 17:01:57 -0700747
748 boolean found = false;
749 for (int i = count - 1; i >= 0; i--) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700750 final View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohend4844c32011-02-18 19:25:06 -0800751 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Michael Jurkaaf442092010-06-10 17:01:57 -0700752
Adam Cohen1b607ed2011-03-03 17:26:50 -0800753 if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
754 lp.isLockedToGrid) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700755 child.getHitRect(frame);
Winson Chung0be025d2011-05-23 17:45:09 -0700756
Winson Chungeecf02d2012-03-02 17:14:58 -0800757 float scale = child.getScaleX();
758 frame = new Rect(child.getLeft(), child.getTop(), child.getRight(),
759 child.getBottom());
Winson Chung0be025d2011-05-23 17:45:09 -0700760 // The child hit rect is relative to the CellLayoutChildren parent, so we need to
761 // offset that by this CellLayout's padding to test an (x,y) point that is relative
762 // to this view.
Winson Chung4b825dcd2011-06-19 12:41:22 -0700763 frame.offset(mPaddingLeft, mPaddingTop);
Winson Chungeecf02d2012-03-02 17:14:58 -0800764 frame.inset((int) (frame.width() * (1f - scale) / 2),
765 (int) (frame.height() * (1f - scale) / 2));
Winson Chung0be025d2011-05-23 17:45:09 -0700766
Michael Jurkaaf442092010-06-10 17:01:57 -0700767 if (frame.contains(x, y)) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700768 cellInfo.cell = child;
769 cellInfo.cellX = lp.cellX;
770 cellInfo.cellY = lp.cellY;
771 cellInfo.spanX = lp.cellHSpan;
772 cellInfo.spanY = lp.cellVSpan;
Michael Jurkaaf442092010-06-10 17:01:57 -0700773 found = true;
Michael Jurkaaf442092010-06-10 17:01:57 -0700774 break;
775 }
776 }
777 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700778
Michael Jurkad771c962011-08-09 15:00:48 -0700779 mLastDownOnOccupiedCell = found;
780
Michael Jurkaaf442092010-06-10 17:01:57 -0700781 if (!found) {
Winson Chung0be025d2011-05-23 17:45:09 -0700782 final int cellXY[] = mTmpXY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700783 pointToCellExact(x, y, cellXY);
784
Michael Jurkaaf442092010-06-10 17:01:57 -0700785 cellInfo.cell = null;
786 cellInfo.cellX = cellXY[0];
787 cellInfo.cellY = cellXY[1];
788 cellInfo.spanX = 1;
789 cellInfo.spanY = 1;
Michael Jurkaaf442092010-06-10 17:01:57 -0700790 }
791 setTag(cellInfo);
792 }
793
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800794 @Override
795 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Cohenc1997fd2011-08-15 18:26:39 -0700796 // First we clear the tag to ensure that on every touch down we start with a fresh slate,
797 // even in the case where we return early. Not clearing here was causing bugs whereby on
798 // long-press we'd end up picking up an item from a previous drag operation.
799 final int action = ev.getAction();
800
801 if (action == MotionEvent.ACTION_DOWN) {
802 clearTagCellInfo();
803 }
804
Michael Jurkadee05892010-07-27 10:01:56 -0700805 if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
806 return true;
807 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800808
809 if (action == MotionEvent.ACTION_DOWN) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700810 setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800811 }
Winson Chungeecf02d2012-03-02 17:14:58 -0800812
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800813 return false;
814 }
815
Adam Cohenc1997fd2011-08-15 18:26:39 -0700816 private void clearTagCellInfo() {
817 final CellInfo cellInfo = mCellInfo;
818 cellInfo.cell = null;
819 cellInfo.cellX = -1;
820 cellInfo.cellY = -1;
821 cellInfo.spanX = 0;
822 cellInfo.spanY = 0;
823 setTag(cellInfo);
824 }
825
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800826 public CellInfo getTag() {
Michael Jurka0280c3b2010-09-17 15:00:07 -0700827 return (CellInfo) super.getTag();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800828 }
829
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700830 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700831 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800832 * @param x X coordinate of the point
833 * @param y Y coordinate of the point
834 * @param result Array of 2 ints to hold the x and y coordinate of the cell
835 */
836 void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700837 final int hStartPadding = getPaddingLeft();
838 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800839
840 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
841 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
842
Adam Cohend22015c2010-07-26 22:02:18 -0700843 final int xAxis = mCountX;
844 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800845
846 if (result[0] < 0) result[0] = 0;
847 if (result[0] >= xAxis) result[0] = xAxis - 1;
848 if (result[1] < 0) result[1] = 0;
849 if (result[1] >= yAxis) result[1] = yAxis - 1;
850 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700851
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800852 /**
853 * Given a point, return the cell that most closely encloses that point
854 * @param x X coordinate of the point
855 * @param y Y coordinate of the point
856 * @param result Array of 2 ints to hold the x and y coordinate of the cell
857 */
858 void pointToCellRounded(int x, int y, int[] result) {
859 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
860 }
861
862 /**
863 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700864 *
865 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800866 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700867 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800868 * @param result Array of 2 ints to hold the x and y coordinate of the point
869 */
870 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700871 final int hStartPadding = getPaddingLeft();
872 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800873
874 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
875 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
876 }
877
Adam Cohene3e27a82011-04-15 12:07:39 -0700878 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800879 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700880 *
881 * @param cellX X coordinate of the cell
882 * @param cellY Y coordinate of the cell
883 *
884 * @param result Array of 2 ints to hold the x and y coordinate of the point
885 */
886 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700887 regionToCenterPoint(cellX, cellY, 1, 1, result);
888 }
889
890 /**
891 * Given a cell coordinate and span return the point that represents the center of the regio
892 *
893 * @param cellX X coordinate of the cell
894 * @param cellY Y coordinate of the cell
895 *
896 * @param result Array of 2 ints to hold the x and y coordinate of the point
897 */
898 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700899 final int hStartPadding = getPaddingLeft();
900 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700901 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
902 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
903 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
904 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700905 }
906
Adam Cohen19f37922012-03-21 11:59:11 -0700907 /**
908 * Given a cell coordinate and span fills out a corresponding pixel rect
909 *
910 * @param cellX X coordinate of the cell
911 * @param cellY Y coordinate of the cell
912 * @param result Rect in which to write the result
913 */
914 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
915 final int hStartPadding = getPaddingLeft();
916 final int vStartPadding = getPaddingTop();
917 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
918 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
919 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
920 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
921 }
922
Adam Cohen482ed822012-03-02 14:15:13 -0800923 public float getDistanceFromCell(float x, float y, int[] cell) {
924 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
925 float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) +
926 Math.pow(y - mTmpPoint[1], 2));
927 return distance;
928 }
929
Romain Guy84f296c2009-11-04 15:00:44 -0800930 int getCellWidth() {
931 return mCellWidth;
932 }
933
934 int getCellHeight() {
935 return mCellHeight;
936 }
937
Adam Cohend4844c32011-02-18 19:25:06 -0800938 int getWidthGap() {
939 return mWidthGap;
940 }
941
942 int getHeightGap() {
943 return mHeightGap;
944 }
945
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700946 Rect getContentRect(Rect r) {
947 if (r == null) {
948 r = new Rect();
949 }
950 int left = getPaddingLeft();
951 int top = getPaddingTop();
Winson Chung4b825dcd2011-06-19 12:41:22 -0700952 int right = left + getWidth() - mPaddingLeft - mPaddingRight;
953 int bottom = top + getHeight() - mPaddingTop - mPaddingBottom;
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700954 r.set(left, top, right, bottom);
955 return r;
956 }
957
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800958 @Override
959 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
960 // TODO: currently ignoring padding
Winson Chungaafa03c2010-06-11 17:34:16 -0700961
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800962 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700963 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
964
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800965 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
966 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700967
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800968 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
969 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
970 }
971
Adam Cohend22015c2010-07-26 22:02:18 -0700972 int numWidthGaps = mCountX - 1;
973 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800974
Adam Cohen234c4cd2011-07-17 21:03:04 -0700975 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700976 int hSpace = widthSpecSize - mPaddingLeft - mPaddingRight;
977 int vSpace = heightSpecSize - mPaddingTop - mPaddingBottom;
978 int hFreeSpace = hSpace - (mCountX * mOriginalCellWidth);
979 int vFreeSpace = vSpace - (mCountY * mOriginalCellHeight);
980 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
981 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700982 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700983 } else {
984 mWidthGap = mOriginalWidthGap;
985 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -0700986 }
Michael Jurka5f1c5092010-09-03 14:15:02 -0700987
Michael Jurka8c920dd2011-01-20 14:16:56 -0800988 // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
989 int newWidth = widthSpecSize;
990 int newHeight = heightSpecSize;
Michael Jurka5f1c5092010-09-03 14:15:02 -0700991 if (widthSpecMode == MeasureSpec.AT_MOST) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700992 newWidth = mPaddingLeft + mPaddingRight + (mCountX * mCellWidth) +
Winson Chungece7f5b2010-10-22 14:54:12 -0700993 ((mCountX - 1) * mWidthGap);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700994 newHeight = mPaddingTop + mPaddingBottom + (mCountY * mCellHeight) +
Winson Chungece7f5b2010-10-22 14:54:12 -0700995 ((mCountY - 1) * mHeightGap);
Michael Jurka5f1c5092010-09-03 14:15:02 -0700996 setMeasuredDimension(newWidth, newHeight);
Michael Jurka5f1c5092010-09-03 14:15:02 -0700997 }
Michael Jurka8c920dd2011-01-20 14:16:56 -0800998
999 int count = getChildCount();
1000 for (int i = 0; i < count; i++) {
1001 View child = getChildAt(i);
Winson Chung4b825dcd2011-06-19 12:41:22 -07001002 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - mPaddingLeft -
1003 mPaddingRight, MeasureSpec.EXACTLY);
1004 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - mPaddingTop -
1005 mPaddingBottom, MeasureSpec.EXACTLY);
Michael Jurka8c920dd2011-01-20 14:16:56 -08001006 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
1007 }
1008 setMeasuredDimension(newWidth, newHeight);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001009 }
1010
1011 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -07001012 protected void onLayout(boolean changed, int l, int t, int r, int b) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001013 int count = getChildCount();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001014 for (int i = 0; i < count; i++) {
Michael Jurka8c920dd2011-01-20 14:16:56 -08001015 View child = getChildAt(i);
Winson Chung4b825dcd2011-06-19 12:41:22 -07001016 child.layout(mPaddingLeft, mPaddingTop,
1017 r - l - mPaddingRight, b - t - mPaddingBottom);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001018 }
1019 }
1020
1021 @Override
Michael Jurkadee05892010-07-27 10:01:56 -07001022 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1023 super.onSizeChanged(w, h, oldw, oldh);
Michael Jurka18014792010-10-14 09:01:34 -07001024 mBackgroundRect.set(0, 0, w, h);
Adam Cohenb5ba0972011-09-07 18:02:31 -07001025 mForegroundRect.set(mForegroundPadding, mForegroundPadding,
1026 w - 2 * mForegroundPadding, h - 2 * mForegroundPadding);
Michael Jurkadee05892010-07-27 10:01:56 -07001027 }
1028
1029 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001030 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001031 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001032 }
1033
1034 @Override
1035 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001036 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001037 }
1038
Michael Jurka5f1c5092010-09-03 14:15:02 -07001039 public float getBackgroundAlpha() {
1040 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -07001041 }
1042
Adam Cohen1b0aaac2010-10-28 11:11:18 -07001043 public void setBackgroundAlphaMultiplier(float multiplier) {
1044 mBackgroundAlphaMultiplier = multiplier;
1045 }
1046
Adam Cohenddb82192010-11-10 16:32:54 -08001047 public float getBackgroundAlphaMultiplier() {
1048 return mBackgroundAlphaMultiplier;
1049 }
1050
Michael Jurka5f1c5092010-09-03 14:15:02 -07001051 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -08001052 if (mBackgroundAlpha != alpha) {
1053 mBackgroundAlpha = alpha;
1054 invalidate();
1055 }
Michael Jurkadee05892010-07-27 10:01:56 -07001056 }
1057
Michael Jurkaa52570f2012-03-20 03:18:20 -07001058 public void setShortcutAndWidgetAlpha(float alpha) {
Michael Jurka0142d492010-08-25 17:46:15 -07001059 final int childCount = getChildCount();
1060 for (int i = 0; i < childCount; i++) {
Michael Jurkadee05892010-07-27 10:01:56 -07001061 getChildAt(i).setAlpha(alpha);
1062 }
1063 }
1064
Michael Jurkaa52570f2012-03-20 03:18:20 -07001065 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
1066 if (getChildCount() > 0) {
1067 return (ShortcutAndWidgetContainer) getChildAt(0);
1068 }
1069 return null;
1070 }
1071
Patrick Dubroy440c3602010-07-13 17:50:32 -07001072 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001073 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -07001074 }
1075
Adam Cohen76fc0852011-06-17 13:26:23 -07001076 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -08001077 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001078 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -08001079 boolean[][] occupied = mOccupied;
1080 if (!permanent) {
1081 occupied = mTmpOccupied;
1082 }
1083
Adam Cohen19f37922012-03-21 11:59:11 -07001084 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001085 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1086 final ItemInfo info = (ItemInfo) child.getTag();
1087
1088 // We cancel any existing animations
1089 if (mReorderAnimators.containsKey(lp)) {
1090 mReorderAnimators.get(lp).cancel();
1091 mReorderAnimators.remove(lp);
1092 }
1093
Adam Cohen482ed822012-03-02 14:15:13 -08001094 final int oldX = lp.x;
1095 final int oldY = lp.y;
1096 if (adjustOccupied) {
1097 occupied[lp.cellX][lp.cellY] = false;
1098 occupied[cellX][cellY] = true;
1099 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001100 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001101 if (permanent) {
1102 lp.cellX = info.cellX = cellX;
1103 lp.cellY = info.cellY = cellY;
1104 } else {
1105 lp.tmpCellX = cellX;
1106 lp.tmpCellY = cellY;
1107 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001108 clc.setupLp(lp);
1109 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001110 final int newX = lp.x;
1111 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001112
Adam Cohen76fc0852011-06-17 13:26:23 -07001113 lp.x = oldX;
1114 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001115
Adam Cohen482ed822012-03-02 14:15:13 -08001116 // Exit early if we're not actually moving the view
1117 if (oldX == newX && oldY == newY) {
1118 lp.isLockedToGrid = true;
1119 return true;
1120 }
1121
1122 ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
1123 va.setDuration(duration);
1124 mReorderAnimators.put(lp, va);
1125
1126 va.addUpdateListener(new AnimatorUpdateListener() {
1127 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001128 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001129 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001130 lp.x = (int) ((1 - r) * oldX + r * newX);
1131 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001132 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001133 }
1134 });
Adam Cohen482ed822012-03-02 14:15:13 -08001135 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001136 boolean cancelled = false;
1137 public void onAnimationEnd(Animator animation) {
1138 // If the animation was cancelled, it means that another animation
1139 // has interrupted this one, and we don't want to lock the item into
1140 // place just yet.
1141 if (!cancelled) {
1142 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001143 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001144 }
1145 if (mReorderAnimators.containsKey(lp)) {
1146 mReorderAnimators.remove(lp);
1147 }
1148 }
1149 public void onAnimationCancel(Animator animation) {
1150 cancelled = true;
1151 }
1152 });
Adam Cohen482ed822012-03-02 14:15:13 -08001153 va.setStartDelay(delay);
1154 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001155 return true;
1156 }
1157 return false;
1158 }
1159
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001160 /**
1161 * Estimate where the top left cell of the dragged item will land if it is dropped.
1162 *
1163 * @param originX The X value of the top left corner of the item
1164 * @param originY The Y value of the top left corner of the item
1165 * @param spanX The number of horizontal cells that the item spans
1166 * @param spanY The number of vertical cells that the item spans
1167 * @param result The estimated drop cell X and Y.
1168 */
1169 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
Adam Cohend22015c2010-07-26 22:02:18 -07001170 final int countX = mCountX;
1171 final int countY = mCountY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001172
Michael Jurkaa63c4522010-08-19 13:52:27 -07001173 // pointToCellRounded takes the top left of a cell but will pad that with
1174 // cellWidth/2 and cellHeight/2 when finding the matching cell
1175 pointToCellRounded(originX, originY, result);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001176
1177 // If the item isn't fully on this screen, snap to the edges
1178 int rightOverhang = result[0] + spanX - countX;
1179 if (rightOverhang > 0) {
1180 result[0] -= rightOverhang; // Snap to right
1181 }
1182 result[0] = Math.max(0, result[0]); // Snap to left
1183 int bottomOverhang = result[1] + spanY - countY;
1184 if (bottomOverhang > 0) {
1185 result[1] -= bottomOverhang; // Snap to bottom
1186 }
1187 result[1] = Math.max(0, result[1]); // Snap to top
1188 }
1189
Adam Cohen482ed822012-03-02 14:15:13 -08001190 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1191 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001192 final int oldDragCellX = mDragCell[0];
1193 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001194
Winson Chungb8c69f32011-10-19 21:36:08 -07001195 if (v != null && dragOffset == null) {
Winson Chunga9abd0e2010-10-27 17:18:37 -07001196 mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
1197 } else {
1198 mDragCenter.set(originX, originY);
1199 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001200
Adam Cohen2801caf2011-05-13 20:57:39 -07001201 if (dragOutline == null && v == null) {
1202 if (mCrosshairsDrawable != null) {
1203 invalidate();
1204 }
1205 return;
1206 }
1207
Adam Cohen482ed822012-03-02 14:15:13 -08001208 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1209 mDragCell[0] = cellX;
1210 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001211 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001212 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001213 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001214
Joe Onorato4be866d2010-10-10 11:26:02 -07001215 int left = topLeft[0];
1216 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001217
Winson Chungb8c69f32011-10-19 21:36:08 -07001218 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001219 // When drawing the drag outline, it did not account for margin offsets
1220 // added by the view's parent.
1221 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1222 left += lp.leftMargin;
1223 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001224
Adam Cohen99e8b402011-03-25 19:23:43 -07001225 // Offsets due to the size difference between the View and the dragOutline.
1226 // There is a size difference to account for the outer blur, which may lie
1227 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001228 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001229 // We center about the x axis
1230 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1231 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001232 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001233 if (dragOffset != null && dragRegion != null) {
1234 // Center the drag region *horizontally* in the cell and apply a drag
1235 // outline offset
1236 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1237 - dragRegion.width()) / 2;
1238 top += dragOffset.y;
1239 } else {
1240 // Center the drag outline in the cell
1241 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1242 - dragOutline.getWidth()) / 2;
1243 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1244 - dragOutline.getHeight()) / 2;
1245 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001246 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001247 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001248 mDragOutlineAnims[oldIndex].animateOut();
1249 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001250 Rect r = mDragOutlines[mDragOutlineCurrent];
1251 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1252 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001253 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001254 }
Winson Chung150fbab2010-09-29 17:14:26 -07001255
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001256 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1257 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001258 }
Patrick Dubroy49250ad2010-10-08 15:33:52 -07001259
1260 // If we are drawing crosshairs, the entire CellLayout needs to be invalidated
1261 if (mCrosshairsDrawable != null) {
1262 invalidate();
1263 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001264 }
1265
Adam Cohene0310962011-04-18 16:15:31 -07001266 public void clearDragOutlines() {
1267 final int oldIndex = mDragOutlineCurrent;
1268 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001269 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001270 }
1271
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001272 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001273 * Find a vacant area that will fit the given bounds nearest the requested
1274 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001275 *
Romain Guy51afc022009-05-04 18:03:43 -07001276 * @param pixelX The X location at which you want to search for a vacant area.
1277 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001278 * @param spanX Horizontal span of the object.
1279 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001280 * @param result Array in which to place the result, or null (in which case a new array will
1281 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001282 * @return The X, Y cell of a vacant area that can contain this object,
1283 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001284 */
Adam Cohend41fbf52012-02-16 23:53:59 -08001285 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
1286 int[] result) {
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001287 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001288 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001289
Michael Jurka6a1435d2010-09-27 17:35:12 -07001290 /**
1291 * Find a vacant area that will fit the given bounds nearest the requested
1292 * cell location. Uses Euclidean distance to score multiple vacant areas.
1293 *
1294 * @param pixelX The X location at which you want to search for a vacant area.
1295 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001296 * @param minSpanX The minimum horizontal span required
1297 * @param minSpanY The minimum vertical span required
1298 * @param spanX Horizontal span of the object.
1299 * @param spanY Vertical span of the object.
1300 * @param result Array in which to place the result, or null (in which case a new array will
1301 * be allocated)
1302 * @return The X, Y cell of a vacant area that can contain this object,
1303 * nearest the requested location.
1304 */
1305 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1306 int spanY, int[] result, int[] resultSpan) {
1307 return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
1308 result, resultSpan);
1309 }
1310
1311 /**
1312 * Find a vacant area that will fit the given bounds nearest the requested
1313 * cell location. Uses Euclidean distance to score multiple vacant areas.
1314 *
1315 * @param pixelX The X location at which you want to search for a vacant area.
1316 * @param pixelY The Y location at which you want to search for a vacant area.
Michael Jurka6a1435d2010-09-27 17:35:12 -07001317 * @param spanX Horizontal span of the object.
1318 * @param spanY Vertical span of the object.
Adam Cohendf035382011-04-11 17:22:04 -07001319 * @param ignoreOccupied If true, the result can be an occupied cell
1320 * @param result Array in which to place the result, or null (in which case a new array will
1321 * be allocated)
Michael Jurka6a1435d2010-09-27 17:35:12 -07001322 * @return The X, Y cell of a vacant area that can contain this object,
1323 * nearest the requested location.
1324 */
Adam Cohendf035382011-04-11 17:22:04 -07001325 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
1326 boolean ignoreOccupied, int[] result) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001327 return findNearestArea(pixelX, pixelY, spanX, spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001328 spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08001329 }
1330
1331 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1332 private void lazyInitTempRectStack() {
1333 if (mTempRectStack.isEmpty()) {
1334 for (int i = 0; i < mCountX * mCountY; i++) {
1335 mTempRectStack.push(new Rect());
1336 }
1337 }
1338 }
Adam Cohen482ed822012-03-02 14:15:13 -08001339
Adam Cohend41fbf52012-02-16 23:53:59 -08001340 private void recycleTempRects(Stack<Rect> used) {
1341 while (!used.isEmpty()) {
1342 mTempRectStack.push(used.pop());
1343 }
1344 }
1345
1346 /**
1347 * Find a vacant area that will fit the given bounds nearest the requested
1348 * cell location. Uses Euclidean distance to score multiple vacant areas.
1349 *
1350 * @param pixelX The X location at which you want to search for a vacant area.
1351 * @param pixelY The Y location at which you want to search for a vacant area.
1352 * @param minSpanX The minimum horizontal span required
1353 * @param minSpanY The minimum vertical span required
1354 * @param spanX Horizontal span of the object.
1355 * @param spanY Vertical span of the object.
1356 * @param ignoreOccupied If true, the result can be an occupied cell
1357 * @param result Array in which to place the result, or null (in which case a new array will
1358 * be allocated)
1359 * @return The X, Y cell of a vacant area that can contain this object,
1360 * nearest the requested location.
1361 */
1362 int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001363 View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
1364 boolean[][] occupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001365 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001366 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08001367 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001368
Adam Cohene3e27a82011-04-15 12:07:39 -07001369 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1370 // to the center of the item, but we are searching based on the top-left cell, so
1371 // we translate the point over to correspond to the top-left.
1372 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1373 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1374
Jeff Sharkey70864282009-04-07 21:08:40 -07001375 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001376 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001377 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001378 final Rect bestRect = new Rect(-1, -1, -1, -1);
1379 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001380
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001381 final int countX = mCountX;
1382 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001383
Adam Cohend41fbf52012-02-16 23:53:59 -08001384 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1385 spanX < minSpanX || spanY < minSpanY) {
1386 return bestXY;
1387 }
1388
1389 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001390 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001391 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1392 int ySize = -1;
1393 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001394 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001395 // First, let's see if this thing fits anywhere
1396 for (int i = 0; i < minSpanX; i++) {
1397 for (int j = 0; j < minSpanY; j++) {
Adam Cohendf035382011-04-11 17:22:04 -07001398 if (occupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001399 continue inner;
1400 }
Michael Jurkac28de512010-08-13 11:27:44 -07001401 }
1402 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001403 xSize = minSpanX;
1404 ySize = minSpanY;
1405
1406 // We know that the item will fit at _some_ acceptable size, now let's see
1407 // how big we can make it. We'll alternate between incrementing x and y spans
1408 // until we hit a limit.
1409 boolean incX = true;
1410 boolean hitMaxX = xSize >= spanX;
1411 boolean hitMaxY = ySize >= spanY;
1412 while (!(hitMaxX && hitMaxY)) {
1413 if (incX && !hitMaxX) {
1414 for (int j = 0; j < ySize; j++) {
1415 if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
1416 // We can't move out horizontally
1417 hitMaxX = true;
1418 }
1419 }
1420 if (!hitMaxX) {
1421 xSize++;
1422 }
1423 } else if (!hitMaxY) {
1424 for (int i = 0; i < xSize; i++) {
1425 if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
1426 // We can't move out vertically
1427 hitMaxY = true;
1428 }
1429 }
1430 if (!hitMaxY) {
1431 ySize++;
1432 }
1433 }
1434 hitMaxX |= xSize >= spanX;
1435 hitMaxY |= ySize >= spanY;
1436 incX = !incX;
1437 }
1438 incX = true;
1439 hitMaxX = xSize >= spanX;
1440 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001441 }
Winson Chung0be025d2011-05-23 17:45:09 -07001442 final int[] cellXY = mTmpXY;
Adam Cohene3e27a82011-04-15 12:07:39 -07001443 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001444
Adam Cohend41fbf52012-02-16 23:53:59 -08001445 // We verify that the current rect is not a sub-rect of any of our previous
1446 // candidates. In this case, the current rect is disqualified in favour of the
1447 // containing rect.
1448 Rect currentRect = mTempRectStack.pop();
1449 currentRect.set(x, y, x + xSize, y + ySize);
1450 boolean contained = false;
1451 for (Rect r : validRegions) {
1452 if (r.contains(currentRect)) {
1453 contained = true;
1454 break;
1455 }
1456 }
1457 validRegions.push(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001458 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
1459 + Math.pow(cellXY[1] - pixelY, 2));
Adam Cohen482ed822012-03-02 14:15:13 -08001460
Adam Cohend41fbf52012-02-16 23:53:59 -08001461 if ((distance <= bestDistance && !contained) ||
1462 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001463 bestDistance = distance;
1464 bestXY[0] = x;
1465 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001466 if (resultSpan != null) {
1467 resultSpan[0] = xSize;
1468 resultSpan[1] = ySize;
1469 }
1470 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001471 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001472 }
1473 }
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001474 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08001475 markCellsAsOccupiedForView(ignoreView, occupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001476
Adam Cohenc0dcf592011-06-01 15:30:43 -07001477 // Return -1, -1 if no suitable location found
1478 if (bestDistance == Double.MAX_VALUE) {
1479 bestXY[0] = -1;
1480 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001481 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001482 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001483 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001484 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001485
Adam Cohen482ed822012-03-02 14:15:13 -08001486 /**
1487 * Find a vacant area that will fit the given bounds nearest the requested
1488 * cell location, and will also weigh in a suggested direction vector of the
1489 * desired location. This method computers distance based on unit grid distances,
1490 * not pixel distances.
1491 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001492 * @param cellX The X cell nearest to which you want to search for a vacant area.
1493 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001494 * @param spanX Horizontal span of the object.
1495 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001496 * @param direction The favored direction in which the views should move from x, y
1497 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1498 * matches exactly. Otherwise we find the best matching direction.
1499 * @param occoupied The array which represents which cells in the CellLayout are occupied
1500 * @param blockOccupied The array which represents which cells in the specified block (cellX,
1501 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001502 * @param result Array in which to place the result, or null (in which case a new array will
1503 * be allocated)
1504 * @return The X, Y cell of a vacant area that can contain this object,
1505 * nearest the requested location.
1506 */
1507 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001508 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001509 // Keep track of best-scoring drop area
1510 final int[] bestXY = result != null ? result : new int[2];
1511 float bestDistance = Float.MAX_VALUE;
1512 int bestDirectionScore = Integer.MIN_VALUE;
1513
1514 final int countX = mCountX;
1515 final int countY = mCountY;
1516
1517 for (int y = 0; y < countY - (spanY - 1); y++) {
1518 inner:
1519 for (int x = 0; x < countX - (spanX - 1); x++) {
1520 // First, let's see if this thing fits anywhere
1521 for (int i = 0; i < spanX; i++) {
1522 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001523 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001524 continue inner;
1525 }
1526 }
1527 }
1528
1529 float distance = (float)
1530 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1531 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001532 computeDirectionVector(x - cellX, y - cellY, curDirection);
1533 // The direction score is just the dot product of the two candidate direction
1534 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001535 int curDirectionScore = direction[0] * curDirection[0] +
1536 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001537 boolean exactDirectionOnly = false;
1538 boolean directionMatches = direction[0] == curDirection[0] &&
1539 direction[0] == curDirection[0];
1540 if ((directionMatches || !exactDirectionOnly) &&
1541 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001542 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1543 bestDistance = distance;
1544 bestDirectionScore = curDirectionScore;
1545 bestXY[0] = x;
1546 bestXY[1] = y;
1547 }
1548 }
1549 }
1550
1551 // Return -1, -1 if no suitable location found
1552 if (bestDistance == Float.MAX_VALUE) {
1553 bestXY[0] = -1;
1554 bestXY[1] = -1;
1555 }
1556 return bestXY;
1557 }
1558
Adam Cohen47a876d2012-03-19 13:21:41 -07001559 private int[] findNearestAreaInDirection(int cellX, int cellY, int spanX, int spanY,
1560 int[] direction,boolean[][] occupied,
1561 boolean blockOccupied[][], int[] result) {
1562 // Keep track of best-scoring drop area
1563 final int[] bestXY = result != null ? result : new int[2];
1564 bestXY[0] = -1;
1565 bestXY[1] = -1;
1566 float bestDistance = Float.MAX_VALUE;
1567
1568 // We use this to march in a single direction
1569 if (direction[0] != 0 && direction[1] != 0) {
1570 return bestXY;
1571 }
1572
1573 // This will only incrememnet one of x or y based on the assertion above
1574 int x = cellX + direction[0];
1575 int y = cellY + direction[1];
1576 while (x >= 0 && x + spanX <= mCountX && y >= 0 && y + spanY <= mCountY) {
1577
1578 boolean fail = false;
1579 for (int i = 0; i < spanX; i++) {
1580 for (int j = 0; j < spanY; j++) {
1581 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
1582 fail = true;
1583 }
1584 }
1585 }
1586 if (!fail) {
1587 float distance = (float)
1588 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1589 if (Float.compare(distance, bestDistance) < 0) {
1590 bestDistance = distance;
1591 bestXY[0] = x;
1592 bestXY[1] = y;
1593 }
1594 }
1595 x += direction[0];
1596 y += direction[1];
1597 }
1598 return bestXY;
1599 }
1600
Adam Cohen482ed822012-03-02 14:15:13 -08001601 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001602 int[] direction, ItemConfiguration currentState) {
1603 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001604 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001605 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001606 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1607
Adam Cohen8baab352012-03-20 17:39:21 -07001608 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001609
1610 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001611 c.x = mTempLocation[0];
1612 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001613 success = true;
1614
1615 }
Adam Cohen8baab352012-03-20 17:39:21 -07001616 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001617 return success;
1618 }
1619
Adam Cohen47a876d2012-03-19 13:21:41 -07001620 // This method looks in the specified direction to see if there is an additional view
1621 // immediately adjecent in that direction
1622 private boolean addViewInDirection(ArrayList<View> views, Rect boundingRect, int[] direction,
Adam Cohen19f37922012-03-21 11:59:11 -07001623 boolean[][] occupied, View dragView, ItemConfiguration currentState) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001624 boolean found = false;
1625
Michael Jurkaa52570f2012-03-20 03:18:20 -07001626 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen47a876d2012-03-19 13:21:41 -07001627 Rect r0 = new Rect(boundingRect);
1628 Rect r1 = new Rect();
1629
1630 int deltaX = 0;
1631 int deltaY = 0;
1632 if (direction[1] < 0) {
1633 r0.set(r0.left, r0.top - 1, r0.right, r0.bottom);
1634 deltaY = -1;
1635 } else if (direction[1] > 0) {
1636 r0.set(r0.left, r0.top, r0.right, r0.bottom + 1);
1637 deltaY = 1;
1638 } else if (direction[0] < 0) {
1639 r0.set(r0.left - 1, r0.top, r0.right, r0.bottom);
1640 deltaX = -1;
1641 } else if (direction[0] > 0) {
1642 r0.set(r0.left, r0.top, r0.right + 1, r0.bottom);
1643 deltaX = 1;
1644 }
1645
1646 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001647 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen19f37922012-03-21 11:59:11 -07001648 if (views.contains(child) || child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001649 CellAndSpan c = currentState.map.get(child);
Adam Cohen47a876d2012-03-19 13:21:41 -07001650
Adam Cohen8baab352012-03-20 17:39:21 -07001651 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1652 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen47a876d2012-03-19 13:21:41 -07001653 if (Rect.intersects(r0, r1)) {
1654 if (!lp.canReorder) {
1655 return false;
1656 }
1657 boolean pushed = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001658 for (int x = c.x; x < c.x + c.spanX; x++) {
1659 for (int y = c.y; y < c.y + c.spanY; y++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001660 boolean inBounds = x - deltaX >= 0 && x -deltaX < mCountX
1661 && y - deltaY >= 0 && y - deltaY < mCountY;
1662 if (inBounds && occupied[x - deltaX][y - deltaY]) {
1663 pushed = true;
1664 }
1665 }
1666 }
1667 if (pushed) {
1668 views.add(child);
Adam Cohen8baab352012-03-20 17:39:21 -07001669 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen47a876d2012-03-19 13:21:41 -07001670 found = true;
1671 }
1672 }
1673 }
1674 return found;
1675 }
1676
Adam Cohen482ed822012-03-02 14:15:13 -08001677 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohen19f37922012-03-21 11:59:11 -07001678 int[] direction, boolean push, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001679 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001680
Adam Cohen8baab352012-03-20 17:39:21 -07001681 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001682 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001683 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001684 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001685 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001686 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001687 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001688 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001689 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001690 }
1691 }
Adam Cohen8baab352012-03-20 17:39:21 -07001692
1693 @SuppressWarnings("unchecked")
1694 ArrayList<View> dup = (ArrayList<View>) views.clone();
1695 // We try and expand the group of views in the direction vector passed, based on
1696 // whether they are physically adjacent, ie. based on "push mechanics".
Adam Cohen19f37922012-03-21 11:59:11 -07001697 while (push && addViewInDirection(dup, boundingRect, direction, mTmpOccupied, dragView,
Adam Cohen8baab352012-03-20 17:39:21 -07001698 currentState)) {
1699 }
1700
1701 // Mark the occupied state as false for the group of views we want to move.
1702 for (View v: dup) {
1703 CellAndSpan c = currentState.map.get(v);
1704 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1705 }
1706
Adam Cohen47a876d2012-03-19 13:21:41 -07001707 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1708 int top = boundingRect.top;
1709 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001710 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
1711 // for tetris-style interlocking.
1712 for (View v: dup) {
1713 CellAndSpan c = currentState.map.get(v);
1714 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001715 }
1716
Adam Cohen482ed822012-03-02 14:15:13 -08001717 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1718
Adam Cohen8baab352012-03-20 17:39:21 -07001719 if (push) {
1720 findNearestAreaInDirection(boundingRect.left, boundingRect.top, boundingRect.width(),
1721 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
1722 } else {
1723 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1724 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
1725 }
Adam Cohen482ed822012-03-02 14:15:13 -08001726
Adam Cohen8baab352012-03-20 17:39:21 -07001727 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001728 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001729 int deltaX = mTempLocation[0] - boundingRect.left;
1730 int deltaY = mTempLocation[1] - boundingRect.top;
1731 for (View v: dup) {
1732 CellAndSpan c = currentState.map.get(v);
1733 c.x += deltaX;
1734 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001735 }
1736 success = true;
1737 }
Adam Cohen8baab352012-03-20 17:39:21 -07001738
1739 // In either case, we set the occupied array as marked for the location of the views
1740 for (View v: dup) {
1741 CellAndSpan c = currentState.map.get(v);
1742 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001743 }
1744 return success;
1745 }
1746
1747 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1748 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1749 }
1750
1751 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07001752 View ignoreView, ItemConfiguration solution) {
Adam Cohen482ed822012-03-02 14:15:13 -08001753
Adam Cohen8baab352012-03-20 17:39:21 -07001754 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08001755 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001756
Adam Cohen8baab352012-03-20 17:39:21 -07001757 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08001758 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001759 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07001760 if (c != null) {
1761 c.x = cellX;
1762 c.y = cellY;
1763 }
Adam Cohen482ed822012-03-02 14:15:13 -08001764 }
Adam Cohen482ed822012-03-02 14:15:13 -08001765 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
1766 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07001767 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08001768 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001769 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08001770 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001771 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001772 if (Rect.intersects(r0, r1)) {
1773 if (!lp.canReorder) {
1774 return false;
1775 }
1776 mIntersectingViews.add(child);
1777 }
1778 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001779
Adam Cohen8baab352012-03-20 17:39:21 -07001780 // We try to move the intersecting views as a block using the push mechanic
Adam Cohen19f37922012-03-21 11:59:11 -07001781 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, true, ignoreView,
1782 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001783 return true;
1784 }
1785 // Try the opposite direction
1786 direction[0] *= -1;
1787 direction[1] *= -1;
Adam Cohen19f37922012-03-21 11:59:11 -07001788 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, true, ignoreView,
1789 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001790 return true;
1791 }
1792 // Switch the direction back
1793 direction[0] *= -1;
1794 direction[1] *= -1;
1795
Adam Cohen8baab352012-03-20 17:39:21 -07001796 // Next we try moving the views as a block , but without requiring the push mechanic
Adam Cohen19f37922012-03-21 11:59:11 -07001797 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, false, ignoreView,
1798 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001799 return true;
1800 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001801
Adam Cohen482ed822012-03-02 14:15:13 -08001802 // Ok, they couldn't move as a block, let's move them individually
1803 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07001804 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08001805 return false;
1806 }
1807 }
1808 return true;
1809 }
1810
1811 /*
1812 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
1813 * the provided point and the provided cell
1814 */
Adam Cohen47a876d2012-03-19 13:21:41 -07001815 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001816 double angle = Math.atan(((float) deltaY) / deltaX);
1817
1818 result[0] = 0;
1819 result[1] = 0;
1820 if (Math.abs(Math.cos(angle)) > 0.5f) {
1821 result[0] = (int) Math.signum(deltaX);
1822 }
1823 if (Math.abs(Math.sin(angle)) > 0.5f) {
1824 result[1] = (int) Math.signum(deltaY);
1825 }
1826 }
1827
Adam Cohen8baab352012-03-20 17:39:21 -07001828 private void copyOccupiedArray(boolean[][] occupied) {
1829 for (int i = 0; i < mCountX; i++) {
1830 for (int j = 0; j < mCountY; j++) {
1831 occupied[i][j] = mOccupied[i][j];
1832 }
1833 }
1834 }
1835
Adam Cohen482ed822012-03-02 14:15:13 -08001836 ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1837 int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07001838 // Copy the current state into the solution. This solution will be manipulated as necessary.
1839 copyCurrentStateToSolution(solution, false);
1840 // Copy the current occupied array into the temporary occupied array. This array will be
1841 // manipulated as necessary to find a solution.
1842 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08001843
1844 // We find the nearest cell into which we would place the dragged item, assuming there's
1845 // nothing in its way.
1846 int result[] = new int[2];
1847 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
1848
1849 boolean success = false;
1850 // First we try the exact nearest position of the item being dragged,
1851 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07001852 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
1853 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08001854
1855 if (!success) {
1856 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
1857 // x, then 1 in y etc.
1858 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
1859 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
1860 dragView, false, solution);
1861 } else if (spanY > minSpanY) {
1862 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
1863 dragView, true, solution);
1864 }
1865 solution.isSolution = false;
1866 } else {
1867 solution.isSolution = true;
1868 solution.dragViewX = result[0];
1869 solution.dragViewY = result[1];
1870 solution.dragViewSpanX = spanX;
1871 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08001872 }
1873 return solution;
1874 }
1875
1876 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001877 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001878 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001879 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001880 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001881 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08001882 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07001883 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001884 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001885 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08001886 }
Adam Cohen8baab352012-03-20 17:39:21 -07001887 solution.map.put(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08001888 }
1889 }
1890
1891 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
1892 for (int i = 0; i < mCountX; i++) {
1893 for (int j = 0; j < mCountY; j++) {
1894 mTmpOccupied[i][j] = false;
1895 }
1896 }
1897
Michael Jurkaa52570f2012-03-20 03:18:20 -07001898 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001899 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001900 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001901 if (child == dragView) continue;
1902 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07001903 CellAndSpan c = solution.map.get(child);
1904 if (c != null) {
1905 lp.tmpCellX = c.x;
1906 lp.tmpCellY = c.y;
1907 lp.cellHSpan = c.spanX;
1908 lp.cellVSpan = c.spanY;
1909 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001910 }
1911 }
1912 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
1913 solution.dragViewSpanY, mTmpOccupied, true);
1914 }
1915
1916 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
1917 commitDragView) {
1918
1919 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
1920 for (int i = 0; i < mCountX; i++) {
1921 for (int j = 0; j < mCountY; j++) {
1922 occupied[i][j] = false;
1923 }
1924 }
1925
Michael Jurkaa52570f2012-03-20 03:18:20 -07001926 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08001927 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001928 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08001929 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07001930 CellAndSpan c = solution.map.get(child);
1931 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07001932 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
1933 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07001934 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001935 }
1936 }
1937 if (commitDragView) {
1938 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
1939 solution.dragViewSpanY, occupied, true);
1940 }
1941 }
1942
Adam Cohen19f37922012-03-21 11:59:11 -07001943 // This method starts or changes the reorder hint animations
1944 private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
1945 int childCount = mShortcutsAndWidgets.getChildCount();
1946 int timeForPriorAnimationToComplete = getMaxCompletionTime();
1947 for (int i = 0; i < childCount; i++) {
1948 View child = mShortcutsAndWidgets.getChildAt(i);
1949 if (child == dragView) continue;
1950 CellAndSpan c = solution.map.get(child);
1951 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1952 if (c != null) {
1953 ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
1954 c.x, c.y, c.spanX, c.spanY);
1955 rha.animate(timeForPriorAnimationToComplete);
1956 }
1957 }
1958 }
1959
1960 // Class which represents the reorder hint animations. These animations show that an item is
1961 // in a temporary state, and hint at where the item will return to.
1962 class ReorderHintAnimation {
1963 View child;
1964 float deltaX;
1965 float deltaY;
1966 private static final int DURATION = 140;
1967 private int repeatCount;
1968 private boolean cancelOnCycleComplete = false;
1969 ValueAnimator va;
1970
1971 public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
1972 int spanX, int spanY) {
1973 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
1974 final int x0 = mTmpPoint[0];
1975 final int y0 = mTmpPoint[1];
1976 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
1977 final int x1 = mTmpPoint[0];
1978 final int y1 = mTmpPoint[1];
1979 final int dX = x1 - x0;
1980 final int dY = y1 - y0;
1981 deltaX = 0;
1982 deltaY = 0;
1983 if (dX == dY && dX == 0) {
1984 } else {
1985 if (dY == 0) {
1986 deltaX = mReorderHintAnimationMagnitude;
1987 } else if (dX == 0) {
1988 deltaY = mReorderHintAnimationMagnitude;
1989 } else {
1990 double angle = Math.atan( (float) (dY) / dX);
1991 deltaX = (int) (Math.cos(angle) * mReorderHintAnimationMagnitude);
1992 deltaY = (int) (Math.sin(angle) * mReorderHintAnimationMagnitude);
1993 }
1994 }
1995 this.child = child;
1996 }
1997
1998 void animate(int delay) {
1999 if (mShakeAnimators.containsKey(child)) {
2000 ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
2001 oldAnimation.completeAnimation();
2002 mShakeAnimators.remove(child);
2003 }
2004 if (deltaX == 0 && deltaY == 0) {
2005 return;
2006 }
2007 va = ValueAnimator.ofFloat(0f, 1f);
2008 va.setRepeatMode(ValueAnimator.REVERSE);
2009 va.setRepeatCount(ValueAnimator.INFINITE);
2010 va.setDuration(DURATION);
2011 va.addUpdateListener(new AnimatorUpdateListener() {
2012 @Override
2013 public void onAnimationUpdate(ValueAnimator animation) {
2014 float r = ((Float) animation.getAnimatedValue()).floatValue();
2015 float x = r * deltaX;
2016 float y = r * deltaY;
2017 child.setTranslationX(x);
2018 child.setTranslationY(y);
2019 }
2020 });
2021 va.addListener(new AnimatorListenerAdapter() {
2022 public void onAnimationRepeat(Animator animation) {
2023 repeatCount++;
2024 // We make sure to end only after a full period
2025 if (cancelOnCycleComplete && repeatCount % 2 == 0) {
2026 va.cancel();
2027 }
2028 }
2029 });
2030 va.setStartDelay(Math.max(REORDER_ANIMATION_DURATION, delay));
2031 mShakeAnimators.put(child, this);
2032 va.start();
2033 }
2034
2035
2036 private void completeAnimation() {
2037 cancelOnCycleComplete = true;
2038 }
2039
2040 // Returns the time required to complete the current oscillating animation
2041 private int completionTime() {
2042 if (repeatCount % 2 == 0) {
2043 return (int) (va.getDuration() - va.getCurrentPlayTime() + DURATION);
2044 } else {
2045 return (int) (va.getDuration() - va.getCurrentPlayTime());
2046 }
2047 }
2048 }
2049
2050 private void completeAndClearReorderHintAnimations() {
2051 for (ReorderHintAnimation a: mShakeAnimators.values()) {
2052 a.completeAnimation();
2053 }
2054 mShakeAnimators.clear();
2055 }
2056
2057 private int getMaxCompletionTime() {
2058 int maxTime = 0;
2059 for (ReorderHintAnimation a: mShakeAnimators.values()) {
2060 maxTime = Math.max(maxTime, a.completionTime());
2061 }
2062 return maxTime;
2063 }
2064
Adam Cohen482ed822012-03-02 14:15:13 -08002065 private void commitTempPlacement() {
2066 for (int i = 0; i < mCountX; i++) {
2067 for (int j = 0; j < mCountY; j++) {
2068 mOccupied[i][j] = mTmpOccupied[i][j];
2069 }
2070 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002071 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002072 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002073 View child = mShortcutsAndWidgets.getChildAt(i);
2074 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2075 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002076 // We do a null check here because the item info can be null in the case of the
2077 // AllApps button in the hotseat.
2078 if (info != null) {
2079 info.cellX = lp.cellX = lp.tmpCellX;
2080 info.cellY = lp.cellY = lp.tmpCellY;
2081 }
Adam Cohen482ed822012-03-02 14:15:13 -08002082 }
Adam Cohen2acce882012-03-28 19:03:19 -07002083 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002084 }
2085
2086 public void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002087 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002088 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002089 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002090 lp.useTmpCoords = useTempCoords;
2091 }
2092 }
2093
Adam Cohen482ed822012-03-02 14:15:13 -08002094 ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
2095 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2096 int[] result = new int[2];
2097 int[] resultSpan = new int[2];
2098 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
2099 resultSpan);
2100 if (result[0] >= 0 && result[1] >= 0) {
2101 copyCurrentStateToSolution(solution, false);
2102 solution.dragViewX = result[0];
2103 solution.dragViewY = result[1];
2104 solution.dragViewSpanX = resultSpan[0];
2105 solution.dragViewSpanY = resultSpan[1];
2106 solution.isSolution = true;
2107 } else {
2108 solution.isSolution = false;
2109 }
2110 return solution;
2111 }
2112
2113 public void prepareChildForDrag(View child) {
2114 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002115 }
2116
Adam Cohen19f37922012-03-21 11:59:11 -07002117 /* This seems like it should be obvious and straight-forward, but when the direction vector
2118 needs to match with the notion of the dragView pushing other views, we have to employ
2119 a slightly more subtle notion of the direction vector. The question is what two points is
2120 the vector between? The center of the dragView and its desired destination? Not quite, as
2121 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2122 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2123 or right, which helps make pushing feel right.
2124 */
2125 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2126 int spanY, View dragView, int[] resultDirection) {
2127 int[] targetDestination = new int[2];
2128
2129 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2130 Rect dragRect = new Rect();
2131 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2132 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2133
2134 Rect dropRegionRect = new Rect();
2135 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2136 dragView, dropRegionRect, mIntersectingViews);
2137
2138 int dropRegionSpanX = dropRegionRect.width();
2139 int dropRegionSpanY = dropRegionRect.height();
2140
2141 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2142 dropRegionRect.height(), dropRegionRect);
2143
2144 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2145 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2146
2147 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2148 deltaX = 0;
2149 }
2150 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2151 deltaY = 0;
2152 }
2153
2154 if (deltaX == 0 && deltaY == 0) {
2155 // No idea what to do, give a random direction.
2156 resultDirection[0] = 1;
2157 resultDirection[1] = 0;
2158 } else {
2159 computeDirectionVector(deltaX, deltaY, resultDirection);
2160 }
2161 }
2162
2163 // For a given cell and span, fetch the set of views intersecting the region.
2164 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2165 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2166 if (boundingRect != null) {
2167 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2168 }
2169 intersectingViews.clear();
2170 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2171 Rect r1 = new Rect();
2172 final int count = mShortcutsAndWidgets.getChildCount();
2173 for (int i = 0; i < count; i++) {
2174 View child = mShortcutsAndWidgets.getChildAt(i);
2175 if (child == dragView) continue;
2176 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2177 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2178 if (Rect.intersects(r0, r1)) {
2179 mIntersectingViews.add(child);
2180 if (boundingRect != null) {
2181 boundingRect.union(r1);
2182 }
2183 }
2184 }
2185 }
2186
2187 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2188 View dragView, int[] result) {
2189 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2190 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2191 mIntersectingViews);
2192 return !mIntersectingViews.isEmpty();
2193 }
2194
2195 void revertTempState() {
2196 if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
2197 final int count = mShortcutsAndWidgets.getChildCount();
2198 for (int i = 0; i < count; i++) {
2199 View child = mShortcutsAndWidgets.getChildAt(i);
2200 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2201 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2202 lp.tmpCellX = lp.cellX;
2203 lp.tmpCellY = lp.cellY;
2204 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2205 0, false, false);
2206 }
2207 }
2208 completeAndClearReorderHintAnimations();
2209 setItemPlacementDirty(false);
2210 }
2211
Adam Cohen482ed822012-03-02 14:15:13 -08002212 int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
2213 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002214 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002215 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002216
2217 if (resultSpan == null) {
2218 resultSpan = new int[2];
2219 }
2220
Adam Cohen19f37922012-03-21 11:59:11 -07002221 // When we are checking drop validity or actually dropping, we don't recompute the
2222 // direction vector, since we want the solution to match the preview, and it's possible
2223 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002224 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2225 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002226 mDirectionVector[0] = mPreviousReorderDirection[0];
2227 mDirectionVector[1] = mPreviousReorderDirection[1];
2228 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002229 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2230 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2231 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002232 }
Adam Cohenb209e632012-03-27 17:09:36 -07002233
Adam Cohen19f37922012-03-21 11:59:11 -07002234 } else {
2235 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2236 mPreviousReorderDirection[0] = mDirectionVector[0];
2237 mPreviousReorderDirection[1] = mDirectionVector[1];
2238 }
2239
Adam Cohen482ed822012-03-02 14:15:13 -08002240 ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
2241 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2242
2243 // We attempt the approach which doesn't shuffle views at all
2244 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2245 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2246
2247 ItemConfiguration finalSolution = null;
2248 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2249 finalSolution = swapSolution;
2250 } else if (noShuffleSolution.isSolution) {
2251 finalSolution = noShuffleSolution;
2252 }
2253
2254 boolean foundSolution = true;
2255 if (!DESTRUCTIVE_REORDER) {
2256 setUseTempCoords(true);
2257 }
2258
2259 if (finalSolution != null) {
2260 result[0] = finalSolution.dragViewX;
2261 result[1] = finalSolution.dragViewY;
2262 resultSpan[0] = finalSolution.dragViewSpanX;
2263 resultSpan[1] = finalSolution.dragViewSpanY;
2264
2265 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2266 // committing anything or animating anything as we just want to determine if a solution
2267 // exists
2268 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2269 if (!DESTRUCTIVE_REORDER) {
2270 copySolutionToTempState(finalSolution, dragView);
2271 }
2272 setItemPlacementDirty(true);
2273 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2274
Adam Cohen19f37922012-03-21 11:59:11 -07002275 if (!DESTRUCTIVE_REORDER &&
2276 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002277 commitTempPlacement();
Adam Cohen19f37922012-03-21 11:59:11 -07002278 completeAndClearReorderHintAnimations();
2279 setItemPlacementDirty(false);
2280 } else {
2281 beginOrAdjustHintAnimations(finalSolution, dragView,
2282 REORDER_ANIMATION_DURATION);
Adam Cohen482ed822012-03-02 14:15:13 -08002283 }
2284 }
2285 } else {
2286 foundSolution = false;
2287 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2288 }
2289
2290 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2291 setUseTempCoords(false);
2292 }
Adam Cohen482ed822012-03-02 14:15:13 -08002293
Michael Jurkaa52570f2012-03-20 03:18:20 -07002294 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002295 return result;
2296 }
2297
Adam Cohen19f37922012-03-21 11:59:11 -07002298 void setItemPlacementDirty(boolean dirty) {
2299 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002300 }
Adam Cohen19f37922012-03-21 11:59:11 -07002301 boolean isItemPlacementDirty() {
2302 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002303 }
2304
2305 private class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002306 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohen482ed822012-03-02 14:15:13 -08002307 boolean isSolution = false;
2308 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2309
2310 int area() {
2311 return dragViewSpanX * dragViewSpanY;
2312 }
Adam Cohen8baab352012-03-20 17:39:21 -07002313 }
2314
2315 private class CellAndSpan {
2316 int x, y;
2317 int spanX, spanY;
2318
2319 public CellAndSpan(int x, int y, int spanX, int spanY) {
2320 this.x = x;
2321 this.y = y;
2322 this.spanX = spanX;
2323 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002324 }
2325 }
2326
Adam Cohendf035382011-04-11 17:22:04 -07002327 /**
2328 * Find a vacant area that will fit the given bounds nearest the requested
2329 * cell location. Uses Euclidean distance to score multiple vacant areas.
2330 *
2331 * @param pixelX The X location at which you want to search for a vacant area.
2332 * @param pixelY The Y location at which you want to search for a vacant area.
2333 * @param spanX Horizontal span of the object.
2334 * @param spanY Vertical span of the object.
2335 * @param ignoreView Considers space occupied by this view as unoccupied
2336 * @param result Previously returned value to possibly recycle.
2337 * @return The X, Y cell of a vacant area that can contain this object,
2338 * nearest the requested location.
2339 */
2340 int[] findNearestVacantArea(
2341 int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
2342 return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
2343 }
2344
2345 /**
Adam Cohend41fbf52012-02-16 23:53:59 -08002346 * Find a vacant area that will fit the given bounds nearest the requested
2347 * cell location. Uses Euclidean distance to score multiple vacant areas.
2348 *
2349 * @param pixelX The X location at which you want to search for a vacant area.
2350 * @param pixelY The Y location at which you want to search for a vacant area.
2351 * @param minSpanX The minimum horizontal span required
2352 * @param minSpanY The minimum vertical span required
2353 * @param spanX Horizontal span of the object.
2354 * @param spanY Vertical span of the object.
2355 * @param ignoreView Considers space occupied by this view as unoccupied
2356 * @param result Previously returned value to possibly recycle.
2357 * @return The X, Y cell of a vacant area that can contain this object,
2358 * nearest the requested location.
2359 */
2360 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
2361 int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
Adam Cohen482ed822012-03-02 14:15:13 -08002362 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
2363 result, resultSpan, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08002364 }
2365
2366 /**
Adam Cohendf035382011-04-11 17:22:04 -07002367 * Find a starting cell position that will fit the given bounds nearest the requested
2368 * cell location. Uses Euclidean distance to score multiple vacant areas.
2369 *
2370 * @param pixelX The X location at which you want to search for a vacant area.
2371 * @param pixelY The Y location at which you want to search for a vacant area.
2372 * @param spanX Horizontal span of the object.
2373 * @param spanY Vertical span of the object.
2374 * @param ignoreView Considers space occupied by this view as unoccupied
2375 * @param result Previously returned value to possibly recycle.
2376 * @return The X, Y cell of a vacant area that can contain this object,
2377 * nearest the requested location.
2378 */
2379 int[] findNearestArea(
2380 int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2381 return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
2382 }
2383
Michael Jurka0280c3b2010-09-17 15:00:07 -07002384 boolean existsEmptyCell() {
2385 return findCellForSpan(null, 1, 1);
2386 }
2387
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002388 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002389 * Finds the upper-left coordinate of the first rectangle in the grid that can
2390 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2391 * then this method will only return coordinates for rectangles that contain the cell
2392 * (intersectX, intersectY)
2393 *
2394 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2395 * can be found.
2396 * @param spanX The horizontal span of the cell we want to find.
2397 * @param spanY The vertical span of the cell we want to find.
2398 *
2399 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002400 */
Michael Jurka0280c3b2010-09-17 15:00:07 -07002401 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Adam Cohen482ed822012-03-02 14:15:13 -08002402 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002403 }
2404
2405 /**
2406 * Like above, but ignores any cells occupied by the item "ignoreView"
2407 *
2408 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2409 * can be found.
2410 * @param spanX The horizontal span of the cell we want to find.
2411 * @param spanY The vertical span of the cell we want to find.
2412 * @param ignoreView The home screen item we should treat as not occupying any space
2413 * @return
2414 */
2415 boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
Adam Cohen482ed822012-03-02 14:15:13 -08002416 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
2417 ignoreView, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002418 }
2419
2420 /**
2421 * Like above, but if intersectX and intersectY are not -1, then this method will try to
2422 * return coordinates for rectangles that contain the cell [intersectX, intersectY]
2423 *
2424 * @param spanX The horizontal span of the cell we want to find.
2425 * @param spanY The vertical span of the cell we want to find.
2426 * @param ignoreView The home screen item we should treat as not occupying any space
2427 * @param intersectX The X coordinate of the cell that we should try to overlap
2428 * @param intersectX The Y coordinate of the cell that we should try to overlap
2429 *
2430 * @return True if a vacant cell of the specified dimension was found, false otherwise.
2431 */
2432 boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
2433 int intersectX, int intersectY) {
2434 return findCellForSpanThatIntersectsIgnoring(
Adam Cohen482ed822012-03-02 14:15:13 -08002435 cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002436 }
2437
2438 /**
2439 * The superset of the above two methods
2440 */
2441 boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002442 int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002443 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08002444 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002445
Michael Jurka28750fb2010-09-24 17:43:49 -07002446 boolean foundCell = false;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002447 while (true) {
2448 int startX = 0;
2449 if (intersectX >= 0) {
2450 startX = Math.max(startX, intersectX - (spanX - 1));
2451 }
2452 int endX = mCountX - (spanX - 1);
2453 if (intersectX >= 0) {
2454 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
2455 }
2456 int startY = 0;
2457 if (intersectY >= 0) {
2458 startY = Math.max(startY, intersectY - (spanY - 1));
2459 }
2460 int endY = mCountY - (spanY - 1);
2461 if (intersectY >= 0) {
2462 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
2463 }
2464
Winson Chungbbc60d82010-11-11 16:34:41 -08002465 for (int y = startY; y < endY && !foundCell; y++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002466 inner:
Winson Chungbbc60d82010-11-11 16:34:41 -08002467 for (int x = startX; x < endX; x++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002468 for (int i = 0; i < spanX; i++) {
2469 for (int j = 0; j < spanY; j++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002470 if (occupied[x + i][y + j]) {
Winson Chungbbc60d82010-11-11 16:34:41 -08002471 // small optimization: we can skip to after the column we just found
Michael Jurka0280c3b2010-09-17 15:00:07 -07002472 // an occupied cell
Winson Chungbbc60d82010-11-11 16:34:41 -08002473 x += i;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002474 continue inner;
2475 }
2476 }
2477 }
2478 if (cellXY != null) {
2479 cellXY[0] = x;
2480 cellXY[1] = y;
2481 }
Michael Jurka28750fb2010-09-24 17:43:49 -07002482 foundCell = true;
2483 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002484 }
2485 }
2486 if (intersectX == -1 && intersectY == -1) {
2487 break;
2488 } else {
2489 // if we failed to find anything, try again but without any requirements of
2490 // intersecting
2491 intersectX = -1;
2492 intersectY = -1;
2493 continue;
2494 }
2495 }
2496
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002497 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08002498 markCellsAsOccupiedForView(ignoreView, occupied);
Michael Jurka28750fb2010-09-24 17:43:49 -07002499 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002500 }
2501
2502 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002503 * A drag event has begun over this layout.
2504 * It may have begun over this layout (in which case onDragChild is called first),
2505 * or it may have begun on another layout.
2506 */
2507 void onDragEnter() {
2508 if (!mDragging) {
2509 // Fade in the drag indicators
2510 if (mCrosshairsAnimator != null) {
2511 mCrosshairsAnimator.animateIn();
2512 }
2513 }
2514 mDragging = true;
2515 }
2516
2517 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002518 * Called when drag has left this CellLayout or has been completed (successfully or not)
2519 */
2520 void onDragExit() {
Joe Onorato4be866d2010-10-10 11:26:02 -07002521 // This can actually be called when we aren't in a drag, e.g. when adding a new
2522 // item to this layout via the customize drawer.
2523 // Guard against that case.
2524 if (mDragging) {
2525 mDragging = false;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002526
Joe Onorato4be866d2010-10-10 11:26:02 -07002527 // Fade out the drag indicators
2528 if (mCrosshairsAnimator != null) {
2529 mCrosshairsAnimator.animateOut();
2530 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002531 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002532
2533 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002534 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002535 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2536 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002537 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002538 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002539 }
2540
2541 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002542 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002543 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002544 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002545 *
2546 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002547 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002548 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002549 if (child != null) {
2550 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002551 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002552 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002553 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002554 }
2555
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002556 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002557 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002558 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002559 * @param cellX X coordinate of upper left corner expressed as a cell position
2560 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002561 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002562 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002563 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002564 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002565 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002566 final int cellWidth = mCellWidth;
2567 final int cellHeight = mCellHeight;
2568 final int widthGap = mWidthGap;
2569 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002570
Winson Chung4b825dcd2011-06-19 12:41:22 -07002571 final int hStartPadding = getPaddingLeft();
2572 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002573
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002574 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2575 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2576
2577 int x = hStartPadding + cellX * (cellWidth + widthGap);
2578 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002579
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002580 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002581 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002582
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002583 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002584 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002585 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07002586 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002587 * @param width Width in pixels
2588 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002589 * @param result An array of length 2 in which to store the result (may be null).
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002590 */
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002591 public int[] rectToCell(int width, int height, int[] result) {
Michael Jurka9987a5c2010-10-08 16:58:12 -07002592 return rectToCell(getResources(), width, height, result);
2593 }
2594
2595 public static int[] rectToCell(Resources resources, int width, int height, int[] result) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002596 // Always assume we're working with the smallest span to make sure we
2597 // reserve enough space in both orientations.
Joe Onorato79e56262009-09-21 15:23:04 -04002598 int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
2599 int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002600 int smallerSize = Math.min(actualWidth, actualHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04002601
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002602 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07002603 int spanX = (int) Math.ceil(width / (float) smallerSize);
2604 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04002605
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07002606 if (result == null) {
2607 return new int[] { spanX, spanY };
2608 }
2609 result[0] = spanX;
2610 result[1] = spanY;
2611 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002612 }
2613
Michael Jurkaf12c75c2011-01-25 22:41:40 -08002614 public int[] cellSpansToSize(int hSpans, int vSpans) {
2615 int[] size = new int[2];
2616 size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
2617 size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
2618 return size;
2619 }
2620
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002621 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08002622 * Calculate the grid spans needed to fit given item
2623 */
2624 public void calculateSpans(ItemInfo info) {
2625 final int minWidth;
2626 final int minHeight;
2627
2628 if (info instanceof LauncherAppWidgetInfo) {
2629 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
2630 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
2631 } else if (info instanceof PendingAddWidgetInfo) {
2632 minWidth = ((PendingAddWidgetInfo) info).minWidth;
2633 minHeight = ((PendingAddWidgetInfo) info).minHeight;
2634 } else {
2635 // It's not a widget, so it must be 1x1
2636 info.spanX = info.spanY = 1;
2637 return;
2638 }
2639 int[] spans = rectToCell(minWidth, minHeight, null);
2640 info.spanX = spans[0];
2641 info.spanY = spans[1];
2642 }
2643
2644 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002645 * Find the first vacant cell, if there is one.
2646 *
2647 * @param vacant Holds the x and y coordinate of the vacant cell
2648 * @param spanX Horizontal cell span.
2649 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -07002650 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002651 * @return True if a vacant cell was found
2652 */
2653 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002654
Michael Jurka0280c3b2010-09-17 15:00:07 -07002655 return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002656 }
2657
2658 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
2659 int xCount, int yCount, boolean[][] occupied) {
2660
Adam Cohen2801caf2011-05-13 20:57:39 -07002661 for (int y = 0; y < yCount; y++) {
2662 for (int x = 0; x < xCount; x++) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002663 boolean available = !occupied[x][y];
2664out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
2665 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
2666 available = available && !occupied[i][j];
2667 if (!available) break out;
2668 }
2669 }
2670
2671 if (available) {
2672 vacant[0] = x;
2673 vacant[1] = y;
2674 return true;
2675 }
2676 }
2677 }
2678
2679 return false;
2680 }
2681
Michael Jurka0280c3b2010-09-17 15:00:07 -07002682 private void clearOccupiedCells() {
2683 for (int x = 0; x < mCountX; x++) {
2684 for (int y = 0; y < mCountY; y++) {
2685 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002686 }
2687 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07002688 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002689
Adam Cohen1b607ed2011-03-03 17:26:50 -08002690 /**
2691 * Given a view, determines how much that view can be expanded in all directions, in terms of
2692 * whether or not there are other items occupying adjacent cells. Used by the
2693 * AppWidgetResizeFrame to determine how the widget can be resized.
2694 */
Adam Cohend4844c32011-02-18 19:25:06 -08002695 public void getExpandabilityArrayForView(View view, int[] expandability) {
Adam Cohen1b607ed2011-03-03 17:26:50 -08002696 final LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohend4844c32011-02-18 19:25:06 -08002697 boolean flag;
2698
Adam Cohen1b607ed2011-03-03 17:26:50 -08002699 expandability[AppWidgetResizeFrame.LEFT] = 0;
Adam Cohend4844c32011-02-18 19:25:06 -08002700 for (int x = lp.cellX - 1; x >= 0; x--) {
2701 flag = false;
2702 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan; y++) {
2703 if (mOccupied[x][y]) flag = true;
2704 }
2705 if (flag) break;
Adam Cohen1b607ed2011-03-03 17:26:50 -08002706 expandability[AppWidgetResizeFrame.LEFT]++;
Adam Cohend4844c32011-02-18 19:25:06 -08002707 }
2708
Adam Cohen1b607ed2011-03-03 17:26:50 -08002709 expandability[AppWidgetResizeFrame.TOP] = 0;
Adam Cohend4844c32011-02-18 19:25:06 -08002710 for (int y = lp.cellY - 1; y >= 0; y--) {
2711 flag = false;
2712 for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan; x++) {
2713 if (mOccupied[x][y]) flag = true;
2714 }
2715 if (flag) break;
Adam Cohen1b607ed2011-03-03 17:26:50 -08002716 expandability[AppWidgetResizeFrame.TOP]++;
2717 }
Adam Cohend4844c32011-02-18 19:25:06 -08002718
Adam Cohen1b607ed2011-03-03 17:26:50 -08002719 expandability[AppWidgetResizeFrame.RIGHT] = 0;
Adam Cohend4844c32011-02-18 19:25:06 -08002720 for (int x = lp.cellX + lp.cellHSpan; x < mCountX; x++) {
2721 flag = false;
2722 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan; y++) {
2723 if (mOccupied[x][y]) flag = true;
2724 }
2725 if (flag) break;
Adam Cohen1b607ed2011-03-03 17:26:50 -08002726 expandability[AppWidgetResizeFrame.RIGHT]++;
2727 }
Adam Cohend4844c32011-02-18 19:25:06 -08002728
Adam Cohen1b607ed2011-03-03 17:26:50 -08002729 expandability[AppWidgetResizeFrame.BOTTOM] = 0;
Adam Cohend4844c32011-02-18 19:25:06 -08002730 for (int y = lp.cellY + lp.cellVSpan; y < mCountY; y++) {
2731 flag = false;
2732 for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan; x++) {
2733 if (mOccupied[x][y]) flag = true;
2734 }
2735 if (flag) break;
Adam Cohen1b607ed2011-03-03 17:26:50 -08002736 expandability[AppWidgetResizeFrame.BOTTOM]++;
2737 }
Adam Cohend4844c32011-02-18 19:25:06 -08002738 }
2739
Adam Cohend41fbf52012-02-16 23:53:59 -08002740 public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002741 markCellsAsUnoccupiedForView(view);
Adam Cohen482ed822012-03-02 14:15:13 -08002742 markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002743 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002744
Adam Cohend4844c32011-02-18 19:25:06 -08002745 public void markCellsAsOccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08002746 markCellsAsOccupiedForView(view, mOccupied);
2747 }
2748 public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002749 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002750 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002751 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002752 }
2753
Adam Cohend4844c32011-02-18 19:25:06 -08002754 public void markCellsAsUnoccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08002755 markCellsAsUnoccupiedForView(view, mOccupied);
2756 }
2757 public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002758 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002759 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002760 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002761 }
2762
Adam Cohen482ed822012-03-02 14:15:13 -08002763 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
2764 boolean value) {
2765 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002766 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
2767 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002768 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002769 }
2770 }
2771 }
2772
Adam Cohen2801caf2011-05-13 20:57:39 -07002773 public int getDesiredWidth() {
Winson Chung4b825dcd2011-06-19 12:41:22 -07002774 return mPaddingLeft + mPaddingRight + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002775 (Math.max((mCountX - 1), 0) * mWidthGap);
2776 }
2777
2778 public int getDesiredHeight() {
Winson Chung4b825dcd2011-06-19 12:41:22 -07002779 return mPaddingTop + mPaddingBottom + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07002780 (Math.max((mCountY - 1), 0) * mHeightGap);
2781 }
2782
Michael Jurka66d72172011-04-12 16:29:25 -07002783 public boolean isOccupied(int x, int y) {
2784 if (x < mCountX && y < mCountY) {
2785 return mOccupied[x][y];
2786 } else {
2787 throw new RuntimeException("Position exceeds the bound of this CellLayout");
2788 }
2789 }
2790
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002791 @Override
2792 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2793 return new CellLayout.LayoutParams(getContext(), attrs);
2794 }
2795
2796 @Override
2797 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2798 return p instanceof CellLayout.LayoutParams;
2799 }
2800
2801 @Override
2802 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2803 return new CellLayout.LayoutParams(p);
2804 }
2805
Winson Chungaafa03c2010-06-11 17:34:16 -07002806 public static class CellLayoutAnimationController extends LayoutAnimationController {
2807 public CellLayoutAnimationController(Animation animation, float delay) {
2808 super(animation, delay);
2809 }
2810
2811 @Override
2812 protected long getDelayForView(View view) {
2813 return (int) (Math.random() * 150);
2814 }
2815 }
2816
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002817 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
2818 /**
2819 * Horizontal location of the item in the grid.
2820 */
2821 @ViewDebug.ExportedProperty
2822 public int cellX;
2823
2824 /**
2825 * Vertical location of the item in the grid.
2826 */
2827 @ViewDebug.ExportedProperty
2828 public int cellY;
2829
2830 /**
Adam Cohen482ed822012-03-02 14:15:13 -08002831 * Temporary horizontal location of the item in the grid during reorder
2832 */
2833 public int tmpCellX;
2834
2835 /**
2836 * Temporary vertical location of the item in the grid during reorder
2837 */
2838 public int tmpCellY;
2839
2840 /**
2841 * Indicates that the temporary coordinates should be used to layout the items
2842 */
2843 public boolean useTmpCoords;
2844
2845 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002846 * Number of cells spanned horizontally by the item.
2847 */
2848 @ViewDebug.ExportedProperty
2849 public int cellHSpan;
2850
2851 /**
2852 * Number of cells spanned vertically by the item.
2853 */
2854 @ViewDebug.ExportedProperty
2855 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07002856
Adam Cohen1b607ed2011-03-03 17:26:50 -08002857 /**
2858 * Indicates whether the item will set its x, y, width and height parameters freely,
2859 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
2860 */
Adam Cohend4844c32011-02-18 19:25:06 -08002861 public boolean isLockedToGrid = true;
2862
Adam Cohen482ed822012-03-02 14:15:13 -08002863 /**
2864 * Indicates whether this item can be reordered. Always true except in the case of the
2865 * the AllApps button.
2866 */
2867 public boolean canReorder = true;
2868
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002869 // X coordinate of the view in the layout.
2870 @ViewDebug.ExportedProperty
2871 int x;
2872 // Y coordinate of the view in the layout.
2873 @ViewDebug.ExportedProperty
2874 int y;
2875
Romain Guy84f296c2009-11-04 15:00:44 -08002876 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07002877
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002878 public LayoutParams(Context c, AttributeSet attrs) {
2879 super(c, attrs);
2880 cellHSpan = 1;
2881 cellVSpan = 1;
2882 }
2883
2884 public LayoutParams(ViewGroup.LayoutParams source) {
2885 super(source);
2886 cellHSpan = 1;
2887 cellVSpan = 1;
2888 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002889
2890 public LayoutParams(LayoutParams source) {
2891 super(source);
2892 this.cellX = source.cellX;
2893 this.cellY = source.cellY;
2894 this.cellHSpan = source.cellHSpan;
2895 this.cellVSpan = source.cellVSpan;
2896 }
2897
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002898 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08002899 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002900 this.cellX = cellX;
2901 this.cellY = cellY;
2902 this.cellHSpan = cellHSpan;
2903 this.cellVSpan = cellVSpan;
2904 }
2905
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002906 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap) {
Adam Cohend4844c32011-02-18 19:25:06 -08002907 if (isLockedToGrid) {
2908 final int myCellHSpan = cellHSpan;
2909 final int myCellVSpan = cellVSpan;
Adam Cohen482ed822012-03-02 14:15:13 -08002910 final int myCellX = useTmpCoords ? tmpCellX : cellX;
2911 final int myCellY = useTmpCoords ? tmpCellY : cellY;
Adam Cohen1b607ed2011-03-03 17:26:50 -08002912
Adam Cohend4844c32011-02-18 19:25:06 -08002913 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
2914 leftMargin - rightMargin;
2915 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
2916 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08002917 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
2918 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08002919 }
2920 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002921
Winson Chungaafa03c2010-06-11 17:34:16 -07002922 public String toString() {
2923 return "(" + this.cellX + ", " + this.cellY + ")";
2924 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07002925
2926 public void setWidth(int width) {
2927 this.width = width;
2928 }
2929
2930 public int getWidth() {
2931 return width;
2932 }
2933
2934 public void setHeight(int height) {
2935 this.height = height;
2936 }
2937
2938 public int getHeight() {
2939 return height;
2940 }
2941
2942 public void setX(int x) {
2943 this.x = x;
2944 }
2945
2946 public int getX() {
2947 return x;
2948 }
2949
2950 public void setY(int y) {
2951 this.y = y;
2952 }
2953
2954 public int getY() {
2955 return y;
2956 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002957 }
2958
Michael Jurka0280c3b2010-09-17 15:00:07 -07002959 // This class stores info for two purposes:
2960 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
2961 // its spanX, spanY, and the screen it is on
2962 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
2963 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
2964 // the CellLayout that was long clicked
Michael Jurkae5fb0f22011-04-11 13:27:46 -07002965 static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002966 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07002967 int cellX = -1;
2968 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002969 int spanX;
2970 int spanY;
2971 int screen;
Winson Chung3d503fb2011-07-13 17:25:49 -07002972 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002973
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002974 @Override
2975 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07002976 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
2977 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002978 }
2979 }
Michael Jurkad771c962011-08-09 15:00:48 -07002980
2981 public boolean lastDownOnOccupiedCell() {
2982 return mLastDownOnOccupiedCell;
2983 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002984}