summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher2/CellLayout.java
diff options
context:
space:
mode:
authorDaniel Sandler <dsandler@android.com>2013-06-05 22:57:57 -0400
committerDaniel Sandler <dsandler@android.com>2013-06-05 23:30:20 -0400
commit325dc23624160689e59fbac708cf6f222b20d025 (patch)
tree3c6a13a52a6e5688c7e4404890e5e8f88d544856 /src/com/android/launcher2/CellLayout.java
parentb582cd201fccece65d36b8915cf84fef3546cffa (diff)
downloadandroid_packages_apps_Trebuchet-325dc23624160689e59fbac708cf6f222b20d025.tar.gz
android_packages_apps_Trebuchet-325dc23624160689e59fbac708cf6f222b20d025.tar.bz2
android_packages_apps_Trebuchet-325dc23624160689e59fbac708cf6f222b20d025.zip
Launcher2 is now Launcher3.
Changes include - moving from com.android.launcher{,2} to com.android.launcher3 - removing wallpapers - new temporary icon Change-Id: I1eabd06059e94a8f3bdf6b620777bd1d2b7c212b
Diffstat (limited to 'src/com/android/launcher2/CellLayout.java')
-rw-r--r--src/com/android/launcher2/CellLayout.java3338
1 files changed, 0 insertions, 3338 deletions
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
deleted file mode 100644
index 024bb37af..000000000
--- a/src/com/android/launcher2/CellLayout.java
+++ /dev/null
@@ -1,3338 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher2;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.NinePatchDrawable;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewDebug;
-import android.view.ViewGroup;
-import android.view.animation.Animation;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.LayoutAnimationController;
-
-import com.android.launcher.R;
-import com.android.launcher2.FolderIcon.FolderRingAnimator;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.Stack;
-
-public class CellLayout extends ViewGroup {
- static final String TAG = "CellLayout";
-
- private Launcher mLauncher;
- private int mCellWidth;
- private int mCellHeight;
-
- private int mCountX;
- private int mCountY;
-
- private int mOriginalWidthGap;
- private int mOriginalHeightGap;
- private int mWidthGap;
- private int mHeightGap;
- private int mMaxGap;
- private boolean mScrollingTransformsDirty = false;
-
- private final Rect mRect = new Rect();
- private final CellInfo mCellInfo = new CellInfo();
-
- // These are temporary variables to prevent having to allocate a new object just to
- // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
- private final int[] mTmpXY = new int[2];
- private final int[] mTmpPoint = new int[2];
- int[] mTempLocation = new int[2];
-
- boolean[][] mOccupied;
- boolean[][] mTmpOccupied;
- private boolean mLastDownOnOccupiedCell = false;
-
- private OnTouchListener mInterceptTouchListener;
-
- private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>();
- private int[] mFolderLeaveBehindCell = {-1, -1};
-
- private int mForegroundAlpha = 0;
- private float mBackgroundAlpha;
- private float mBackgroundAlphaMultiplier = 1.0f;
-
- private Drawable mNormalBackground;
- private Drawable mActiveGlowBackground;
- private Drawable mOverScrollForegroundDrawable;
- private Drawable mOverScrollLeft;
- private Drawable mOverScrollRight;
- private Rect mBackgroundRect;
- private Rect mForegroundRect;
- private int mForegroundPadding;
-
- // If we're actively dragging something over this screen, mIsDragOverlapping is true
- private boolean mIsDragOverlapping = false;
- private final Point mDragCenter = new Point();
-
- // These arrays are used to implement the drag visualization on x-large screens.
- // They are used as circular arrays, indexed by mDragOutlineCurrent.
- private Rect[] mDragOutlines = new Rect[4];
- private float[] mDragOutlineAlphas = new float[mDragOutlines.length];
- private InterruptibleInOutAnimator[] mDragOutlineAnims =
- new InterruptibleInOutAnimator[mDragOutlines.length];
-
- // Used as an index into the above 3 arrays; indicates which is the most current value.
- private int mDragOutlineCurrent = 0;
- private final Paint mDragOutlinePaint = new Paint();
-
- private BubbleTextView mPressedOrFocusedIcon;
-
- private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
- HashMap<CellLayout.LayoutParams, Animator>();
- private HashMap<View, ReorderHintAnimation>
- mShakeAnimators = new HashMap<View, ReorderHintAnimation>();
-
- private boolean mItemPlacementDirty = false;
-
- // When a drag operation is in progress, holds the nearest cell to the touch point
- private final int[] mDragCell = new int[2];
-
- private boolean mDragging = false;
-
- private TimeInterpolator mEaseOutInterpolator;
- private ShortcutAndWidgetContainer mShortcutsAndWidgets;
-
- private boolean mIsHotseat = false;
- private float mHotseatScale = 1f;
-
- public static final int MODE_DRAG_OVER = 0;
- public static final int MODE_ON_DROP = 1;
- public static final int MODE_ON_DROP_EXTERNAL = 2;
- public static final int MODE_ACCEPT_DROP = 3;
- private static final boolean DESTRUCTIVE_REORDER = false;
- private static final boolean DEBUG_VISUALIZE_OCCUPIED = false;
-
- static final int LANDSCAPE = 0;
- static final int PORTRAIT = 1;
-
- private static final float REORDER_HINT_MAGNITUDE = 0.12f;
- private static final int REORDER_ANIMATION_DURATION = 150;
- private float mReorderHintAnimationMagnitude;
-
- private ArrayList<View> mIntersectingViews = new ArrayList<View>();
- private Rect mOccupiedRect = new Rect();
- private int[] mDirectionVector = new int[2];
- int[] mPreviousReorderDirection = new int[2];
- private static final int INVALID_DIRECTION = -100;
- private DropTarget.DragEnforcer mDragEnforcer;
-
- private final static PorterDuffXfermode sAddBlendMode =
- new PorterDuffXfermode(PorterDuff.Mode.ADD);
- private final static Paint sPaint = new Paint();
-
- public CellLayout(Context context) {
- this(context, null);
- }
-
- public CellLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public CellLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mDragEnforcer = new DropTarget.DragEnforcer(context);
-
- // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
- // the user where a dragged item will land when dropped.
- setWillNotDraw(false);
- setClipToPadding(false);
- mLauncher = (Launcher) context;
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
-
- mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
- mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
- mWidthGap = mOriginalWidthGap = a.getDimensionPixelSize(R.styleable.CellLayout_widthGap, 0);
- mHeightGap = mOriginalHeightGap = a.getDimensionPixelSize(R.styleable.CellLayout_heightGap, 0);
- mMaxGap = a.getDimensionPixelSize(R.styleable.CellLayout_maxGap, 0);
- mCountX = LauncherModel.getCellCountX();
- mCountY = LauncherModel.getCellCountY();
- mOccupied = new boolean[mCountX][mCountY];
- mTmpOccupied = new boolean[mCountX][mCountY];
- mPreviousReorderDirection[0] = INVALID_DIRECTION;
- mPreviousReorderDirection[1] = INVALID_DIRECTION;
-
- a.recycle();
-
- setAlwaysDrawnWithCacheEnabled(false);
-
- final Resources res = getResources();
- mHotseatScale = (res.getInteger(R.integer.hotseat_item_scale_percentage) / 100f);
-
- mNormalBackground = res.getDrawable(R.drawable.homescreen_blue_normal_holo);
- mActiveGlowBackground = res.getDrawable(R.drawable.homescreen_blue_strong_holo);
-
- mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
- mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
- mForegroundPadding =
- res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding);
-
- mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE *
- res.getDimensionPixelSize(R.dimen.app_icon_size));
-
- mNormalBackground.setFilterBitmap(true);
- mActiveGlowBackground.setFilterBitmap(true);
-
- // Initialize the data structures used for the drag visualization.
-
- mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
-
-
- mDragCell[0] = mDragCell[1] = -1;
- for (int i = 0; i < mDragOutlines.length; i++) {
- mDragOutlines[i] = new Rect(-1, -1, -1, -1);
- }
-
- // When dragging things around the home screens, we show a green outline of
- // where the item will land. The outlines gradually fade out, leaving a trail
- // behind the drag path.
- // Set up all the animations that are used to implement this fading.
- final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
- final float fromAlphaValue = 0;
- final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha);
-
- Arrays.fill(mDragOutlineAlphas, fromAlphaValue);
-
- for (int i = 0; i < mDragOutlineAnims.length; i++) {
- final InterruptibleInOutAnimator anim =
- new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue);
- anim.getAnimator().setInterpolator(mEaseOutInterpolator);
- final int thisIndex = i;
- anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
- public void onAnimationUpdate(ValueAnimator animation) {
- final Bitmap outline = (Bitmap)anim.getTag();
-
- // If an animation is started and then stopped very quickly, we can still
- // get spurious updates we've cleared the tag. Guard against this.
- if (outline == null) {
- @SuppressWarnings("all") // suppress dead code warning
- final boolean debug = false;
- if (debug) {
- Object val = animation.getAnimatedValue();
- Log.d(TAG, "anim " + thisIndex + " update: " + val +
- ", isStopped " + anim.isStopped());
- }
- // Try to prevent it from continuing to run
- animation.cancel();
- } else {
- mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue();
- CellLayout.this.invalidate(mDragOutlines[thisIndex]);
- }
- }
- });
- // The animation holds a reference to the drag outline bitmap as long is it's
- // running. This way the bitmap can be GCed when the animations are complete.
- anim.getAnimator().addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) {
- anim.setTag(null);
- }
- }
- });
- mDragOutlineAnims[i] = anim;
- }
-
- mBackgroundRect = new Rect();
- mForegroundRect = new Rect();
-
- mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context);
- mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
- mCountX);
-
- addView(mShortcutsAndWidgets);
- }
-
- static int widthInPortrait(Resources r, int numCells) {
- // We use this method from Workspace to figure out how many rows/columns Launcher should
- // have. We ignore the left/right padding on CellLayout because it turns out in our design
- // the padding extends outside the visible screen size, but it looked fine anyway.
- int cellWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width);
- int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
- r.getDimensionPixelSize(R.dimen.workspace_height_gap));
-
- return minGap * (numCells - 1) + cellWidth * numCells;
- }
-
- static int heightInLandscape(Resources r, int numCells) {
- // We use this method from Workspace to figure out how many rows/columns Launcher should
- // have. We ignore the left/right padding on CellLayout because it turns out in our design
- // the padding extends outside the visible screen size, but it looked fine anyway.
- int cellHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height);
- int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap),
- r.getDimensionPixelSize(R.dimen.workspace_height_gap));
-
- return minGap * (numCells - 1) + cellHeight * numCells;
- }
-
- public void enableHardwareLayers() {
- mShortcutsAndWidgets.setLayerType(LAYER_TYPE_HARDWARE, sPaint);
- }
-
- public void disableHardwareLayers() {
- mShortcutsAndWidgets.setLayerType(LAYER_TYPE_NONE, sPaint);
- }
-
- public void buildHardwareLayer() {
- mShortcutsAndWidgets.buildLayer();
- }
-
- public float getChildrenScale() {
- return mIsHotseat ? mHotseatScale : 1.0f;
- }
-
- public void setGridSize(int x, int y) {
- mCountX = x;
- mCountY = y;
- mOccupied = new boolean[mCountX][mCountY];
- mTmpOccupied = new boolean[mCountX][mCountY];
- mTempRectStack.clear();
- mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
- mCountX);
- requestLayout();
- }
-
- // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
- public void setInvertIfRtl(boolean invert) {
- mShortcutsAndWidgets.setInvertIfRtl(invert);
- }
-
- private void invalidateBubbleTextView(BubbleTextView icon) {
- final int padding = icon.getPressedOrFocusedBackgroundPadding();
- invalidate(icon.getLeft() + getPaddingLeft() - padding,
- icon.getTop() + getPaddingTop() - padding,
- icon.getRight() + getPaddingLeft() + padding,
- icon.getBottom() + getPaddingTop() + padding);
- }
-
- void setOverScrollAmount(float r, boolean left) {
- if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
- mOverScrollForegroundDrawable = mOverScrollLeft;
- } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) {
- mOverScrollForegroundDrawable = mOverScrollRight;
- }
-
- mForegroundAlpha = (int) Math.round((r * 255));
- mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha);
- invalidate();
- }
-
- void setPressedOrFocusedIcon(BubbleTextView icon) {
- // We draw the pressed or focused BubbleTextView's background in CellLayout because it
- // requires an expanded clip rect (due to the glow's blur radius)
- BubbleTextView oldIcon = mPressedOrFocusedIcon;
- mPressedOrFocusedIcon = icon;
- if (oldIcon != null) {
- invalidateBubbleTextView(oldIcon);
- }
- if (mPressedOrFocusedIcon != null) {
- invalidateBubbleTextView(mPressedOrFocusedIcon);
- }
- }
-
- void setIsDragOverlapping(boolean isDragOverlapping) {
- if (mIsDragOverlapping != isDragOverlapping) {
- mIsDragOverlapping = isDragOverlapping;
- invalidate();
- }
- }
-
- boolean getIsDragOverlapping() {
- return mIsDragOverlapping;
- }
-
- protected void setOverscrollTransformsDirty(boolean dirty) {
- mScrollingTransformsDirty = dirty;
- }
-
- protected void resetOverscrollTransforms() {
- if (mScrollingTransformsDirty) {
- setOverscrollTransformsDirty(false);
- setTranslationX(0);
- setRotationY(0);
- // It doesn't matter if we pass true or false here, the important thing is that we
- // pass 0, which results in the overscroll drawable not being drawn any more.
- setOverScrollAmount(0, false);
- setPivotX(getMeasuredWidth() / 2);
- setPivotY(getMeasuredHeight() / 2);
- }
- }
-
- public void scaleRect(Rect r, float scale) {
- if (scale != 1.0f) {
- r.left = (int) (r.left * scale + 0.5f);
- r.top = (int) (r.top * scale + 0.5f);
- r.right = (int) (r.right * scale + 0.5f);
- r.bottom = (int) (r.bottom * scale + 0.5f);
- }
- }
-
- Rect temp = new Rect();
- void scaleRectAboutCenter(Rect in, Rect out, float scale) {
- int cx = in.centerX();
- int cy = in.centerY();
- out.set(in);
- out.offset(-cx, -cy);
- scaleRect(out, scale);
- out.offset(cx, cy);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
- // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
- // When we're small, we are either drawn normally or in the "accepts drops" state (during
- // a drag). However, we also drag the mini hover background *over* one of those two
- // backgrounds
- if (mBackgroundAlpha > 0.0f) {
- Drawable bg;
-
- if (mIsDragOverlapping) {
- // In the mini case, we draw the active_glow bg *over* the active background
- bg = mActiveGlowBackground;
- } else {
- bg = mNormalBackground;
- }
-
- bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
- bg.setBounds(mBackgroundRect);
- bg.draw(canvas);
- }
-
- final Paint paint = mDragOutlinePaint;
- for (int i = 0; i < mDragOutlines.length; i++) {
- final float alpha = mDragOutlineAlphas[i];
- if (alpha > 0) {
- final Rect r = mDragOutlines[i];
- scaleRectAboutCenter(r, temp, getChildrenScale());
- final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
- paint.setAlpha((int)(alpha + .5f));
- canvas.drawBitmap(b, null, temp, paint);
- }
- }
-
- // We draw the pressed or focused BubbleTextView's background in CellLayout because it
- // requires an expanded clip rect (due to the glow's blur radius)
- if (mPressedOrFocusedIcon != null) {
- final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
- final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
- if (b != null) {
- canvas.drawBitmap(b,
- mPressedOrFocusedIcon.getLeft() + getPaddingLeft() - padding,
- mPressedOrFocusedIcon.getTop() + getPaddingTop() - padding,
- null);
- }
- }
-
- if (DEBUG_VISUALIZE_OCCUPIED) {
- int[] pt = new int[2];
- ColorDrawable cd = new ColorDrawable(Color.RED);
- cd.setBounds(0, 0, mCellWidth, mCellHeight);
- for (int i = 0; i < mCountX; i++) {
- for (int j = 0; j < mCountY; j++) {
- if (mOccupied[i][j]) {
- cellToPoint(i, j, pt);
- canvas.save();
- canvas.translate(pt[0], pt[1]);
- cd.draw(canvas);
- canvas.restore();
- }
- }
- }
- }
-
- int previewOffset = FolderRingAnimator.sPreviewSize;
-
- // The folder outer / inner ring image(s)
- for (int i = 0; i < mFolderOuterRings.size(); i++) {
- FolderRingAnimator fra = mFolderOuterRings.get(i);
-
- // Draw outer ring
- Drawable d = FolderRingAnimator.sSharedOuterRingDrawable;
- int width = (int) fra.getOuterRingSize();
- int height = width;
- cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
-
- int centerX = mTempLocation[0] + mCellWidth / 2;
- int centerY = mTempLocation[1] + previewOffset / 2;
-
- canvas.save();
- canvas.translate(centerX - width / 2, centerY - height / 2);
- d.setBounds(0, 0, width, height);
- d.draw(canvas);
- canvas.restore();
-
- // Draw inner ring
- d = FolderRingAnimator.sSharedInnerRingDrawable;
- width = (int) fra.getInnerRingSize();
- height = width;
- cellToPoint(fra.mCellX, fra.mCellY, mTempLocation);
-
- centerX = mTempLocation[0] + mCellWidth / 2;
- centerY = mTempLocation[1] + previewOffset / 2;
- canvas.save();
- canvas.translate(centerX - width / 2, centerY - width / 2);
- d.setBounds(0, 0, width, height);
- d.draw(canvas);
- canvas.restore();
- }
-
- if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) {
- Drawable d = FolderIcon.sSharedFolderLeaveBehind;
- int width = d.getIntrinsicWidth();
- int height = d.getIntrinsicHeight();
-
- cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation);
- int centerX = mTempLocation[0] + mCellWidth / 2;
- int centerY = mTempLocation[1] + previewOffset / 2;
-
- canvas.save();
- canvas.translate(centerX - width / 2, centerY - width / 2);
- d.setBounds(0, 0, width, height);
- d.draw(canvas);
- canvas.restore();
- }
- }
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
- if (mForegroundAlpha > 0) {
- mOverScrollForegroundDrawable.setBounds(mForegroundRect);
- Paint p = ((NinePatchDrawable) mOverScrollForegroundDrawable).getPaint();
- p.setXfermode(sAddBlendMode);
- mOverScrollForegroundDrawable.draw(canvas);
- p.setXfermode(null);
- }
- }
-
- public void showFolderAccept(FolderRingAnimator fra) {
- mFolderOuterRings.add(fra);
- }
-
- public void hideFolderAccept(FolderRingAnimator fra) {
- if (mFolderOuterRings.contains(fra)) {
- mFolderOuterRings.remove(fra);
- }
- invalidate();
- }
-
- public void setFolderLeaveBehindCell(int x, int y) {
- mFolderLeaveBehindCell[0] = x;
- mFolderLeaveBehindCell[1] = y;
- invalidate();
- }
-
- public void clearFolderLeaveBehind() {
- mFolderLeaveBehindCell[0] = -1;
- mFolderLeaveBehindCell[1] = -1;
- invalidate();
- }
-
- @Override
- public boolean shouldDelayChildPressedState() {
- return false;
- }
-
- public void restoreInstanceState(SparseArray<Parcelable> states) {
- dispatchRestoreInstanceState(states);
- }
-
- @Override
- public void cancelLongPress() {
- super.cancelLongPress();
-
- // Cancel long press for all children
- final int count = getChildCount();
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- child.cancelLongPress();
- }
- }
-
- public void setOnInterceptTouchListener(View.OnTouchListener listener) {
- mInterceptTouchListener = listener;
- }
-
- int getCountX() {
- return mCountX;
- }
-
- int getCountY() {
- return mCountY;
- }
-
- public void setIsHotseat(boolean isHotseat) {
- mIsHotseat = isHotseat;
- }
-
- public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
- boolean markCells) {
- final LayoutParams lp = params;
-
- // Hotseat icons - remove text
- if (child instanceof BubbleTextView) {
- BubbleTextView bubbleChild = (BubbleTextView) child;
-
- Resources res = getResources();
- if (mIsHotseat) {
- bubbleChild.setTextColor(res.getColor(android.R.color.transparent));
- } else {
- bubbleChild.setTextColor(res.getColor(R.color.workspace_icon_text_color));
- }
- }
-
- child.setScaleX(getChildrenScale());
- child.setScaleY(getChildrenScale());
-
- // Generate an id for each view, this assumes we have at most 256x256 cells
- // per workspace screen
- if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
- // If the horizontal or vertical span is set to -1, it is taken to
- // mean that it spans the extent of the CellLayout
- if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
- if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
-
- child.setId(childId);
-
- mShortcutsAndWidgets.addView(child, index, lp);
-
- if (markCells) markCellsAsOccupiedForView(child);
-
- return true;
- }
- return false;
- }
-
- @Override
- public void removeAllViews() {
- clearOccupiedCells();
- mShortcutsAndWidgets.removeAllViews();
- }
-
- @Override
- public void removeAllViewsInLayout() {
- if (mShortcutsAndWidgets.getChildCount() > 0) {
- clearOccupiedCells();
- mShortcutsAndWidgets.removeAllViewsInLayout();
- }
- }
-
- public void removeViewWithoutMarkingCells(View view) {
- mShortcutsAndWidgets.removeView(view);
- }
-
- @Override
- public void removeView(View view) {
- markCellsAsUnoccupiedForView(view);
- mShortcutsAndWidgets.removeView(view);
- }
-
- @Override
- public void removeViewAt(int index) {
- markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index));
- mShortcutsAndWidgets.removeViewAt(index);
- }
-
- @Override
- public void removeViewInLayout(View view) {
- markCellsAsUnoccupiedForView(view);
- mShortcutsAndWidgets.removeViewInLayout(view);
- }
-
- @Override
- public void removeViews(int start, int count) {
- for (int i = start; i < start + count; i++) {
- markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
- }
- mShortcutsAndWidgets.removeViews(start, count);
- }
-
- @Override
- public void removeViewsInLayout(int start, int count) {
- for (int i = start; i < start + count; i++) {
- markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i));
- }
- mShortcutsAndWidgets.removeViewsInLayout(start, count);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
- }
-
- public void setTagToCellInfoForPoint(int touchX, int touchY) {
- final CellInfo cellInfo = mCellInfo;
- Rect frame = mRect;
- final int x = touchX + getScrollX();
- final int y = touchY + getScrollY();
- final int count = mShortcutsAndWidgets.getChildCount();
-
- boolean found = false;
- for (int i = count - 1; i >= 0; i--) {
- final View child = mShortcutsAndWidgets.getChildAt(i);
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
- if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
- lp.isLockedToGrid) {
- child.getHitRect(frame);
-
- float scale = child.getScaleX();
- frame = new Rect(child.getLeft(), child.getTop(), child.getRight(),
- child.getBottom());
- // The child hit rect is relative to the CellLayoutChildren parent, so we need to
- // offset that by this CellLayout's padding to test an (x,y) point that is relative
- // to this view.
- frame.offset(getPaddingLeft(), getPaddingTop());
- frame.inset((int) (frame.width() * (1f - scale) / 2),
- (int) (frame.height() * (1f - scale) / 2));
-
- if (frame.contains(x, y)) {
- cellInfo.cell = child;
- cellInfo.cellX = lp.cellX;
- cellInfo.cellY = lp.cellY;
- cellInfo.spanX = lp.cellHSpan;
- cellInfo.spanY = lp.cellVSpan;
- found = true;
- break;
- }
- }
- }
-
- mLastDownOnOccupiedCell = found;
-
- if (!found) {
- final int cellXY[] = mTmpXY;
- pointToCellExact(x, y, cellXY);
-
- cellInfo.cell = null;
- cellInfo.cellX = cellXY[0];
- cellInfo.cellY = cellXY[1];
- cellInfo.spanX = 1;
- cellInfo.spanY = 1;
- }
- setTag(cellInfo);
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- // First we clear the tag to ensure that on every touch down we start with a fresh slate,
- // even in the case where we return early. Not clearing here was causing bugs whereby on
- // long-press we'd end up picking up an item from a previous drag operation.
- final int action = ev.getAction();
-
- if (action == MotionEvent.ACTION_DOWN) {
- clearTagCellInfo();
- }
-
- if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
- return true;
- }
-
- if (action == MotionEvent.ACTION_DOWN) {
- setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
- }
-
- return false;
- }
-
- private void clearTagCellInfo() {
- final CellInfo cellInfo = mCellInfo;
- cellInfo.cell = null;
- cellInfo.cellX = -1;
- cellInfo.cellY = -1;
- cellInfo.spanX = 0;
- cellInfo.spanY = 0;
- setTag(cellInfo);
- }
-
- public CellInfo getTag() {
- return (CellInfo) super.getTag();
- }
-
- /**
- * Given a point, return the cell that strictly encloses that point
- * @param x X coordinate of the point
- * @param y Y coordinate of the point
- * @param result Array of 2 ints to hold the x and y coordinate of the cell
- */
- void pointToCellExact(int x, int y, int[] result) {
- final int hStartPadding = getPaddingLeft();
- final int vStartPadding = getPaddingTop();
-
- result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
- result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
-
- final int xAxis = mCountX;
- final int yAxis = mCountY;
-
- if (result[0] < 0) result[0] = 0;
- if (result[0] >= xAxis) result[0] = xAxis - 1;
- if (result[1] < 0) result[1] = 0;
- if (result[1] >= yAxis) result[1] = yAxis - 1;
- }
-
- /**
- * Given a point, return the cell that most closely encloses that point
- * @param x X coordinate of the point
- * @param y Y coordinate of the point
- * @param result Array of 2 ints to hold the x and y coordinate of the cell
- */
- void pointToCellRounded(int x, int y, int[] result) {
- pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
- }
-
- /**
- * Given a cell coordinate, return the point that represents the upper left corner of that cell
- *
- * @param cellX X coordinate of the cell
- * @param cellY Y coordinate of the cell
- *
- * @param result Array of 2 ints to hold the x and y coordinate of the point
- */
- void cellToPoint(int cellX, int cellY, int[] result) {
- final int hStartPadding = getPaddingLeft();
- final int vStartPadding = getPaddingTop();
-
- result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
- result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
- }
-
- /**
- * Given a cell coordinate, return the point that represents the center of the cell
- *
- * @param cellX X coordinate of the cell
- * @param cellY Y coordinate of the cell
- *
- * @param result Array of 2 ints to hold the x and y coordinate of the point
- */
- void cellToCenterPoint(int cellX, int cellY, int[] result) {
- regionToCenterPoint(cellX, cellY, 1, 1, result);
- }
-
- /**
- * Given a cell coordinate and span return the point that represents the center of the regio
- *
- * @param cellX X coordinate of the cell
- * @param cellY Y coordinate of the cell
- *
- * @param result Array of 2 ints to hold the x and y coordinate of the point
- */
- void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
- final int hStartPadding = getPaddingLeft();
- final int vStartPadding = getPaddingTop();
- result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) +
- (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2;
- result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) +
- (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2;
- }
-
- /**
- * Given a cell coordinate and span fills out a corresponding pixel rect
- *
- * @param cellX X coordinate of the cell
- * @param cellY Y coordinate of the cell
- * @param result Rect in which to write the result
- */
- void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
- final int hStartPadding = getPaddingLeft();
- final int vStartPadding = getPaddingTop();
- final int left = hStartPadding + cellX * (mCellWidth + mWidthGap);
- final int top = vStartPadding + cellY * (mCellHeight + mHeightGap);
- result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap),
- top + (spanY * mCellHeight + (spanY - 1) * mHeightGap));
- }
-
- public float getDistanceFromCell(float x, float y, int[] cell) {
- cellToCenterPoint(cell[0], cell[1], mTmpPoint);
- float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) +
- Math.pow(y - mTmpPoint[1], 2));
- return distance;
- }
-
- int getCellWidth() {
- return mCellWidth;
- }
-
- int getCellHeight() {
- return mCellHeight;
- }
-
- int getWidthGap() {
- return mWidthGap;
- }
-
- int getHeightGap() {
- return mHeightGap;
- }
-
- Rect getContentRect(Rect r) {
- if (r == null) {
- r = new Rect();
- }
- int left = getPaddingLeft();
- int top = getPaddingTop();
- int right = left + getWidth() - getPaddingLeft() - getPaddingRight();
- int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom();
- r.set(left, top, right, bottom);
- return r;
- }
-
- static void getMetrics(Rect metrics, Resources res, int measureWidth, int measureHeight,
- int countX, int countY, int orientation) {
- int numWidthGaps = countX - 1;
- int numHeightGaps = countY - 1;
-
- int widthGap;
- int heightGap;
- int cellWidth;
- int cellHeight;
- int paddingLeft;
- int paddingRight;
- int paddingTop;
- int paddingBottom;
-
- int maxGap = res.getDimensionPixelSize(R.dimen.workspace_max_gap);
- if (orientation == LANDSCAPE) {
- cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_land);
- cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_land);
- widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_land);
- heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_land);
- paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_land);
- paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_land);
- paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_land);
- paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_land);
- } else {
- // PORTRAIT
- cellWidth = res.getDimensionPixelSize(R.dimen.workspace_cell_width_port);
- cellHeight = res.getDimensionPixelSize(R.dimen.workspace_cell_height_port);
- widthGap = res.getDimensionPixelSize(R.dimen.workspace_width_gap_port);
- heightGap = res.getDimensionPixelSize(R.dimen.workspace_height_gap_port);
- paddingLeft = res.getDimensionPixelSize(R.dimen.cell_layout_left_padding_port);
- paddingRight = res.getDimensionPixelSize(R.dimen.cell_layout_right_padding_port);
- paddingTop = res.getDimensionPixelSize(R.dimen.cell_layout_top_padding_port);
- paddingBottom = res.getDimensionPixelSize(R.dimen.cell_layout_bottom_padding_port);
- }
-
- if (widthGap < 0 || heightGap < 0) {
- int hSpace = measureWidth - paddingLeft - paddingRight;
- int vSpace = measureHeight - paddingTop - paddingBottom;
- int hFreeSpace = hSpace - (countX * cellWidth);
- int vFreeSpace = vSpace - (countY * cellHeight);
- widthGap = Math.min(maxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
- heightGap = Math.min(maxGap, numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
- }
- metrics.set(cellWidth, cellHeight, widthGap, heightGap);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
- int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
-
- int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
- int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
-
- if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
- throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
- }
-
- int numWidthGaps = mCountX - 1;
- int numHeightGaps = mCountY - 1;
-
- if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
- int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight();
- int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom();
- int hFreeSpace = hSpace - (mCountX * mCellWidth);
- int vFreeSpace = vSpace - (mCountY * mCellHeight);
- mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
- mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
- mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
- mCountX);
- } else {
- mWidthGap = mOriginalWidthGap;
- mHeightGap = mOriginalHeightGap;
- }
-
- // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
- int newWidth = widthSpecSize;
- int newHeight = heightSpecSize;
- if (widthSpecMode == MeasureSpec.AT_MOST) {
- newWidth = getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
- ((mCountX - 1) * mWidthGap);
- newHeight = getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
- ((mCountY - 1) * mHeightGap);
- setMeasuredDimension(newWidth, newHeight);
- }
-
- int count = getChildCount();
- for (int i = 0; i < count; i++) {
- View child = getChildAt(i);
- int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() -
- getPaddingRight(), MeasureSpec.EXACTLY);
- int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() -
- getPaddingBottom(), MeasureSpec.EXACTLY);
- child.measure(childWidthMeasureSpec, childheightMeasureSpec);
- }
- setMeasuredDimension(newWidth, newHeight);
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int count = getChildCount();
- for (int i = 0; i < count; i++) {
- View child = getChildAt(i);
- child.layout(getPaddingLeft(), getPaddingTop(),
- r - l - getPaddingRight(), b - t - getPaddingBottom());
- }
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- mBackgroundRect.set(0, 0, w, h);
- mForegroundRect.set(mForegroundPadding, mForegroundPadding,
- w - mForegroundPadding, h - mForegroundPadding);
- }
-
- @Override
- protected void setChildrenDrawingCacheEnabled(boolean enabled) {
- mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled);
- }
-
- @Override
- protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
- mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled);
- }
-
- public float getBackgroundAlpha() {
- return mBackgroundAlpha;
- }
-
- public void setBackgroundAlphaMultiplier(float multiplier) {
- if (mBackgroundAlphaMultiplier != multiplier) {
- mBackgroundAlphaMultiplier = multiplier;
- invalidate();
- }
- }
-
- public float getBackgroundAlphaMultiplier() {
- return mBackgroundAlphaMultiplier;
- }
-
- public void setBackgroundAlpha(float alpha) {
- if (mBackgroundAlpha != alpha) {
- mBackgroundAlpha = alpha;
- invalidate();
- }
- }
-
- public void setShortcutAndWidgetAlpha(float alpha) {
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- getChildAt(i).setAlpha(alpha);
- }
- }
-
- public ShortcutAndWidgetContainer getShortcutsAndWidgets() {
- if (getChildCount() > 0) {
- return (ShortcutAndWidgetContainer) getChildAt(0);
- }
- return null;
- }
-
- public View getChildAt(int x, int y) {
- return mShortcutsAndWidgets.getChildAt(x, y);
- }
-
- public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
- int delay, boolean permanent, boolean adjustOccupied) {
- ShortcutAndWidgetContainer clc = getShortcutsAndWidgets();
- boolean[][] occupied = mOccupied;
- if (!permanent) {
- occupied = mTmpOccupied;
- }
-
- if (clc.indexOfChild(child) != -1) {
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- final ItemInfo info = (ItemInfo) child.getTag();
-
- // We cancel any existing animations
- if (mReorderAnimators.containsKey(lp)) {
- mReorderAnimators.get(lp).cancel();
- mReorderAnimators.remove(lp);
- }
-
- final int oldX = lp.x;
- final int oldY = lp.y;
- if (adjustOccupied) {
- occupied[lp.cellX][lp.cellY] = false;
- occupied[cellX][cellY] = true;
- }
- lp.isLockedToGrid = true;
- if (permanent) {
- lp.cellX = info.cellX = cellX;
- lp.cellY = info.cellY = cellY;
- } else {
- lp.tmpCellX = cellX;
- lp.tmpCellY = cellY;
- }
- clc.setupLp(lp);
- lp.isLockedToGrid = false;
- final int newX = lp.x;
- final int newY = lp.y;
-
- lp.x = oldX;
- lp.y = oldY;
-
- // Exit early if we're not actually moving the view
- if (oldX == newX && oldY == newY) {
- lp.isLockedToGrid = true;
- return true;
- }
-
- ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
- va.setDuration(duration);
- mReorderAnimators.put(lp, va);
-
- va.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float r = ((Float) animation.getAnimatedValue()).floatValue();
- lp.x = (int) ((1 - r) * oldX + r * newX);
- lp.y = (int) ((1 - r) * oldY + r * newY);
- child.requestLayout();
- }
- });
- va.addListener(new AnimatorListenerAdapter() {
- boolean cancelled = false;
- public void onAnimationEnd(Animator animation) {
- // If the animation was cancelled, it means that another animation
- // has interrupted this one, and we don't want to lock the item into
- // place just yet.
- if (!cancelled) {
- lp.isLockedToGrid = true;
- child.requestLayout();
- }
- if (mReorderAnimators.containsKey(lp)) {
- mReorderAnimators.remove(lp);
- }
- }
- public void onAnimationCancel(Animator animation) {
- cancelled = true;
- }
- });
- va.setStartDelay(delay);
- va.start();
- return true;
- }
- return false;
- }
-
- /**
- * Estimate where the top left cell of the dragged item will land if it is dropped.
- *
- * @param originX The X value of the top left corner of the item
- * @param originY The Y value of the top left corner of the item
- * @param spanX The number of horizontal cells that the item spans
- * @param spanY The number of vertical cells that the item spans
- * @param result The estimated drop cell X and Y.
- */
- void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
- final int countX = mCountX;
- final int countY = mCountY;
-
- // pointToCellRounded takes the top left of a cell but will pad that with
- // cellWidth/2 and cellHeight/2 when finding the matching cell
- pointToCellRounded(originX, originY, result);
-
- // If the item isn't fully on this screen, snap to the edges
- int rightOverhang = result[0] + spanX - countX;
- if (rightOverhang > 0) {
- result[0] -= rightOverhang; // Snap to right
- }
- result[0] = Math.max(0, result[0]); // Snap to left
- int bottomOverhang = result[1] + spanY - countY;
- if (bottomOverhang > 0) {
- result[1] -= bottomOverhang; // Snap to bottom
- }
- result[1] = Math.max(0, result[1]); // Snap to top
- }
-
- void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX,
- int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) {
- final int oldDragCellX = mDragCell[0];
- final int oldDragCellY = mDragCell[1];
-
- if (v != null && dragOffset == null) {
- mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
- } else {
- mDragCenter.set(originX, originY);
- }
-
- if (dragOutline == null && v == null) {
- return;
- }
-
- if (cellX != oldDragCellX || cellY != oldDragCellY) {
- mDragCell[0] = cellX;
- mDragCell[1] = cellY;
- // Find the top left corner of the rect the object will occupy
- final int[] topLeft = mTmpPoint;
- cellToPoint(cellX, cellY, topLeft);
-
- int left = topLeft[0];
- int top = topLeft[1];
-
- if (v != null && dragOffset == null) {
- // When drawing the drag outline, it did not account for margin offsets
- // added by the view's parent.
- MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
- left += lp.leftMargin;
- top += lp.topMargin;
-
- // Offsets due to the size difference between the View and the dragOutline.
- // There is a size difference to account for the outer blur, which may lie
- // outside the bounds of the view.
- top += (v.getHeight() - dragOutline.getHeight()) / 2;
- // We center about the x axis
- left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
- - dragOutline.getWidth()) / 2;
- } else {
- if (dragOffset != null && dragRegion != null) {
- // Center the drag region *horizontally* in the cell and apply a drag
- // outline offset
- left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
- - dragRegion.width()) / 2;
- top += dragOffset.y;
- } else {
- // Center the drag outline in the cell
- left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap)
- - dragOutline.getWidth()) / 2;
- top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap)
- - dragOutline.getHeight()) / 2;
- }
- }
- final int oldIndex = mDragOutlineCurrent;
- mDragOutlineAnims[oldIndex].animateOut();
- mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
- Rect r = mDragOutlines[mDragOutlineCurrent];
- r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight());
- if (resize) {
- cellToRect(cellX, cellY, spanX, spanY, r);
- }
-
- mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
- mDragOutlineAnims[mDragOutlineCurrent].animateIn();
- }
- }
-
- public void clearDragOutlines() {
- final int oldIndex = mDragOutlineCurrent;
- mDragOutlineAnims[oldIndex].animateOut();
- mDragCell[0] = mDragCell[1] = -1;
- }
-
- /**
- * Find a vacant area that will fit the given bounds nearest the requested
- * cell location. Uses Euclidean distance to score multiple vacant areas.
- *
- * @param pixelX The X location at which you want to search for a vacant area.
- * @param pixelY The Y location at which you want to search for a vacant area.
- * @param spanX Horizontal span of the object.
- * @param spanY Vertical span of the object.
- * @param result Array in which to place the result, or null (in which case a new array will
- * be allocated)
- * @return The X, Y cell of a vacant area that can contain this object,
- * nearest the requested location.
- */
- int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
- int[] result) {
- return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result);
- }
-
- /**
- * Find a vacant area that will fit the given bounds nearest the requested
- * cell location. Uses Euclidean distance to score multiple vacant areas.
- *
- * @param pixelX The X location at which you want to search for a vacant area.
- * @param pixelY The Y location at which you want to search for a vacant area.
- * @param minSpanX The minimum horizontal span required
- * @param minSpanY The minimum vertical span required
- * @param spanX Horizontal span of the object.
- * @param spanY Vertical span of the object.
- * @param result Array in which to place the result, or null (in which case a new array will
- * be allocated)
- * @return The X, Y cell of a vacant area that can contain this object,
- * nearest the requested location.
- */
- int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
- int spanY, int[] result, int[] resultSpan) {
- return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null,
- result, resultSpan);
- }
-
- /**
- * Find a vacant area that will fit the given bounds nearest the requested
- * cell location. Uses Euclidean distance to score multiple vacant areas.
- *
- * @param pixelX The X location at which you want to search for a vacant area.
- * @param pixelY The Y location at which you want to search for a vacant area.
- * @param spanX Horizontal span of the object.
- * @param spanY Vertical span of the object.
- * @param ignoreOccupied If true, the result can be an occupied cell
- * @param result Array in which to place the result, or null (in which case a new array will
- * be allocated)
- * @return The X, Y cell of a vacant area that can contain this object,
- * nearest the requested location.
- */
- int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
- boolean ignoreOccupied, int[] result) {
- return findNearestArea(pixelX, pixelY, spanX, spanY,
- spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied);
- }
-
- private final Stack<Rect> mTempRectStack = new Stack<Rect>();
- private void lazyInitTempRectStack() {
- if (mTempRectStack.isEmpty()) {
- for (int i = 0; i < mCountX * mCountY; i++) {
- mTempRectStack.push(new Rect());
- }
- }
- }
-
- private void recycleTempRects(Stack<Rect> used) {
- while (!used.isEmpty()) {
- mTempRectStack.push(used.pop());
- }
- }
-
- /**
- * Find a vacant area that will fit the given bounds nearest the requested
- * cell location. Uses Euclidean distance to score multiple vacant areas.
- *
- * @param pixelX The X location at which you want to search for a vacant area.
- * @param pixelY The Y location at which you want to search for a vacant area.
- * @param minSpanX The minimum horizontal span required
- * @param minSpanY The minimum vertical span required
- * @param spanX Horizontal span of the object.
- * @param spanY Vertical span of the object.
- * @param ignoreOccupied If true, the result can be an occupied cell
- * @param result Array in which to place the result, or null (in which case a new array will
- * be allocated)
- * @return The X, Y cell of a vacant area that can contain this object,
- * nearest the requested location.
- */
- int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
- View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan,
- boolean[][] occupied) {
- lazyInitTempRectStack();
- // mark space take by ignoreView as available (method checks if ignoreView is null)
- markCellsAsUnoccupiedForView(ignoreView, occupied);
-
- // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds
- // to the center of the item, but we are searching based on the top-left cell, so
- // we translate the point over to correspond to the top-left.
- pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f;
- pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f;
-
- // Keep track of best-scoring drop area
- final int[] bestXY = result != null ? result : new int[2];
- double bestDistance = Double.MAX_VALUE;
- final Rect bestRect = new Rect(-1, -1, -1, -1);
- final Stack<Rect> validRegions = new Stack<Rect>();
-
- final int countX = mCountX;
- final int countY = mCountY;
-
- if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 ||
- spanX < minSpanX || spanY < minSpanY) {
- return bestXY;
- }
-
- for (int y = 0; y < countY - (minSpanY - 1); y++) {
- inner:
- for (int x = 0; x < countX - (minSpanX - 1); x++) {
- int ySize = -1;
- int xSize = -1;
- if (ignoreOccupied) {
- // First, let's see if this thing fits anywhere
- for (int i = 0; i < minSpanX; i++) {
- for (int j = 0; j < minSpanY; j++) {
- if (occupied[x + i][y + j]) {
- continue inner;
- }
- }
- }
- xSize = minSpanX;
- ySize = minSpanY;
-
- // We know that the item will fit at _some_ acceptable size, now let's see
- // how big we can make it. We'll alternate between incrementing x and y spans
- // until we hit a limit.
- boolean incX = true;
- boolean hitMaxX = xSize >= spanX;
- boolean hitMaxY = ySize >= spanY;
- while (!(hitMaxX && hitMaxY)) {
- if (incX && !hitMaxX) {
- for (int j = 0; j < ySize; j++) {
- if (x + xSize > countX -1 || occupied[x + xSize][y + j]) {
- // We can't move out horizontally
- hitMaxX = true;
- }
- }
- if (!hitMaxX) {
- xSize++;
- }
- } else if (!hitMaxY) {
- for (int i = 0; i < xSize; i++) {
- if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) {
- // We can't move out vertically
- hitMaxY = true;
- }
- }
- if (!hitMaxY) {
- ySize++;
- }
- }
- hitMaxX |= xSize >= spanX;
- hitMaxY |= ySize >= spanY;
- incX = !incX;
- }
- incX = true;
- hitMaxX = xSize >= spanX;
- hitMaxY = ySize >= spanY;
- }
- final int[] cellXY = mTmpXY;
- cellToCenterPoint(x, y, cellXY);
-
- // We verify that the current rect is not a sub-rect of any of our previous
- // candidates. In this case, the current rect is disqualified in favour of the
- // containing rect.
- Rect currentRect = mTempRectStack.pop();
- currentRect.set(x, y, x + xSize, y + ySize);
- boolean contained = false;
- for (Rect r : validRegions) {
- if (r.contains(currentRect)) {
- contained = true;
- break;
- }
- }
- validRegions.push(currentRect);
- double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
- + Math.pow(cellXY[1] - pixelY, 2));
-
- if ((distance <= bestDistance && !contained) ||
- currentRect.contains(bestRect)) {
- bestDistance = distance;
- bestXY[0] = x;
- bestXY[1] = y;
- if (resultSpan != null) {
- resultSpan[0] = xSize;
- resultSpan[1] = ySize;
- }
- bestRect.set(currentRect);
- }
- }
- }
- // re-mark space taken by ignoreView as occupied
- markCellsAsOccupiedForView(ignoreView, occupied);
-
- // Return -1, -1 if no suitable location found
- if (bestDistance == Double.MAX_VALUE) {
- bestXY[0] = -1;
- bestXY[1] = -1;
- }
- recycleTempRects(validRegions);
- return bestXY;
- }
-
- /**
- * Find a vacant area that will fit the given bounds nearest the requested
- * cell location, and will also weigh in a suggested direction vector of the
- * desired location. This method computers distance based on unit grid distances,
- * not pixel distances.
- *
- * @param cellX The X cell nearest to which you want to search for a vacant area.
- * @param cellY The Y cell nearest which you want to search for a vacant area.
- * @param spanX Horizontal span of the object.
- * @param spanY Vertical span of the object.
- * @param direction The favored direction in which the views should move from x, y
- * @param exactDirectionOnly If this parameter is true, then only solutions where the direction
- * matches exactly. Otherwise we find the best matching direction.
- * @param occoupied The array which represents which cells in the CellLayout are occupied
- * @param blockOccupied The array which represents which cells in the specified block (cellX,
- * cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
- * @param result Array in which to place the result, or null (in which case a new array will
- * be allocated)
- * @return The X, Y cell of a vacant area that can contain this object,
- * nearest the requested location.
- */
- private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
- boolean[][] occupied, boolean blockOccupied[][], int[] result) {
- // Keep track of best-scoring drop area
- final int[] bestXY = result != null ? result : new int[2];
- float bestDistance = Float.MAX_VALUE;
- int bestDirectionScore = Integer.MIN_VALUE;
-
- final int countX = mCountX;
- final int countY = mCountY;
-
- for (int y = 0; y < countY - (spanY - 1); y++) {
- inner:
- for (int x = 0; x < countX - (spanX - 1); x++) {
- // First, let's see if this thing fits anywhere
- for (int i = 0; i < spanX; i++) {
- for (int j = 0; j < spanY; j++) {
- if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
- continue inner;
- }
- }
- }
-
- float distance = (float)
- Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY));
- int[] curDirection = mTmpPoint;
- computeDirectionVector(x - cellX, y - cellY, curDirection);
- // The direction score is just the dot product of the two candidate direction
- // and that passed in.
- int curDirectionScore = direction[0] * curDirection[0] +
- direction[1] * curDirection[1];
- boolean exactDirectionOnly = false;
- boolean directionMatches = direction[0] == curDirection[0] &&
- direction[0] == curDirection[0];
- if ((directionMatches || !exactDirectionOnly) &&
- Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
- bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
- bestDistance = distance;
- bestDirectionScore = curDirectionScore;
- bestXY[0] = x;
- bestXY[1] = y;
- }
- }
- }
-
- // Return -1, -1 if no suitable location found
- if (bestDistance == Float.MAX_VALUE) {
- bestXY[0] = -1;
- bestXY[1] = -1;
- }
- return bestXY;
- }
-
- private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
- int[] direction, ItemConfiguration currentState) {
- CellAndSpan c = currentState.map.get(v);
- boolean success = false;
- markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
- markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
-
- findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation);
-
- if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
- c.x = mTempLocation[0];
- c.y = mTempLocation[1];
- success = true;
- }
- markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
- return success;
- }
-
- /**
- * This helper class defines a cluster of views. It helps with defining complex edges
- * of the cluster and determining how those edges interact with other views. The edges
- * essentially define a fine-grained boundary around the cluster of views -- like a more
- * precise version of a bounding box.
- */
- private class ViewCluster {
- final static int LEFT = 0;
- final static int TOP = 1;
- final static int RIGHT = 2;
- final static int BOTTOM = 3;
-
- ArrayList<View> views;
- ItemConfiguration config;
- Rect boundingRect = new Rect();
-
- int[] leftEdge = new int[mCountY];
- int[] rightEdge = new int[mCountY];
- int[] topEdge = new int[mCountX];
- int[] bottomEdge = new int[mCountX];
- boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty;
-
- @SuppressWarnings("unchecked")
- public ViewCluster(ArrayList<View> views, ItemConfiguration config) {
- this.views = (ArrayList<View>) views.clone();
- this.config = config;
- resetEdges();
- }
-
- void resetEdges() {
- for (int i = 0; i < mCountX; i++) {
- topEdge[i] = -1;
- bottomEdge[i] = -1;
- }
- for (int i = 0; i < mCountY; i++) {
- leftEdge[i] = -1;
- rightEdge[i] = -1;
- }
- leftEdgeDirty = true;
- rightEdgeDirty = true;
- bottomEdgeDirty = true;
- topEdgeDirty = true;
- boundingRectDirty = true;
- }
-
- void computeEdge(int which, int[] edge) {
- int count = views.size();
- for (int i = 0; i < count; i++) {
- CellAndSpan cs = config.map.get(views.get(i));
- switch (which) {
- case LEFT:
- int left = cs.x;
- for (int j = cs.y; j < cs.y + cs.spanY; j++) {
- if (left < edge[j] || edge[j] < 0) {
- edge[j] = left;
- }
- }
- break;
- case RIGHT:
- int right = cs.x + cs.spanX;
- for (int j = cs.y; j < cs.y + cs.spanY; j++) {
- if (right > edge[j]) {
- edge[j] = right;
- }
- }
- break;
- case TOP:
- int top = cs.y;
- for (int j = cs.x; j < cs.x + cs.spanX; j++) {
- if (top < edge[j] || edge[j] < 0) {
- edge[j] = top;
- }
- }
- break;
- case BOTTOM:
- int bottom = cs.y + cs.spanY;
- for (int j = cs.x; j < cs.x + cs.spanX; j++) {
- if (bottom > edge[j]) {
- edge[j] = bottom;
- }
- }
- break;
- }
- }
- }
-
- boolean isViewTouchingEdge(View v, int whichEdge) {
- CellAndSpan cs = config.map.get(v);
-
- int[] edge = getEdge(whichEdge);
-
- switch (whichEdge) {
- case LEFT:
- for (int i = cs.y; i < cs.y + cs.spanY; i++) {
- if (edge[i] == cs.x + cs.spanX) {
- return true;
- }
- }
- break;
- case RIGHT:
- for (int i = cs.y; i < cs.y + cs.spanY; i++) {
- if (edge[i] == cs.x) {
- return true;
- }
- }
- break;
- case TOP:
- for (int i = cs.x; i < cs.x + cs.spanX; i++) {
- if (edge[i] == cs.y + cs.spanY) {
- return true;
- }
- }
- break;
- case BOTTOM:
- for (int i = cs.x; i < cs.x + cs.spanX; i++) {
- if (edge[i] == cs.y) {
- return true;
- }
- }
- break;
- }
- return false;
- }
-
- void shift(int whichEdge, int delta) {
- for (View v: views) {
- CellAndSpan c = config.map.get(v);
- switch (whichEdge) {
- case LEFT:
- c.x -= delta;
- break;
- case RIGHT:
- c.x += delta;
- break;
- case TOP:
- c.y -= delta;
- break;
- case BOTTOM:
- default:
- c.y += delta;
- break;
- }
- }
- resetEdges();
- }
-
- public void addView(View v) {
- views.add(v);
- resetEdges();
- }
-
- public Rect getBoundingRect() {
- if (boundingRectDirty) {
- boolean first = true;
- for (View v: views) {
- CellAndSpan c = config.map.get(v);
- if (first) {
- boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
- first = false;
- } else {
- boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
- }
- }
- }
- return boundingRect;
- }
-
- public int[] getEdge(int which) {
- switch (which) {
- case LEFT:
- return getLeftEdge();
- case RIGHT:
- return getRightEdge();
- case TOP:
- return getTopEdge();
- case BOTTOM:
- default:
- return getBottomEdge();
- }
- }
-
- public int[] getLeftEdge() {
- if (leftEdgeDirty) {
- computeEdge(LEFT, leftEdge);
- }
- return leftEdge;
- }
-
- public int[] getRightEdge() {
- if (rightEdgeDirty) {
- computeEdge(RIGHT, rightEdge);
- }
- return rightEdge;
- }
-
- public int[] getTopEdge() {
- if (topEdgeDirty) {
- computeEdge(TOP, topEdge);
- }
- return topEdge;
- }
-
- public int[] getBottomEdge() {
- if (bottomEdgeDirty) {
- computeEdge(BOTTOM, bottomEdge);
- }
- return bottomEdge;
- }
-
- PositionComparator comparator = new PositionComparator();
- class PositionComparator implements Comparator<View> {
- int whichEdge = 0;
- public int compare(View left, View right) {
- CellAndSpan l = config.map.get(left);
- CellAndSpan r = config.map.get(right);
- switch (whichEdge) {
- case LEFT:
- return (r.x + r.spanX) - (l.x + l.spanX);
- case RIGHT:
- return l.x - r.x;
- case TOP:
- return (r.y + r.spanY) - (l.y + l.spanY);
- case BOTTOM:
- default:
- return l.y - r.y;
- }
- }
- }
-
- public void sortConfigurationForEdgePush(int edge) {
- comparator.whichEdge = edge;
- Collections.sort(config.sortedViews, comparator);
- }
- }
-
- private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
- int[] direction, View dragView, ItemConfiguration currentState) {
-
- ViewCluster cluster = new ViewCluster(views, currentState);
- Rect clusterRect = cluster.getBoundingRect();
- int whichEdge;
- int pushDistance;
- boolean fail = false;
-
- // Determine the edge of the cluster that will be leading the push and how far
- // the cluster must be shifted.
- if (direction[0] < 0) {
- whichEdge = ViewCluster.LEFT;
- pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left;
- } else if (direction[0] > 0) {
- whichEdge = ViewCluster.RIGHT;
- pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left;
- } else if (direction[1] < 0) {
- whichEdge = ViewCluster.TOP;
- pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top;
- } else {
- whichEdge = ViewCluster.BOTTOM;
- pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top;
- }
-
- // Break early for invalid push distance.
- if (pushDistance <= 0) {
- return false;
- }
-
- // Mark the occupied state as false for the group of views we want to move.
- for (View v: views) {
- CellAndSpan c = currentState.map.get(v);
- markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
- }
-
- // We save the current configuration -- if we fail to find a solution we will revert
- // to the initial state. The process of finding a solution modifies the configuration
- // in place, hence the need for revert in the failure case.
- currentState.save();
-
- // The pushing algorithm is simplified by considering the views in the order in which
- // they would be pushed by the cluster. For example, if the cluster is leading with its
- // left edge, we consider sort the views by their right edge, from right to left.
- cluster.sortConfigurationForEdgePush(whichEdge);
-
- while (pushDistance > 0 && !fail) {
- for (View v: currentState.sortedViews) {
- // For each view that isn't in the cluster, we see if the leading edge of the
- // cluster is contacting the edge of that view. If so, we add that view to the
- // cluster.
- if (!cluster.views.contains(v) && v != dragView) {
- if (cluster.isViewTouchingEdge(v, whichEdge)) {
- LayoutParams lp = (LayoutParams) v.getLayoutParams();
- if (!lp.canReorder) {
- // The push solution includes the all apps button, this is not viable.
- fail = true;
- break;
- }
- cluster.addView(v);
- CellAndSpan c = currentState.map.get(v);
-
- // Adding view to cluster, mark it as not occupied.
- markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
- }
- }
- }
- pushDistance--;
-
- // The cluster has been completed, now we move the whole thing over in the appropriate
- // direction.
- cluster.shift(whichEdge, 1);
- }
-
- boolean foundSolution = false;
- clusterRect = cluster.getBoundingRect();
-
- // Due to the nature of the algorithm, the only check required to verify a valid solution
- // is to ensure that completed shifted cluster lies completely within the cell layout.
- if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 &&
- clusterRect.bottom <= mCountY) {
- foundSolution = true;
- } else {
- currentState.restore();
- }
-
- // In either case, we set the occupied array as marked for the location of the views
- for (View v: cluster.views) {
- CellAndSpan c = currentState.map.get(v);
- markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
- }
-
- return foundSolution;
- }
-
- private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
- int[] direction, View dragView, ItemConfiguration currentState) {
- if (views.size() == 0) return true;
-
- boolean success = false;
- Rect boundingRect = null;
- // We construct a rect which represents the entire group of views passed in
- for (View v: views) {
- CellAndSpan c = currentState.map.get(v);
- if (boundingRect == null) {
- boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
- } else {
- boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
- }
- }
-
- // Mark the occupied state as false for the group of views we want to move.
- for (View v: views) {
- CellAndSpan c = currentState.map.get(v);
- markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false);
- }
-
- boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()];
- int top = boundingRect.top;
- int left = boundingRect.left;
- // We mark more precisely which parts of the bounding rect are truly occupied, allowing
- // for interlocking.
- for (View v: views) {
- CellAndSpan c = currentState.map.get(v);
- markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true);
- }
-
- markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true);
-
- findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(),
- boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation);
-
- // If we successfuly found a location by pushing the block of views, we commit it
- if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) {
- int deltaX = mTempLocation[0] - boundingRect.left;
- int deltaY = mTempLocation[1] - boundingRect.top;
- for (View v: views) {
- CellAndSpan c = currentState.map.get(v);
- c.x += deltaX;
- c.y += deltaY;
- }
- success = true;
- }
-
- // In either case, we set the occupied array as marked for the location of the views
- for (View v: views) {
- CellAndSpan c = currentState.map.get(v);
- markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
- }
- return success;
- }
-
- private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) {
- markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value);
- }
-
- // This method tries to find a reordering solution which satisfies the push mechanic by trying
- // to push items in each of the cardinal directions, in an order based on the direction vector
- // passed.
- private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied,
- int[] direction, View ignoreView, ItemConfiguration solution) {
- if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) {
- // If the direction vector has two non-zero components, we try pushing
- // separately in each of the components.
- int temp = direction[1];
- direction[1] = 0;
-
- if (pushViewsToTempLocation(intersectingViews, occupied, direction,
- ignoreView, solution)) {
- return true;
- }
- direction[1] = temp;
- temp = direction[0];
- direction[0] = 0;
-
- if (pushViewsToTempLocation(intersectingViews, occupied, direction,
- ignoreView, solution)) {
- return true;
- }
- // Revert the direction
- direction[0] = temp;
-
- // Now we try pushing in each component of the opposite direction
- direction[0] *= -1;
- direction[1] *= -1;
- temp = direction[1];
- direction[1] = 0;
- if (pushViewsToTempLocation(intersectingViews, occupied, direction,
- ignoreView, solution)) {
- return true;
- }
-
- direction[1] = temp;
- temp = direction[0];
- direction[0] = 0;
- if (pushViewsToTempLocation(intersectingViews, occupied, direction,
- ignoreView, solution)) {
- return true;
- }
- // revert the direction
- direction[0] = temp;
- direction[0] *= -1;
- direction[1] *= -1;
-
- } else {
- // If the direction vector has a single non-zero component, we push first in the
- // direction of the vector
- if (pushViewsToTempLocation(intersectingViews, occupied, direction,
- ignoreView, solution)) {
- return true;
- }
- // Then we try the opposite direction
- direction[0] *= -1;
- direction[1] *= -1;
- if (pushViewsToTempLocation(intersectingViews, occupied, direction,
- ignoreView, solution)) {
- return true;
- }
- // Switch the direction back
- direction[0] *= -1;
- direction[1] *= -1;
-
- // If we have failed to find a push solution with the above, then we try
- // to find a solution by pushing along the perpendicular axis.
-
- // Swap the components
- int temp = direction[1];
- direction[1] = direction[0];
- direction[0] = temp;
- if (pushViewsToTempLocation(intersectingViews, occupied, direction,
- ignoreView, solution)) {
- return true;
- }
-
- // Then we try the opposite direction
- direction[0] *= -1;
- direction[1] *= -1;
- if (pushViewsToTempLocation(intersectingViews, occupied, direction,
- ignoreView, solution)) {
- return true;
- }
- // Switch the direction back
- direction[0] *= -1;
- direction[1] *= -1;
-
- // Swap the components back
- temp = direction[1];
- direction[1] = direction[0];
- direction[0] = temp;
- }
- return false;
- }
-
- private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
- View ignoreView, ItemConfiguration solution) {
- // Return early if get invalid cell positions
- if (cellX < 0 || cellY < 0) return false;
-
- mIntersectingViews.clear();
- mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
-
- // Mark the desired location of the view currently being dragged.
- if (ignoreView != null) {
- CellAndSpan c = solution.map.get(ignoreView);
- if (c != null) {
- c.x = cellX;
- c.y = cellY;
- }
- }
- Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
- Rect r1 = new Rect();
- for (View child: solution.map.keySet()) {
- if (child == ignoreView) continue;
- CellAndSpan c = solution.map.get(child);
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
- r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY);
- if (Rect.intersects(r0, r1)) {
- if (!lp.canReorder) {
- return false;
- }
- mIntersectingViews.add(child);
- }
- }
-
- // First we try to find a solution which respects the push mechanic. That is,
- // we try to find a solution such that no displaced item travels through another item
- // without also displacing that item.
- if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView,
- solution)) {
- return true;
- }
-
- // Next we try moving the views as a block, but without requiring the push mechanic.
- if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView,
- solution)) {
- return true;
- }
-
- // Ok, they couldn't move as a block, let's move them individually
- for (View v : mIntersectingViews) {
- if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) {
- return false;
- }
- }
- return true;
- }
-
- /*
- * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
- * the provided point and the provided cell
- */
- private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
- double angle = Math.atan(((float) deltaY) / deltaX);
-
- result[0] = 0;
- result[1] = 0;
- if (Math.abs(Math.cos(angle)) > 0.5f) {
- result[0] = (int) Math.signum(deltaX);
- }
- if (Math.abs(Math.sin(angle)) > 0.5f) {
- result[1] = (int) Math.signum(deltaY);
- }
- }
-
- private void copyOccupiedArray(boolean[][] occupied) {
- for (int i = 0; i < mCountX; i++) {
- for (int j = 0; j < mCountY; j++) {
- occupied[i][j] = mOccupied[i][j];
- }
- }
- }
-
- ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
- int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) {
- // Copy the current state into the solution. This solution will be manipulated as necessary.
- copyCurrentStateToSolution(solution, false);
- // Copy the current occupied array into the temporary occupied array. This array will be
- // manipulated as necessary to find a solution.
- copyOccupiedArray(mTmpOccupied);
-
- // We find the nearest cell into which we would place the dragged item, assuming there's
- // nothing in its way.
- int result[] = new int[2];
- result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
-
- boolean success = false;
- // First we try the exact nearest position of the item being dragged,
- // we will then want to try to move this around to other neighbouring positions
- success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
- solution);
-
- if (!success) {
- // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
- // x, then 1 in y etc.
- if (spanX > minSpanX && (minSpanY == spanY || decX)) {
- return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction,
- dragView, false, solution);
- } else if (spanY > minSpanY) {
- return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction,
- dragView, true, solution);
- }
- solution.isSolution = false;
- } else {
- solution.isSolution = true;
- solution.dragViewX = result[0];
- solution.dragViewY = result[1];
- solution.dragViewSpanX = spanX;
- solution.dragViewSpanY = spanY;
- }
- return solution;
- }
-
- private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
- int childCount = mShortcutsAndWidgets.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = mShortcutsAndWidgets.getChildAt(i);
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
- CellAndSpan c;
- if (temp) {
- c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
- } else {
- c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
- }
- solution.add(child, c);
- }
- }
-
- private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
- for (int i = 0; i < mCountX; i++) {
- for (int j = 0; j < mCountY; j++) {
- mTmpOccupied[i][j] = false;
- }
- }
-
- int childCount = mShortcutsAndWidgets.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = mShortcutsAndWidgets.getChildAt(i);
- if (child == dragView) continue;
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
- CellAndSpan c = solution.map.get(child);
- if (c != null) {
- lp.tmpCellX = c.x;
- lp.tmpCellY = c.y;
- lp.cellHSpan = c.spanX;
- lp.cellVSpan = c.spanY;
- markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true);
- }
- }
- markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
- solution.dragViewSpanY, mTmpOccupied, true);
- }
-
- private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean
- commitDragView) {
-
- boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied;
- for (int i = 0; i < mCountX; i++) {
- for (int j = 0; j < mCountY; j++) {
- occupied[i][j] = false;
- }
- }
-
- int childCount = mShortcutsAndWidgets.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = mShortcutsAndWidgets.getChildAt(i);
- if (child == dragView) continue;
- CellAndSpan c = solution.map.get(child);
- if (c != null) {
- animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0,
- DESTRUCTIVE_REORDER, false);
- markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true);
- }
- }
- if (commitDragView) {
- markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX,
- solution.dragViewSpanY, occupied, true);
- }
- }
-
- // This method starts or changes the reorder hint animations
- private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) {
- int childCount = mShortcutsAndWidgets.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = mShortcutsAndWidgets.getChildAt(i);
- if (child == dragView) continue;
- CellAndSpan c = solution.map.get(child);
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (c != null) {
- ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY,
- c.x, c.y, c.spanX, c.spanY);
- rha.animate();
- }
- }
- }
-
- // Class which represents the reorder hint animations. These animations show that an item is
- // in a temporary state, and hint at where the item will return to.
- class ReorderHintAnimation {
- View child;
- float finalDeltaX;
- float finalDeltaY;
- float initDeltaX;
- float initDeltaY;
- float finalScale;
- float initScale;
- private static final int DURATION = 300;
- Animator a;
-
- public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1,
- int spanX, int spanY) {
- regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
- final int x0 = mTmpPoint[0];
- final int y0 = mTmpPoint[1];
- regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint);
- final int x1 = mTmpPoint[0];
- final int y1 = mTmpPoint[1];
- final int dX = x1 - x0;
- final int dY = y1 - y0;
- finalDeltaX = 0;
- finalDeltaY = 0;
- if (dX == dY && dX == 0) {
- } else {
- if (dY == 0) {
- finalDeltaX = - Math.signum(dX) * mReorderHintAnimationMagnitude;
- } else if (dX == 0) {
- finalDeltaY = - Math.signum(dY) * mReorderHintAnimationMagnitude;
- } else {
- double angle = Math.atan( (float) (dY) / dX);
- finalDeltaX = (int) (- Math.signum(dX) *
- Math.abs(Math.cos(angle) * mReorderHintAnimationMagnitude));
- finalDeltaY = (int) (- Math.signum(dY) *
- Math.abs(Math.sin(angle) * mReorderHintAnimationMagnitude));
- }
- }
- initDeltaX = child.getTranslationX();
- initDeltaY = child.getTranslationY();
- finalScale = getChildrenScale() - 4.0f / child.getWidth();
- initScale = child.getScaleX();
- this.child = child;
- }
-
- void animate() {
- if (mShakeAnimators.containsKey(child)) {
- ReorderHintAnimation oldAnimation = mShakeAnimators.get(child);
- oldAnimation.cancel();
- mShakeAnimators.remove(child);
- if (finalDeltaX == 0 && finalDeltaY == 0) {
- completeAnimationImmediately();
- return;
- }
- }
- if (finalDeltaX == 0 && finalDeltaY == 0) {
- return;
- }
- ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f);
- a = va;
- va.setRepeatMode(ValueAnimator.REVERSE);
- va.setRepeatCount(ValueAnimator.INFINITE);
- va.setDuration(DURATION);
- va.setStartDelay((int) (Math.random() * 60));
- va.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float r = ((Float) animation.getAnimatedValue()).floatValue();
- float x = r * finalDeltaX + (1 - r) * initDeltaX;
- float y = r * finalDeltaY + (1 - r) * initDeltaY;
- child.setTranslationX(x);
- child.setTranslationY(y);
- float s = r * finalScale + (1 - r) * initScale;
- child.setScaleX(s);
- child.setScaleY(s);
- }
- });
- va.addListener(new AnimatorListenerAdapter() {
- public void onAnimationRepeat(Animator animation) {
- // We make sure to end only after a full period
- initDeltaX = 0;
- initDeltaY = 0;
- initScale = getChildrenScale();
- }
- });
- mShakeAnimators.put(child, this);
- va.start();
- }
-
- private void cancel() {
- if (a != null) {
- a.cancel();
- }
- }
-
- private void completeAnimationImmediately() {
- if (a != null) {
- a.cancel();
- }
-
- AnimatorSet s = LauncherAnimUtils.createAnimatorSet();
- a = s;
- s.playTogether(
- LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()),
- LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()),
- LauncherAnimUtils.ofFloat(child, "translationX", 0f),
- LauncherAnimUtils.ofFloat(child, "translationY", 0f)
- );
- s.setDuration(REORDER_ANIMATION_DURATION);
- s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f));
- s.start();
- }
- }
-
- private void completeAndClearReorderHintAnimations() {
- for (ReorderHintAnimation a: mShakeAnimators.values()) {
- a.completeAnimationImmediately();
- }
- mShakeAnimators.clear();
- }
-
- private void commitTempPlacement() {
- for (int i = 0; i < mCountX; i++) {
- for (int j = 0; j < mCountY; j++) {
- mOccupied[i][j] = mTmpOccupied[i][j];
- }
- }
- int childCount = mShortcutsAndWidgets.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = mShortcutsAndWidgets.getChildAt(i);
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
- ItemInfo info = (ItemInfo) child.getTag();
- // We do a null check here because the item info can be null in the case of the
- // AllApps button in the hotseat.
- if (info != null) {
- if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY ||
- info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) {
- info.requiresDbUpdate = true;
- }
- info.cellX = lp.cellX = lp.tmpCellX;
- info.cellY = lp.cellY = lp.tmpCellY;
- info.spanX = lp.cellHSpan;
- info.spanY = lp.cellVSpan;
- }
- }
- mLauncher.getWorkspace().updateItemLocationsInDatabase(this);
- }
-
- public void setUseTempCoords(boolean useTempCoords) {
- int childCount = mShortcutsAndWidgets.getChildCount();
- for (int i = 0; i < childCount; i++) {
- LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams();
- lp.useTmpCoords = useTempCoords;
- }
- }
-
- ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY,
- int spanX, int spanY, View dragView, ItemConfiguration solution) {
- int[] result = new int[2];
- int[] resultSpan = new int[2];
- findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result,
- resultSpan);
- if (result[0] >= 0 && result[1] >= 0) {
- copyCurrentStateToSolution(solution, false);
- solution.dragViewX = result[0];
- solution.dragViewY = result[1];
- solution.dragViewSpanX = resultSpan[0];
- solution.dragViewSpanY = resultSpan[1];
- solution.isSolution = true;
- } else {
- solution.isSolution = false;
- }
- return solution;
- }
-
- public void prepareChildForDrag(View child) {
- markCellsAsUnoccupiedForView(child);
- }
-
- /* This seems like it should be obvious and straight-forward, but when the direction vector
- needs to match with the notion of the dragView pushing other views, we have to employ
- a slightly more subtle notion of the direction vector. The question is what two points is
- the vector between? The center of the dragView and its desired destination? Not quite, as
- this doesn't necessarily coincide with the interaction of the dragView and items occupying
- those cells. Instead we use some heuristics to often lock the vector to up, down, left
- or right, which helps make pushing feel right.
- */
- private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
- int spanY, View dragView, int[] resultDirection) {
- int[] targetDestination = new int[2];
-
- findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
- Rect dragRect = new Rect();
- regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
- dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
-
- Rect dropRegionRect = new Rect();
- getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY,
- dragView, dropRegionRect, mIntersectingViews);
-
- int dropRegionSpanX = dropRegionRect.width();
- int dropRegionSpanY = dropRegionRect.height();
-
- regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
- dropRegionRect.height(), dropRegionRect);
-
- int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
- int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
-
- if (dropRegionSpanX == mCountX || spanX == mCountX) {
- deltaX = 0;
- }
- if (dropRegionSpanY == mCountY || spanY == mCountY) {
- deltaY = 0;
- }
-
- if (deltaX == 0 && deltaY == 0) {
- // No idea what to do, give a random direction.
- resultDirection[0] = 1;
- resultDirection[1] = 0;
- } else {
- computeDirectionVector(deltaX, deltaY, resultDirection);
- }
- }
-
- // For a given cell and span, fetch the set of views intersecting the region.
- private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
- View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
- if (boundingRect != null) {
- boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
- }
- intersectingViews.clear();
- Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY);
- Rect r1 = new Rect();
- final int count = mShortcutsAndWidgets.getChildCount();
- for (int i = 0; i < count; i++) {
- View child = mShortcutsAndWidgets.getChildAt(i);
- if (child == dragView) continue;
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
- r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
- if (Rect.intersects(r0, r1)) {
- mIntersectingViews.add(child);
- if (boundingRect != null) {
- boundingRect.union(r1);
- }
- }
- }
- }
-
- boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
- View dragView, int[] result) {
- result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
- getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
- mIntersectingViews);
- return !mIntersectingViews.isEmpty();
- }
-
- void revertTempState() {
- if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return;
- final int count = mShortcutsAndWidgets.getChildCount();
- for (int i = 0; i < count; i++) {
- View child = mShortcutsAndWidgets.getChildAt(i);
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
- lp.tmpCellX = lp.cellX;
- lp.tmpCellY = lp.cellY;
- animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
- 0, false, false);
- }
- }
- completeAndClearReorderHintAnimations();
- setItemPlacementDirty(false);
- }
-
- boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY,
- View dragView, int[] direction, boolean commit) {
- int[] pixelXY = new int[2];
- regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY);
-
- // First we determine if things have moved enough to cause a different layout
- ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY,
- spanX, spanY, direction, dragView, true, new ItemConfiguration());
-
- setUseTempCoords(true);
- if (swapSolution != null && swapSolution.isSolution) {
- // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
- // committing anything or animating anything as we just want to determine if a solution
- // exists
- copySolutionToTempState(swapSolution, dragView);
- setItemPlacementDirty(true);
- animateItemsToSolution(swapSolution, dragView, commit);
-
- if (commit) {
- commitTempPlacement();
- completeAndClearReorderHintAnimations();
- setItemPlacementDirty(false);
- } else {
- beginOrAdjustHintAnimations(swapSolution, dragView,
- REORDER_ANIMATION_DURATION);
- }
- mShortcutsAndWidgets.requestLayout();
- }
- return swapSolution.isSolution;
- }
-
- int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
- View dragView, int[] result, int resultSpan[], int mode) {
- // First we determine if things have moved enough to cause a different layout
- result = findNearestArea(pixelX, pixelY, spanX, spanY, result);
-
- if (resultSpan == null) {
- resultSpan = new int[2];
- }
-
- // When we are checking drop validity or actually dropping, we don't recompute the
- // direction vector, since we want the solution to match the preview, and it's possible
- // that the exact position of the item has changed to result in a new reordering outcome.
- if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP)
- && mPreviousReorderDirection[0] != INVALID_DIRECTION) {
- mDirectionVector[0] = mPreviousReorderDirection[0];
- mDirectionVector[1] = mPreviousReorderDirection[1];
- // We reset this vector after drop
- if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
- mPreviousReorderDirection[0] = INVALID_DIRECTION;
- mPreviousReorderDirection[1] = INVALID_DIRECTION;
- }
- } else {
- getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
- mPreviousReorderDirection[0] = mDirectionVector[0];
- mPreviousReorderDirection[1] = mDirectionVector[1];
- }
-
- ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY,
- spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
-
- // We attempt the approach which doesn't shuffle views at all
- ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX,
- minSpanY, spanX, spanY, dragView, new ItemConfiguration());
-
- ItemConfiguration finalSolution = null;
- if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) {
- finalSolution = swapSolution;
- } else if (noShuffleSolution.isSolution) {
- finalSolution = noShuffleSolution;
- }
-
- boolean foundSolution = true;
- if (!DESTRUCTIVE_REORDER) {
- setUseTempCoords(true);
- }
-
- if (finalSolution != null) {
- result[0] = finalSolution.dragViewX;
- result[1] = finalSolution.dragViewY;
- resultSpan[0] = finalSolution.dragViewSpanX;
- resultSpan[1] = finalSolution.dragViewSpanY;
-
- // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother
- // committing anything or animating anything as we just want to determine if a solution
- // exists
- if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) {
- if (!DESTRUCTIVE_REORDER) {
- copySolutionToTempState(finalSolution, dragView);
- }
- setItemPlacementDirty(true);
- animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP);
-
- if (!DESTRUCTIVE_REORDER &&
- (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
- commitTempPlacement();
- completeAndClearReorderHintAnimations();
- setItemPlacementDirty(false);
- } else {
- beginOrAdjustHintAnimations(finalSolution, dragView,
- REORDER_ANIMATION_DURATION);
- }
- }
- } else {
- foundSolution = false;
- result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1;
- }
-
- if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) {
- setUseTempCoords(false);
- }
-
- mShortcutsAndWidgets.requestLayout();
- return result;
- }
-
- void setItemPlacementDirty(boolean dirty) {
- mItemPlacementDirty = dirty;
- }
- boolean isItemPlacementDirty() {
- return mItemPlacementDirty;
- }
-
- private class ItemConfiguration {
- HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>();
- private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>();
- ArrayList<View> sortedViews = new ArrayList<View>();
- boolean isSolution = false;
- int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY;
-
- void save() {
- // Copy current state into savedMap
- for (View v: map.keySet()) {
- map.get(v).copy(savedMap.get(v));
- }
- }
-
- void restore() {
- // Restore current state from savedMap
- for (View v: savedMap.keySet()) {
- savedMap.get(v).copy(map.get(v));
- }
- }
-
- void add(View v, CellAndSpan cs) {
- map.put(v, cs);
- savedMap.put(v, new CellAndSpan());
- sortedViews.add(v);
- }
-
- int area() {
- return dragViewSpanX * dragViewSpanY;
- }
- }
-
- private class CellAndSpan {
- int x, y;
- int spanX, spanY;
-
- public CellAndSpan() {
- }
-
- public void copy(CellAndSpan copy) {
- copy.x = x;
- copy.y = y;
- copy.spanX = spanX;
- copy.spanY = spanY;
- }
-
- public CellAndSpan(int x, int y, int spanX, int spanY) {
- this.x = x;
- this.y = y;
- this.spanX = spanX;
- this.spanY = spanY;
- }
-
- public String toString() {
- return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")";
- }
-
- }
-
- /**
- * Find a vacant area that will fit the given bounds nearest the requested
- * cell location. Uses Euclidean distance to score multiple vacant areas.
- *
- * @param pixelX The X location at which you want to search for a vacant area.
- * @param pixelY The Y location at which you want to search for a vacant area.
- * @param spanX Horizontal span of the object.
- * @param spanY Vertical span of the object.
- * @param ignoreView Considers space occupied by this view as unoccupied
- * @param result Previously returned value to possibly recycle.
- * @return The X, Y cell of a vacant area that can contain this object,
- * nearest the requested location.
- */
- int[] findNearestVacantArea(
- int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
- return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
- }
-
- /**
- * Find a vacant area that will fit the given bounds nearest the requested
- * cell location. Uses Euclidean distance to score multiple vacant areas.
- *
- * @param pixelX The X location at which you want to search for a vacant area.
- * @param pixelY The Y location at which you want to search for a vacant area.
- * @param minSpanX The minimum horizontal span required
- * @param minSpanY The minimum vertical span required
- * @param spanX Horizontal span of the object.
- * @param spanY Vertical span of the object.
- * @param ignoreView Considers space occupied by this view as unoccupied
- * @param result Previously returned value to possibly recycle.
- * @return The X, Y cell of a vacant area that can contain this object,
- * nearest the requested location.
- */
- int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
- int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) {
- return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true,
- result, resultSpan, mOccupied);
- }
-
- /**
- * Find a starting cell position that will fit the given bounds nearest the requested
- * cell location. Uses Euclidean distance to score multiple vacant areas.
- *
- * @param pixelX The X location at which you want to search for a vacant area.
- * @param pixelY The Y location at which you want to search for a vacant area.
- * @param spanX Horizontal span of the object.
- * @param spanY Vertical span of the object.
- * @param ignoreView Considers space occupied by this view as unoccupied
- * @param result Previously returned value to possibly recycle.
- * @return The X, Y cell of a vacant area that can contain this object,
- * nearest the requested location.
- */
- int[] findNearestArea(
- int pixelX, int pixelY, int spanX, int spanY, int[] result) {
- return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
- }
-
- boolean existsEmptyCell() {
- return findCellForSpan(null, 1, 1);
- }
-
- /**
- * Finds the upper-left coordinate of the first rectangle in the grid that can
- * hold a cell of the specified dimensions. If intersectX and intersectY are not -1,
- * then this method will only return coordinates for rectangles that contain the cell
- * (intersectX, intersectY)
- *
- * @param cellXY The array that will contain the position of a vacant cell if such a cell
- * can be found.
- * @param spanX The horizontal span of the cell we want to find.
- * @param spanY The vertical span of the cell we want to find.
- *
- * @return True if a vacant cell of the specified dimension was found, false otherwise.
- */
- boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
- return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied);
- }
-
- /**
- * Like above, but ignores any cells occupied by the item "ignoreView"
- *
- * @param cellXY The array that will contain the position of a vacant cell if such a cell
- * can be found.
- * @param spanX The horizontal span of the cell we want to find.
- * @param spanY The vertical span of the cell we want to find.
- * @param ignoreView The home screen item we should treat as not occupying any space
- * @return
- */
- boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) {
- return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1,
- ignoreView, mOccupied);
- }
-
- /**
- * Like above, but if intersectX and intersectY are not -1, then this method will try to
- * return coordinates for rectangles that contain the cell [intersectX, intersectY]
- *
- * @param spanX The horizontal span of the cell we want to find.
- * @param spanY The vertical span of the cell we want to find.
- * @param ignoreView The home screen item we should treat as not occupying any space
- * @param intersectX The X coordinate of the cell that we should try to overlap
- * @param intersectX The Y coordinate of the cell that we should try to overlap
- *
- * @return True if a vacant cell of the specified dimension was found, false otherwise.
- */
- boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY,
- int intersectX, int intersectY) {
- return findCellForSpanThatIntersectsIgnoring(
- cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied);
- }
-
- /**
- * The superset of the above two methods
- */
- boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY,
- int intersectX, int intersectY, View ignoreView, boolean occupied[][]) {
- // mark space take by ignoreView as available (method checks if ignoreView is null)
- markCellsAsUnoccupiedForView(ignoreView, occupied);
-
- boolean foundCell = false;
- while (true) {
- int startX = 0;
- if (intersectX >= 0) {
- startX = Math.max(startX, intersectX - (spanX - 1));
- }
- int endX = mCountX - (spanX - 1);
- if (intersectX >= 0) {
- endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0));
- }
- int startY = 0;
- if (intersectY >= 0) {
- startY = Math.max(startY, intersectY - (spanY - 1));
- }
- int endY = mCountY - (spanY - 1);
- if (intersectY >= 0) {
- endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0));
- }
-
- for (int y = startY; y < endY && !foundCell; y++) {
- inner:
- for (int x = startX; x < endX; x++) {
- for (int i = 0; i < spanX; i++) {
- for (int j = 0; j < spanY; j++) {
- if (occupied[x + i][y + j]) {
- // small optimization: we can skip to after the column we just found
- // an occupied cell
- x += i;
- continue inner;
- }
- }
- }
- if (cellXY != null) {
- cellXY[0] = x;
- cellXY[1] = y;
- }
- foundCell = true;
- break;
- }
- }
- if (intersectX == -1 && intersectY == -1) {
- break;
- } else {
- // if we failed to find anything, try again but without any requirements of
- // intersecting
- intersectX = -1;
- intersectY = -1;
- continue;
- }
- }
-
- // re-mark space taken by ignoreView as occupied
- markCellsAsOccupiedForView(ignoreView, occupied);
- return foundCell;
- }
-
- /**
- * A drag event has begun over this layout.
- * It may have begun over this layout (in which case onDragChild is called first),
- * or it may have begun on another layout.
- */
- void onDragEnter() {
- mDragEnforcer.onDragEnter();
- mDragging = true;
- }
-
- /**
- * Called when drag has left this CellLayout or has been completed (successfully or not)
- */
- void onDragExit() {
- mDragEnforcer.onDragExit();
- // This can actually be called when we aren't in a drag, e.g. when adding a new
- // item to this layout via the customize drawer.
- // Guard against that case.
- if (mDragging) {
- mDragging = false;
- }
-
- // Invalidate the drag data
- mDragCell[0] = mDragCell[1] = -1;
- mDragOutlineAnims[mDragOutlineCurrent].animateOut();
- mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length;
- revertTempState();
- setIsDragOverlapping(false);
- }
-
- /**
- * Mark a child as having been dropped.
- * At the beginning of the drag operation, the child may have been on another
- * screen, but it is re-parented before this method is called.
- *
- * @param child The child that is being dropped
- */
- void onDropChild(View child) {
- if (child != null) {
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
- lp.dropped = true;
- child.requestLayout();
- }
- }
-
- /**
- * Computes a bounding rectangle for a range of cells
- *
- * @param cellX X coordinate of upper left corner expressed as a cell position
- * @param cellY Y coordinate of upper left corner expressed as a cell position
- * @param cellHSpan Width in cells
- * @param cellVSpan Height in cells
- * @param resultRect Rect into which to put the results
- */
- public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) {
- final int cellWidth = mCellWidth;
- final int cellHeight = mCellHeight;
- final int widthGap = mWidthGap;
- final int heightGap = mHeightGap;
-
- final int hStartPadding = getPaddingLeft();
- final int vStartPadding = getPaddingTop();
-
- int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
- int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
-
- int x = hStartPadding + cellX * (cellWidth + widthGap);
- int y = vStartPadding + cellY * (cellHeight + heightGap);
-
- resultRect.set(x, y, x + width, y + height);
- }
-
- /**
- * Computes the required horizontal and vertical cell spans to always
- * fit the given rectangle.
- *
- * @param width Width in pixels
- * @param height Height in pixels
- * @param result An array of length 2 in which to store the result (may be null).
- */
- public int[] rectToCell(int width, int height, int[] result) {
- return rectToCell(getResources(), width, height, result);
- }
-
- public static int[] rectToCell(Resources resources, int width, int height, int[] result) {
- // Always assume we're working with the smallest span to make sure we
- // reserve enough space in both orientations.
- int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
- int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
- int smallerSize = Math.min(actualWidth, actualHeight);
-
- // Always round up to next largest cell
- int spanX = (int) Math.ceil(width / (float) smallerSize);
- int spanY = (int) Math.ceil(height / (float) smallerSize);
-
- if (result == null) {
- return new int[] { spanX, spanY };
- }
- result[0] = spanX;
- result[1] = spanY;
- return result;
- }
-
- public int[] cellSpansToSize(int hSpans, int vSpans) {
- int[] size = new int[2];
- size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap;
- size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap;
- return size;
- }
-
- /**
- * Calculate the grid spans needed to fit given item
- */
- public void calculateSpans(ItemInfo info) {
- final int minWidth;
- final int minHeight;
-
- if (info instanceof LauncherAppWidgetInfo) {
- minWidth = ((LauncherAppWidgetInfo) info).minWidth;
- minHeight = ((LauncherAppWidgetInfo) info).minHeight;
- } else if (info instanceof PendingAddWidgetInfo) {
- minWidth = ((PendingAddWidgetInfo) info).minWidth;
- minHeight = ((PendingAddWidgetInfo) info).minHeight;
- } else {
- // It's not a widget, so it must be 1x1
- info.spanX = info.spanY = 1;
- return;
- }
- int[] spans = rectToCell(minWidth, minHeight, null);
- info.spanX = spans[0];
- info.spanY = spans[1];
- }
-
- /**
- * Find the first vacant cell, if there is one.
- *
- * @param vacant Holds the x and y coordinate of the vacant cell
- * @param spanX Horizontal cell span.
- * @param spanY Vertical cell span.
- *
- * @return True if a vacant cell was found
- */
- public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
-
- return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied);
- }
-
- static boolean findVacantCell(int[] vacant, int spanX, int spanY,
- int xCount, int yCount, boolean[][] occupied) {
-
- for (int y = 0; y < yCount; y++) {
- for (int x = 0; x < xCount; x++) {
- boolean available = !occupied[x][y];
-out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
- for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
- available = available && !occupied[i][j];
- if (!available) break out;
- }
- }
-
- if (available) {
- vacant[0] = x;
- vacant[1] = y;
- return true;
- }
- }
- }
-
- return false;
- }
-
- private void clearOccupiedCells() {
- for (int x = 0; x < mCountX; x++) {
- for (int y = 0; y < mCountY; y++) {
- mOccupied[x][y] = false;
- }
- }
- }
-
- public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) {
- markCellsAsUnoccupiedForView(view);
- markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true);
- }
-
- public void markCellsAsOccupiedForView(View view) {
- markCellsAsOccupiedForView(view, mOccupied);
- }
- public void markCellsAsOccupiedForView(View view, boolean[][] occupied) {
- if (view == null || view.getParent() != mShortcutsAndWidgets) return;
- LayoutParams lp = (LayoutParams) view.getLayoutParams();
- markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true);
- }
-
- public void markCellsAsUnoccupiedForView(View view) {
- markCellsAsUnoccupiedForView(view, mOccupied);
- }
- public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) {
- if (view == null || view.getParent() != mShortcutsAndWidgets) return;
- LayoutParams lp = (LayoutParams) view.getLayoutParams();
- markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false);
- }
-
- private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied,
- boolean value) {
- if (cellX < 0 || cellY < 0) return;
- for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
- for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
- occupied[x][y] = value;
- }
- }
- }
-
- public int getDesiredWidth() {
- return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) +
- (Math.max((mCountX - 1), 0) * mWidthGap);
- }
-
- public int getDesiredHeight() {
- return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) +
- (Math.max((mCountY - 1), 0) * mHeightGap);
- }
-
- public boolean isOccupied(int x, int y) {
- if (x < mCountX && y < mCountY) {
- return mOccupied[x][y];
- } else {
- throw new RuntimeException("Position exceeds the bound of this CellLayout");
- }
- }
-
- @Override
- public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
- return new CellLayout.LayoutParams(getContext(), attrs);
- }
-
- @Override
- protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
- return p instanceof CellLayout.LayoutParams;
- }
-
- @Override
- protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
- return new CellLayout.LayoutParams(p);
- }
-
- public static class CellLayoutAnimationController extends LayoutAnimationController {
- public CellLayoutAnimationController(Animation animation, float delay) {
- super(animation, delay);
- }
-
- @Override
- protected long getDelayForView(View view) {
- return (int) (Math.random() * 150);
- }
- }
-
- public static class LayoutParams extends ViewGroup.MarginLayoutParams {
- /**
- * Horizontal location of the item in the grid.
- */
- @ViewDebug.ExportedProperty
- public int cellX;
-
- /**
- * Vertical location of the item in the grid.
- */
- @ViewDebug.ExportedProperty
- public int cellY;
-
- /**
- * Temporary horizontal location of the item in the grid during reorder
- */
- public int tmpCellX;
-
- /**
- * Temporary vertical location of the item in the grid during reorder
- */
- public int tmpCellY;
-
- /**
- * Indicates that the temporary coordinates should be used to layout the items
- */
- public boolean useTmpCoords;
-
- /**
- * Number of cells spanned horizontally by the item.
- */
- @ViewDebug.ExportedProperty
- public int cellHSpan;
-
- /**
- * Number of cells spanned vertically by the item.
- */
- @ViewDebug.ExportedProperty
- public int cellVSpan;
-
- /**
- * Indicates whether the item will set its x, y, width and height parameters freely,
- * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan.
- */
- public boolean isLockedToGrid = true;
-
- /**
- * Indicates whether this item can be reordered. Always true except in the case of the
- * the AllApps button.
- */
- public boolean canReorder = true;
-
- // X coordinate of the view in the layout.
- @ViewDebug.ExportedProperty
- int x;
- // Y coordinate of the view in the layout.
- @ViewDebug.ExportedProperty
- int y;
-
- boolean dropped;
-
- public LayoutParams(Context c, AttributeSet attrs) {
- super(c, attrs);
- cellHSpan = 1;
- cellVSpan = 1;
- }
-
- public LayoutParams(ViewGroup.LayoutParams source) {
- super(source);
- cellHSpan = 1;
- cellVSpan = 1;
- }
-
- public LayoutParams(LayoutParams source) {
- super(source);
- this.cellX = source.cellX;
- this.cellY = source.cellY;
- this.cellHSpan = source.cellHSpan;
- this.cellVSpan = source.cellVSpan;
- }
-
- public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
- super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
- this.cellX = cellX;
- this.cellY = cellY;
- this.cellHSpan = cellHSpan;
- this.cellVSpan = cellVSpan;
- }
-
- public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
- boolean invertHorizontally, int colCount) {
- if (isLockedToGrid) {
- final int myCellHSpan = cellHSpan;
- final int myCellVSpan = cellVSpan;
- int myCellX = useTmpCoords ? tmpCellX : cellX;
- int myCellY = useTmpCoords ? tmpCellY : cellY;
-
- if (invertHorizontally) {
- myCellX = colCount - myCellX - cellHSpan;
- }
-
- width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
- leftMargin - rightMargin;
- height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
- topMargin - bottomMargin;
- x = (int) (myCellX * (cellWidth + widthGap) + leftMargin);
- y = (int) (myCellY * (cellHeight + heightGap) + topMargin);
- }
- }
-
- public String toString() {
- return "(" + this.cellX + ", " + this.cellY + ")";
- }
-
- public void setWidth(int width) {
- this.width = width;
- }
-
- public int getWidth() {
- return width;
- }
-
- public void setHeight(int height) {
- this.height = height;
- }
-
- public int getHeight() {
- return height;
- }
-
- public void setX(int x) {
- this.x = x;
- }
-
- public int getX() {
- return x;
- }
-
- public void setY(int y) {
- this.y = y;
- }
-
- public int getY() {
- return y;
- }
- }
-
- // This class stores info for two purposes:
- // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY,
- // its spanX, spanY, and the screen it is on
- // 2. When long clicking on an empty cell in a CellLayout, we save information about the
- // cellX and cellY coordinates and which page was clicked. We then set this as a tag on
- // the CellLayout that was long clicked
- static final class CellInfo {
- View cell;
- int cellX = -1;
- int cellY = -1;
- int spanX;
- int spanY;
- int screen;
- long container;
-
- @Override
- public String toString() {
- return "Cell[view=" + (cell == null ? "null" : cell.getClass())
- + ", x=" + cellX + ", y=" + cellY + "]";
- }
- }
-
- public boolean lastDownOnOccupiedCell() {
- return mLastDownOnOccupiedCell;
- }
-}