/* * Copyright (C) 2010 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.launcher3; import java.util.HashMap; import java.util.Stack; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; import com.mediatek.launcher3.ext.LauncherLog; /** * An abstraction of the original CellLayout which supports laying out items * which span multiple cells into a grid-like layout. Also supports dimming * to give a preview of its contents. */ public class PagedViewCellLayout extends ViewGroup implements Page { static final String TAG = "PagedViewCellLayout"; private int mCellCountX; private int mCellCountY; private int mOriginalCellWidth; private int mOriginalCellHeight; private int mCellWidth; private int mCellHeight; private int mOriginalWidthGap; private int mOriginalHeightGap; private int mWidthGap; private int mHeightGap; protected PagedViewCellLayoutChildren mChildren; /// M: add for OP09.@{ private int mMaxGap; private DropTarget.DragEnforcer mDragEnforcer; private boolean mDragging = false; // When a drag operation is in progress, holds the nearest cell to the touch point private final int[] mDragCell = new int[2]; private final CellInfo mCellInfo = new CellInfo(); private final Rect mRect = new Rect(); private final int[] mTmpXY = new int[2]; private HashMap<PagedViewCellLayout.LayoutParams, Animator> mReorderAnimators = new HashMap<PagedViewCellLayout.LayoutParams, Animator>(); private boolean[][] mOccupied; private boolean[][] mTmpOccupied; private final int[] mTmpPoint = new int[2]; private final Stack<Rect> mTempRectStack = new Stack<Rect>(); /// M: add for OP09.}@ public PagedViewCellLayout(Context context) { this(context, null); } public PagedViewCellLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public PagedViewCellLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setAlwaysDrawnWithCacheEnabled(false); // setup default cell parameters LauncherAppState app = LauncherAppState.getInstance(); DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); mOriginalCellWidth = mCellWidth = grid.cellWidthPx; mOriginalCellHeight = mCellHeight = grid.cellHeightPx; mCellCountX = (int) grid.numColumns; mCellCountY = (int) grid.numRows; mOriginalWidthGap = mOriginalHeightGap = mWidthGap = mHeightGap = -1; mChildren = new PagedViewCellLayoutChildren(context); mChildren.setCellDimensions(mCellWidth, mCellHeight); mChildren.setGap(mWidthGap, mHeightGap); addView(mChildren); if (LauncherLog.DEBUG) { LauncherLog.d(TAG, "Constructor: mCellCountX = " + mCellCountX + ", mCellCountY = " + mCellCountY + ",mMaxGap = " + mMaxGap + ", this = " + this); } /// M: enlarge the max gap to make app list can be divided to 4 cells with same size. if (LauncherExtPlugin.getInstance().getOperatorCheckerExt(context).supportEditAndHideApps()) { mMaxGap *= 2; } mOccupied = new boolean[mCellCountX][mCellCountY]; mTmpOccupied = new boolean[mCellCountX][mCellCountY]; mDragEnforcer = new DropTarget.DragEnforcer(context); } public int getCellWidth() { return mCellWidth; } public int getCellHeight() { return mCellHeight; } @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 boolean addViewToCellLayout(View child, int index, int childId, PagedViewCellLayout.LayoutParams params) { final PagedViewCellLayout.LayoutParams lp = params; if (LauncherLog.DEBUG) { LauncherLog.d(TAG, "addViewToCellLayout: index = " + index + ", child = " + child.getTag() + ", this = " + this); } // Generate an id for each view, this assumes we have at most 256x256 cells // per workspace screen if (lp.cellX >= 0 && lp.cellX <= (mCellCountX - 1) && lp.cellY >= 0 && (lp.cellY <= mCellCountY - 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 = mCellCountX; if (lp.cellVSpan < 0) lp.cellVSpan = mCellCountY; child.setId(childId); mChildren.addView(child, index, lp); /// M: mark the position as occupied, add for OP09. markCellsAsOccupiedForView(child); return true; } return false; } @Override public void removeAllViewsOnPage() { if (LauncherLog.DEBUG) { LauncherLog.d(TAG, "removeAllViewsOnPage: mChildren = " + mChildren + ", this = " + this); } /// M: clear all occupied information, add for OP09. clearOccupiedCells(); mChildren.removeAllViews(); setLayerType(LAYER_TYPE_NONE, null); } @Override public void removeViewOnPageAt(int index) { if (LauncherLog.DEBUG) { LauncherLog.d(TAG, "removeViewOnPageAt: mChildren = " + mChildren + ", index = " + index); } /// M: mark the position as vacant cell, add for OP09. markCellsAsUnoccupiedForView(mChildren.getChildAt(index)); mChildren.removeViewAt(index); } /** * Clears all the key listeners for the individual icons. */ public void resetChildrenOnKeyListeners() { int childCount = mChildren.getChildCount(); for (int j = 0; j < childCount; ++j) { mChildren.getChildAt(j).setOnKeyListener(null); } } @Override public int getPageChildCount() { return mChildren.getChildCount(); } public PagedViewCellLayoutChildren getChildrenLayout() { return mChildren; } @Override public View getChildOnPageAt(int i) { return mChildren.getChildAt(i); } @Override public int indexOfChildOnPage(View v) { return mChildren.indexOfChild(v); } public int getCellCountX() { return mCellCountX; } public int getCellCountY() { return mCellCountY; } 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 = mCellCountX - 1; int numHeightGaps = mCellCountY - 1; if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) { int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight(); int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom(); int hFreeSpace = hSpace - (mCellCountX * mOriginalCellWidth); int vFreeSpace = vSpace - (mCellCountY * mOriginalCellHeight); mWidthGap = numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0; mHeightGap = numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0; if (LauncherLog.DEBUG_LAYOUT) { LauncherLog.d(TAG, "onMeasure 0: mMaxGap = " + mMaxGap + ", numWidthGaps = " + numWidthGaps + ", hFreeSpace = " + hFreeSpace + ", mOriginalCellWidth =" + mOriginalCellWidth + ", mOriginalCellHeight = " + mOriginalCellHeight + ", mWidthGap = " + mWidthGap); } mChildren.setGap(mWidthGap, mHeightGap); } else { mWidthGap = mOriginalWidthGap; mHeightGap = mOriginalHeightGap; } // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY int newWidth = widthSpecSize; int newHeight = heightSpecSize; if (LauncherLog.DEBUG_LAYOUT) { LauncherLog.d(TAG, "onMeasure 1: newWidth = " + newWidth + ", newHeight = " + newHeight + ", widthSpecMode = " + widthSpecMode + ",mPaddingLeft = " + mPaddingLeft + ", mPaddingRight = " + mPaddingRight + ",mCellCountX = " + mCellCountX + ", mCellWidth = " + mCellWidth + ", mWidthGap = " + mWidthGap + ", mOriginalWidthGap =" + mOriginalWidthGap + ", mOriginalHeightGap = " + mOriginalHeightGap + ", mOriginalCellWidth =" + mOriginalCellWidth + ", mOriginalCellHeight = " + mOriginalCellHeight + ", this = " + this); } if (widthSpecMode == MeasureSpec.AT_MOST) { newWidth = getPaddingLeft() + getPaddingRight() + (mCellCountX * mCellWidth) + ((mCellCountX - 1) * mWidthGap); newHeight = getPaddingTop() + getPaddingBottom() + (mCellCountY * mCellHeight) + ((mCellCountY - 1) * mHeightGap); if (LauncherLog.DEBUG_LAYOUT) { LauncherLog.d(TAG, "onMeasure 2: newWidth = " + newWidth + ", newHeight = " + newHeight + ", this = " + this); } setMeasuredDimension(newWidth, newHeight); } final int count = getChildCount(); /* * If user switch two tabs quickly, measure process will be delayed, the * newWidth(newHeight) may be 0, after minus the padding, the * measure width passed to child may be a negative value. When adding to * measureMode to get MeasureSpec, the measure mode could be changed. * Using 0 as the measureWidth if this happens to keep measure mode right. */ final int childMeasureWidth = Math.max(0, newWidth - getPaddingLeft() - getPaddingRight()); final int childMeasureHeight = Math.max(0, newHeight - getPaddingTop() - getPaddingBottom()); for (int i = 0; i < count; i++) { View child = getChildAt(i); int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childMeasureWidth, MeasureSpec.EXACTLY); int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(childMeasureHeight, MeasureSpec.EXACTLY); child.measure(childWidthMeasureSpec, childheightMeasureSpec); } if (LauncherLog.DEBUG_LAYOUT) { LauncherLog.d(TAG, "onMeasure 4: newWidth = " + newWidth + ", newHeight = " + newHeight + ", this = " + this); } setMeasuredDimension(newWidth, newHeight); } int getContentWidth() { return getWidthBeforeFirstLayout() + getPaddingLeft() + getPaddingRight(); } int getContentHeight() { if (mCellCountY > 0) { return mCellCountY * mCellHeight + (mCellCountY - 1) * Math.max(0, mHeightGap); } return 0; } int getWidthBeforeFirstLayout() { if (mCellCountX > 0) { return mCellCountX * mCellWidth + (mCellCountX - 1) * Math.max(0, mWidthGap); } return 0; } @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 public boolean onTouchEvent(MotionEvent event) { boolean result = super.onTouchEvent(event); int count = getPageChildCount(); if (count > 0) { // We only intercept the touch if we are tapping in empty space after the final row View child = getChildOnPageAt(count - 1); int bottom = child.getBottom(); int numRows = (int) Math.ceil((float) getPageChildCount() / getCellCountX()); if (numRows < getCellCountY()) { // Add a little bit of buffer if there is room for another row bottom += mCellHeight / 2; } result = result || (event.getY() < bottom); } return result; } public void enableCenteredContent(boolean enabled) { mChildren.enableCenteredContent(enabled); } @Override protected void setChildrenDrawingCacheEnabled(boolean enabled) { mChildren.setChildrenDrawingCacheEnabled(enabled); } public void setCellCount(int xCount, int yCount) { if (LauncherLog.DEBUG_LAYOUT) { LauncherLog.d(TAG, "setCellCount xCount = " + yCount + ", mCellCountX = " + mCellCountX + ", mCellCountY = " + mCellCountY + ", this = " + this, new Throwable( "setCellCount")); } mCellCountX = xCount; mCellCountY = yCount; /// M: for op09 mOccupied = new boolean[mCellCountX][mCellCountY]; mTmpOccupied = new boolean[mCellCountX][mCellCountY]; requestLayout(); } public void setGap(int widthGap, int heightGap) { mOriginalWidthGap = mWidthGap = widthGap; mOriginalHeightGap = mHeightGap = heightGap; mChildren.setGap(widthGap, heightGap); } public int[] getCellCountForDimensions(int width, int height) { // Always assume we're working with the smallest span to make sure we // reserve enough space in both orientations int smallerSize = Math.min(mCellWidth, mCellHeight); // Always round up to next largest cell int spanX = (width + smallerSize) / smallerSize; int spanY = (height + smallerSize) / smallerSize; if (LauncherLog.DEBUG_LAYOUT) { LauncherLog.d(TAG, "getCellCountForDimensions width = " + width + ", height =" + height + ", spanX = " + spanX + ", spanY = " + spanY + ", this = " + this); } return new int[] { spanX, spanY }; } /** * Start dragging the specified child * * @param child The child that is being dragged */ void onDragChild(View child) { PagedViewCellLayout.LayoutParams lp = (PagedViewCellLayout.LayoutParams) child.getLayoutParams(); lp.isDragging = true; } /** * Estimates the number of cells that the specified width would take up. */ public int estimateCellHSpan(int width) { // We don't show the next/previous pages any more, so we use the full width, minus the // padding int availWidth = width - (getPaddingLeft() + getPaddingRight()); // We know that we have to fit N cells with N-1 width gaps, so we just juggle to solve for N int n = Math.max(1, (availWidth + mWidthGap) / (mCellWidth + mWidthGap)); if (LauncherLog.DEBUG_LAYOUT) { LauncherLog.d(TAG, "estimateCellHSpan width = " + width + ", availWidth = " + availWidth + ", n = " + n + ", this = " + this); } // We don't do anything fancy to determine if we squeeze another row in. return n; } /** * Estimates the number of cells that the specified height would take up. */ public int estimateCellVSpan(int height) { // The space for a page is the height - top padding (current page) - bottom padding (current // page) int availHeight = height - (getPaddingTop() + getPaddingBottom()); // We know that we have to fit N cells with N-1 height gaps, so we juggle to solve for N int n = Math.max(1, (availHeight + mHeightGap) / (mCellHeight + mHeightGap)); if (LauncherLog.DEBUG_LAYOUT) { LauncherLog.d(TAG, "estimateCellVSpan width = " + height + ", availHeight = " + availHeight + ", n = " + n + ", this = " + this); } // We don't do anything fancy to determine if we squeeze another row in. return n; } /** Returns an estimated center position of the cell at the specified index */ public int[] estimateCellPosition(int x, int y) { int[] result = new int[] { getPaddingLeft() + (x * mCellWidth) + (x * mWidthGap) + (mCellWidth / 2), getPaddingTop() + (y * mCellHeight) + (y * mHeightGap) + (mCellHeight / 2) }; if (LauncherLog.DEBUG_LAYOUT) { LauncherLog.d(TAG, "estimateCellPosition x = " + x + ", y = " + y + ", result[0] = " + result[0] + ", result[1] = " + result[1] + ", this = " + this); } return result; } public void calculateCellCount(int width, int height, int maxCellCountX, int maxCellCountY) { mCellCountX = Math.min(maxCellCountX, estimateCellHSpan(width)); mCellCountY = Math.min(maxCellCountY, estimateCellVSpan(height)); if (LauncherLog.DEBUG_LAYOUT) { LauncherLog.d(TAG, "calculateCellCount width = " + width + ", height = " + height + ", maxCellCountX = " + maxCellCountX + ", maxCellCountY = " + maxCellCountY + ", mCellCountX = " + mCellCountX + ", mCellCountY = " + mCellCountY + ", this = " + this); } requestLayout(); } /** * Estimates the width that the number of hSpan cells will take up. */ public int estimateCellWidth(int hSpan) { if (LauncherLog.DEBUG_LAYOUT) { LauncherLog.d(TAG, "estimeateCellWidth hSpan = " + hSpan + ", mCellWidth = " + mCellWidth + ", this = " + this); } // TODO: we need to take widthGap into effect return hSpan * mCellWidth; } /** * Estimates the height that the number of vSpan cells will take up. */ public int estimateCellHeight(int vSpan) { if (LauncherLog.DEBUG_LAYOUT) { LauncherLog.d(TAG, "estimateCellHeight sSpan = " + vSpan + ", mCellHeight = " + mCellHeight + ", this = " + this); } // TODO: we need to take heightGap into effect return vSpan * mCellHeight; } @Override public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { return new PagedViewCellLayout.LayoutParams(getContext(), attrs); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof PagedViewCellLayout.LayoutParams; } @Override protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new PagedViewCellLayout.LayoutParams(p); } 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; /** * 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; /** * Is this item currently being dragged */ public boolean isDragging; // a data object that you can bind to this layout params private Object mTag; // X coordinate of the view in the layout. @ViewDebug.ExportedProperty int x; // Y coordinate of the view in the layout. @ViewDebug.ExportedProperty int y; /// M: Add for op09 start. @{ /** * 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 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; boolean dropped; /// M: Add for op09 end. }@ public LayoutParams() { super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); cellHSpan = 1; cellVSpan = 1; } 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(Context context, int cellWidth, int cellHeight, int widthGap, int heightGap, int hStartPadding, int vStartPadding) { final int myCellHSpan = cellHSpan; final int myCellVSpan = cellVSpan; final int myCellX = cellX; final int myCellY = cellY; width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) - leftMargin - rightMargin; height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) - topMargin - bottomMargin; if (LauncherAppState.getInstance().isScreenLarge()) { x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin; y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin; } else { x = myCellX * (cellWidth + widthGap) + leftMargin; y = myCellY * (cellHeight + heightGap) + topMargin; } } public Object getTag() { return mTag; } public void setTag(Object tag) { mTag = tag; } public String toString() { return "(" + this.cellX + ", " + this.cellY + ", " + this.cellHSpan + ", " + this.cellVSpan + ")"; } } /// M: Add for op09 start. @{ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (!Launcher.isInEditMode()) { return super.onInterceptTouchEvent(ev); } // First we clear the tag to ensure that on every touch down we start // with a fresh state, 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 (LauncherLog.DEBUG_MOTION) { LauncherLog.d(TAG, "onInterceptTouchEvent: action = " + action); } // Set set the cell info of the touch position as tag of cell layout. if (action == MotionEvent.ACTION_DOWN) { if (LauncherLog.DEBUG_EDIT) { LauncherLog.d(TAG, "onInterceptTouchEvent: mCellInfo = " + mCellInfo); } clearTagCellInfo(); setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY()); } return super.onInterceptTouchEvent(ev); } /** * M: Get child at the given position. * * @param x * @param y * @return */ public View getChildAt(final int x, final int y) { return mChildren.getChildAt(x, y); } /** * M: 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, merge from CellLayout. */ void onDragEnter() { if (LauncherLog.DEBUG) { LauncherLog.d(TAG, "onDragEnter: mDragging = " + mDragging + ", this = " + this); } mDragEnforcer.onDragEnter(); mDragging = true; } /** * M: Called when drag has left this CellLayout or has been completed * (successfully or not), merge from CellLayout. */ void onDragExit() { if (LauncherLog.DEBUG) { LauncherLog.d(TAG, "onDragExit: mDragging = " + mDragging + ", this = " + this); } // 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. mDragEnforcer.onDragExit(); mDragging = false; // Invalidate the drag data mDragCell[0] = -1; mDragCell[1] = -1; } /** * M: 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, merge from CellLayout. * * @param child The child that is being dropped */ void onDropChild(final View child) { if (LauncherLog.DEBUG) { LauncherLog.d(TAG, "onDropChild: child = " + child + ", this = " + this); } if (child != null) { LayoutParams lp = (LayoutParams) child.getLayoutParams(); lp.dropped = true; child.requestLayout(); } } /** * M: Animate child to the given position, merge from CellLayout. * * @param child * @param cellX * @param cellY * @param duration * @param delay * @param permanent * @param adjustOccupied * @return */ public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration, int delay, boolean permanent, boolean adjustOccupied) { PagedViewCellLayoutChildren clc = getChildrenLayout(); boolean[][] occupied = mOccupied; if (!permanent) { occupied = mTmpOccupied; } if (clc.indexOfChild(child) != -1) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final AppInfo info = (AppInfo) 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 = cellX; info.cellX = cellX; lp.cellY = cellY; info.cellY = cellY; info.pos = cellY * mCellCountX + cellX; } 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 = ValueAnimator.ofFloat(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; } /** * M: Clear occupied array, mark all cell as vacant cell, add for OP09. */ private void clearOccupiedCells() { for (int x = 0; x < mCellCountX; x++) { for (int y = 0; y < mCellCountY; y++) { mOccupied[x][y] = false; } } } /** * M: Mark the cell of the view position as occupied. * * @param view */ public void markCellsAsOccupiedForView(View view) { markCellsAsOccupiedForView(view, mOccupied); } /** * M: Mark the cell of the view position as occupied. * * @param view * @param occupied */ public void markCellsAsOccupiedForView(View view, boolean[][] occupied) { if (view == null || view.getParent() != mChildren) { return; } LayoutParams lp = (LayoutParams) view.getLayoutParams(); markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true); } /** * M: Mark the cell of the view position as vacant. * * @param view */ public void markCellsAsUnoccupiedForView(View view) { markCellsAsUnoccupiedForView(view, mOccupied); } /** * M: Mark the cell of the view position as vacant. * * @param view * @param occupied */ public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) { if (view == null || view.getParent() != mChildren) { return; } LayoutParams lp = (LayoutParams) view.getLayoutParams(); markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false); } /** * M: Mark the cell of the specified position as vacant or occupied. * * @param cellX * @param cellY * @param spanX * @param spanY * @param occupied * @param value True if occupied, false vacant. */ 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 < mCellCountX; x++) { for (int y = cellY; y < cellY + spanY && y < mCellCountY; y++) { occupied[x][y] = value; } } } /** * M: 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; long screen = -1; int pos = -1; @Override public String toString() { return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + ", x=" + cellX + ", y=" + cellY + ",screen = " + screen + ",pos = " + pos + "]"; } } /** * M: Lazy init temp rect stack, merge from CellLayout. */ private void lazyInitTempRectStack() { if (mTempRectStack.isEmpty()) { for (int i = 0; i < mCellCountX * mCellCountY; i++) { mTempRectStack.push(new Rect()); } } } /** * M: Recycle used rects, merge from CellLayout. * * @param used */ private void recycleTempRects(Stack<Rect> used) { while (!used.isEmpty()) { mTempRectStack.push(used.pop()); } } /** * M: Find a starting cell position that will fit the given bounds nearest the * requested cell location. Uses Euclidean distance to score multiple vacant * areas, merge from CellLayout. * * @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); } /** * M: 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); } /** * M: 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. 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 = mCellCountX; final int countY = mCellCountY; 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; } /** * M: 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 = mCellCountX; final int countY = mCellCountY; 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; } /** * M: Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to * vector between the provided point and the provided cell, merge from CellLayout. */ 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); } } /** * M: Given a point, return the cell that strictly encloses that point, merge * from CellLayout. * * @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 = mCellCountX; final int yAxis = mCellCountY; if (result[0] < 0) { result[0] = 0; } else if (result[0] >= xAxis) { result[0] = xAxis - 1; } if (result[1] < 0) { result[1] = 0; } else if (result[1] >= yAxis) { result[1] = yAxis - 1; } } /** * M: Given a cell coordinate, return the point that represents the center of * the cell, merge from CellLayout. * * @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); } /** * M: Given a cell coordinate and span return the point that represents the * center of the region, merge from CellLayout. * * @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; } /** * M: Set the cell info of the given position as tag of the current cell * layout, merge from CellLayout. * * @param touchX * @param touchY */ private void setTagToCellInfoForPoint(final int touchX, final int touchY) { final CellInfo cellInfo = mCellInfo; Rect frame = mRect; final int x = touchX + getScrollX(); final int y = touchY + getScrollY(); final int count = mChildren.getChildCount(); boolean found = false; for (int i = count - 1; i >= 0; i--) { final View child = mChildren.getChildAt(i); final AppInfo info = (AppInfo) child.getTag(); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if ((child.getVisibility() == VISIBLE || child.getAnimation() != null)) { 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.screen = info.screenId; cellInfo.cellX = lp.cellX; cellInfo.cellY = lp.cellY; cellInfo.pos = cellInfo.cellY * mCellCountX + cellInfo.cellX; found = true; break; } } } if (!found) { final int cellXY[] = mTmpXY; pointToCellExact(x, y, cellXY); cellInfo.cell = null; cellInfo.cellX = cellXY[0]; cellInfo.cellY = cellXY[1]; cellInfo.pos = cellInfo.cellX * mCellCountY + cellInfo.cellY; } setTag(cellInfo); } /** * M: Reset cell info and set it as tag, merge from CellLayout. */ private void clearTagCellInfo() { final CellInfo cellInfo = mCellInfo; cellInfo.cell = null; cellInfo.cellX = -1; cellInfo.cellY = -1; cellInfo.pos = -1; setTag(cellInfo); } /// M: Add for op09 end. }@ }