diff options
author | Daniel Sandler <dsandler@android.com> | 2013-06-05 22:57:57 -0400 |
---|---|---|
committer | Daniel Sandler <dsandler@android.com> | 2013-06-05 23:30:20 -0400 |
commit | 325dc23624160689e59fbac708cf6f222b20d025 (patch) | |
tree | 3c6a13a52a6e5688c7e4404890e5e8f88d544856 /src/com/android/launcher2/CellLayout.java | |
parent | b582cd201fccece65d36b8915cf84fef3546cffa (diff) | |
download | android_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.java | 3338 |
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; - } -} |