blob: 35598a2f24e27527d968cb6ceec07b01f62357f9 [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
Daniel Sandler325dc232013-06-05 22:57:57 -040017package com.android.launcher3;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080018
Joe Onorato4be866d2010-10-10 11:26:02 -070019import android.animation.Animator;
Michael Jurka629758f2012-06-14 16:18:21 -070020import android.animation.AnimatorListenerAdapter;
Brandon Keely50e6e562012-05-08 16:28:49 -070021import android.animation.AnimatorSet;
Chet Haase00397b12010-10-07 11:13:10 -070022import android.animation.TimeInterpolator;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070023import android.animation.ValueAnimator;
24import android.animation.ValueAnimator.AnimatorUpdateListener;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080025import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040026import android.content.res.Resources;
Winson Chungaafa03c2010-06-11 17:34:16 -070027import android.content.res.TypedArray;
Joe Onorato4be866d2010-10-10 11:26:02 -070028import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070029import android.graphics.Canvas;
Andrew Flynn0dca1ec2012-02-29 13:33:22 -080030import android.graphics.Color;
Joe Onorato4be866d2010-10-10 11:26:02 -070031import android.graphics.Paint;
Patrick Dubroyde7658b2010-09-27 11:15:43 -070032import android.graphics.Point;
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;
Adam Cohen1462de32012-07-24 22:34:36 -070039import android.os.Parcelable;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080040import android.util.AttributeSet;
Joe Onorato4be866d2010-10-10 11:26:02 -070041import android.util.Log;
Adam Cohen1462de32012-07-24 22:34:36 -070042import android.util.SparseArray;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080043import android.view.MotionEvent;
44import android.view.View;
45import android.view.ViewDebug;
46import android.view.ViewGroup;
Winson Chungaafa03c2010-06-11 17:34:16 -070047import android.view.animation.Animation;
Winson Chung150fbab2010-09-29 17:14:26 -070048import android.view.animation.DecelerateInterpolator;
Winson Chungaafa03c2010-06-11 17:34:16 -070049import android.view.animation.LayoutAnimationController;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080050
Daniel Sandler325dc232013-06-05 22:57:57 -040051import com.android.launcher3.R;
52import com.android.launcher3.FolderIcon.FolderRingAnimator;
Patrick Dubroy8e58e912010-10-14 13:21:48 -070053
Adam Cohen69ce2e52011-07-03 19:25:21 -070054import java.util.ArrayList;
Adam Cohenc0dcf592011-06-01 15:30:43 -070055import java.util.Arrays;
Adam Cohenf3900c22012-11-16 18:28:11 -080056import java.util.Collections;
57import java.util.Comparator;
Adam Cohenbfbfd262011-06-13 16:55:12 -070058import java.util.HashMap;
Adam Cohend41fbf52012-02-16 23:53:59 -080059import java.util.Stack;
Adam Cohenc0dcf592011-06-01 15:30:43 -070060
Michael Jurkabdb5c532011-02-01 15:05:06 -080061public class CellLayout extends ViewGroup {
Winson Chungaafa03c2010-06-11 17:34:16 -070062 static final String TAG = "CellLayout";
63
Adam Cohen2acce882012-03-28 19:03:19 -070064 private Launcher mLauncher;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080065 private int mCellWidth;
66 private int mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070067
Adam Cohend22015c2010-07-26 22:02:18 -070068 private int mCountX;
69 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080070
Adam Cohen234c4cd2011-07-17 21:03:04 -070071 private int mOriginalWidthGap;
72 private int mOriginalHeightGap;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080073 private int mWidthGap;
74 private int mHeightGap;
Winson Chung4b825dcd2011-06-19 12:41:22 -070075 private int mMaxGap;
Adam Cohenebea84d2011-11-09 17:20:41 -080076 private boolean mScrollingTransformsDirty = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080077
78 private final Rect mRect = new Rect();
79 private final CellInfo mCellInfo = new CellInfo();
Winson Chungaafa03c2010-06-11 17:34:16 -070080
Patrick Dubroyde7658b2010-09-27 11:15:43 -070081 // These are temporary variables to prevent having to allocate a new object just to
82 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
Winson Chung0be025d2011-05-23 17:45:09 -070083 private final int[] mTmpXY = new int[2];
Patrick Dubroyde7658b2010-09-27 11:15:43 -070084 private final int[] mTmpPoint = new int[2];
Adam Cohen69ce2e52011-07-03 19:25:21 -070085 int[] mTempLocation = new int[2];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070086
The Android Open Source Project31dd5032009-03-03 19:32:27 -080087 boolean[][] mOccupied;
Adam Cohen482ed822012-03-02 14:15:13 -080088 boolean[][] mTmpOccupied;
Michael Jurkad771c962011-08-09 15:00:48 -070089 private boolean mLastDownOnOccupiedCell = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080090
Michael Jurkadee05892010-07-27 10:01:56 -070091 private OnTouchListener mInterceptTouchListener;
92
Adam Cohen69ce2e52011-07-03 19:25:21 -070093 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
Adam Cohenc51934b2011-07-26 21:07:43 -070094 private int[] mFolderLeaveBehindCell = {-1, -1};
Adam Cohen69ce2e52011-07-03 19:25:21 -070095
Adam Cohenb5ba0972011-09-07 18:02:31 -070096 private int mForegroundAlpha = 0;
Michael Jurka5f1c5092010-09-03 14:15:02 -070097 private float mBackgroundAlpha;
Adam Cohen1b0aaac2010-10-28 11:11:18 -070098 private float mBackgroundAlphaMultiplier = 1.0f;
Adam Cohenf34bab52010-09-30 14:11:56 -070099
Michael Jurka33945b22010-12-21 18:19:38 -0800100 private Drawable mNormalBackground;
Michael Jurka33945b22010-12-21 18:19:38 -0800101 private Drawable mActiveGlowBackground;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700102 private Drawable mOverScrollForegroundDrawable;
103 private Drawable mOverScrollLeft;
104 private Drawable mOverScrollRight;
Michael Jurka18014792010-10-14 09:01:34 -0700105 private Rect mBackgroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700106 private Rect mForegroundRect;
Adam Cohenb5ba0972011-09-07 18:02:31 -0700107 private int mForegroundPadding;
Patrick Dubroy1262e362010-10-06 15:49:50 -0700108
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700109 // These values allow a fixed measurement to be set on the CellLayout.
110 private int mFixedWidth = -1;
111 private int mFixedHeight = -1;
112
Michael Jurka33945b22010-12-21 18:19:38 -0800113 // If we're actively dragging something over this screen, mIsDragOverlapping is true
114 private boolean mIsDragOverlapping = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700115 private final Point mDragCenter = new Point();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700116
Winson Chung150fbab2010-09-29 17:14:26 -0700117 // These arrays are used to implement the drag visualization on x-large screens.
Joe Onorato4be866d2010-10-10 11:26:02 -0700118 // They are used as circular arrays, indexed by mDragOutlineCurrent.
Adam Cohend41fbf52012-02-16 23:53:59 -0800119 private Rect[] mDragOutlines = new Rect[4];
Chet Haase472b2812010-10-14 07:02:04 -0700120 private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
Joe Onorato4be866d2010-10-10 11:26:02 -0700121 private InterruptibleInOutAnimator[] mDragOutlineAnims =
122 new InterruptibleInOutAnimator[mDragOutlines.length];
Winson Chung150fbab2010-09-29 17:14:26 -0700123
124 // Used as an index into the above 3 arrays; indicates which is the most current value.
Joe Onorato4be866d2010-10-10 11:26:02 -0700125 private int mDragOutlineCurrent = 0;
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700126 private final Paint mDragOutlinePaint = new Paint();
Winson Chung150fbab2010-09-29 17:14:26 -0700127
Patrick Dubroy96864c32011-03-10 17:17:23 -0800128 private BubbleTextView mPressedOrFocusedIcon;
129
Adam Cohen482ed822012-03-02 14:15:13 -0800130 private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
131 HashMap<CellLayout.LayoutParams, Animator>();
Adam Cohen19f37922012-03-21 11:59:11 -0700132 private HashMap<View, ReorderHintAnimation>
133 mShakeAnimators = new HashMap<View, ReorderHintAnimation>();
134
135 private boolean mItemPlacementDirty = false;
Adam Cohenbfbfd262011-06-13 16:55:12 -0700136
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700137 // When a drag operation is in progress, holds the nearest cell to the touch point
138 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800139
Joe Onorato4be866d2010-10-10 11:26:02 -0700140 private boolean mDragging = false;
141
Patrick Dubroyce34a972010-10-19 10:34:32 -0700142 private TimeInterpolator mEaseOutInterpolator;
Michael Jurkaa52570f2012-03-20 03:18:20 -0700143 private ShortcutAndWidgetContainer mShortcutsAndWidgets;
Patrick Dubroyce34a972010-10-19 10:34:32 -0700144
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800145 private boolean mIsHotseat = false;
Adam Cohen307fe232012-08-16 17:55:58 -0700146 private float mHotseatScale = 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 Cohena897f392012-04-27 18:12:05 -0700155 static final int LANDSCAPE = 0;
156 static final int PORTRAIT = 1;
157
Adam Cohen7bdfc972012-05-22 16:50:35 -0700158 private static final float REORDER_HINT_MAGNITUDE = 0.12f;
Adam Cohen19f37922012-03-21 11:59:11 -0700159 private static final int REORDER_ANIMATION_DURATION = 150;
160 private float mReorderHintAnimationMagnitude;
161
Adam Cohen482ed822012-03-02 14:15:13 -0800162 private ArrayList<View> mIntersectingViews = new ArrayList<View>();
163 private Rect mOccupiedRect = new Rect();
164 private int[] mDirectionVector = new int[2];
Adam Cohen19f37922012-03-21 11:59:11 -0700165 int[] mPreviousReorderDirection = new int[2];
Adam Cohenb209e632012-03-27 17:09:36 -0700166 private static final int INVALID_DIRECTION = -100;
Adam Cohenc6cc61d2012-04-04 12:47:08 -0700167 private DropTarget.DragEnforcer mDragEnforcer;
Adam Cohen482ed822012-03-02 14:15:13 -0800168
Romain Guy8a0bff52012-05-06 13:14:33 -0700169 private final static PorterDuffXfermode sAddBlendMode =
170 new PorterDuffXfermode(PorterDuff.Mode.ADD);
Michael Jurkaca993832012-06-29 15:17:04 -0700171 private final static Paint sPaint = new Paint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700172
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800173 public CellLayout(Context context) {
174 this(context, null);
175 }
176
177 public CellLayout(Context context, AttributeSet attrs) {
178 this(context, attrs, 0);
179 }
180
181 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
182 super(context, attrs, defStyle);
Michael Jurka8b805b12012-04-18 14:23:14 -0700183 mDragEnforcer = new DropTarget.DragEnforcer(context);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700184
185 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
186 // the user where a dragged item will land when dropped.
187 setWillNotDraw(false);
Romain Guyce3cbd12013-02-25 15:00:36 -0800188 setClipToPadding(false);
Adam Cohen2acce882012-03-28 19:03:19 -0700189 mLauncher = (Launcher) context;
Michael Jurkaa63c4522010-08-19 13:52:27 -0700190
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800191 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
192
Adam Cohenf4bd5792012-04-27 11:35:29 -0700193 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
194 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
Adam Cohen234c4cd2011-07-17 21:03:04 -0700195 mWidthGap = mOriginalWidthGap = a.getDimensionPixelSize(R.styleable.CellLayout_widthGap, 0);
196 mHeightGap = mOriginalHeightGap = a.getDimensionPixelSize(R.styleable.CellLayout_heightGap, 0);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700197 mMaxGap = a.getDimensionPixelSize(R.styleable.CellLayout_maxGap, 0);
Adam Cohend22015c2010-07-26 22:02:18 -0700198 mCountX = LauncherModel.getCellCountX();
199 mCountY = LauncherModel.getCellCountY();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700200 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800201 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen5b53f292012-03-29 14:30:35 -0700202 mPreviousReorderDirection[0] = INVALID_DIRECTION;
203 mPreviousReorderDirection[1] = INVALID_DIRECTION;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800204
205 a.recycle();
206
207 setAlwaysDrawnWithCacheEnabled(false);
208
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700209 final Resources res = getResources();
Adam Cohen307fe232012-08-16 17:55:58 -0700210 mHotseatScale = (res.getInteger(R.integer.hotseat_item_scale_percentage) / 100f);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700211
Winson Chung967289b2011-06-30 18:09:30 -0700212 mNormalBackground = res.getDrawable(R.drawable.homescreen_blue_normal_holo);
Winson Chungdea74b72011-09-13 18:06:43 -0700213 mActiveGlowBackground = res.getDrawable(R.drawable.homescreen_blue_strong_holo);
Michael Jurka33945b22010-12-21 18:19:38 -0800214
Adam Cohenb5ba0972011-09-07 18:02:31 -0700215 mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
216 mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
217 mForegroundPadding =
218 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
Michael Jurka33945b22010-12-21 18:19:38 -0800219
Adam Cohen19f37922012-03-21 11:59:11 -0700220 mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE *
221 res.getDimensionPixelSize(R.dimen.app_icon_size));
222
Winson Chungb26f3d62011-06-02 10:49:29 -0700223 mNormalBackground.setFilterBitmap(true);
Winson Chungb26f3d62011-06-02 10:49:29 -0700224 mActiveGlowBackground.setFilterBitmap(true);
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700225
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700226 // Initialize the data structures used for the drag visualization.
Winson Chung150fbab2010-09-29 17:14:26 -0700227
Patrick Dubroyce34a972010-10-19 10:34:32 -0700228 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700229
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700230
Winson Chungb8c69f32011-10-19 21:36:08 -0700231 mDragCell[0] = mDragCell[1] = -1;
Joe Onorato4be866d2010-10-10 11:26:02 -0700232 for (int i = 0; i < mDragOutlines.length; i++) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800233 mDragOutlines[i] = new Rect(-1, -1, -1, -1);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700234 }
235
236 // When dragging things around the home screens, we show a green outline of
237 // where the item will land. The outlines gradually fade out, leaving a trail
238 // behind the drag path.
239 // Set up all the animations that are used to implement this fading.
240 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
Chet Haase472b2812010-10-14 07:02:04 -0700241 final float fromAlphaValue = 0;
242 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
Joe Onorato4be866d2010-10-10 11:26:02 -0700243
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700244 Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
Joe Onorato4be866d2010-10-10 11:26:02 -0700245
246 for (int i = 0; i < mDragOutlineAnims.length; i++) {
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700247 final InterruptibleInOutAnimator anim =
Michael Jurkaf1ad6082013-03-13 12:55:46 +0100248 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
Patrick Dubroyce34a972010-10-19 10:34:32 -0700249 anim.getAnimator().setInterpolator(mEaseOutInterpolator);
Patrick Dubroy046e7eb2010-10-06 12:14:43 -0700250 final int thisIndex = i;
Chet Haase472b2812010-10-14 07:02:04 -0700251 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700252 public void onAnimationUpdate(ValueAnimator animation) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700253 final Bitmap outline = (Bitmap)anim.getTag();
254
255 // If an animation is started and then stopped very quickly, we can still
256 // get spurious updates we've cleared the tag. Guard against this.
257 if (outline == null) {
Michael Jurka3a9fced2012-04-13 14:44:29 -0700258 @SuppressWarnings("all") // suppress dead code warning
259 final boolean debug = false;
260 if (debug) {
Patrick Dubroyfe6bd872010-10-13 17:32:10 -0700261 Object val = animation.getAnimatedValue();
262 Log.d(TAG, "anim " + thisIndex + " update: " + val +
263 ", isStopped " + anim.isStopped());
264 }
Joe Onorato4be866d2010-10-10 11:26:02 -0700265 // Try to prevent it from continuing to run
266 animation.cancel();
267 } else {
Chet Haase472b2812010-10-14 07:02:04 -0700268 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
Adam Cohend41fbf52012-02-16 23:53:59 -0800269 CellLayout.this.invalidate(mDragOutlines[thisIndex]);
Joe Onorato4be866d2010-10-10 11:26:02 -0700270 }
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700271 }
272 });
Joe Onorato4be866d2010-10-10 11:26:02 -0700273 // The animation holds a reference to the drag outline bitmap as long is it's
274 // running. This way the bitmap can be GCed when the animations are complete.
Chet Haase472b2812010-10-14 07:02:04 -0700275 anim.getAnimator().addListener(new AnimatorListenerAdapter() {
Michael Jurka3c4c20f2010-10-28 15:36:06 -0700276 @Override
Joe Onorato4be866d2010-10-10 11:26:02 -0700277 public void onAnimationEnd(Animator animation) {
Chet Haase472b2812010-10-14 07:02:04 -0700278 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
Joe Onorato4be866d2010-10-10 11:26:02 -0700279 anim.setTag(null);
280 }
281 }
282 });
283 mDragOutlineAnims[i] = anim;
Patrick Dubroyde7658b2010-09-27 11:15:43 -0700284 }
Patrick Dubroyce34a972010-10-19 10:34:32 -0700285
Michael Jurka18014792010-10-14 09:01:34 -0700286 mBackgroundRect = new Rect();
Adam Cohenb5ba0972011-09-07 18:02:31 -0700287 mForegroundRect = new Rect();
Michael Jurkabea15192010-11-17 12:33:46 -0800288
Michael Jurkaa52570f2012-03-20 03:18:20 -0700289 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
Adam Cohen2374abf2013-04-16 14:56:57 -0700290 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
291 mCountX);
292
Michael Jurkaa52570f2012-03-20 03:18:20 -0700293 addView(mShortcutsAndWidgets);
Michael Jurka18014792010-10-14 09:01:34 -0700294 }
295
Michael Jurkaf6440da2011-04-05 14:50:34 -0700296 static int widthInPortrait(Resources r, int numCells) {
297 // We use this method from Workspace to figure out how many rows/columns Launcher should
298 // have. We ignore the left/right padding on CellLayout because it turns out in our design
299 // the padding extends outside the visible screen size, but it looked fine anyway.
Michael Jurkaf6440da2011-04-05 14:50:34 -0700300 int cellWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700301 int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
302 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
Michael Jurkaf6440da2011-04-05 14:50:34 -0700303
Winson Chung4b825dcd2011-06-19 12:41:22 -0700304 return minGap * (numCells - 1) + cellWidth * numCells;
Michael Jurkaf6440da2011-04-05 14:50:34 -0700305 }
306
Michael Jurkaf6440da2011-04-05 14:50:34 -0700307 static int heightInLandscape(Resources r, int numCells) {
308 // We use this method from Workspace to figure out how many rows/columns Launcher should
309 // have. We ignore the left/right padding on CellLayout because it turns out in our design
310 // the padding extends outside the visible screen size, but it looked fine anyway.
Michael Jurkaf6440da2011-04-05 14:50:34 -0700311 int cellHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height);
Winson Chung4b825dcd2011-06-19 12:41:22 -0700312 int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
313 r.getDimensionPixelSize(R.dimen.workspace_height_gap));
Michael Jurkaf6440da2011-04-05 14:50:34 -0700314
Winson Chung4b825dcd2011-06-19 12:41:22 -0700315 return minGap * (numCells - 1) + cellHeight * numCells;
Michael Jurkaf6440da2011-04-05 14:50:34 -0700316 }
317
Adam Cohen2801caf2011-05-13 20:57:39 -0700318 public void enableHardwareLayers() {
Michael Jurkaca993832012-06-29 15:17:04 -0700319 mShortcutsAndWidgets.setLayerType(LAYER_TYPE_HARDWARE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700320 }
321
322 public void disableHardwareLayers() {
Michael Jurkaca993832012-06-29 15:17:04 -0700323 mShortcutsAndWidgets.setLayerType(LAYER_TYPE_NONE, sPaint);
Michael Jurkad51f33a2012-06-28 15:35:26 -0700324 }
325
326 public void buildHardwareLayer() {
327 mShortcutsAndWidgets.buildLayer();
Adam Cohen2801caf2011-05-13 20:57:39 -0700328 }
329
Adam Cohen307fe232012-08-16 17:55:58 -0700330 public float getChildrenScale() {
331 return mIsHotseat ? mHotseatScale : 1.0f;
332 }
333
Adam Cohen2801caf2011-05-13 20:57:39 -0700334 public void setGridSize(int x, int y) {
335 mCountX = x;
336 mCountY = y;
337 mOccupied = new boolean[mCountX][mCountY];
Adam Cohen482ed822012-03-02 14:15:13 -0800338 mTmpOccupied = new boolean[mCountX][mCountY];
Adam Cohen7fbec102012-03-27 12:42:19 -0700339 mTempRectStack.clear();
Adam Cohen2374abf2013-04-16 14:56:57 -0700340 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
341 mCountX);
Adam Cohen76fc0852011-06-17 13:26:23 -0700342 requestLayout();
Adam Cohen2801caf2011-05-13 20:57:39 -0700343 }
344
Adam Cohen2374abf2013-04-16 14:56:57 -0700345 // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
346 public void setInvertIfRtl(boolean invert) {
347 mShortcutsAndWidgets.setInvertIfRtl(invert);
348 }
349
Patrick Dubroy96864c32011-03-10 17:17:23 -0800350 private void invalidateBubbleTextView(BubbleTextView icon) {
351 final int padding = icon.getPressedOrFocusedBackgroundPadding();
Winson Chung4b825dcd2011-06-19 12:41:22 -0700352 invalidate(icon.getLeft() + getPaddingLeft() - padding,
353 icon.getTop() + getPaddingTop() - padding,
354 icon.getRight() + getPaddingLeft() + padding,
355 icon.getBottom() + getPaddingTop() + padding);
Patrick Dubroy96864c32011-03-10 17:17:23 -0800356 }
357
Adam Cohenb5ba0972011-09-07 18:02:31 -0700358 void setOverScrollAmount(float r, boolean left) {
359 if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
360 mOverScrollForegroundDrawable = mOverScrollLeft;
361 } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
362 mOverScrollForegroundDrawable = mOverScrollRight;
363 }
364
365 mForegroundAlpha = (int) Math.round((r * 255));
366 mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
367 invalidate();
368 }
369
Patrick Dubroy96864c32011-03-10 17:17:23 -0800370 void setPressedOrFocusedIcon(BubbleTextView icon) {
371 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
372 // requires an expanded clip rect (due to the glow's blur radius)
373 BubbleTextView oldIcon = mPressedOrFocusedIcon;
374 mPressedOrFocusedIcon = icon;
375 if (oldIcon != null) {
376 invalidateBubbleTextView(oldIcon);
377 }
378 if (mPressedOrFocusedIcon != null) {
379 invalidateBubbleTextView(mPressedOrFocusedIcon);
380 }
381 }
382
Michael Jurka33945b22010-12-21 18:19:38 -0800383 void setIsDragOverlapping(boolean isDragOverlapping) {
384 if (mIsDragOverlapping != isDragOverlapping) {
385 mIsDragOverlapping = isDragOverlapping;
386 invalidate();
387 }
388 }
389
390 boolean getIsDragOverlapping() {
391 return mIsDragOverlapping;
392 }
393
Adam Cohenebea84d2011-11-09 17:20:41 -0800394 protected void setOverscrollTransformsDirty(boolean dirty) {
395 mScrollingTransformsDirty = dirty;
396 }
397
398 protected void resetOverscrollTransforms() {
399 if (mScrollingTransformsDirty) {
400 setOverscrollTransformsDirty(false);
401 setTranslationX(0);
402 setRotationY(0);
403 // It doesn't matter if we pass true or false here, the important thing is that we
404 // pass 0, which results in the overscroll drawable not being drawn any more.
405 setOverScrollAmount(0, false);
406 setPivotX(getMeasuredWidth() / 2);
407 setPivotY(getMeasuredHeight() / 2);
408 }
409 }
410
Adam Cohen307fe232012-08-16 17:55:58 -0700411 public void scaleRect(Rect r, float scale) {
412 if (scale != 1.0f) {
413 r.left = (int) (r.left * scale + 0.5f);
414 r.top = (int) (r.top * scale + 0.5f);
415 r.right = (int) (r.right * scale + 0.5f);
416 r.bottom = (int) (r.bottom * scale + 0.5f);
417 }
418 }
419
420 Rect temp = new Rect();
421 void scaleRectAboutCenter(Rect in, Rect out, float scale) {
422 int cx = in.centerX();
423 int cy = in.centerY();
424 out.set(in);
425 out.offset(-cx, -cy);
426 scaleRect(out, scale);
427 out.offset(cx, cy);
428 }
429
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700430 @Override
Patrick Dubroy1262e362010-10-06 15:49:50 -0700431 protected void onDraw(Canvas canvas) {
Michael Jurka3e7c7632010-10-02 16:01:03 -0700432 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
433 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
434 // When we're small, we are either drawn normally or in the "accepts drops" state (during
435 // a drag). However, we also drag the mini hover background *over* one of those two
436 // backgrounds
Winson Chungb26f3d62011-06-02 10:49:29 -0700437 if (mBackgroundAlpha > 0.0f) {
Adam Cohenf34bab52010-09-30 14:11:56 -0700438 Drawable bg;
Michael Jurka33945b22010-12-21 18:19:38 -0800439
440 if (mIsDragOverlapping) {
441 // In the mini case, we draw the active_glow bg *over* the active background
Michael Jurkabdf78552011-10-31 14:34:25 -0700442 bg = mActiveGlowBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700443 } else {
Michael Jurkabdf78552011-10-31 14:34:25 -0700444 bg = mNormalBackground;
Adam Cohenf34bab52010-09-30 14:11:56 -0700445 }
Michael Jurka33945b22010-12-21 18:19:38 -0800446
447 bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
448 bg.setBounds(mBackgroundRect);
449 bg.draw(canvas);
Michael Jurkaa63c4522010-08-19 13:52:27 -0700450 }
Romain Guya6abce82009-11-10 02:54:41 -0800451
Patrick Dubroy8e58e912010-10-14 13:21:48 -0700452 final Paint paint = mDragOutlinePaint;
Joe Onorato4be866d2010-10-10 11:26:02 -0700453 for (int i = 0; i < mDragOutlines.length; i++) {
Chet Haase472b2812010-10-14 07:02:04 -0700454 final float alpha = mDragOutlineAlphas[i];
Joe Onorato4be866d2010-10-10 11:26:02 -0700455 if (alpha > 0) {
Adam Cohend41fbf52012-02-16 23:53:59 -0800456 final Rect r = mDragOutlines[i];
Adam Cohen307fe232012-08-16 17:55:58 -0700457 scaleRectAboutCenter(r, temp, getChildrenScale());
Joe Onorato4be866d2010-10-10 11:26:02 -0700458 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
Chet Haase472b2812010-10-14 07:02:04 -0700459 paint.setAlpha((int)(alpha + .5f));
Adam Cohen307fe232012-08-16 17:55:58 -0700460 canvas.drawBitmap(b, null, temp, paint);
Winson Chung150fbab2010-09-29 17:14:26 -0700461 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700462 }
Patrick Dubroy96864c32011-03-10 17:17:23 -0800463
464 // We draw the pressed or focused BubbleTextView's background in CellLayout because it
465 // requires an expanded clip rect (due to the glow's blur radius)
466 if (mPressedOrFocusedIcon != null) {
467 final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
468 final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
469 if (b != null) {
470 canvas.drawBitmap(b,
Winson Chung4b825dcd2011-06-19 12:41:22 -0700471 mPressedOrFocusedIcon.getLeft() + getPaddingLeft() - padding,
472 mPressedOrFocusedIcon.getTop() + getPaddingTop() - padding,
Patrick Dubroy96864c32011-03-10 17:17:23 -0800473 null);
474 }
475 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700476
Adam Cohen482ed822012-03-02 14:15:13 -0800477 if (DEBUG_VISUALIZE_OCCUPIED) {
478 int[] pt = new int[2];
479 ColorDrawable cd = new ColorDrawable(Color.RED);
Adam Cohene7587d22012-05-24 18:50:02 -0700480 cd.setBounds(0, 0, mCellWidth, mCellHeight);
Adam Cohen482ed822012-03-02 14:15:13 -0800481 for (int i = 0; i < mCountX; i++) {
482 for (int j = 0; j < mCountY; j++) {
483 if (mOccupied[i][j]) {
484 cellToPoint(i, j, pt);
485 canvas.save();
486 canvas.translate(pt[0], pt[1]);
487 cd.draw(canvas);
488 canvas.restore();
489 }
490 }
491 }
492 }
493
Andrew Flynn850d2e72012-04-26 16:51:20 -0700494 int previewOffset = FolderRingAnimator.sPreviewSize;
495
Adam Cohen69ce2e52011-07-03 19:25:21 -0700496 // The folder outer / inner ring image(s)
497 for (int i = 0; i < mFolderOuterRings.size(); i++) {
498 FolderRingAnimator fra = mFolderOuterRings.get(i);
499
500 // Draw outer ring
501 Drawable d = FolderRingAnimator.sSharedOuterRingDrawable;
502 int width = (int) fra.getOuterRingSize();
503 int height = width;
504 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
505
506 int centerX = mTempLocation[0] + mCellWidth / 2;
Andrew Flynn850d2e72012-04-26 16:51:20 -0700507 int centerY = mTempLocation[1] + previewOffset / 2;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700508
509 canvas.save();
510 canvas.translate(centerX - width / 2, centerY - height / 2);
511 d.setBounds(0, 0, width, height);
512 d.draw(canvas);
513 canvas.restore();
514
515 // Draw inner ring
516 d = FolderRingAnimator.sSharedInnerRingDrawable;
517 width = (int) fra.getInnerRingSize();
518 height = width;
519 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
520
521 centerX = mTempLocation[0] + mCellWidth / 2;
Andrew Flynn850d2e72012-04-26 16:51:20 -0700522 centerY = mTempLocation[1] + previewOffset / 2;
Adam Cohen69ce2e52011-07-03 19:25:21 -0700523 canvas.save();
524 canvas.translate(centerX - width / 2, centerY - width / 2);
525 d.setBounds(0, 0, width, height);
526 d.draw(canvas);
527 canvas.restore();
528 }
Adam Cohenc51934b2011-07-26 21:07:43 -0700529
530 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
531 Drawable d = FolderIcon.sSharedFolderLeaveBehind;
532 int width = d.getIntrinsicWidth();
533 int height = d.getIntrinsicHeight();
534
535 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
536 int centerX = mTempLocation[0] + mCellWidth / 2;
Andrew Flynn850d2e72012-04-26 16:51:20 -0700537 int centerY = mTempLocation[1] + previewOffset / 2;
Adam Cohenc51934b2011-07-26 21:07:43 -0700538
539 canvas.save();
540 canvas.translate(centerX - width / 2, centerY - width / 2);
541 d.setBounds(0, 0, width, height);
542 d.draw(canvas);
543 canvas.restore();
544 }
Adam Cohen69ce2e52011-07-03 19:25:21 -0700545 }
546
Adam Cohenb5ba0972011-09-07 18:02:31 -0700547 @Override
548 protected void dispatchDraw(Canvas canvas) {
549 super.dispatchDraw(canvas);
550 if (mForegroundAlpha > 0) {
551 mOverScrollForegroundDrawable.setBounds(mForegroundRect);
552 Paint p = ((NinePatchDrawable) mOverScrollForegroundDrawable).getPaint();
Romain Guy8a0bff52012-05-06 13:14:33 -0700553 p.setXfermode(sAddBlendMode);
Adam Cohenb5ba0972011-09-07 18:02:31 -0700554 mOverScrollForegroundDrawable.draw(canvas);
555 p.setXfermode(null);
556 }
557 }
558
Adam Cohen69ce2e52011-07-03 19:25:21 -0700559 public void showFolderAccept(FolderRingAnimator fra) {
560 mFolderOuterRings.add(fra);
561 }
562
563 public void hideFolderAccept(FolderRingAnimator fra) {
564 if (mFolderOuterRings.contains(fra)) {
565 mFolderOuterRings.remove(fra);
566 }
567 invalidate();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700568 }
569
Adam Cohenc51934b2011-07-26 21:07:43 -0700570 public void setFolderLeaveBehindCell(int x, int y) {
571 mFolderLeaveBehindCell[0] = x;
572 mFolderLeaveBehindCell[1] = y;
573 invalidate();
574 }
575
576 public void clearFolderLeaveBehind() {
577 mFolderLeaveBehindCell[0] = -1;
578 mFolderLeaveBehindCell[1] = -1;
579 invalidate();
580 }
581
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700582 @Override
Michael Jurkae6235dd2011-10-04 15:02:05 -0700583 public boolean shouldDelayChildPressedState() {
584 return false;
585 }
586
Adam Cohen1462de32012-07-24 22:34:36 -0700587 public void restoreInstanceState(SparseArray<Parcelable> states) {
588 dispatchRestoreInstanceState(states);
589 }
590
Michael Jurkae6235dd2011-10-04 15:02:05 -0700591 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700592 public void cancelLongPress() {
593 super.cancelLongPress();
594
595 // Cancel long press for all children
596 final int count = getChildCount();
597 for (int i = 0; i < count; i++) {
598 final View child = getChildAt(i);
599 child.cancelLongPress();
600 }
601 }
602
Michael Jurkadee05892010-07-27 10:01:56 -0700603 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
604 mInterceptTouchListener = listener;
605 }
606
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800607 int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700608 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800609 }
610
611 int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700612 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800613 }
614
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800615 public void setIsHotseat(boolean isHotseat) {
616 mIsHotseat = isHotseat;
617 }
618
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800619 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
Andrew Flynn850d2e72012-04-26 16:51:20 -0700620 boolean markCells) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700621 final LayoutParams lp = params;
622
Andrew Flynnde38e422012-05-08 11:22:15 -0700623 // Hotseat icons - remove text
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800624 if (child instanceof BubbleTextView) {
625 BubbleTextView bubbleChild = (BubbleTextView) child;
626
Andrew Flynnde38e422012-05-08 11:22:15 -0700627 Resources res = getResources();
628 if (mIsHotseat) {
629 bubbleChild.setTextColor(res.getColor(android.R.color.transparent));
630 } else {
631 bubbleChild.setTextColor(res.getColor(R.color.workspace_icon_text_color));
Andrew Flynn0dca1ec2012-02-29 13:33:22 -0800632 }
633 }
634
Adam Cohen307fe232012-08-16 17:55:58 -0700635 child.setScaleX(getChildrenScale());
636 child.setScaleY(getChildrenScale());
637
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800638 // Generate an id for each view, this assumes we have at most 256x256 cells
639 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700640 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700641 // If the horizontal or vertical span is set to -1, it is taken to
642 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700643 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
644 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800645
Winson Chungaafa03c2010-06-11 17:34:16 -0700646 child.setId(childId);
647
Michael Jurkaa52570f2012-03-20 03:18:20 -0700648 mShortcutsAndWidgets.addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700649
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700650 if (markCells) markCellsAsOccupiedForView(child);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700651
Winson Chungaafa03c2010-06-11 17:34:16 -0700652 return true;
653 }
654 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800655 }
Michael Jurka3e7c7632010-10-02 16:01:03 -0700656
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800657 @Override
Michael Jurka0280c3b2010-09-17 15:00:07 -0700658 public void removeAllViews() {
659 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700660 mShortcutsAndWidgets.removeAllViews();
Michael Jurka0280c3b2010-09-17 15:00:07 -0700661 }
662
663 @Override
664 public void removeAllViewsInLayout() {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700665 if (mShortcutsAndWidgets.getChildCount() > 0) {
Michael Jurka7cfc2822011-08-02 20:19:24 -0700666 clearOccupiedCells();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700667 mShortcutsAndWidgets.removeAllViewsInLayout();
Michael Jurka7cfc2822011-08-02 20:19:24 -0700668 }
Michael Jurka0280c3b2010-09-17 15:00:07 -0700669 }
670
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700671 public void removeViewWithoutMarkingCells(View view) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700672 mShortcutsAndWidgets.removeView(view);
Michael Jurkaf3ca3ab2010-10-20 17:08:24 -0700673 }
674
Michael Jurka0280c3b2010-09-17 15:00:07 -0700675 @Override
676 public void removeView(View view) {
677 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700678 mShortcutsAndWidgets.removeView(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700679 }
680
681 @Override
682 public void removeViewAt(int index) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700683 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
684 mShortcutsAndWidgets.removeViewAt(index);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700685 }
686
687 @Override
688 public void removeViewInLayout(View view) {
689 markCellsAsUnoccupiedForView(view);
Michael Jurkaa52570f2012-03-20 03:18:20 -0700690 mShortcutsAndWidgets.removeViewInLayout(view);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700691 }
692
693 @Override
694 public void removeViews(int start, int count) {
695 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700696 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700697 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700698 mShortcutsAndWidgets.removeViews(start, count);
Michael Jurka0280c3b2010-09-17 15:00:07 -0700699 }
700
701 @Override
702 public void removeViewsInLayout(int start, int count) {
703 for (int i = start; i < start + count; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700704 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
Michael Jurka0280c3b2010-09-17 15:00:07 -0700705 }
Michael Jurkaa52570f2012-03-20 03:18:20 -0700706 mShortcutsAndWidgets.removeViewsInLayout(start, count);
Michael Jurkaabded662011-03-04 12:06:57 -0800707 }
708
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800709 @Override
710 protected void onAttachedToWindow() {
711 super.onAttachedToWindow();
Adam Cohendcd297f2013-06-18 13:13:40 -0700712 if (getParent() instanceof Workspace) {
713 Workspace workspace = (Workspace) getParent();
714 mCellInfo.screenId = workspace.getIdForScreen(this);
715 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800716 }
717
Michael Jurkaaf442092010-06-10 17:01:57 -0700718 public void setTagToCellInfoForPoint(int touchX, int touchY) {
719 final CellInfo cellInfo = mCellInfo;
Winson Chungeecf02d2012-03-02 17:14:58 -0800720 Rect frame = mRect;
Michael Jurka8b805b12012-04-18 14:23:14 -0700721 final int x = touchX + getScrollX();
722 final int y = touchY + getScrollY();
Michael Jurkaa52570f2012-03-20 03:18:20 -0700723 final int count = mShortcutsAndWidgets.getChildCount();
Michael Jurkaaf442092010-06-10 17:01:57 -0700724
725 boolean found = false;
726 for (int i = count - 1; i >= 0; i--) {
Michael Jurkaa52570f2012-03-20 03:18:20 -0700727 final View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohend4844c32011-02-18 19:25:06 -0800728 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Michael Jurkaaf442092010-06-10 17:01:57 -0700729
Adam Cohen1b607ed2011-03-03 17:26:50 -0800730 if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
731 lp.isLockedToGrid) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700732 child.getHitRect(frame);
Winson Chung0be025d2011-05-23 17:45:09 -0700733
Winson Chungeecf02d2012-03-02 17:14:58 -0800734 float scale = child.getScaleX();
735 frame = new Rect(child.getLeft(), child.getTop(), child.getRight(),
736 child.getBottom());
Winson Chung0be025d2011-05-23 17:45:09 -0700737 // The child hit rect is relative to the CellLayoutChildren parent, so we need to
738 // offset that by this CellLayout's padding to test an (x,y) point that is relative
739 // to this view.
Michael Jurka8b805b12012-04-18 14:23:14 -0700740 frame.offset(getPaddingLeft(), getPaddingTop());
Winson Chungeecf02d2012-03-02 17:14:58 -0800741 frame.inset((int) (frame.width() * (1f - scale) / 2),
742 (int) (frame.height() * (1f - scale) / 2));
Winson Chung0be025d2011-05-23 17:45:09 -0700743
Michael Jurkaaf442092010-06-10 17:01:57 -0700744 if (frame.contains(x, y)) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700745 cellInfo.cell = child;
746 cellInfo.cellX = lp.cellX;
747 cellInfo.cellY = lp.cellY;
748 cellInfo.spanX = lp.cellHSpan;
749 cellInfo.spanY = lp.cellVSpan;
Michael Jurkaaf442092010-06-10 17:01:57 -0700750 found = true;
Michael Jurkaaf442092010-06-10 17:01:57 -0700751 break;
752 }
753 }
754 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700755
Michael Jurkad771c962011-08-09 15:00:48 -0700756 mLastDownOnOccupiedCell = found;
757
Michael Jurkaaf442092010-06-10 17:01:57 -0700758 if (!found) {
Winson Chung0be025d2011-05-23 17:45:09 -0700759 final int cellXY[] = mTmpXY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700760 pointToCellExact(x, y, cellXY);
761
Michael Jurkaaf442092010-06-10 17:01:57 -0700762 cellInfo.cell = null;
763 cellInfo.cellX = cellXY[0];
764 cellInfo.cellY = cellXY[1];
765 cellInfo.spanX = 1;
766 cellInfo.spanY = 1;
Michael Jurkaaf442092010-06-10 17:01:57 -0700767 }
768 setTag(cellInfo);
769 }
770
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800771 @Override
772 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Cohenc1997fd2011-08-15 18:26:39 -0700773 // First we clear the tag to ensure that on every touch down we start with a fresh slate,
774 // even in the case where we return early. Not clearing here was causing bugs whereby on
775 // long-press we'd end up picking up an item from a previous drag operation.
776 final int action = ev.getAction();
777
778 if (action == MotionEvent.ACTION_DOWN) {
779 clearTagCellInfo();
780 }
781
Michael Jurkadee05892010-07-27 10:01:56 -0700782 if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
783 return true;
784 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800785
786 if (action == MotionEvent.ACTION_DOWN) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700787 setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800788 }
Winson Chungeecf02d2012-03-02 17:14:58 -0800789
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800790 return false;
791 }
792
Adam Cohenc1997fd2011-08-15 18:26:39 -0700793 private void clearTagCellInfo() {
794 final CellInfo cellInfo = mCellInfo;
795 cellInfo.cell = null;
796 cellInfo.cellX = -1;
797 cellInfo.cellY = -1;
798 cellInfo.spanX = 0;
799 cellInfo.spanY = 0;
800 setTag(cellInfo);
801 }
802
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800803 public CellInfo getTag() {
Michael Jurka0280c3b2010-09-17 15:00:07 -0700804 return (CellInfo) super.getTag();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800805 }
806
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700807 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700808 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800809 * @param x X coordinate of the point
810 * @param y Y coordinate of the point
811 * @param result Array of 2 ints to hold the x and y coordinate of the cell
812 */
813 void pointToCellExact(int x, int y, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700814 final int hStartPadding = getPaddingLeft();
815 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800816
817 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
818 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
819
Adam Cohend22015c2010-07-26 22:02:18 -0700820 final int xAxis = mCountX;
821 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800822
823 if (result[0] < 0) result[0] = 0;
824 if (result[0] >= xAxis) result[0] = xAxis - 1;
825 if (result[1] < 0) result[1] = 0;
826 if (result[1] >= yAxis) result[1] = yAxis - 1;
827 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700828
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800829 /**
830 * Given a point, return the cell that most closely encloses that point
831 * @param x X coordinate of the point
832 * @param y Y coordinate of the point
833 * @param result Array of 2 ints to hold the x and y coordinate of the cell
834 */
835 void pointToCellRounded(int x, int y, int[] result) {
836 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
837 }
838
839 /**
840 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700841 *
842 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800843 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700844 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800845 * @param result Array of 2 ints to hold the x and y coordinate of the point
846 */
847 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700848 final int hStartPadding = getPaddingLeft();
849 final int vStartPadding = getPaddingTop();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800850
851 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
852 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
853 }
854
Adam Cohene3e27a82011-04-15 12:07:39 -0700855 /**
Adam Cohen482ed822012-03-02 14:15:13 -0800856 * Given a cell coordinate, return the point that represents the center of the cell
Adam Cohene3e27a82011-04-15 12:07:39 -0700857 *
858 * @param cellX X coordinate of the cell
859 * @param cellY Y coordinate of the cell
860 *
861 * @param result Array of 2 ints to hold the x and y coordinate of the point
862 */
863 void cellToCenterPoint(int cellX, int cellY, int[] result) {
Adam Cohen47a876d2012-03-19 13:21:41 -0700864 regionToCenterPoint(cellX, cellY, 1, 1, result);
865 }
866
867 /**
868 * Given a cell coordinate and span return the point that represents the center of the regio
869 *
870 * @param cellX X coordinate of the cell
871 * @param cellY Y coordinate of the cell
872 *
873 * @param result Array of 2 ints to hold the x and y coordinate of the point
874 */
875 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
Winson Chung4b825dcd2011-06-19 12:41:22 -0700876 final int hStartPadding = getPaddingLeft();
877 final int vStartPadding = getPaddingTop();
Adam Cohen47a876d2012-03-19 13:21:41 -0700878 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
879 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
880 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
881 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
Adam Cohene3e27a82011-04-15 12:07:39 -0700882 }
883
Adam Cohen19f37922012-03-21 11:59:11 -0700884 /**
885 * Given a cell coordinate and span fills out a corresponding pixel rect
886 *
887 * @param cellX X coordinate of the cell
888 * @param cellY Y coordinate of the cell
889 * @param result Rect in which to write the result
890 */
891 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
892 final int hStartPadding = getPaddingLeft();
893 final int vStartPadding = getPaddingTop();
894 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
895 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
896 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
897 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
898 }
899
Adam Cohen482ed822012-03-02 14:15:13 -0800900 public float getDistanceFromCell(float x, float y, int[] cell) {
901 cellToCenterPoint(cell[0], cell[1], mTmpPoint);
902 float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) +
903 Math.pow(y - mTmpPoint[1], 2));
904 return distance;
905 }
906
Romain Guy84f296c2009-11-04 15:00:44 -0800907 int getCellWidth() {
908 return mCellWidth;
909 }
910
911 int getCellHeight() {
912 return mCellHeight;
913 }
914
Adam Cohend4844c32011-02-18 19:25:06 -0800915 int getWidthGap() {
916 return mWidthGap;
917 }
918
919 int getHeightGap() {
920 return mHeightGap;
921 }
922
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700923 Rect getContentRect(Rect r) {
924 if (r == null) {
925 r = new Rect();
926 }
927 int left = getPaddingLeft();
928 int top = getPaddingTop();
Michael Jurka8b805b12012-04-18 14:23:14 -0700929 int right = left + getWidth() - getPaddingLeft() - getPaddingRight();
930 int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom();
Adam Cohen7f4eabe2011-04-21 16:19:16 -0700931 r.set(left, top, right, bottom);
932 return r;
933 }
934
Adam Cohena897f392012-04-27 18:12:05 -0700935 static void getMetrics(Rect metrics, Resources res, int measureWidth, int measureHeight,
936 int countX, int countY, int orientation) {
937 int numWidthGaps = countX - 1;
938 int numHeightGaps = countY - 1;
Adam Cohenf4bd5792012-04-27 11:35:29 -0700939
940 int widthGap;
941 int heightGap;
942 int cellWidth;
943 int cellHeight;
944 int paddingLeft;
945 int paddingRight;
946 int paddingTop;
947 int paddingBottom;
948
Adam Cohena897f392012-04-27 18:12:05 -0700949 int maxGap = res.getDimensionPixelSize(R.dimen.workspace_max_gap);
Adam Cohenf4bd5792012-04-27 11:35:29 -0700950 if (orientation == LANDSCAPE) {
951 cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_land);
952 cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_land);
953 widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_land);
954 heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_land);
955 paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_land);
956 paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_land);
957 paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_land);
958 paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_land);
959 } else {
960 // PORTRAIT
961 cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_port);
962 cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_port);
963 widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_port);
964 heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_port);
965 paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_port);
966 paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_port);
967 paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_port);
968 paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_port);
969 }
970
971 if (widthGap < 0 || heightGap < 0) {
972 int hSpace = measureWidth - paddingLeft - paddingRight;
973 int vSpace = measureHeight - paddingTop - paddingBottom;
Adam Cohena897f392012-04-27 18:12:05 -0700974 int hFreeSpace = hSpace - (countX * cellWidth);
975 int vFreeSpace = vSpace - (countY * cellHeight);
976 widthGap = Math.min(maxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
977 heightGap = Math.min(maxGap, numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Adam Cohenf4bd5792012-04-27 11:35:29 -0700978 }
979 metrics.set(cellWidth, cellHeight, widthGap, heightGap);
980 }
981
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700982 public void setFixedSize(int width, int height) {
983 mFixedWidth = width;
984 mFixedHeight = height;
985 }
986
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800987 @Override
988 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800989 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700990 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
991
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800992 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
993 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700994
Adam Cohenf0f4eda2013-06-06 21:27:03 -0700995 int newWidth = widthSpecSize;
996 int newHeight = heightSpecSize;
997 if (mFixedWidth > 0 && mFixedHeight > 0) {
998 newWidth = mFixedWidth;
999 newHeight = mFixedHeight;
1000 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001001 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
1002 }
1003
Adam Cohend22015c2010-07-26 22:02:18 -07001004 int numWidthGaps = mCountX - 1;
1005 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001006
Adam Cohen234c4cd2011-07-17 21:03:04 -07001007 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
Michael Jurkadd13e3d2012-05-01 12:38:17 -07001008 int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight();
1009 int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom();
Adam Cohenf4bd5792012-04-27 11:35:29 -07001010 int hFreeSpace = hSpace - (mCountX * mCellWidth);
1011 int vFreeSpace = vSpace - (mCountY * mCellHeight);
Winson Chung4b825dcd2011-06-19 12:41:22 -07001012 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
1013 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
Adam Cohen2374abf2013-04-16 14:56:57 -07001014 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
1015 mCountX);
Adam Cohen234c4cd2011-07-17 21:03:04 -07001016 } else {
1017 mWidthGap = mOriginalWidthGap;
1018 mHeightGap = mOriginalHeightGap;
Winson Chungece7f5b2010-10-22 14:54:12 -07001019 }
Michael Jurka5f1c5092010-09-03 14:15:02 -07001020
Michael Jurka8c920dd2011-01-20 14:16:56 -08001021 // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
Michael Jurka5f1c5092010-09-03 14:15:02 -07001022 if (widthSpecMode == MeasureSpec.AT_MOST) {
Michael Jurka8b805b12012-04-18 14:23:14 -07001023 newWidth = getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Winson Chungece7f5b2010-10-22 14:54:12 -07001024 ((mCountX - 1) * mWidthGap);
Michael Jurka8b805b12012-04-18 14:23:14 -07001025 newHeight = getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Winson Chungece7f5b2010-10-22 14:54:12 -07001026 ((mCountY - 1) * mHeightGap);
Michael Jurka5f1c5092010-09-03 14:15:02 -07001027 setMeasuredDimension(newWidth, newHeight);
Michael Jurka5f1c5092010-09-03 14:15:02 -07001028 }
Michael Jurka8c920dd2011-01-20 14:16:56 -08001029
1030 int count = getChildCount();
1031 for (int i = 0; i < count; i++) {
1032 View child = getChildAt(i);
Michael Jurka8b805b12012-04-18 14:23:14 -07001033 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() -
1034 getPaddingRight(), MeasureSpec.EXACTLY);
1035 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() -
1036 getPaddingBottom(), MeasureSpec.EXACTLY);
Michael Jurka8c920dd2011-01-20 14:16:56 -08001037 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
1038 }
1039 setMeasuredDimension(newWidth, newHeight);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001040 }
1041
1042 @Override
Michael Jurka28750fb2010-09-24 17:43:49 -07001043 protected void onLayout(boolean changed, int l, int t, int r, int b) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001044 int count = getChildCount();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001045 for (int i = 0; i < count; i++) {
Michael Jurka8c920dd2011-01-20 14:16:56 -08001046 View child = getChildAt(i);
Michael Jurka8b805b12012-04-18 14:23:14 -07001047 child.layout(getPaddingLeft(), getPaddingTop(),
1048 r - l - getPaddingRight(), b - t - getPaddingBottom());
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001049 }
1050 }
1051
1052 @Override
Michael Jurkadee05892010-07-27 10:01:56 -07001053 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1054 super.onSizeChanged(w, h, oldw, oldh);
Michael Jurka18014792010-10-14 09:01:34 -07001055 mBackgroundRect.set(0, 0, w, h);
Adam Cohenb5ba0972011-09-07 18:02:31 -07001056 mForegroundRect.set(mForegroundPadding, mForegroundPadding,
Adam Cohen215b4162012-08-30 13:14:08 -07001057 w - mForegroundPadding, h - mForegroundPadding);
Michael Jurkadee05892010-07-27 10:01:56 -07001058 }
1059
1060 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001061 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001062 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001063 }
1064
1065 @Override
1066 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001067 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001068 }
1069
Michael Jurka5f1c5092010-09-03 14:15:02 -07001070 public float getBackgroundAlpha() {
1071 return mBackgroundAlpha;
Michael Jurkadee05892010-07-27 10:01:56 -07001072 }
1073
Adam Cohen1b0aaac2010-10-28 11:11:18 -07001074 public void setBackgroundAlphaMultiplier(float multiplier) {
Michael Jurkaa3d30ad2012-05-08 13:43:43 -07001075 if (mBackgroundAlphaMultiplier != multiplier) {
1076 mBackgroundAlphaMultiplier = multiplier;
1077 invalidate();
1078 }
Adam Cohen1b0aaac2010-10-28 11:11:18 -07001079 }
1080
Adam Cohenddb82192010-11-10 16:32:54 -08001081 public float getBackgroundAlphaMultiplier() {
1082 return mBackgroundAlphaMultiplier;
1083 }
1084
Michael Jurka5f1c5092010-09-03 14:15:02 -07001085 public void setBackgroundAlpha(float alpha) {
Michael Jurkaafaa0502011-12-13 18:22:50 -08001086 if (mBackgroundAlpha != alpha) {
1087 mBackgroundAlpha = alpha;
1088 invalidate();
1089 }
Michael Jurkadee05892010-07-27 10:01:56 -07001090 }
1091
Michael Jurkaa52570f2012-03-20 03:18:20 -07001092 public void setShortcutAndWidgetAlpha(float alpha) {
Michael Jurka0142d492010-08-25 17:46:15 -07001093 final int childCount = getChildCount();
1094 for (int i = 0; i < childCount; i++) {
Michael Jurkadee05892010-07-27 10:01:56 -07001095 getChildAt(i).setAlpha(alpha);
1096 }
1097 }
1098
Michael Jurkaa52570f2012-03-20 03:18:20 -07001099 public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
1100 if (getChildCount() > 0) {
1101 return (ShortcutAndWidgetContainer) getChildAt(0);
1102 }
1103 return null;
1104 }
1105
Patrick Dubroy440c3602010-07-13 17:50:32 -07001106 public View getChildAt(int x, int y) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001107 return mShortcutsAndWidgets.getChildAt(x, y);
Patrick Dubroy440c3602010-07-13 17:50:32 -07001108 }
1109
Adam Cohen76fc0852011-06-17 13:26:23 -07001110 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
Adam Cohen482ed822012-03-02 14:15:13 -08001111 int delay, boolean permanent, boolean adjustOccupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07001112 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
Adam Cohen482ed822012-03-02 14:15:13 -08001113 boolean[][] occupied = mOccupied;
1114 if (!permanent) {
1115 occupied = mTmpOccupied;
1116 }
1117
Adam Cohen19f37922012-03-21 11:59:11 -07001118 if (clc.indexOfChild(child) != -1) {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001119 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1120 final ItemInfo info = (ItemInfo) child.getTag();
1121
1122 // We cancel any existing animations
1123 if (mReorderAnimators.containsKey(lp)) {
1124 mReorderAnimators.get(lp).cancel();
1125 mReorderAnimators.remove(lp);
1126 }
1127
Adam Cohen482ed822012-03-02 14:15:13 -08001128 final int oldX = lp.x;
1129 final int oldY = lp.y;
1130 if (adjustOccupied) {
1131 occupied[lp.cellX][lp.cellY] = false;
1132 occupied[cellX][cellY] = true;
1133 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001134 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001135 if (permanent) {
1136 lp.cellX = info.cellX = cellX;
1137 lp.cellY = info.cellY = cellY;
1138 } else {
1139 lp.tmpCellX = cellX;
1140 lp.tmpCellY = cellY;
1141 }
Adam Cohenbfbfd262011-06-13 16:55:12 -07001142 clc.setupLp(lp);
1143 lp.isLockedToGrid = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001144 final int newX = lp.x;
1145 final int newY = lp.y;
Adam Cohenbfbfd262011-06-13 16:55:12 -07001146
Adam Cohen76fc0852011-06-17 13:26:23 -07001147 lp.x = oldX;
1148 lp.y = oldY;
Adam Cohen76fc0852011-06-17 13:26:23 -07001149
Adam Cohen482ed822012-03-02 14:15:13 -08001150 // Exit early if we're not actually moving the view
1151 if (oldX == newX && oldY == newY) {
1152 lp.isLockedToGrid = true;
1153 return true;
1154 }
1155
Michael Jurkaf1ad6082013-03-13 12:55:46 +01001156 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohen482ed822012-03-02 14:15:13 -08001157 va.setDuration(duration);
1158 mReorderAnimators.put(lp, va);
1159
1160 va.addUpdateListener(new AnimatorUpdateListener() {
1161 @Override
Adam Cohenbfbfd262011-06-13 16:55:12 -07001162 public void onAnimationUpdate(ValueAnimator animation) {
Adam Cohen482ed822012-03-02 14:15:13 -08001163 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohen19f37922012-03-21 11:59:11 -07001164 lp.x = (int) ((1 - r) * oldX + r * newX);
1165 lp.y = (int) ((1 - r) * oldY + r * newY);
Adam Cohen6b8a02d2012-03-22 15:13:40 -07001166 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001167 }
1168 });
Adam Cohen482ed822012-03-02 14:15:13 -08001169 va.addListener(new AnimatorListenerAdapter() {
Adam Cohenbfbfd262011-06-13 16:55:12 -07001170 boolean cancelled = false;
1171 public void onAnimationEnd(Animator animation) {
1172 // If the animation was cancelled, it means that another animation
1173 // has interrupted this one, and we don't want to lock the item into
1174 // place just yet.
1175 if (!cancelled) {
1176 lp.isLockedToGrid = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001177 child.requestLayout();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001178 }
1179 if (mReorderAnimators.containsKey(lp)) {
1180 mReorderAnimators.remove(lp);
1181 }
1182 }
1183 public void onAnimationCancel(Animator animation) {
1184 cancelled = true;
1185 }
1186 });
Adam Cohen482ed822012-03-02 14:15:13 -08001187 va.setStartDelay(delay);
1188 va.start();
Adam Cohenbfbfd262011-06-13 16:55:12 -07001189 return true;
1190 }
1191 return false;
1192 }
1193
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001194 /**
1195 * Estimate where the top left cell of the dragged item will land if it is dropped.
1196 *
1197 * @param originX The X value of the top left corner of the item
1198 * @param originY The Y value of the top left corner of the item
1199 * @param spanX The number of horizontal cells that the item spans
1200 * @param spanY The number of vertical cells that the item spans
1201 * @param result The estimated drop cell X and Y.
1202 */
1203 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
Adam Cohend22015c2010-07-26 22:02:18 -07001204 final int countX = mCountX;
1205 final int countY = mCountY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001206
Michael Jurkaa63c4522010-08-19 13:52:27 -07001207 // pointToCellRounded takes the top left of a cell but will pad that with
1208 // cellWidth/2 and cellHeight/2 when finding the matching cell
1209 pointToCellRounded(originX, originY, result);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001210
1211 // If the item isn't fully on this screen, snap to the edges
1212 int rightOverhang = result[0] + spanX - countX;
1213 if (rightOverhang > 0) {
1214 result[0] -= rightOverhang; // Snap to right
1215 }
1216 result[0] = Math.max(0, result[0]); // Snap to left
1217 int bottomOverhang = result[1] + spanY - countY;
1218 if (bottomOverhang > 0) {
1219 result[1] -= bottomOverhang; // Snap to bottom
1220 }
1221 result[1] = Math.max(0, result[1]); // Snap to top
1222 }
1223
Adam Cohen482ed822012-03-02 14:15:13 -08001224 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
1225 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001226 final int oldDragCellX = mDragCell[0];
1227 final int oldDragCellY = mDragCell[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001228
Winson Chungb8c69f32011-10-19 21:36:08 -07001229 if (v != null && dragOffset == null) {
Winson Chunga9abd0e2010-10-27 17:18:37 -07001230 mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
1231 } else {
1232 mDragCenter.set(originX, originY);
1233 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001234
Adam Cohen2801caf2011-05-13 20:57:39 -07001235 if (dragOutline == null && v == null) {
Adam Cohen2801caf2011-05-13 20:57:39 -07001236 return;
1237 }
1238
Adam Cohen482ed822012-03-02 14:15:13 -08001239 if (cellX != oldDragCellX || cellY != oldDragCellY) {
1240 mDragCell[0] = cellX;
1241 mDragCell[1] = cellY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001242 // Find the top left corner of the rect the object will occupy
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001243 final int[] topLeft = mTmpPoint;
Adam Cohen482ed822012-03-02 14:15:13 -08001244 cellToPoint(cellX, cellY, topLeft);
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001245
Joe Onorato4be866d2010-10-10 11:26:02 -07001246 int left = topLeft[0];
1247 int top = topLeft[1];
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001248
Winson Chungb8c69f32011-10-19 21:36:08 -07001249 if (v != null && dragOffset == null) {
Adam Cohen99e8b402011-03-25 19:23:43 -07001250 // When drawing the drag outline, it did not account for margin offsets
1251 // added by the view's parent.
1252 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
1253 left += lp.leftMargin;
1254 top += lp.topMargin;
Winson Chung150fbab2010-09-29 17:14:26 -07001255
Adam Cohen99e8b402011-03-25 19:23:43 -07001256 // Offsets due to the size difference between the View and the dragOutline.
1257 // There is a size difference to account for the outer blur, which may lie
1258 // outside the bounds of the view.
Winson Chunga9abd0e2010-10-27 17:18:37 -07001259 top += (v.getHeight() - dragOutline.getHeight()) / 2;
Adam Cohenae915ce2011-08-25 13:47:22 -07001260 // We center about the x axis
1261 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1262 - dragOutline.getWidth()) / 2;
Adam Cohen66396872011-04-15 17:50:36 -07001263 } else {
Winson Chungb8c69f32011-10-19 21:36:08 -07001264 if (dragOffset != null && dragRegion != null) {
1265 // Center the drag region *horizontally* in the cell and apply a drag
1266 // outline offset
1267 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1268 - dragRegion.width()) / 2;
1269 top += dragOffset.y;
1270 } else {
1271 // Center the drag outline in the cell
1272 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
1273 - dragOutline.getWidth()) / 2;
1274 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
1275 - dragOutline.getHeight()) / 2;
1276 }
Winson Chunga9abd0e2010-10-27 17:18:37 -07001277 }
Joe Onorato4be866d2010-10-10 11:26:02 -07001278 final int oldIndex = mDragOutlineCurrent;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001279 mDragOutlineAnims[oldIndex].animateOut();
1280 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
Adam Cohend41fbf52012-02-16 23:53:59 -08001281 Rect r = mDragOutlines[mDragOutlineCurrent];
1282 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
1283 if (resize) {
Adam Cohen482ed822012-03-02 14:15:13 -08001284 cellToRect(cellX, cellY, spanX, spanY, r);
Adam Cohend41fbf52012-02-16 23:53:59 -08001285 }
Winson Chung150fbab2010-09-29 17:14:26 -07001286
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07001287 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
1288 mDragOutlineAnims[mDragOutlineCurrent].animateIn();
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001289 }
1290 }
1291
Adam Cohene0310962011-04-18 16:15:31 -07001292 public void clearDragOutlines() {
1293 final int oldIndex = mDragOutlineCurrent;
1294 mDragOutlineAnims[oldIndex].animateOut();
Adam Cohend41fbf52012-02-16 23:53:59 -08001295 mDragCell[0] = mDragCell[1] = -1;
Adam Cohene0310962011-04-18 16:15:31 -07001296 }
1297
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001298 /**
Jeff Sharkey70864282009-04-07 21:08:40 -07001299 * Find a vacant area that will fit the given bounds nearest the requested
1300 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -07001301 *
Romain Guy51afc022009-05-04 18:03:43 -07001302 * @param pixelX The X location at which you want to search for a vacant area.
1303 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -07001304 * @param spanX Horizontal span of the object.
1305 * @param spanY Vertical span of the object.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001306 * @param result Array in which to place the result, or null (in which case a new array will
1307 * be allocated)
Jeff Sharkey70864282009-04-07 21:08:40 -07001308 * @return The X, Y cell of a vacant area that can contain this object,
1309 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001310 */
Adam Cohend41fbf52012-02-16 23:53:59 -08001311 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
1312 int[] result) {
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001313 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
Michael Jurka6a1435d2010-09-27 17:35:12 -07001314 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001315
Michael Jurka6a1435d2010-09-27 17:35:12 -07001316 /**
1317 * Find a vacant area that will fit the given bounds nearest the requested
1318 * cell location. Uses Euclidean distance to score multiple vacant areas.
1319 *
1320 * @param pixelX The X location at which you want to search for a vacant area.
1321 * @param pixelY The Y location at which you want to search for a vacant area.
Adam Cohend41fbf52012-02-16 23:53:59 -08001322 * @param minSpanX The minimum horizontal span required
1323 * @param minSpanY The minimum vertical span required
1324 * @param spanX Horizontal span of the object.
1325 * @param spanY Vertical span of the object.
1326 * @param result Array in which to place the result, or null (in which case a new array will
1327 * be allocated)
1328 * @return The X, Y cell of a vacant area that can contain this object,
1329 * nearest the requested location.
1330 */
1331 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
1332 int spanY, int[] result, int[] resultSpan) {
1333 return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
1334 result, resultSpan);
1335 }
1336
1337 /**
1338 * Find a vacant area that will fit the given bounds nearest the requested
1339 * cell location. Uses Euclidean distance to score multiple vacant areas.
1340 *
1341 * @param pixelX The X location at which you want to search for a vacant area.
1342 * @param pixelY The Y location at which you want to search for a vacant area.
Michael Jurka6a1435d2010-09-27 17:35:12 -07001343 * @param spanX Horizontal span of the object.
1344 * @param spanY Vertical span of the object.
Adam Cohendf035382011-04-11 17:22:04 -07001345 * @param ignoreOccupied If true, the result can be an occupied cell
1346 * @param result Array in which to place the result, or null (in which case a new array will
1347 * be allocated)
Michael Jurka6a1435d2010-09-27 17:35:12 -07001348 * @return The X, Y cell of a vacant area that can contain this object,
1349 * nearest the requested location.
1350 */
Adam Cohendf035382011-04-11 17:22:04 -07001351 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
1352 boolean ignoreOccupied, int[] result) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001353 return findNearestArea(pixelX, pixelY, spanX, spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001354 spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08001355 }
1356
1357 private final Stack<Rect> mTempRectStack = new Stack<Rect>();
1358 private void lazyInitTempRectStack() {
1359 if (mTempRectStack.isEmpty()) {
1360 for (int i = 0; i < mCountX * mCountY; i++) {
1361 mTempRectStack.push(new Rect());
1362 }
1363 }
1364 }
Adam Cohen482ed822012-03-02 14:15:13 -08001365
Adam Cohend41fbf52012-02-16 23:53:59 -08001366 private void recycleTempRects(Stack<Rect> used) {
1367 while (!used.isEmpty()) {
1368 mTempRectStack.push(used.pop());
1369 }
1370 }
1371
1372 /**
1373 * Find a vacant area that will fit the given bounds nearest the requested
1374 * cell location. Uses Euclidean distance to score multiple vacant areas.
1375 *
1376 * @param pixelX The X location at which you want to search for a vacant area.
1377 * @param pixelY The Y location at which you want to search for a vacant area.
1378 * @param minSpanX The minimum horizontal span required
1379 * @param minSpanY The minimum vertical span required
1380 * @param spanX Horizontal span of the object.
1381 * @param spanY Vertical span of the object.
1382 * @param ignoreOccupied If true, the result can be an occupied cell
1383 * @param result Array in which to place the result, or null (in which case a new array will
1384 * be allocated)
1385 * @return The X, Y cell of a vacant area that can contain this object,
1386 * nearest the requested location.
1387 */
1388 int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08001389 View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
1390 boolean[][] occupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001391 lazyInitTempRectStack();
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001392 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08001393 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001394
Adam Cohene3e27a82011-04-15 12:07:39 -07001395 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
1396 // to the center of the item, but we are searching based on the top-left cell, so
1397 // we translate the point over to correspond to the top-left.
1398 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
1399 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
1400
Jeff Sharkey70864282009-04-07 21:08:40 -07001401 // Keep track of best-scoring drop area
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001402 final int[] bestXY = result != null ? result : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -07001403 double bestDistance = Double.MAX_VALUE;
Adam Cohend41fbf52012-02-16 23:53:59 -08001404 final Rect bestRect = new Rect(-1, -1, -1, -1);
1405 final Stack<Rect> validRegions = new Stack<Rect>();
Winson Chungaafa03c2010-06-11 17:34:16 -07001406
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001407 final int countX = mCountX;
1408 final int countY = mCountY;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07001409
Adam Cohend41fbf52012-02-16 23:53:59 -08001410 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
1411 spanX < minSpanX || spanY < minSpanY) {
1412 return bestXY;
1413 }
1414
1415 for (int y = 0; y < countY - (minSpanY - 1); y++) {
Michael Jurkac28de512010-08-13 11:27:44 -07001416 inner:
Adam Cohend41fbf52012-02-16 23:53:59 -08001417 for (int x = 0; x < countX - (minSpanX - 1); x++) {
1418 int ySize = -1;
1419 int xSize = -1;
Adam Cohendf035382011-04-11 17:22:04 -07001420 if (ignoreOccupied) {
Adam Cohend41fbf52012-02-16 23:53:59 -08001421 // First, let's see if this thing fits anywhere
1422 for (int i = 0; i < minSpanX; i++) {
1423 for (int j = 0; j < minSpanY; j++) {
Adam Cohendf035382011-04-11 17:22:04 -07001424 if (occupied[x + i][y + j]) {
Adam Cohendf035382011-04-11 17:22:04 -07001425 continue inner;
1426 }
Michael Jurkac28de512010-08-13 11:27:44 -07001427 }
1428 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001429 xSize = minSpanX;
1430 ySize = minSpanY;
1431
1432 // We know that the item will fit at _some_ acceptable size, now let's see
1433 // how big we can make it. We'll alternate between incrementing x and y spans
1434 // until we hit a limit.
1435 boolean incX = true;
1436 boolean hitMaxX = xSize >= spanX;
1437 boolean hitMaxY = ySize >= spanY;
1438 while (!(hitMaxX && hitMaxY)) {
1439 if (incX && !hitMaxX) {
1440 for (int j = 0; j < ySize; j++) {
1441 if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
1442 // We can't move out horizontally
1443 hitMaxX = true;
1444 }
1445 }
1446 if (!hitMaxX) {
1447 xSize++;
1448 }
1449 } else if (!hitMaxY) {
1450 for (int i = 0; i < xSize; i++) {
1451 if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
1452 // We can't move out vertically
1453 hitMaxY = true;
1454 }
1455 }
1456 if (!hitMaxY) {
1457 ySize++;
1458 }
1459 }
1460 hitMaxX |= xSize >= spanX;
1461 hitMaxY |= ySize >= spanY;
1462 incX = !incX;
1463 }
1464 incX = true;
1465 hitMaxX = xSize >= spanX;
1466 hitMaxY = ySize >= spanY;
Michael Jurkac28de512010-08-13 11:27:44 -07001467 }
Winson Chung0be025d2011-05-23 17:45:09 -07001468 final int[] cellXY = mTmpXY;
Adam Cohene3e27a82011-04-15 12:07:39 -07001469 cellToCenterPoint(x, y, cellXY);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001470
Adam Cohend41fbf52012-02-16 23:53:59 -08001471 // We verify that the current rect is not a sub-rect of any of our previous
1472 // candidates. In this case, the current rect is disqualified in favour of the
1473 // containing rect.
1474 Rect currentRect = mTempRectStack.pop();
1475 currentRect.set(x, y, x + xSize, y + ySize);
1476 boolean contained = false;
1477 for (Rect r : validRegions) {
1478 if (r.contains(currentRect)) {
1479 contained = true;
1480 break;
1481 }
1482 }
1483 validRegions.push(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001484 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
1485 + Math.pow(cellXY[1] - pixelY, 2));
Adam Cohen482ed822012-03-02 14:15:13 -08001486
Adam Cohend41fbf52012-02-16 23:53:59 -08001487 if ((distance <= bestDistance && !contained) ||
1488 currentRect.contains(bestRect)) {
Michael Jurkac28de512010-08-13 11:27:44 -07001489 bestDistance = distance;
1490 bestXY[0] = x;
1491 bestXY[1] = y;
Adam Cohend41fbf52012-02-16 23:53:59 -08001492 if (resultSpan != null) {
1493 resultSpan[0] = xSize;
1494 resultSpan[1] = ySize;
1495 }
1496 bestRect.set(currentRect);
Michael Jurkac28de512010-08-13 11:27:44 -07001497 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001498 }
1499 }
Michael Jurkac6ee42e2010-09-30 12:04:50 -07001500 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08001501 markCellsAsOccupiedForView(ignoreView, occupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001502
Adam Cohenc0dcf592011-06-01 15:30:43 -07001503 // Return -1, -1 if no suitable location found
1504 if (bestDistance == Double.MAX_VALUE) {
1505 bestXY[0] = -1;
1506 bestXY[1] = -1;
Jeff Sharkey70864282009-04-07 21:08:40 -07001507 }
Adam Cohend41fbf52012-02-16 23:53:59 -08001508 recycleTempRects(validRegions);
Adam Cohenc0dcf592011-06-01 15:30:43 -07001509 return bestXY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001510 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001511
Adam Cohen482ed822012-03-02 14:15:13 -08001512 /**
1513 * Find a vacant area that will fit the given bounds nearest the requested
1514 * cell location, and will also weigh in a suggested direction vector of the
1515 * desired location. This method computers distance based on unit grid distances,
1516 * not pixel distances.
1517 *
Adam Cohen47a876d2012-03-19 13:21:41 -07001518 * @param cellX The X cell nearest to which you want to search for a vacant area.
1519 * @param cellY The Y cell nearest which you want to search for a vacant area.
Adam Cohen482ed822012-03-02 14:15:13 -08001520 * @param spanX Horizontal span of the object.
1521 * @param spanY Vertical span of the object.
Adam Cohen47a876d2012-03-19 13:21:41 -07001522 * @param direction The favored direction in which the views should move from x, y
1523 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
1524 * matches exactly. Otherwise we find the best matching direction.
1525 * @param occoupied The array which represents which cells in the CellLayout are occupied
1526 * @param blockOccupied The array which represents which cells in the specified block (cellX,
1527 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
Adam Cohen482ed822012-03-02 14:15:13 -08001528 * @param result Array in which to place the result, or null (in which case a new array will
1529 * be allocated)
1530 * @return The X, Y cell of a vacant area that can contain this object,
1531 * nearest the requested location.
1532 */
1533 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen47a876d2012-03-19 13:21:41 -07001534 boolean[][] occupied, boolean blockOccupied[][], int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08001535 // Keep track of best-scoring drop area
1536 final int[] bestXY = result != null ? result : new int[2];
1537 float bestDistance = Float.MAX_VALUE;
1538 int bestDirectionScore = Integer.MIN_VALUE;
1539
1540 final int countX = mCountX;
1541 final int countY = mCountY;
1542
1543 for (int y = 0; y < countY - (spanY - 1); y++) {
1544 inner:
1545 for (int x = 0; x < countX - (spanX - 1); x++) {
1546 // First, let's see if this thing fits anywhere
1547 for (int i = 0; i < spanX; i++) {
1548 for (int j = 0; j < spanY; j++) {
Adam Cohen47a876d2012-03-19 13:21:41 -07001549 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
Adam Cohen482ed822012-03-02 14:15:13 -08001550 continue inner;
1551 }
1552 }
1553 }
1554
1555 float distance = (float)
1556 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
1557 int[] curDirection = mTmpPoint;
Adam Cohen47a876d2012-03-19 13:21:41 -07001558 computeDirectionVector(x - cellX, y - cellY, curDirection);
1559 // The direction score is just the dot product of the two candidate direction
1560 // and that passed in.
Adam Cohen482ed822012-03-02 14:15:13 -08001561 int curDirectionScore = direction[0] * curDirection[0] +
1562 direction[1] * curDirection[1];
Adam Cohen47a876d2012-03-19 13:21:41 -07001563 boolean exactDirectionOnly = false;
1564 boolean directionMatches = direction[0] == curDirection[0] &&
1565 direction[0] == curDirection[0];
1566 if ((directionMatches || !exactDirectionOnly) &&
1567 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
Adam Cohen482ed822012-03-02 14:15:13 -08001568 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
1569 bestDistance = distance;
1570 bestDirectionScore = curDirectionScore;
1571 bestXY[0] = x;
1572 bestXY[1] = y;
1573 }
1574 }
1575 }
1576
1577 // Return -1, -1 if no suitable location found
1578 if (bestDistance == Float.MAX_VALUE) {
1579 bestXY[0] = -1;
1580 bestXY[1] = -1;
1581 }
1582 return bestXY;
1583 }
1584
1585 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
Adam Cohen8baab352012-03-20 17:39:21 -07001586 int[] direction, ItemConfiguration currentState) {
1587 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001588 boolean success = false;
Adam Cohen8baab352012-03-20 17:39:21 -07001589 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
Adam Cohen482ed822012-03-02 14:15:13 -08001590 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1591
Adam Cohen8baab352012-03-20 17:39:21 -07001592 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001593
1594 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001595 c.x = mTempLocation[0];
1596 c.y = mTempLocation[1];
Adam Cohen482ed822012-03-02 14:15:13 -08001597 success = true;
Adam Cohen482ed822012-03-02 14:15:13 -08001598 }
Adam Cohen8baab352012-03-20 17:39:21 -07001599 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001600 return success;
1601 }
1602
Adam Cohenf3900c22012-11-16 18:28:11 -08001603 /**
1604 * This helper class defines a cluster of views. It helps with defining complex edges
1605 * of the cluster and determining how those edges interact with other views. The edges
1606 * essentially define a fine-grained boundary around the cluster of views -- like a more
1607 * precise version of a bounding box.
1608 */
1609 private class ViewCluster {
1610 final static int LEFT = 0;
1611 final static int TOP = 1;
1612 final static int RIGHT = 2;
1613 final static int BOTTOM = 3;
Adam Cohen47a876d2012-03-19 13:21:41 -07001614
Adam Cohenf3900c22012-11-16 18:28:11 -08001615 ArrayList<View> views;
1616 ItemConfiguration config;
1617 Rect boundingRect = new Rect();
Adam Cohen47a876d2012-03-19 13:21:41 -07001618
Adam Cohenf3900c22012-11-16 18:28:11 -08001619 int[] leftEdge = new int[mCountY];
1620 int[] rightEdge = new int[mCountY];
1621 int[] topEdge = new int[mCountX];
1622 int[] bottomEdge = new int[mCountX];
1623 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
1624
1625 @SuppressWarnings("unchecked")
1626 public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
1627 this.views = (ArrayList<View>) views.clone();
1628 this.config = config;
1629 resetEdges();
Adam Cohen47a876d2012-03-19 13:21:41 -07001630 }
1631
Adam Cohenf3900c22012-11-16 18:28:11 -08001632 void resetEdges() {
1633 for (int i = 0; i < mCountX; i++) {
1634 topEdge[i] = -1;
1635 bottomEdge[i] = -1;
1636 }
1637 for (int i = 0; i < mCountY; i++) {
1638 leftEdge[i] = -1;
1639 rightEdge[i] = -1;
1640 }
1641 leftEdgeDirty = true;
1642 rightEdgeDirty = true;
1643 bottomEdgeDirty = true;
1644 topEdgeDirty = true;
1645 boundingRectDirty = true;
1646 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001647
Adam Cohenf3900c22012-11-16 18:28:11 -08001648 void computeEdge(int which, int[] edge) {
1649 int count = views.size();
1650 for (int i = 0; i < count; i++) {
1651 CellAndSpan cs = config.map.get(views.get(i));
1652 switch (which) {
1653 case LEFT:
1654 int left = cs.x;
1655 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1656 if (left < edge[j] || edge[j] < 0) {
1657 edge[j] = left;
Adam Cohena56dc102012-07-13 13:41:42 -07001658 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001659 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001660 break;
1661 case RIGHT:
1662 int right = cs.x + cs.spanX;
1663 for (int j = cs.y; j < cs.y + cs.spanY; j++) {
1664 if (right > edge[j]) {
1665 edge[j] = right;
1666 }
1667 }
1668 break;
1669 case TOP:
1670 int top = cs.y;
1671 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1672 if (top < edge[j] || edge[j] < 0) {
1673 edge[j] = top;
1674 }
1675 }
1676 break;
1677 case BOTTOM:
1678 int bottom = cs.y + cs.spanY;
1679 for (int j = cs.x; j < cs.x + cs.spanX; j++) {
1680 if (bottom > edge[j]) {
1681 edge[j] = bottom;
1682 }
1683 }
1684 break;
Adam Cohen47a876d2012-03-19 13:21:41 -07001685 }
1686 }
1687 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001688
1689 boolean isViewTouchingEdge(View v, int whichEdge) {
1690 CellAndSpan cs = config.map.get(v);
1691
1692 int[] edge = getEdge(whichEdge);
1693
1694 switch (whichEdge) {
1695 case LEFT:
1696 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1697 if (edge[i] == cs.x + cs.spanX) {
1698 return true;
1699 }
1700 }
1701 break;
1702 case RIGHT:
1703 for (int i = cs.y; i < cs.y + cs.spanY; i++) {
1704 if (edge[i] == cs.x) {
1705 return true;
1706 }
1707 }
1708 break;
1709 case TOP:
1710 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1711 if (edge[i] == cs.y + cs.spanY) {
1712 return true;
1713 }
1714 }
1715 break;
1716 case BOTTOM:
1717 for (int i = cs.x; i < cs.x + cs.spanX; i++) {
1718 if (edge[i] == cs.y) {
1719 return true;
1720 }
1721 }
1722 break;
1723 }
1724 return false;
1725 }
1726
1727 void shift(int whichEdge, int delta) {
1728 for (View v: views) {
1729 CellAndSpan c = config.map.get(v);
1730 switch (whichEdge) {
1731 case LEFT:
1732 c.x -= delta;
1733 break;
1734 case RIGHT:
1735 c.x += delta;
1736 break;
1737 case TOP:
1738 c.y -= delta;
1739 break;
1740 case BOTTOM:
1741 default:
1742 c.y += delta;
1743 break;
1744 }
1745 }
1746 resetEdges();
1747 }
1748
1749 public void addView(View v) {
1750 views.add(v);
1751 resetEdges();
1752 }
1753
1754 public Rect getBoundingRect() {
1755 if (boundingRectDirty) {
1756 boolean first = true;
1757 for (View v: views) {
1758 CellAndSpan c = config.map.get(v);
1759 if (first) {
1760 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1761 first = false;
1762 } else {
1763 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
1764 }
1765 }
1766 }
1767 return boundingRect;
1768 }
1769
1770 public int[] getEdge(int which) {
1771 switch (which) {
1772 case LEFT:
1773 return getLeftEdge();
1774 case RIGHT:
1775 return getRightEdge();
1776 case TOP:
1777 return getTopEdge();
1778 case BOTTOM:
1779 default:
1780 return getBottomEdge();
1781 }
1782 }
1783
1784 public int[] getLeftEdge() {
1785 if (leftEdgeDirty) {
1786 computeEdge(LEFT, leftEdge);
1787 }
1788 return leftEdge;
1789 }
1790
1791 public int[] getRightEdge() {
1792 if (rightEdgeDirty) {
1793 computeEdge(RIGHT, rightEdge);
1794 }
1795 return rightEdge;
1796 }
1797
1798 public int[] getTopEdge() {
1799 if (topEdgeDirty) {
1800 computeEdge(TOP, topEdge);
1801 }
1802 return topEdge;
1803 }
1804
1805 public int[] getBottomEdge() {
1806 if (bottomEdgeDirty) {
1807 computeEdge(BOTTOM, bottomEdge);
1808 }
1809 return bottomEdge;
1810 }
1811
1812 PositionComparator comparator = new PositionComparator();
1813 class PositionComparator implements Comparator<View> {
1814 int whichEdge = 0;
1815 public int compare(View left, View right) {
1816 CellAndSpan l = config.map.get(left);
1817 CellAndSpan r = config.map.get(right);
1818 switch (whichEdge) {
1819 case LEFT:
1820 return (r.x + r.spanX) - (l.x + l.spanX);
1821 case RIGHT:
1822 return l.x - r.x;
1823 case TOP:
1824 return (r.y + r.spanY) - (l.y + l.spanY);
1825 case BOTTOM:
1826 default:
1827 return l.y - r.y;
1828 }
1829 }
1830 }
1831
1832 public void sortConfigurationForEdgePush(int edge) {
1833 comparator.whichEdge = edge;
1834 Collections.sort(config.sortedViews, comparator);
1835 }
Adam Cohen47a876d2012-03-19 13:21:41 -07001836 }
1837
Adam Cohenf3900c22012-11-16 18:28:11 -08001838 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
1839 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohene0489502012-08-27 15:18:53 -07001840
Adam Cohenf3900c22012-11-16 18:28:11 -08001841 ViewCluster cluster = new ViewCluster(views, currentState);
1842 Rect clusterRect = cluster.getBoundingRect();
1843 int whichEdge;
1844 int pushDistance;
1845 boolean fail = false;
1846
1847 // Determine the edge of the cluster that will be leading the push and how far
1848 // the cluster must be shifted.
1849 if (direction[0] < 0) {
1850 whichEdge = ViewCluster.LEFT;
1851 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
Adam Cohene0489502012-08-27 15:18:53 -07001852 } else if (direction[0] > 0) {
Adam Cohenf3900c22012-11-16 18:28:11 -08001853 whichEdge = ViewCluster.RIGHT;
1854 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
1855 } else if (direction[1] < 0) {
1856 whichEdge = ViewCluster.TOP;
1857 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
1858 } else {
1859 whichEdge = ViewCluster.BOTTOM;
1860 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
Adam Cohene0489502012-08-27 15:18:53 -07001861 }
1862
Adam Cohenf3900c22012-11-16 18:28:11 -08001863 // Break early for invalid push distance.
1864 if (pushDistance <= 0) {
1865 return false;
Adam Cohene0489502012-08-27 15:18:53 -07001866 }
Adam Cohenf3900c22012-11-16 18:28:11 -08001867
1868 // Mark the occupied state as false for the group of views we want to move.
1869 for (View v: views) {
1870 CellAndSpan c = currentState.map.get(v);
1871 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1872 }
1873
1874 // We save the current configuration -- if we fail to find a solution we will revert
1875 // to the initial state. The process of finding a solution modifies the configuration
1876 // in place, hence the need for revert in the failure case.
1877 currentState.save();
1878
1879 // The pushing algorithm is simplified by considering the views in the order in which
1880 // they would be pushed by the cluster. For example, if the cluster is leading with its
1881 // left edge, we consider sort the views by their right edge, from right to left.
1882 cluster.sortConfigurationForEdgePush(whichEdge);
1883
1884 while (pushDistance > 0 && !fail) {
1885 for (View v: currentState.sortedViews) {
1886 // For each view that isn't in the cluster, we see if the leading edge of the
1887 // cluster is contacting the edge of that view. If so, we add that view to the
1888 // cluster.
1889 if (!cluster.views.contains(v) && v != dragView) {
1890 if (cluster.isViewTouchingEdge(v, whichEdge)) {
1891 LayoutParams lp = (LayoutParams) v.getLayoutParams();
1892 if (!lp.canReorder) {
1893 // The push solution includes the all apps button, this is not viable.
1894 fail = true;
1895 break;
1896 }
1897 cluster.addView(v);
1898 CellAndSpan c = currentState.map.get(v);
1899
1900 // Adding view to cluster, mark it as not occupied.
1901 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1902 }
1903 }
1904 }
1905 pushDistance--;
1906
1907 // The cluster has been completed, now we move the whole thing over in the appropriate
1908 // direction.
1909 cluster.shift(whichEdge, 1);
1910 }
1911
1912 boolean foundSolution = false;
1913 clusterRect = cluster.getBoundingRect();
1914
1915 // Due to the nature of the algorithm, the only check required to verify a valid solution
1916 // is to ensure that completed shifted cluster lies completely within the cell layout.
1917 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
1918 clusterRect.bottom <= mCountY) {
1919 foundSolution = true;
1920 } else {
1921 currentState.restore();
1922 }
1923
1924 // In either case, we set the occupied array as marked for the location of the views
1925 for (View v: cluster.views) {
1926 CellAndSpan c = currentState.map.get(v);
1927 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
1928 }
1929
1930 return foundSolution;
Adam Cohene0489502012-08-27 15:18:53 -07001931 }
1932
Adam Cohen482ed822012-03-02 14:15:13 -08001933 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
Adam Cohenf3900c22012-11-16 18:28:11 -08001934 int[] direction, View dragView, ItemConfiguration currentState) {
Adam Cohen482ed822012-03-02 14:15:13 -08001935 if (views.size() == 0) return true;
Adam Cohen482ed822012-03-02 14:15:13 -08001936
Adam Cohen8baab352012-03-20 17:39:21 -07001937 boolean success = false;
Adam Cohen482ed822012-03-02 14:15:13 -08001938 Rect boundingRect = null;
Adam Cohen8baab352012-03-20 17:39:21 -07001939 // We construct a rect which represents the entire group of views passed in
Adam Cohen482ed822012-03-02 14:15:13 -08001940 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001941 CellAndSpan c = currentState.map.get(v);
Adam Cohen482ed822012-03-02 14:15:13 -08001942 if (boundingRect == null) {
Adam Cohen8baab352012-03-20 17:39:21 -07001943 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001944 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07001945 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08001946 }
1947 }
Adam Cohen8baab352012-03-20 17:39:21 -07001948
Adam Cohen8baab352012-03-20 17:39:21 -07001949 // Mark the occupied state as false for the group of views we want to move.
Adam Cohenf3900c22012-11-16 18:28:11 -08001950 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001951 CellAndSpan c = currentState.map.get(v);
1952 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
1953 }
1954
Adam Cohen47a876d2012-03-19 13:21:41 -07001955 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
1956 int top = boundingRect.top;
1957 int left = boundingRect.left;
Adam Cohen8baab352012-03-20 17:39:21 -07001958 // We mark more precisely which parts of the bounding rect are truly occupied, allowing
Adam Cohena56dc102012-07-13 13:41:42 -07001959 // for interlocking.
Adam Cohenf3900c22012-11-16 18:28:11 -08001960 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001961 CellAndSpan c = currentState.map.get(v);
1962 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
Adam Cohen47a876d2012-03-19 13:21:41 -07001963 }
1964
Adam Cohen482ed822012-03-02 14:15:13 -08001965 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
1966
Adam Cohenf3900c22012-11-16 18:28:11 -08001967 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
1968 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
Adam Cohen482ed822012-03-02 14:15:13 -08001969
Adam Cohen8baab352012-03-20 17:39:21 -07001970 // If we successfuly found a location by pushing the block of views, we commit it
Adam Cohen482ed822012-03-02 14:15:13 -08001971 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
Adam Cohen8baab352012-03-20 17:39:21 -07001972 int deltaX = mTempLocation[0] - boundingRect.left;
1973 int deltaY = mTempLocation[1] - boundingRect.top;
Adam Cohenf3900c22012-11-16 18:28:11 -08001974 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001975 CellAndSpan c = currentState.map.get(v);
1976 c.x += deltaX;
1977 c.y += deltaY;
Adam Cohen482ed822012-03-02 14:15:13 -08001978 }
1979 success = true;
1980 }
Adam Cohen8baab352012-03-20 17:39:21 -07001981
1982 // In either case, we set the occupied array as marked for the location of the views
Adam Cohenf3900c22012-11-16 18:28:11 -08001983 for (View v: views) {
Adam Cohen8baab352012-03-20 17:39:21 -07001984 CellAndSpan c = currentState.map.get(v);
1985 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08001986 }
1987 return success;
1988 }
1989
1990 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
1991 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
1992 }
1993
Adam Cohen4abc5bd2012-05-29 21:06:03 -07001994 // This method tries to find a reordering solution which satisfies the push mechanic by trying
1995 // to push items in each of the cardinal directions, in an order based on the direction vector
1996 // passed.
1997 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
1998 int[] direction, View ignoreView, ItemConfiguration solution) {
1999 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
2000 // If the direction vector has two non-zero components, we try pushing
2001 // separately in each of the components.
2002 int temp = direction[1];
2003 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08002004
2005 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002006 ignoreView, solution)) {
2007 return true;
2008 }
2009 direction[1] = temp;
2010 temp = direction[0];
2011 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08002012
2013 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002014 ignoreView, solution)) {
2015 return true;
2016 }
2017 // Revert the direction
2018 direction[0] = temp;
2019
2020 // Now we try pushing in each component of the opposite direction
2021 direction[0] *= -1;
2022 direction[1] *= -1;
2023 temp = direction[1];
2024 direction[1] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08002025 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002026 ignoreView, solution)) {
2027 return true;
2028 }
2029
2030 direction[1] = temp;
2031 temp = direction[0];
2032 direction[0] = 0;
Adam Cohenf3900c22012-11-16 18:28:11 -08002033 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002034 ignoreView, solution)) {
2035 return true;
2036 }
2037 // revert the direction
2038 direction[0] = temp;
2039 direction[0] *= -1;
2040 direction[1] *= -1;
2041
2042 } else {
2043 // If the direction vector has a single non-zero component, we push first in the
2044 // direction of the vector
Adam Cohenf3900c22012-11-16 18:28:11 -08002045 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002046 ignoreView, solution)) {
2047 return true;
2048 }
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002049 // Then we try the opposite direction
2050 direction[0] *= -1;
2051 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08002052 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002053 ignoreView, solution)) {
2054 return true;
2055 }
2056 // Switch the direction back
2057 direction[0] *= -1;
2058 direction[1] *= -1;
2059
2060 // If we have failed to find a push solution with the above, then we try
2061 // to find a solution by pushing along the perpendicular axis.
2062
2063 // Swap the components
2064 int temp = direction[1];
2065 direction[1] = direction[0];
2066 direction[0] = temp;
Adam Cohenf3900c22012-11-16 18:28:11 -08002067 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002068 ignoreView, solution)) {
2069 return true;
2070 }
2071
2072 // Then we try the opposite direction
2073 direction[0] *= -1;
2074 direction[1] *= -1;
Adam Cohenf3900c22012-11-16 18:28:11 -08002075 if (pushViewsToTempLocation(intersectingViews, occupied, direction,
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002076 ignoreView, solution)) {
2077 return true;
2078 }
2079 // Switch the direction back
2080 direction[0] *= -1;
2081 direction[1] *= -1;
2082
2083 // Swap the components back
2084 temp = direction[1];
2085 direction[1] = direction[0];
2086 direction[0] = temp;
2087 }
2088 return false;
2089 }
2090
Adam Cohen482ed822012-03-02 14:15:13 -08002091 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
Adam Cohen8baab352012-03-20 17:39:21 -07002092 View ignoreView, ItemConfiguration solution) {
Winson Chunge3e03bc2012-05-01 15:10:11 -07002093 // Return early if get invalid cell positions
2094 if (cellX < 0 || cellY < 0) return false;
Adam Cohen482ed822012-03-02 14:15:13 -08002095
Adam Cohen8baab352012-03-20 17:39:21 -07002096 mIntersectingViews.clear();
Adam Cohen482ed822012-03-02 14:15:13 -08002097 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08002098
Adam Cohen8baab352012-03-20 17:39:21 -07002099 // Mark the desired location of the view currently being dragged.
Adam Cohen482ed822012-03-02 14:15:13 -08002100 if (ignoreView != null) {
Adam Cohen8baab352012-03-20 17:39:21 -07002101 CellAndSpan c = solution.map.get(ignoreView);
Adam Cohen19f37922012-03-21 11:59:11 -07002102 if (c != null) {
2103 c.x = cellX;
2104 c.y = cellY;
2105 }
Adam Cohen482ed822012-03-02 14:15:13 -08002106 }
Adam Cohen482ed822012-03-02 14:15:13 -08002107 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2108 Rect r1 = new Rect();
Adam Cohen8baab352012-03-20 17:39:21 -07002109 for (View child: solution.map.keySet()) {
Adam Cohen482ed822012-03-02 14:15:13 -08002110 if (child == ignoreView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002111 CellAndSpan c = solution.map.get(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002112 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002113 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
Adam Cohen482ed822012-03-02 14:15:13 -08002114 if (Rect.intersects(r0, r1)) {
2115 if (!lp.canReorder) {
2116 return false;
2117 }
2118 mIntersectingViews.add(child);
2119 }
2120 }
Adam Cohen47a876d2012-03-19 13:21:41 -07002121
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002122 // First we try to find a solution which respects the push mechanic. That is,
2123 // we try to find a solution such that no displaced item travels through another item
2124 // without also displacing that item.
2125 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07002126 solution)) {
Adam Cohen47a876d2012-03-19 13:21:41 -07002127 return true;
2128 }
Adam Cohen47a876d2012-03-19 13:21:41 -07002129
Adam Cohen4abc5bd2012-05-29 21:06:03 -07002130 // Next we try moving the views as a block, but without requiring the push mechanic.
Adam Cohenf3900c22012-11-16 18:28:11 -08002131 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
Adam Cohen19f37922012-03-21 11:59:11 -07002132 solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002133 return true;
2134 }
Adam Cohen47a876d2012-03-19 13:21:41 -07002135
Adam Cohen482ed822012-03-02 14:15:13 -08002136 // Ok, they couldn't move as a block, let's move them individually
2137 for (View v : mIntersectingViews) {
Adam Cohen8baab352012-03-20 17:39:21 -07002138 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002139 return false;
2140 }
2141 }
2142 return true;
2143 }
2144
2145 /*
2146 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
2147 * the provided point and the provided cell
2148 */
Adam Cohen47a876d2012-03-19 13:21:41 -07002149 private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
Adam Cohen482ed822012-03-02 14:15:13 -08002150 double angle = Math.atan(((float) deltaY) / deltaX);
2151
2152 result[0] = 0;
2153 result[1] = 0;
2154 if (Math.abs(Math.cos(angle)) > 0.5f) {
2155 result[0] = (int) Math.signum(deltaX);
2156 }
2157 if (Math.abs(Math.sin(angle)) > 0.5f) {
2158 result[1] = (int) Math.signum(deltaY);
2159 }
2160 }
2161
Adam Cohen8baab352012-03-20 17:39:21 -07002162 private void copyOccupiedArray(boolean[][] occupied) {
2163 for (int i = 0; i < mCountX; i++) {
2164 for (int j = 0; j < mCountY; j++) {
2165 occupied[i][j] = mOccupied[i][j];
2166 }
2167 }
2168 }
2169
Adam Cohen482ed822012-03-02 14:15:13 -08002170 ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
2171 int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
Adam Cohen8baab352012-03-20 17:39:21 -07002172 // Copy the current state into the solution. This solution will be manipulated as necessary.
2173 copyCurrentStateToSolution(solution, false);
2174 // Copy the current occupied array into the temporary occupied array. This array will be
2175 // manipulated as necessary to find a solution.
2176 copyOccupiedArray(mTmpOccupied);
Adam Cohen482ed822012-03-02 14:15:13 -08002177
2178 // We find the nearest cell into which we would place the dragged item, assuming there's
2179 // nothing in its way.
2180 int result[] = new int[2];
2181 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2182
2183 boolean success = false;
2184 // First we try the exact nearest position of the item being dragged,
2185 // we will then want to try to move this around to other neighbouring positions
Adam Cohen8baab352012-03-20 17:39:21 -07002186 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
2187 solution);
Adam Cohen482ed822012-03-02 14:15:13 -08002188
2189 if (!success) {
2190 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
2191 // x, then 1 in y etc.
2192 if (spanX > minSpanX && (minSpanY == spanY || decX)) {
2193 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
2194 dragView, false, solution);
2195 } else if (spanY > minSpanY) {
2196 return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
2197 dragView, true, solution);
2198 }
2199 solution.isSolution = false;
2200 } else {
2201 solution.isSolution = true;
2202 solution.dragViewX = result[0];
2203 solution.dragViewY = result[1];
2204 solution.dragViewSpanX = spanX;
2205 solution.dragViewSpanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002206 }
2207 return solution;
2208 }
2209
2210 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002211 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002212 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002213 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002214 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002215 CellAndSpan c;
Adam Cohen482ed822012-03-02 14:15:13 -08002216 if (temp) {
Adam Cohen8baab352012-03-20 17:39:21 -07002217 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002218 } else {
Adam Cohen8baab352012-03-20 17:39:21 -07002219 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
Adam Cohen482ed822012-03-02 14:15:13 -08002220 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002221 solution.add(child, c);
Adam Cohen482ed822012-03-02 14:15:13 -08002222 }
2223 }
2224
2225 private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
2226 for (int i = 0; i < mCountX; i++) {
2227 for (int j = 0; j < mCountY; j++) {
2228 mTmpOccupied[i][j] = false;
2229 }
2230 }
2231
Michael Jurkaa52570f2012-03-20 03:18:20 -07002232 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002233 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002234 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002235 if (child == dragView) continue;
2236 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen8baab352012-03-20 17:39:21 -07002237 CellAndSpan c = solution.map.get(child);
2238 if (c != null) {
2239 lp.tmpCellX = c.x;
2240 lp.tmpCellY = c.y;
2241 lp.cellHSpan = c.spanX;
2242 lp.cellVSpan = c.spanY;
2243 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002244 }
2245 }
2246 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2247 solution.dragViewSpanY, mTmpOccupied, true);
2248 }
2249
2250 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
2251 commitDragView) {
2252
2253 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
2254 for (int i = 0; i < mCountX; i++) {
2255 for (int j = 0; j < mCountY; j++) {
2256 occupied[i][j] = false;
2257 }
2258 }
2259
Michael Jurkaa52570f2012-03-20 03:18:20 -07002260 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002261 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002262 View child = mShortcutsAndWidgets.getChildAt(i);
Adam Cohen482ed822012-03-02 14:15:13 -08002263 if (child == dragView) continue;
Adam Cohen8baab352012-03-20 17:39:21 -07002264 CellAndSpan c = solution.map.get(child);
2265 if (c != null) {
Adam Cohen19f37922012-03-21 11:59:11 -07002266 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
2267 DESTRUCTIVE_REORDER, false);
Adam Cohen8baab352012-03-20 17:39:21 -07002268 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
Adam Cohen482ed822012-03-02 14:15:13 -08002269 }
2270 }
2271 if (commitDragView) {
2272 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
2273 solution.dragViewSpanY, occupied, true);
2274 }
2275 }
2276
Adam Cohen19f37922012-03-21 11:59:11 -07002277 // This method starts or changes the reorder hint animations
2278 private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
2279 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen19f37922012-03-21 11:59:11 -07002280 for (int i = 0; i < childCount; i++) {
2281 View child = mShortcutsAndWidgets.getChildAt(i);
2282 if (child == dragView) continue;
2283 CellAndSpan c = solution.map.get(child);
2284 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2285 if (c != null) {
2286 ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
2287 c.x, c.y, c.spanX, c.spanY);
Adam Cohend024f982012-05-23 18:26:45 -07002288 rha.animate();
Adam Cohen19f37922012-03-21 11:59:11 -07002289 }
2290 }
2291 }
2292
2293 // Class which represents the reorder hint animations. These animations show that an item is
2294 // in a temporary state, and hint at where the item will return to.
2295 class ReorderHintAnimation {
2296 View child;
Adam Cohend024f982012-05-23 18:26:45 -07002297 float finalDeltaX;
2298 float finalDeltaY;
2299 float initDeltaX;
2300 float initDeltaY;
2301 float finalScale;
2302 float initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002303 private static final int DURATION = 300;
Adam Cohene7587d22012-05-24 18:50:02 -07002304 Animator a;
Adam Cohen19f37922012-03-21 11:59:11 -07002305
2306 public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
2307 int spanX, int spanY) {
2308 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
2309 final int x0 = mTmpPoint[0];
2310 final int y0 = mTmpPoint[1];
2311 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
2312 final int x1 = mTmpPoint[0];
2313 final int y1 = mTmpPoint[1];
2314 final int dX = x1 - x0;
2315 final int dY = y1 - y0;
Adam Cohend024f982012-05-23 18:26:45 -07002316 finalDeltaX = 0;
2317 finalDeltaY = 0;
Adam Cohen19f37922012-03-21 11:59:11 -07002318 if (dX == dY && dX == 0) {
2319 } else {
2320 if (dY == 0) {
Adam Cohend024f982012-05-23 18:26:45 -07002321 finalDeltaX = - Math.signum(dX) * mReorderHintAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002322 } else if (dX == 0) {
Adam Cohend024f982012-05-23 18:26:45 -07002323 finalDeltaY = - Math.signum(dY) * mReorderHintAnimationMagnitude;
Adam Cohen19f37922012-03-21 11:59:11 -07002324 } else {
2325 double angle = Math.atan( (float) (dY) / dX);
Adam Cohend024f982012-05-23 18:26:45 -07002326 finalDeltaX = (int) (- Math.signum(dX) *
Adam Cohenfe41ac62012-05-23 14:00:37 -07002327 Math.abs(Math.cos(angle) * mReorderHintAnimationMagnitude));
Adam Cohend024f982012-05-23 18:26:45 -07002328 finalDeltaY = (int) (- Math.signum(dY) *
Adam Cohenfe41ac62012-05-23 14:00:37 -07002329 Math.abs(Math.sin(angle) * mReorderHintAnimationMagnitude));
Adam Cohen19f37922012-03-21 11:59:11 -07002330 }
2331 }
Adam Cohend024f982012-05-23 18:26:45 -07002332 initDeltaX = child.getTranslationX();
2333 initDeltaY = child.getTranslationY();
Adam Cohen307fe232012-08-16 17:55:58 -07002334 finalScale = getChildrenScale() - 4.0f / child.getWidth();
Adam Cohend024f982012-05-23 18:26:45 -07002335 initScale = child.getScaleX();
Adam Cohen19f37922012-03-21 11:59:11 -07002336 this.child = child;
2337 }
2338
Adam Cohend024f982012-05-23 18:26:45 -07002339 void animate() {
Adam Cohen19f37922012-03-21 11:59:11 -07002340 if (mShakeAnimators.containsKey(child)) {
2341 ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
Adam Cohend024f982012-05-23 18:26:45 -07002342 oldAnimation.cancel();
Adam Cohen19f37922012-03-21 11:59:11 -07002343 mShakeAnimators.remove(child);
Adam Cohene7587d22012-05-24 18:50:02 -07002344 if (finalDeltaX == 0 && finalDeltaY == 0) {
2345 completeAnimationImmediately();
2346 return;
2347 }
Adam Cohen19f37922012-03-21 11:59:11 -07002348 }
Adam Cohend024f982012-05-23 18:26:45 -07002349 if (finalDeltaX == 0 && finalDeltaY == 0) {
Adam Cohen19f37922012-03-21 11:59:11 -07002350 return;
2351 }
Michael Jurkaf1ad6082013-03-13 12:55:46 +01002352 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
Adam Cohene7587d22012-05-24 18:50:02 -07002353 a = va;
Adam Cohen19f37922012-03-21 11:59:11 -07002354 va.setRepeatMode(ValueAnimator.REVERSE);
2355 va.setRepeatCount(ValueAnimator.INFINITE);
Adam Cohen7bdfc972012-05-22 16:50:35 -07002356 va.setDuration(DURATION);
Adam Cohend024f982012-05-23 18:26:45 -07002357 va.setStartDelay((int) (Math.random() * 60));
Adam Cohen19f37922012-03-21 11:59:11 -07002358 va.addUpdateListener(new AnimatorUpdateListener() {
2359 @Override
2360 public void onAnimationUpdate(ValueAnimator animation) {
2361 float r = ((Float) animation.getAnimatedValue()).floatValue();
Adam Cohend024f982012-05-23 18:26:45 -07002362 float x = r * finalDeltaX + (1 - r) * initDeltaX;
2363 float y = r * finalDeltaY + (1 - r) * initDeltaY;
Adam Cohen19f37922012-03-21 11:59:11 -07002364 child.setTranslationX(x);
2365 child.setTranslationY(y);
Adam Cohend024f982012-05-23 18:26:45 -07002366 float s = r * finalScale + (1 - r) * initScale;
Brandon Keely50e6e562012-05-08 16:28:49 -07002367 child.setScaleX(s);
2368 child.setScaleY(s);
Adam Cohen19f37922012-03-21 11:59:11 -07002369 }
2370 });
2371 va.addListener(new AnimatorListenerAdapter() {
2372 public void onAnimationRepeat(Animator animation) {
Adam Cohen19f37922012-03-21 11:59:11 -07002373 // We make sure to end only after a full period
Adam Cohend024f982012-05-23 18:26:45 -07002374 initDeltaX = 0;
2375 initDeltaY = 0;
Adam Cohen307fe232012-08-16 17:55:58 -07002376 initScale = getChildrenScale();
Adam Cohen19f37922012-03-21 11:59:11 -07002377 }
2378 });
Adam Cohen19f37922012-03-21 11:59:11 -07002379 mShakeAnimators.put(child, this);
2380 va.start();
2381 }
2382
Adam Cohend024f982012-05-23 18:26:45 -07002383 private void cancel() {
Adam Cohene7587d22012-05-24 18:50:02 -07002384 if (a != null) {
2385 a.cancel();
2386 }
Adam Cohen19f37922012-03-21 11:59:11 -07002387 }
Adam Cohene7587d22012-05-24 18:50:02 -07002388
Brandon Keely50e6e562012-05-08 16:28:49 -07002389 private void completeAnimationImmediately() {
Adam Cohene7587d22012-05-24 18:50:02 -07002390 if (a != null) {
2391 a.cancel();
2392 }
Brandon Keely50e6e562012-05-08 16:28:49 -07002393
Michael Jurka2ecf9952012-06-18 12:52:28 -07002394 AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
Adam Cohene7587d22012-05-24 18:50:02 -07002395 a = s;
Brandon Keely50e6e562012-05-08 16:28:49 -07002396 s.playTogether(
Adam Cohen307fe232012-08-16 17:55:58 -07002397 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
2398 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
Michael Jurka2ecf9952012-06-18 12:52:28 -07002399 LauncherAnimUtils.ofFloat(child, "translationX", 0f),
2400 LauncherAnimUtils.ofFloat(child, "translationY", 0f)
Brandon Keely50e6e562012-05-08 16:28:49 -07002401 );
2402 s.setDuration(REORDER_ANIMATION_DURATION);
2403 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
2404 s.start();
2405 }
Adam Cohen19f37922012-03-21 11:59:11 -07002406 }
2407
2408 private void completeAndClearReorderHintAnimations() {
2409 for (ReorderHintAnimation a: mShakeAnimators.values()) {
Brandon Keely50e6e562012-05-08 16:28:49 -07002410 a.completeAnimationImmediately();
Adam Cohen19f37922012-03-21 11:59:11 -07002411 }
2412 mShakeAnimators.clear();
2413 }
2414
Adam Cohen482ed822012-03-02 14:15:13 -08002415 private void commitTempPlacement() {
2416 for (int i = 0; i < mCountX; i++) {
2417 for (int j = 0; j < mCountY; j++) {
2418 mOccupied[i][j] = mTmpOccupied[i][j];
2419 }
2420 }
Michael Jurkaa52570f2012-03-20 03:18:20 -07002421 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002422 for (int i = 0; i < childCount; i++) {
Adam Cohenea889a22012-03-27 16:45:39 -07002423 View child = mShortcutsAndWidgets.getChildAt(i);
2424 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2425 ItemInfo info = (ItemInfo) child.getTag();
Adam Cohen2acce882012-03-28 19:03:19 -07002426 // We do a null check here because the item info can be null in the case of the
2427 // AllApps button in the hotseat.
2428 if (info != null) {
Adam Cohen487f7dd2012-06-28 18:12:10 -07002429 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
2430 info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
2431 info.requiresDbUpdate = true;
2432 }
Adam Cohen2acce882012-03-28 19:03:19 -07002433 info.cellX = lp.cellX = lp.tmpCellX;
2434 info.cellY = lp.cellY = lp.tmpCellY;
Adam Cohenbebf0422012-04-11 18:06:28 -07002435 info.spanX = lp.cellHSpan;
2436 info.spanY = lp.cellVSpan;
Adam Cohen2acce882012-03-28 19:03:19 -07002437 }
Adam Cohen482ed822012-03-02 14:15:13 -08002438 }
Adam Cohen2acce882012-03-28 19:03:19 -07002439 mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
Adam Cohen482ed822012-03-02 14:15:13 -08002440 }
2441
2442 public void setUseTempCoords(boolean useTempCoords) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002443 int childCount = mShortcutsAndWidgets.getChildCount();
Adam Cohen482ed822012-03-02 14:15:13 -08002444 for (int i = 0; i < childCount; i++) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07002445 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08002446 lp.useTmpCoords = useTempCoords;
2447 }
2448 }
2449
Adam Cohen482ed822012-03-02 14:15:13 -08002450 ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
2451 int spanX, int spanY, View dragView, ItemConfiguration solution) {
2452 int[] result = new int[2];
2453 int[] resultSpan = new int[2];
2454 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
2455 resultSpan);
2456 if (result[0] >= 0 && result[1] >= 0) {
2457 copyCurrentStateToSolution(solution, false);
2458 solution.dragViewX = result[0];
2459 solution.dragViewY = result[1];
2460 solution.dragViewSpanX = resultSpan[0];
2461 solution.dragViewSpanY = resultSpan[1];
2462 solution.isSolution = true;
2463 } else {
2464 solution.isSolution = false;
2465 }
2466 return solution;
2467 }
2468
2469 public void prepareChildForDrag(View child) {
2470 markCellsAsUnoccupiedForView(child);
Adam Cohen482ed822012-03-02 14:15:13 -08002471 }
2472
Adam Cohen19f37922012-03-21 11:59:11 -07002473 /* This seems like it should be obvious and straight-forward, but when the direction vector
2474 needs to match with the notion of the dragView pushing other views, we have to employ
2475 a slightly more subtle notion of the direction vector. The question is what two points is
2476 the vector between? The center of the dragView and its desired destination? Not quite, as
2477 this doesn't necessarily coincide with the interaction of the dragView and items occupying
2478 those cells. Instead we use some heuristics to often lock the vector to up, down, left
2479 or right, which helps make pushing feel right.
2480 */
2481 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
2482 int spanY, View dragView, int[] resultDirection) {
2483 int[] targetDestination = new int[2];
2484
2485 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
2486 Rect dragRect = new Rect();
2487 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
2488 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
2489
2490 Rect dropRegionRect = new Rect();
2491 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
2492 dragView, dropRegionRect, mIntersectingViews);
2493
2494 int dropRegionSpanX = dropRegionRect.width();
2495 int dropRegionSpanY = dropRegionRect.height();
2496
2497 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
2498 dropRegionRect.height(), dropRegionRect);
2499
2500 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
2501 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
2502
2503 if (dropRegionSpanX == mCountX || spanX == mCountX) {
2504 deltaX = 0;
2505 }
2506 if (dropRegionSpanY == mCountY || spanY == mCountY) {
2507 deltaY = 0;
2508 }
2509
2510 if (deltaX == 0 && deltaY == 0) {
2511 // No idea what to do, give a random direction.
2512 resultDirection[0] = 1;
2513 resultDirection[1] = 0;
2514 } else {
2515 computeDirectionVector(deltaX, deltaY, resultDirection);
2516 }
2517 }
2518
2519 // For a given cell and span, fetch the set of views intersecting the region.
2520 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
2521 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
2522 if (boundingRect != null) {
2523 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
2524 }
2525 intersectingViews.clear();
2526 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
2527 Rect r1 = new Rect();
2528 final int count = mShortcutsAndWidgets.getChildCount();
2529 for (int i = 0; i < count; i++) {
2530 View child = mShortcutsAndWidgets.getChildAt(i);
2531 if (child == dragView) continue;
2532 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2533 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
2534 if (Rect.intersects(r0, r1)) {
2535 mIntersectingViews.add(child);
2536 if (boundingRect != null) {
2537 boundingRect.union(r1);
2538 }
2539 }
2540 }
2541 }
2542
2543 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
2544 View dragView, int[] result) {
2545 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
2546 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
2547 mIntersectingViews);
2548 return !mIntersectingViews.isEmpty();
2549 }
2550
2551 void revertTempState() {
2552 if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
2553 final int count = mShortcutsAndWidgets.getChildCount();
2554 for (int i = 0; i < count; i++) {
2555 View child = mShortcutsAndWidgets.getChildAt(i);
2556 LayoutParams lp = (LayoutParams) child.getLayoutParams();
2557 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
2558 lp.tmpCellX = lp.cellX;
2559 lp.tmpCellY = lp.cellY;
2560 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
2561 0, false, false);
2562 }
2563 }
2564 completeAndClearReorderHintAnimations();
2565 setItemPlacementDirty(false);
2566 }
2567
Adam Cohenbebf0422012-04-11 18:06:28 -07002568 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
2569 View dragView, int[] direction, boolean commit) {
2570 int[] pixelXY = new int[2];
2571 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
2572
2573 // First we determine if things have moved enough to cause a different layout
2574 ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY,
2575 spanX, spanY, direction, dragView, true, new ItemConfiguration());
2576
2577 setUseTempCoords(true);
2578 if (swapSolution != null && swapSolution.isSolution) {
2579 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2580 // committing anything or animating anything as we just want to determine if a solution
2581 // exists
2582 copySolutionToTempState(swapSolution, dragView);
2583 setItemPlacementDirty(true);
2584 animateItemsToSolution(swapSolution, dragView, commit);
2585
2586 if (commit) {
2587 commitTempPlacement();
2588 completeAndClearReorderHintAnimations();
2589 setItemPlacementDirty(false);
2590 } else {
2591 beginOrAdjustHintAnimations(swapSolution, dragView,
2592 REORDER_ANIMATION_DURATION);
2593 }
2594 mShortcutsAndWidgets.requestLayout();
2595 }
2596 return swapSolution.isSolution;
2597 }
2598
Adam Cohen482ed822012-03-02 14:15:13 -08002599 int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
2600 View dragView, int[] result, int resultSpan[], int mode) {
Adam Cohen482ed822012-03-02 14:15:13 -08002601 // First we determine if things have moved enough to cause a different layout
Adam Cohen47a876d2012-03-19 13:21:41 -07002602 result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
Adam Cohen482ed822012-03-02 14:15:13 -08002603
2604 if (resultSpan == null) {
2605 resultSpan = new int[2];
2606 }
2607
Adam Cohen19f37922012-03-21 11:59:11 -07002608 // When we are checking drop validity or actually dropping, we don't recompute the
2609 // direction vector, since we want the solution to match the preview, and it's possible
2610 // that the exact position of the item has changed to result in a new reordering outcome.
Adam Cohenb209e632012-03-27 17:09:36 -07002611 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
2612 && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
Adam Cohen19f37922012-03-21 11:59:11 -07002613 mDirectionVector[0] = mPreviousReorderDirection[0];
2614 mDirectionVector[1] = mPreviousReorderDirection[1];
2615 // We reset this vector after drop
Adam Cohenb209e632012-03-27 17:09:36 -07002616 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2617 mPreviousReorderDirection[0] = INVALID_DIRECTION;
2618 mPreviousReorderDirection[1] = INVALID_DIRECTION;
Adam Cohen19f37922012-03-21 11:59:11 -07002619 }
2620 } else {
2621 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
2622 mPreviousReorderDirection[0] = mDirectionVector[0];
2623 mPreviousReorderDirection[1] = mDirectionVector[1];
2624 }
2625
Adam Cohen482ed822012-03-02 14:15:13 -08002626 ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
2627 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
2628
2629 // We attempt the approach which doesn't shuffle views at all
2630 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
2631 minSpanY, spanX, spanY, dragView, new ItemConfiguration());
2632
2633 ItemConfiguration finalSolution = null;
2634 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
2635 finalSolution = swapSolution;
2636 } else if (noShuffleSolution.isSolution) {
2637 finalSolution = noShuffleSolution;
2638 }
2639
2640 boolean foundSolution = true;
2641 if (!DESTRUCTIVE_REORDER) {
2642 setUseTempCoords(true);
2643 }
2644
2645 if (finalSolution != null) {
2646 result[0] = finalSolution.dragViewX;
2647 result[1] = finalSolution.dragViewY;
2648 resultSpan[0] = finalSolution.dragViewSpanX;
2649 resultSpan[1] = finalSolution.dragViewSpanY;
2650
2651 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
2652 // committing anything or animating anything as we just want to determine if a solution
2653 // exists
2654 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
2655 if (!DESTRUCTIVE_REORDER) {
2656 copySolutionToTempState(finalSolution, dragView);
2657 }
2658 setItemPlacementDirty(true);
2659 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
2660
Adam Cohen19f37922012-03-21 11:59:11 -07002661 if (!DESTRUCTIVE_REORDER &&
2662 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
Adam Cohen482ed822012-03-02 14:15:13 -08002663 commitTempPlacement();
Adam Cohen19f37922012-03-21 11:59:11 -07002664 completeAndClearReorderHintAnimations();
2665 setItemPlacementDirty(false);
2666 } else {
2667 beginOrAdjustHintAnimations(finalSolution, dragView,
2668 REORDER_ANIMATION_DURATION);
Adam Cohen482ed822012-03-02 14:15:13 -08002669 }
2670 }
2671 } else {
2672 foundSolution = false;
2673 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
2674 }
2675
2676 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
2677 setUseTempCoords(false);
2678 }
Adam Cohen482ed822012-03-02 14:15:13 -08002679
Michael Jurkaa52570f2012-03-20 03:18:20 -07002680 mShortcutsAndWidgets.requestLayout();
Adam Cohen482ed822012-03-02 14:15:13 -08002681 return result;
2682 }
2683
Adam Cohen19f37922012-03-21 11:59:11 -07002684 void setItemPlacementDirty(boolean dirty) {
2685 mItemPlacementDirty = dirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002686 }
Adam Cohen19f37922012-03-21 11:59:11 -07002687 boolean isItemPlacementDirty() {
2688 return mItemPlacementDirty;
Adam Cohen482ed822012-03-02 14:15:13 -08002689 }
2690
2691 private class ItemConfiguration {
Adam Cohen8baab352012-03-20 17:39:21 -07002692 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
Adam Cohenf3900c22012-11-16 18:28:11 -08002693 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
2694 ArrayList<View> sortedViews = new ArrayList<View>();
Adam Cohen482ed822012-03-02 14:15:13 -08002695 boolean isSolution = false;
2696 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
2697
Adam Cohenf3900c22012-11-16 18:28:11 -08002698 void save() {
2699 // Copy current state into savedMap
2700 for (View v: map.keySet()) {
2701 map.get(v).copy(savedMap.get(v));
2702 }
2703 }
2704
2705 void restore() {
2706 // Restore current state from savedMap
2707 for (View v: savedMap.keySet()) {
2708 savedMap.get(v).copy(map.get(v));
2709 }
2710 }
2711
2712 void add(View v, CellAndSpan cs) {
2713 map.put(v, cs);
2714 savedMap.put(v, new CellAndSpan());
2715 sortedViews.add(v);
2716 }
2717
Adam Cohen482ed822012-03-02 14:15:13 -08002718 int area() {
2719 return dragViewSpanX * dragViewSpanY;
2720 }
Adam Cohen8baab352012-03-20 17:39:21 -07002721 }
2722
2723 private class CellAndSpan {
2724 int x, y;
2725 int spanX, spanY;
2726
Adam Cohenf3900c22012-11-16 18:28:11 -08002727 public CellAndSpan() {
2728 }
2729
2730 public void copy(CellAndSpan copy) {
2731 copy.x = x;
2732 copy.y = y;
2733 copy.spanX = spanX;
2734 copy.spanY = spanY;
2735 }
2736
Adam Cohen8baab352012-03-20 17:39:21 -07002737 public CellAndSpan(int x, int y, int spanX, int spanY) {
2738 this.x = x;
2739 this.y = y;
2740 this.spanX = spanX;
2741 this.spanY = spanY;
Adam Cohen482ed822012-03-02 14:15:13 -08002742 }
Adam Cohenf3900c22012-11-16 18:28:11 -08002743
2744 public String toString() {
2745 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
2746 }
2747
Adam Cohen482ed822012-03-02 14:15:13 -08002748 }
2749
Adam Cohendf035382011-04-11 17:22:04 -07002750 /**
2751 * Find a vacant area that will fit the given bounds nearest the requested
2752 * cell location. Uses Euclidean distance to score multiple vacant areas.
2753 *
2754 * @param pixelX The X location at which you want to search for a vacant area.
2755 * @param pixelY The Y location at which you want to search for a vacant area.
2756 * @param spanX Horizontal span of the object.
2757 * @param spanY Vertical span of the object.
2758 * @param ignoreView Considers space occupied by this view as unoccupied
2759 * @param result Previously returned value to possibly recycle.
2760 * @return The X, Y cell of a vacant area that can contain this object,
2761 * nearest the requested location.
2762 */
2763 int[] findNearestVacantArea(
2764 int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
2765 return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
2766 }
2767
2768 /**
Adam Cohend41fbf52012-02-16 23:53:59 -08002769 * Find a vacant area that will fit the given bounds nearest the requested
2770 * cell location. Uses Euclidean distance to score multiple vacant areas.
2771 *
2772 * @param pixelX The X location at which you want to search for a vacant area.
2773 * @param pixelY The Y location at which you want to search for a vacant area.
2774 * @param minSpanX The minimum horizontal span required
2775 * @param minSpanY The minimum vertical span required
2776 * @param spanX Horizontal span of the object.
2777 * @param spanY Vertical span of the object.
2778 * @param ignoreView Considers space occupied by this view as unoccupied
2779 * @param result Previously returned value to possibly recycle.
2780 * @return The X, Y cell of a vacant area that can contain this object,
2781 * nearest the requested location.
2782 */
2783 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
2784 int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
Adam Cohen482ed822012-03-02 14:15:13 -08002785 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
2786 result, resultSpan, mOccupied);
Adam Cohend41fbf52012-02-16 23:53:59 -08002787 }
2788
2789 /**
Adam Cohendf035382011-04-11 17:22:04 -07002790 * Find a starting cell position that will fit the given bounds nearest the requested
2791 * cell location. Uses Euclidean distance to score multiple vacant areas.
2792 *
2793 * @param pixelX The X location at which you want to search for a vacant area.
2794 * @param pixelY The Y location at which you want to search for a vacant area.
2795 * @param spanX Horizontal span of the object.
2796 * @param spanY Vertical span of the object.
2797 * @param ignoreView Considers space occupied by this view as unoccupied
2798 * @param result Previously returned value to possibly recycle.
2799 * @return The X, Y cell of a vacant area that can contain this object,
2800 * nearest the requested location.
2801 */
2802 int[] findNearestArea(
2803 int pixelX, int pixelY, int spanX, int spanY, int[] result) {
2804 return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
2805 }
2806
Michael Jurka0280c3b2010-09-17 15:00:07 -07002807 boolean existsEmptyCell() {
2808 return findCellForSpan(null, 1, 1);
2809 }
2810
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002811 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002812 * Finds the upper-left coordinate of the first rectangle in the grid that can
2813 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
2814 * then this method will only return coordinates for rectangles that contain the cell
2815 * (intersectX, intersectY)
2816 *
2817 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2818 * can be found.
2819 * @param spanX The horizontal span of the cell we want to find.
2820 * @param spanY The vertical span of the cell we want to find.
2821 *
2822 * @return True if a vacant cell of the specified dimension was found, false otherwise.
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002823 */
Michael Jurka0280c3b2010-09-17 15:00:07 -07002824 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Adam Cohen482ed822012-03-02 14:15:13 -08002825 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002826 }
2827
2828 /**
2829 * Like above, but ignores any cells occupied by the item "ignoreView"
2830 *
2831 * @param cellXY The array that will contain the position of a vacant cell if such a cell
2832 * can be found.
2833 * @param spanX The horizontal span of the cell we want to find.
2834 * @param spanY The vertical span of the cell we want to find.
2835 * @param ignoreView The home screen item we should treat as not occupying any space
2836 * @return
2837 */
2838 boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
Adam Cohen482ed822012-03-02 14:15:13 -08002839 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
2840 ignoreView, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002841 }
2842
2843 /**
2844 * Like above, but if intersectX and intersectY are not -1, then this method will try to
2845 * return coordinates for rectangles that contain the cell [intersectX, intersectY]
2846 *
2847 * @param spanX The horizontal span of the cell we want to find.
2848 * @param spanY The vertical span of the cell we want to find.
2849 * @param ignoreView The home screen item we should treat as not occupying any space
2850 * @param intersectX The X coordinate of the cell that we should try to overlap
2851 * @param intersectX The Y coordinate of the cell that we should try to overlap
2852 *
2853 * @return True if a vacant cell of the specified dimension was found, false otherwise.
2854 */
2855 boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
2856 int intersectX, int intersectY) {
2857 return findCellForSpanThatIntersectsIgnoring(
Adam Cohen482ed822012-03-02 14:15:13 -08002858 cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002859 }
2860
2861 /**
2862 * The superset of the above two methods
2863 */
2864 boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
Adam Cohen482ed822012-03-02 14:15:13 -08002865 int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002866 // mark space take by ignoreView as available (method checks if ignoreView is null)
Adam Cohen482ed822012-03-02 14:15:13 -08002867 markCellsAsUnoccupiedForView(ignoreView, occupied);
Michael Jurka0280c3b2010-09-17 15:00:07 -07002868
Michael Jurka28750fb2010-09-24 17:43:49 -07002869 boolean foundCell = false;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002870 while (true) {
2871 int startX = 0;
2872 if (intersectX >= 0) {
2873 startX = Math.max(startX, intersectX - (spanX - 1));
2874 }
2875 int endX = mCountX - (spanX - 1);
2876 if (intersectX >= 0) {
2877 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
2878 }
2879 int startY = 0;
2880 if (intersectY >= 0) {
2881 startY = Math.max(startY, intersectY - (spanY - 1));
2882 }
2883 int endY = mCountY - (spanY - 1);
2884 if (intersectY >= 0) {
2885 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
2886 }
2887
Winson Chungbbc60d82010-11-11 16:34:41 -08002888 for (int y = startY; y < endY && !foundCell; y++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002889 inner:
Winson Chungbbc60d82010-11-11 16:34:41 -08002890 for (int x = startX; x < endX; x++) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07002891 for (int i = 0; i < spanX; i++) {
2892 for (int j = 0; j < spanY; j++) {
Adam Cohen482ed822012-03-02 14:15:13 -08002893 if (occupied[x + i][y + j]) {
Winson Chungbbc60d82010-11-11 16:34:41 -08002894 // small optimization: we can skip to after the column we just found
Michael Jurka0280c3b2010-09-17 15:00:07 -07002895 // an occupied cell
Winson Chungbbc60d82010-11-11 16:34:41 -08002896 x += i;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002897 continue inner;
2898 }
2899 }
2900 }
2901 if (cellXY != null) {
2902 cellXY[0] = x;
2903 cellXY[1] = y;
2904 }
Michael Jurka28750fb2010-09-24 17:43:49 -07002905 foundCell = true;
2906 break;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002907 }
2908 }
2909 if (intersectX == -1 && intersectY == -1) {
2910 break;
2911 } else {
2912 // if we failed to find anything, try again but without any requirements of
2913 // intersecting
2914 intersectX = -1;
2915 intersectY = -1;
2916 continue;
2917 }
2918 }
2919
Michael Jurkac6ee42e2010-09-30 12:04:50 -07002920 // re-mark space taken by ignoreView as occupied
Adam Cohen482ed822012-03-02 14:15:13 -08002921 markCellsAsOccupiedForView(ignoreView, occupied);
Michael Jurka28750fb2010-09-24 17:43:49 -07002922 return foundCell;
Michael Jurka0280c3b2010-09-17 15:00:07 -07002923 }
2924
2925 /**
Winson Chungc07918d2011-07-01 15:35:26 -07002926 * A drag event has begun over this layout.
2927 * It may have begun over this layout (in which case onDragChild is called first),
2928 * or it may have begun on another layout.
2929 */
2930 void onDragEnter() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002931 mDragEnforcer.onDragEnter();
Winson Chungc07918d2011-07-01 15:35:26 -07002932 mDragging = true;
2933 }
2934
2935 /**
Michael Jurka0280c3b2010-09-17 15:00:07 -07002936 * Called when drag has left this CellLayout or has been completed (successfully or not)
2937 */
2938 void onDragExit() {
Adam Cohenc6cc61d2012-04-04 12:47:08 -07002939 mDragEnforcer.onDragExit();
Joe Onorato4be866d2010-10-10 11:26:02 -07002940 // This can actually be called when we aren't in a drag, e.g. when adding a new
2941 // item to this layout via the customize drawer.
2942 // Guard against that case.
2943 if (mDragging) {
2944 mDragging = false;
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002945 }
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002946
2947 // Invalidate the drag data
Adam Cohend41fbf52012-02-16 23:53:59 -08002948 mDragCell[0] = mDragCell[1] = -1;
Patrick Dubroy08ae2ec2010-10-14 23:54:22 -07002949 mDragOutlineAnims[mDragOutlineCurrent].animateOut();
2950 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
Adam Cohen19f37922012-03-21 11:59:11 -07002951 revertTempState();
Michael Jurka33945b22010-12-21 18:19:38 -08002952 setIsDragOverlapping(false);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002953 }
2954
2955 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002956 * Mark a child as having been dropped.
Patrick Dubroyde7658b2010-09-27 11:15:43 -07002957 * At the beginning of the drag operation, the child may have been on another
Patrick Dubroyce34a972010-10-19 10:34:32 -07002958 * screen, but it is re-parented before this method is called.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002959 *
2960 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002961 */
Adam Cohen716b51e2011-06-30 12:09:54 -07002962 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -07002963 if (child != null) {
2964 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guy84f296c2009-11-04 15:00:44 -08002965 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -07002966 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -07002967 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002968 }
2969
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002970 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002971 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -07002972 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002973 * @param cellX X coordinate of upper left corner expressed as a cell position
2974 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -07002975 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002976 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002977 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002978 */
Adam Cohend41fbf52012-02-16 23:53:59 -08002979 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002980 final int cellWidth = mCellWidth;
2981 final int cellHeight = mCellHeight;
2982 final int widthGap = mWidthGap;
2983 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -07002984
Winson Chung4b825dcd2011-06-19 12:41:22 -07002985 final int hStartPadding = getPaddingLeft();
2986 final int vStartPadding = getPaddingTop();
Winson Chungaafa03c2010-06-11 17:34:16 -07002987
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002988 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
2989 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
2990
2991 int x = hStartPadding + cellX * (cellWidth + widthGap);
2992 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07002993
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07002994 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002995 }
Winson Chungaafa03c2010-06-11 17:34:16 -07002996
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002997 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07002998 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08002999 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07003000 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003001 * @param width Width in pixels
3002 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07003003 * @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 -08003004 */
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07003005 public int[] rectToCell(int width, int height, int[] result) {
Michael Jurka9987a5c2010-10-08 16:58:12 -07003006 return rectToCell(getResources(), width, height, result);
3007 }
3008
3009 public static int[] rectToCell(Resources resources, int width, int height, int[] result) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003010 // Always assume we're working with the smallest span to make sure we
3011 // reserve enough space in both orientations.
Joe Onorato79e56262009-09-21 15:23:04 -04003012 int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
3013 int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003014 int smallerSize = Math.min(actualWidth, actualHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04003015
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003016 // Always round up to next largest cell
Winson Chung54c725c2011-08-03 12:03:40 -07003017 int spanX = (int) Math.ceil(width / (float) smallerSize);
3018 int spanY = (int) Math.ceil(height / (float) smallerSize);
Joe Onorato79e56262009-09-21 15:23:04 -04003019
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07003020 if (result == null) {
3021 return new int[] { spanX, spanY };
3022 }
3023 result[0] = spanX;
3024 result[1] = spanY;
3025 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003026 }
3027
Michael Jurkaf12c75c2011-01-25 22:41:40 -08003028 public int[] cellSpansToSize(int hSpans, int vSpans) {
3029 int[] size = new int[2];
3030 size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
3031 size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
3032 return size;
3033 }
3034
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003035 /**
Patrick Dubroy047379a2010-12-19 22:02:04 -08003036 * Calculate the grid spans needed to fit given item
3037 */
3038 public void calculateSpans(ItemInfo info) {
3039 final int minWidth;
3040 final int minHeight;
3041
3042 if (info instanceof LauncherAppWidgetInfo) {
3043 minWidth = ((LauncherAppWidgetInfo) info).minWidth;
3044 minHeight = ((LauncherAppWidgetInfo) info).minHeight;
3045 } else if (info instanceof PendingAddWidgetInfo) {
3046 minWidth = ((PendingAddWidgetInfo) info).minWidth;
3047 minHeight = ((PendingAddWidgetInfo) info).minHeight;
3048 } else {
3049 // It's not a widget, so it must be 1x1
3050 info.spanX = info.spanY = 1;
3051 return;
3052 }
3053 int[] spans = rectToCell(minWidth, minHeight, null);
3054 info.spanX = spans[0];
3055 info.spanY = spans[1];
3056 }
3057
3058 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003059 * Find the first vacant cell, if there is one.
3060 *
3061 * @param vacant Holds the x and y coordinate of the vacant cell
3062 * @param spanX Horizontal cell span.
3063 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -07003064 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003065 * @return True if a vacant cell was found
3066 */
3067 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003068
Michael Jurka0280c3b2010-09-17 15:00:07 -07003069 return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003070 }
3071
3072 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
3073 int xCount, int yCount, boolean[][] occupied) {
3074
Adam Cohen2801caf2011-05-13 20:57:39 -07003075 for (int y = 0; y < yCount; y++) {
3076 for (int x = 0; x < xCount; x++) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003077 boolean available = !occupied[x][y];
3078out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
3079 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
3080 available = available && !occupied[i][j];
3081 if (!available) break out;
3082 }
3083 }
3084
3085 if (available) {
3086 vacant[0] = x;
3087 vacant[1] = y;
3088 return true;
3089 }
3090 }
3091 }
3092
3093 return false;
3094 }
3095
Michael Jurka0280c3b2010-09-17 15:00:07 -07003096 private void clearOccupiedCells() {
3097 for (int x = 0; x < mCountX; x++) {
3098 for (int y = 0; y < mCountY; y++) {
3099 mOccupied[x][y] = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003100 }
3101 }
Michael Jurka0280c3b2010-09-17 15:00:07 -07003102 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003103
Adam Cohend41fbf52012-02-16 23:53:59 -08003104 public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
Michael Jurka0280c3b2010-09-17 15:00:07 -07003105 markCellsAsUnoccupiedForView(view);
Adam Cohen482ed822012-03-02 14:15:13 -08003106 markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003107 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003108
Adam Cohend4844c32011-02-18 19:25:06 -08003109 public void markCellsAsOccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08003110 markCellsAsOccupiedForView(view, mOccupied);
3111 }
3112 public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07003113 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003114 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08003115 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003116 }
3117
Adam Cohend4844c32011-02-18 19:25:06 -08003118 public void markCellsAsUnoccupiedForView(View view) {
Adam Cohen482ed822012-03-02 14:15:13 -08003119 markCellsAsUnoccupiedForView(view, mOccupied);
3120 }
3121 public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
Michael Jurkaa52570f2012-03-20 03:18:20 -07003122 if (view == null || view.getParent() != mShortcutsAndWidgets) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003123 LayoutParams lp = (LayoutParams) view.getLayoutParams();
Adam Cohen482ed822012-03-02 14:15:13 -08003124 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
Michael Jurka0280c3b2010-09-17 15:00:07 -07003125 }
3126
Adam Cohen482ed822012-03-02 14:15:13 -08003127 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
3128 boolean value) {
3129 if (cellX < 0 || cellY < 0) return;
Michael Jurka0280c3b2010-09-17 15:00:07 -07003130 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
3131 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
Adam Cohen482ed822012-03-02 14:15:13 -08003132 occupied[x][y] = value;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003133 }
3134 }
3135 }
3136
Adam Cohen2801caf2011-05-13 20:57:39 -07003137 public int getDesiredWidth() {
Michael Jurka8b805b12012-04-18 14:23:14 -07003138 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
Adam Cohen2801caf2011-05-13 20:57:39 -07003139 (Math.max((mCountX - 1), 0) * mWidthGap);
3140 }
3141
3142 public int getDesiredHeight() {
Michael Jurka8b805b12012-04-18 14:23:14 -07003143 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
Adam Cohen2801caf2011-05-13 20:57:39 -07003144 (Math.max((mCountY - 1), 0) * mHeightGap);
3145 }
3146
Michael Jurka66d72172011-04-12 16:29:25 -07003147 public boolean isOccupied(int x, int y) {
3148 if (x < mCountX && y < mCountY) {
3149 return mOccupied[x][y];
3150 } else {
3151 throw new RuntimeException("Position exceeds the bound of this CellLayout");
3152 }
3153 }
3154
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003155 @Override
3156 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
3157 return new CellLayout.LayoutParams(getContext(), attrs);
3158 }
3159
3160 @Override
3161 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
3162 return p instanceof CellLayout.LayoutParams;
3163 }
3164
3165 @Override
3166 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
3167 return new CellLayout.LayoutParams(p);
3168 }
3169
Winson Chungaafa03c2010-06-11 17:34:16 -07003170 public static class CellLayoutAnimationController extends LayoutAnimationController {
3171 public CellLayoutAnimationController(Animation animation, float delay) {
3172 super(animation, delay);
3173 }
3174
3175 @Override
3176 protected long getDelayForView(View view) {
3177 return (int) (Math.random() * 150);
3178 }
3179 }
3180
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003181 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
3182 /**
3183 * Horizontal location of the item in the grid.
3184 */
3185 @ViewDebug.ExportedProperty
3186 public int cellX;
3187
3188 /**
3189 * Vertical location of the item in the grid.
3190 */
3191 @ViewDebug.ExportedProperty
3192 public int cellY;
3193
3194 /**
Adam Cohen482ed822012-03-02 14:15:13 -08003195 * Temporary horizontal location of the item in the grid during reorder
3196 */
3197 public int tmpCellX;
3198
3199 /**
3200 * Temporary vertical location of the item in the grid during reorder
3201 */
3202 public int tmpCellY;
3203
3204 /**
3205 * Indicates that the temporary coordinates should be used to layout the items
3206 */
3207 public boolean useTmpCoords;
3208
3209 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003210 * Number of cells spanned horizontally by the item.
3211 */
3212 @ViewDebug.ExportedProperty
3213 public int cellHSpan;
3214
3215 /**
3216 * Number of cells spanned vertically by the item.
3217 */
3218 @ViewDebug.ExportedProperty
3219 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07003220
Adam Cohen1b607ed2011-03-03 17:26:50 -08003221 /**
3222 * Indicates whether the item will set its x, y, width and height parameters freely,
3223 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
3224 */
Adam Cohend4844c32011-02-18 19:25:06 -08003225 public boolean isLockedToGrid = true;
3226
Adam Cohen482ed822012-03-02 14:15:13 -08003227 /**
3228 * Indicates whether this item can be reordered. Always true except in the case of the
3229 * the AllApps button.
3230 */
3231 public boolean canReorder = true;
3232
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003233 // X coordinate of the view in the layout.
3234 @ViewDebug.ExportedProperty
3235 int x;
3236 // Y coordinate of the view in the layout.
3237 @ViewDebug.ExportedProperty
3238 int y;
3239
Romain Guy84f296c2009-11-04 15:00:44 -08003240 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07003241
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003242 public LayoutParams(Context c, AttributeSet attrs) {
3243 super(c, attrs);
3244 cellHSpan = 1;
3245 cellVSpan = 1;
3246 }
3247
3248 public LayoutParams(ViewGroup.LayoutParams source) {
3249 super(source);
3250 cellHSpan = 1;
3251 cellVSpan = 1;
3252 }
Winson Chungaafa03c2010-06-11 17:34:16 -07003253
3254 public LayoutParams(LayoutParams source) {
3255 super(source);
3256 this.cellX = source.cellX;
3257 this.cellY = source.cellY;
3258 this.cellHSpan = source.cellHSpan;
3259 this.cellVSpan = source.cellVSpan;
3260 }
3261
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003262 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08003263 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003264 this.cellX = cellX;
3265 this.cellY = cellY;
3266 this.cellHSpan = cellHSpan;
3267 this.cellVSpan = cellVSpan;
3268 }
3269
Adam Cohen2374abf2013-04-16 14:56:57 -07003270 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
3271 boolean invertHorizontally, int colCount) {
Adam Cohend4844c32011-02-18 19:25:06 -08003272 if (isLockedToGrid) {
3273 final int myCellHSpan = cellHSpan;
3274 final int myCellVSpan = cellVSpan;
Adam Cohen2374abf2013-04-16 14:56:57 -07003275 int myCellX = useTmpCoords ? tmpCellX : cellX;
3276 int myCellY = useTmpCoords ? tmpCellY : cellY;
3277
3278 if (invertHorizontally) {
3279 myCellX = colCount - myCellX - cellHSpan;
3280 }
Adam Cohen1b607ed2011-03-03 17:26:50 -08003281
Adam Cohend4844c32011-02-18 19:25:06 -08003282 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
3283 leftMargin - rightMargin;
3284 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
3285 topMargin - bottomMargin;
Winson Chungeecf02d2012-03-02 17:14:58 -08003286 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
3287 y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
Adam Cohend4844c32011-02-18 19:25:06 -08003288 }
3289 }
Winson Chungaafa03c2010-06-11 17:34:16 -07003290
Winson Chungaafa03c2010-06-11 17:34:16 -07003291 public String toString() {
3292 return "(" + this.cellX + ", " + this.cellY + ")";
3293 }
Adam Cohen7f4eabe2011-04-21 16:19:16 -07003294
3295 public void setWidth(int width) {
3296 this.width = width;
3297 }
3298
3299 public int getWidth() {
3300 return width;
3301 }
3302
3303 public void setHeight(int height) {
3304 this.height = height;
3305 }
3306
3307 public int getHeight() {
3308 return height;
3309 }
3310
3311 public void setX(int x) {
3312 this.x = x;
3313 }
3314
3315 public int getX() {
3316 return x;
3317 }
3318
3319 public void setY(int y) {
3320 this.y = y;
3321 }
3322
3323 public int getY() {
3324 return y;
3325 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003326 }
3327
Michael Jurka0280c3b2010-09-17 15:00:07 -07003328 // This class stores info for two purposes:
3329 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
3330 // its spanX, spanY, and the screen it is on
3331 // 2. When long clicking on an empty cell in a CellLayout, we save information about the
3332 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
3333 // the CellLayout that was long clicked
Michael Jurkae5fb0f22011-04-11 13:27:46 -07003334 static final class CellInfo {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003335 View cell;
Michael Jurkaa63c4522010-08-19 13:52:27 -07003336 int cellX = -1;
3337 int cellY = -1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003338 int spanX;
3339 int spanY;
Adam Cohendcd297f2013-06-18 13:13:40 -07003340 long screenId;
Winson Chung3d503fb2011-07-13 17:25:49 -07003341 long container;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003342
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003343 @Override
3344 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07003345 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
3346 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003347 }
3348 }
Michael Jurkad771c962011-08-09 15:00:48 -07003349
3350 public boolean lastDownOnOccupiedCell() {
3351 return mLastDownOnOccupiedCell;
3352 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08003353}