Merge "Decoupling the reorder logic from the CellLayout view" into tm-qpr-dev

This commit is contained in:
Sebastián Franco
2023-03-07 00:24:33 +00:00
committed by Android (Google) Code Review
4 changed files with 393 additions and 226 deletions
+36 -145
View File
@@ -65,6 +65,7 @@ import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.celllayout.CellPosMapper.CellPos;
import com.android.launcher3.celllayout.ReorderAlgorithm;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.PreviewBackground;
@@ -115,7 +116,7 @@ public class CellLayout extends ViewGroup {
final PointF mTmpPointF = new PointF();
protected GridOccupancy mOccupied;
protected GridOccupancy mTmpOccupied;
public GridOccupancy mTmpOccupied;
private OnTouchListener mInterceptTouchListener;
@@ -194,7 +195,7 @@ public class CellLayout extends ViewGroup {
private final ArrayList<View> mIntersectingViews = new ArrayList<>();
private final Rect mOccupiedRect = new Rect();
private final int[] mDirectionVector = new int[2];
public final int[] mDirectionVector = new int[2];
ItemConfiguration mPreviousSolution = null;
private static final int INVALID_DIRECTION = -100;
@@ -1246,8 +1247,8 @@ public class CellLayout extends ViewGroup {
* @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) {
public int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
int spanX, int spanY, int[] result, int[] resultSpan) {
return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, false,
result, resultSpan);
}
@@ -1380,6 +1381,10 @@ public class CellLayout extends ViewGroup {
return bestXY;
}
public GridOccupancy getOccupied() {
return mOccupied;
}
private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
mTmpOccupied.clear();
@@ -1655,38 +1660,8 @@ public class CellLayout extends ViewGroup {
}
}
/**
* Returns a "reorder" where we simply drop the item in the closest empty space, without moving
* any other item in the way.
*
* @param pixelX X coordinate in pixels in the screen
* @param pixelY Y coordinate in pixels in the screen
* @param spanX horizontal cell span
* @param spanY vertical cell span
* @return the configuration that represents the found reorder
*/
ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, int minSpanX,
int minSpanY, int spanX, int spanY) {
ItemConfiguration solution = new ItemConfiguration();
int[] result = new int[2];
int[] resultSpan = new int[2];
findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
resultSpan);
if (result[0] >= 0 && result[1] >= 0) {
copyCurrentStateToSolution(solution, false);
solution.cellX = result[0];
solution.cellY = result[1];
solution.spanX = resultSpan[0];
solution.spanY = resultSpan[1];
solution.isSolution = true;
} else {
solution.isSolution = false;
}
return solution;
}
// 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,
public 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);
@@ -1711,7 +1686,7 @@ public class CellLayout extends ViewGroup {
}
}
boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
public boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
View dragView, int[] result) {
result = findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY, result);
getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
@@ -2257,7 +2232,7 @@ public class CellLayout extends ViewGroup {
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,
public void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
int spanY, View dragView, int[] resultDirection) {
//TODO(adamcohen) b/151776141 use the items visual center for the direction vector
@@ -2349,7 +2324,7 @@ public class CellLayout extends ViewGroup {
return success;
}
private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
public 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;
@@ -2405,55 +2380,18 @@ public class CellLayout extends ViewGroup {
return true;
}
public ReorderAlgorithm createReorderAlgorithm() {
return new ReorderAlgorithm(this);
}
protected ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
ItemConfiguration solution) {
return findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
direction, dragView, decX, solution);
return createReorderAlgorithm().findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
spanX, spanY, direction, dragView, decX, solution);
}
protected ItemConfiguration findReorderSolutionRecursive(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.
mOccupied.copyTo(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 = findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY, result);
boolean success;
// 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 findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX - 1,
spanY, direction, dragView, false, solution);
} else if (spanY > minSpanY) {
return findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX,
spanY - 1, direction, dragView, true, solution);
}
solution.isSolution = false;
} else {
solution.isSolution = true;
solution.cellX = result[0];
solution.cellY = result[1];
solution.spanX = spanX;
solution.spanY = spanY;
}
return solution;
}
protected void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
public void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
int childCount = mShortcutsAndWidgets.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = mShortcutsAndWidgets.getChildAt(i);
@@ -2468,35 +2406,6 @@ public class CellLayout extends ViewGroup {
}
}
/**
* Returns a "reorder" if there is empty space without rearranging anything.
*
* @param pixelX X coordinate in pixels in the screen
* @param pixelY Y coordinate in pixels in the screen
* @param spanX horizontal cell span
* @param spanY vertical cell span
* @param dragView view being dragged in reorder
* @return the configuration that represents the found reorder
*/
public ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX,
int spanY, View dragView) {
int[] result = new int[2];
if (isNearestDropLocationOccupied(pixelX, pixelY, spanX, spanY, dragView, result)) {
result[0] = result[1] = -1;
}
ItemConfiguration solution = new ItemConfiguration();
copyCurrentStateToSolution(solution, false);
solution.isSolution = result[0] != -1;
if (!solution.isSolution) {
return solution;
}
solution.cellX = result[0];
solution.cellY = result[1];
solution.spanX = spanX;
solution.spanY = spanY;
return solution;
}
/**
* When the user drags an Item in the workspace sometimes we need to move the items already in
* the workspace to make space for the new item, this function return a solution for that
@@ -2514,29 +2423,8 @@ public class CellLayout extends ViewGroup {
*/
public ItemConfiguration calculateReorder(int pixelX, int pixelY, int minSpanX, int minSpanY,
int spanX, int spanY, View dragView) {
getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
ItemConfiguration dropInPlaceSolution = dropInPlaceSolution(pixelX, pixelY, spanX, spanY,
dragView);
// Find a solution involving pushing / displacing any items in the way
ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration());
// We attempt the approach which doesn't shuffle views at all
ItemConfiguration closestSpaceSolution = closestEmptySpaceReorder(pixelX, pixelY, minSpanX,
minSpanY, spanX, spanY);
// If the reorder solution requires resizing (shrinking) the item being dropped, we instead
// favor a solution in which the item is not resized, but
if (swapSolution.isSolution && swapSolution.area() >= closestSpaceSolution.area()) {
return swapSolution;
} else if (closestSpaceSolution.isSolution) {
return closestSpaceSolution;
} else if (dropInPlaceSolution.isSolution) {
return dropInPlaceSolution;
}
return null;
return createReorderAlgorithm().calculateReorder(pixelX, pixelY, minSpanX, minSpanY,
spanX, spanY, dragView);
}
int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
@@ -2588,7 +2476,7 @@ public class CellLayout extends ViewGroup {
* {@link MODE_ON_DROP}, {@link MODE_ON_DROP_EXTERNAL}, {@link MODE_ACCEPT_DROP}
* defined in {@link CellLayout}.
*/
void performReorder(ItemConfiguration solution, View dragView, int mode) {
public void performReorder(ItemConfiguration solution, View dragView, int mode) {
if (mode == MODE_SHOW_REORDER_HINT) {
beginOrAdjustReorderPreviewAnimations(solution, dragView,
ReorderPreviewAnimation.MODE_HINT);
@@ -2634,38 +2522,41 @@ public class CellLayout extends ViewGroup {
return mItemPlacementDirty;
}
static class ItemConfiguration extends CellAndSpan {
final ArrayMap<View, CellAndSpan> map = new ArrayMap<>();
/**
* Represents the solution to a reorder of items in the Workspace.
*/
public static class ItemConfiguration extends CellAndSpan {
public final ArrayMap<View, CellAndSpan> map = new ArrayMap<>();
private final ArrayMap<View, CellAndSpan> savedMap = new ArrayMap<>();
final ArrayList<View> sortedViews = new ArrayList<>();
ArrayList<View> intersectingViews;
boolean isSolution = false;
public final ArrayList<View> sortedViews = new ArrayList<>();
public ArrayList<View> intersectingViews;
public boolean isSolution = false;
void save() {
public void save() {
// Copy current state into savedMap
for (View v: map.keySet()) {
savedMap.get(v).copyFrom(map.get(v));
}
}
void restore() {
public void restore() {
// Restore current state from savedMap
for (View v: savedMap.keySet()) {
map.get(v).copyFrom(savedMap.get(v));
}
}
void add(View v, CellAndSpan cs) {
public void add(View v, CellAndSpan cs) {
map.put(v, cs);
savedMap.put(v, new CellAndSpan());
sortedViews.add(v);
}
int area() {
public int area() {
return spanX * spanY;
}
void getBoundingRectForViews(ArrayList<View> views, Rect outRect) {
public void getBoundingRectForViews(ArrayList<View> views, Rect outRect) {
boolean first = true;
for (View v: views) {
CellAndSpan c = map.get(v);
@@ -23,11 +23,11 @@ import android.util.AttributeSet;
import android.view.View;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.celllayout.MulticellReorderAlgorithm;
import com.android.launcher3.celllayout.ReorderAlgorithm;
import com.android.launcher3.util.CellAndSpan;
import com.android.launcher3.util.GridOccupancy;
import java.util.function.Supplier;
/**
* CellLayout that simulates a split in the middle for use in foldable devices.
*/
@@ -36,8 +36,6 @@ public class MultipageCellLayout extends CellLayout {
private final Drawable mLeftBackground;
private final Drawable mRightBackground;
private View mSeam;
private boolean mSeamWasAdded = false;
public MultipageCellLayout(Context context) {
@@ -62,7 +60,6 @@ public class MultipageCellLayout extends CellLayout {
mCountX = deviceProfile.inv.numColumns * 2;
mCountY = deviceProfile.inv.numRows;
mSeam = new View(getContext());
setGridSize(mCountX, mCountY);
}
@@ -74,90 +71,18 @@ public class MultipageCellLayout extends CellLayout {
cellX++;
}
int finalCellX = cellX;
return simulateSeam(
return ((MulticellReorderAlgorithm) createReorderAlgorithm()).simulateSeam(
() -> super.createAreaForResize(finalCellX, cellY, spanX, spanY, dragView,
direction, commit));
}
@Override
ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, int minSpanX, int minSpanY,
int spanX, int spanY) {
return removeSeamFromSolution(simulateSeam(
() -> super.closestEmptySpaceReorder(pixelX, pixelY, minSpanX, minSpanY, spanX,
spanY)));
public ReorderAlgorithm createReorderAlgorithm() {
return new MulticellReorderAlgorithm(this);
}
@Override
protected ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
ItemConfiguration solution) {
return removeSeamFromSolution(simulateSeam(
() -> super.findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
direction, dragView, decX, solution)));
}
@Override
public ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX, int spanY,
View dragView) {
return removeSeamFromSolution(simulateSeam(
() -> super.dropInPlaceSolution(pixelX, pixelY, spanX, spanY, dragView)));
}
void addSeam() {
CellLayoutLayoutParams lp = new CellLayoutLayoutParams(mCountX / 2, 0, 1, mCountY);
mSeamWasAdded = true;
lp.canReorder = false;
mCountX++;
mShortcutsAndWidgets.addViewInLayout(mSeam, lp);
mOccupied = createGridOccupancyWithSeam(mOccupied);
mTmpOccupied = new GridOccupancy(mCountX, mCountY);
}
void removeSeam() {
mCountX--;
mShortcutsAndWidgets.removeViewInLayout(mSeam);
mTmpOccupied = new GridOccupancy(mCountX, mCountY);
mSeamWasAdded = false;
}
protected <T> T simulateSeam(Supplier<T> f) {
if (mSeamWasAdded) {
return f.get();
}
GridOccupancy auxGrid = mOccupied;
addSeam();
T res = f.get();
removeSeam();
mOccupied = auxGrid;
return res;
}
private ItemConfiguration removeSeamFromSolution(ItemConfiguration solution) {
solution.map.forEach((view, cell) -> cell.cellX = cell.cellX > mCountX / 2
? cell.cellX - 1 : cell.cellX);
solution.cellX = solution.cellX > mCountX / 2 ? solution.cellX - 1 : solution.cellX;
return solution;
}
GridOccupancy createGridOccupancyWithSeam(GridOccupancy gridOccupancy) {
GridOccupancy grid = new GridOccupancy(getCountX(), getCountY());
for (int x = 0; x < getCountX(); x++) {
for (int y = 0; y < getCountY(); y++) {
int offset = x >= getCountX() / 2 ? 1 : 0;
if (x == getCountX() / 2) {
grid.cells[x][y] = true;
} else {
grid.cells[x][y] = gridOccupancy.cells[x - offset][y];
}
}
}
return grid;
}
@Override
protected void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
public void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
int childCount = mShortcutsAndWidgets.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = mShortcutsAndWidgets.getChildAt(i);
@@ -196,4 +121,24 @@ public class MultipageCellLayout extends CellLayout {
mLeftBackground.setBounds(rect.left, rect.top, rect.right / 2 - 20, rect.bottom);
mRightBackground.setBounds(rect.right / 2 + 20, rect.top, rect.right, rect.bottom);
}
public void setCountX(int countX) {
mCountX = countX;
}
public void setCountY(int countY) {
mCountY = countY;
}
public void setOccupied(GridOccupancy occupied) {
mOccupied = occupied;
}
public boolean isSeamWasAdded() {
return mSeamWasAdded;
}
public void setSeamWasAdded(boolean seamWasAdded) {
mSeamWasAdded = seamWasAdded;
}
}
@@ -0,0 +1,127 @@
/*
* Copyright (C) 2023 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.celllayout;
import android.view.View;
import com.android.launcher3.CellLayout;
import com.android.launcher3.MultipageCellLayout;
import com.android.launcher3.util.GridOccupancy;
import java.util.function.Supplier;
/**
* Variant of ReorderAlgorithm which simulates a foldable screen and adds a seam in the middle
* to prevent items to be placed in the middle.
*/
public class MulticellReorderAlgorithm extends ReorderAlgorithm {
private final View mSeam;
public MulticellReorderAlgorithm(CellLayout cellLayout) {
super(cellLayout);
mSeam = new View(cellLayout.getContext());
}
private CellLayout.ItemConfiguration removeSeamFromSolution(
CellLayout.ItemConfiguration solution) {
solution.map.forEach((view, cell) -> cell.cellX =
cell.cellX > mCellLayout.getCountX() / 2 ? cell.cellX - 1 : cell.cellX);
solution.cellX =
solution.cellX > mCellLayout.getCountX() / 2 ? solution.cellX - 1 : solution.cellX;
return solution;
}
@Override
public CellLayout.ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY,
int minSpanX, int minSpanY,
int spanX, int spanY) {
return removeSeamFromSolution(simulateSeam(
() -> super.closestEmptySpaceReorder(pixelX, pixelY, minSpanX, minSpanY, spanX,
spanY)));
}
@Override
public CellLayout.ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
CellLayout.ItemConfiguration solution) {
return removeSeamFromSolution(simulateSeam(
() -> super.findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
direction, dragView, decX, solution)));
}
@Override
public CellLayout.ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX,
int spanY,
View dragView) {
return removeSeamFromSolution(simulateSeam(
() -> super.dropInPlaceSolution(pixelX, pixelY, spanX, spanY, dragView)));
}
void addSeam() {
MultipageCellLayout mcl = (MultipageCellLayout) mCellLayout;
mcl.setSeamWasAdded(true);
CellLayoutLayoutParams lp = new CellLayoutLayoutParams(mcl.getCountX() / 2, 0, 1,
mcl.getCountY());
lp.canReorder = false;
mcl.setCountX(mcl.getCountX() + 1);
mcl.getShortcutsAndWidgets().addViewInLayout(mSeam, lp);
mcl.setOccupied(createGridOccupancyWithSeam(mcl.getOccupied()));
mcl.mTmpOccupied = new GridOccupancy(mcl.getCountX(), mcl.getCountY());
}
void removeSeam() {
MultipageCellLayout mcl = (MultipageCellLayout) mCellLayout;
mcl.setCountX(mcl.getCountX() - 1);
mcl.getShortcutsAndWidgets().removeViewInLayout(mSeam);
mcl.mTmpOccupied = new GridOccupancy(mcl.getCountX(), mcl.getCountY());
mcl.setSeamWasAdded(false);
}
/**
* The function supplied here will execute while the CellLayout has a simulated seam added.
* @param f function to run under simulation
* @param <T> return value of the supplied function
* @return Value of supplied function
*/
public <T> T simulateSeam(Supplier<T> f) {
MultipageCellLayout mcl = (MultipageCellLayout) mCellLayout;
if (mcl.isSeamWasAdded()) {
return f.get();
}
GridOccupancy auxGrid = mcl.getOccupied();
addSeam();
T res = f.get();
removeSeam();
mcl.setOccupied(auxGrid);
return res;
}
GridOccupancy createGridOccupancyWithSeam(GridOccupancy gridOccupancy) {
GridOccupancy grid = new GridOccupancy(mCellLayout.getCountX(), mCellLayout.getCountY());
for (int x = 0; x < mCellLayout.getCountX(); x++) {
for (int y = 0; y < mCellLayout.getCountY(); y++) {
int offset = x >= mCellLayout.getCountX() / 2 ? 1 : 0;
if (x == mCellLayout.getCountX() / 2) {
grid.cells[x][y] = true;
} else {
grid.cells[x][y] = gridOccupancy.cells[x - offset][y];
}
}
}
return grid;
}
}
@@ -0,0 +1,204 @@
/*
* Copyright (C) 2023 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.celllayout;
import android.view.View;
import com.android.launcher3.CellLayout;
/**
* Contains the logic of a reorder.
*
* The content of this class was extracted from {@link CellLayout} and should mimic the exact
* same behaviour.
*/
public class ReorderAlgorithm {
CellLayout mCellLayout;
public ReorderAlgorithm(CellLayout cellLayout) {
mCellLayout = cellLayout;
}
/**
* This method differs from closestEmptySpaceReorder and dropInPlaceSolution because this method
* will move items around and will change the shape of the item if possible to try to find a
* solution.
*
* When changing the size of the widget this method will try first subtracting -1 in the x
* dimension and then subtracting -1 in the y dimension until finding a possible solution or
* until it no longer can reduce the span.
*
* @param pixelX X coordinate in pixels in the screen
* @param pixelY Y coordinate in pixels in the screen
* @param minSpanX minimum possible horizontal span it will try to find a solution for.
* @param minSpanY minimum possible vertical span it will try to find a solution for.
* @param spanX horizontal cell span
* @param spanY vertical cell span
* @param direction direction in which it will try to push the items intersecting the desired
* view
* @param dragView view being dragged in reorder
* @param decX whether it will decrease the horizontal or vertical span if it can't find a
* solution for the current span.
* @param solution variable to store the solution
* @return the same solution variable
*/
public CellLayout.ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
CellLayout.ItemConfiguration solution) {
// Copy the current state into the solution. This solution will be manipulated as necessary.
mCellLayout.copyCurrentStateToSolution(solution, false);
// Copy the current occupied array into the temporary occupied array. This array will be
// manipulated as necessary to find a solution.
mCellLayout.getOccupied().copyTo(mCellLayout.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 = mCellLayout.findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY, result);
boolean success;
// 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 = mCellLayout.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 findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
direction, dragView, false, solution);
} else if (spanY > minSpanY) {
return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
direction, dragView, true, solution);
}
solution.isSolution = false;
} else {
solution.isSolution = true;
solution.cellX = result[0];
solution.cellY = result[1];
solution.spanX = spanX;
solution.spanY = spanY;
}
return solution;
}
/**
* Returns a "reorder" if there is empty space without rearranging anything.
*
* @param pixelX X coordinate in pixels in the screen
* @param pixelY Y coordinate in pixels in the screen
* @param spanX horizontal cell span
* @param spanY vertical cell span
* @param dragView view being dragged in reorder
* @return the configuration that represents the found reorder
*/
public CellLayout.ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX,
int spanY, View dragView) {
int[] result = new int[2];
if (mCellLayout.isNearestDropLocationOccupied(pixelX, pixelY, spanX, spanY, dragView,
result)) {
result[0] = result[1] = -1;
}
CellLayout.ItemConfiguration solution = new CellLayout.ItemConfiguration();
mCellLayout.copyCurrentStateToSolution(solution, false);
solution.isSolution = result[0] != -1;
if (!solution.isSolution) {
return solution;
}
solution.cellX = result[0];
solution.cellY = result[1];
solution.spanX = spanX;
solution.spanY = spanY;
return solution;
}
/**
* Returns a "reorder" where we simply drop the item in the closest empty space, without moving
* any other item in the way.
*
* @param pixelX X coordinate in pixels in the screen
* @param pixelY Y coordinate in pixels in the screen
* @param spanX horizontal cell span
* @param spanY vertical cell span
* @return the configuration that represents the found reorder
*/
public CellLayout.ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY,
int minSpanX, int minSpanY, int spanX, int spanY) {
CellLayout.ItemConfiguration solution = new CellLayout.ItemConfiguration();
int[] result = new int[2];
int[] resultSpan = new int[2];
mCellLayout.findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
resultSpan);
if (result[0] >= 0 && result[1] >= 0) {
mCellLayout.copyCurrentStateToSolution(solution, false);
solution.cellX = result[0];
solution.cellY = result[1];
solution.spanX = resultSpan[0];
solution.spanY = resultSpan[1];
solution.isSolution = true;
} else {
solution.isSolution = false;
}
return solution;
}
/**
* When the user drags an Item in the workspace sometimes we need to move the items already in
* the workspace to make space for the new item, this function return a solution for that
* reorder.
*
* @param pixelX X coordinate in the screen of the dragView in pixels
* @param pixelY Y coordinate in the screen of the dragView in pixels
* @param minSpanX minimum horizontal span the item can be shrunk to
* @param minSpanY minimum vertical span the item can be shrunk to
* @param spanX occupied horizontal span
* @param spanY occupied vertical span
* @param dragView the view of the item being draged
* @return returns a solution for the given parameters, the solution contains all the icons and
* the locations they should be in the given solution.
*/
public CellLayout.ItemConfiguration calculateReorder(int pixelX, int pixelY, int minSpanX,
int minSpanY, int spanX, int spanY, View dragView) {
mCellLayout.getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView,
mCellLayout.mDirectionVector);
CellLayout.ItemConfiguration dropInPlaceSolution = dropInPlaceSolution(pixelX, pixelY,
spanX, spanY,
dragView);
// Find a solution involving pushing / displacing any items in the way
CellLayout.ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX,
minSpanY, spanX, spanY, mCellLayout.mDirectionVector, dragView, true,
new CellLayout.ItemConfiguration());
// We attempt the approach which doesn't shuffle views at all
CellLayout.ItemConfiguration closestSpaceSolution = closestEmptySpaceReorder(
pixelX, pixelY, minSpanX, minSpanY, spanX, spanY);
// If the reorder solution requires resizing (shrinking) the item being dropped, we instead
// favor a solution in which the item is not resized, but
if (swapSolution.isSolution && swapSolution.area() >= closestSpaceSolution.area()) {
return swapSolution;
} else if (closestSpaceSolution.isSolution) {
return closestSpaceSolution;
} else if (dropInPlaceSolution.isSolution) {
return dropInPlaceSolution;
}
return null;
}
}