Move logic only used on the reorder from the cellLayout to ReorderAlgorithm

This is a no-op thoroughly tested by ReorderAlgorithmUnitTest.

Flag: NA
Bug: 229292911
Test: ReorderAlgorithmUnitTest
Change-Id: I9909477274aadbbf928c9fdeb4157d697c4c7175
This commit is contained in:
Sebastian Franco
2023-11-30 11:04:30 -06:00
parent 0734ef2d7c
commit a7ff1558c9
2 changed files with 268 additions and 267 deletions
@@ -20,6 +20,7 @@ import android.view.View;
import com.android.launcher3.CellLayout;
import com.android.launcher3.util.CellAndSpan;
import com.android.launcher3.util.GridOccupancy;
import java.util.ArrayList;
import java.util.Comparator;
@@ -157,14 +158,14 @@ public class ReorderAlgorithm {
// 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 (mCellLayout.attemptPushInDirection(intersectingViews, occupiedRect, direction,
if (attemptPushInDirection(intersectingViews, occupiedRect, direction,
ignoreView,
solution)) {
return true;
}
// Next we try moving the views as a block, but without requiring the push mechanic.
if (mCellLayout.addViewsToTempLocation(intersectingViews, occupiedRect, direction,
if (addViewsToTempLocation(intersectingViews, occupiedRect, direction,
ignoreView,
solution)) {
return true;
@@ -172,13 +173,276 @@ public class ReorderAlgorithm {
// Ok, they couldn't move as a block, let's move them individually
for (View v : intersectingViews) {
if (!mCellLayout.addViewToTempLocation(v, occupiedRect, direction, solution)) {
if (!addViewToTempLocation(v, occupiedRect, direction, solution)) {
return false;
}
}
return true;
}
private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
int[] direction, ItemConfiguration currentState) {
CellAndSpan c = currentState.map.get(v);
boolean success = false;
mCellLayout.mTmpOccupied.markCells(c, false);
mCellLayout.mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
int[] tmpLocation = mCellLayout.findNearestArea(c.cellX, c.cellY, c.spanX, c.spanY,
direction, mCellLayout.mTmpOccupied.cells, null, new int[2]);
if (tmpLocation[0] >= 0 && tmpLocation[1] >= 0) {
c.cellX = tmpLocation[0];
c.cellY = tmpLocation[1];
success = true;
}
mCellLayout.mTmpOccupied.markCells(c, true);
return success;
}
private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
int[] direction, View dragView, ItemConfiguration currentState) {
ViewCluster cluster = new ViewCluster(mCellLayout, 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);
mCellLayout.mTmpOccupied.markCells(c, 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)) {
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) 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.
mCellLayout.mTmpOccupied.markCells(c, 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 <= mCellLayout.getCountX()
&& clusterRect.top >= 0 && clusterRect.bottom <= mCellLayout.getCountY()) {
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);
mCellLayout.mTmpOccupied.markCells(c, true);
}
return foundSolution;
}
// 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 addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop,
int[] direction, View dragView, ItemConfiguration currentState) {
if (views.isEmpty()) return true;
boolean success = false;
Rect boundingRect = new Rect();
// We construct a rect which represents the entire group of views passed in
currentState.getBoundingRectForViews(views, boundingRect);
// 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);
mCellLayout.mTmpOccupied.markCells(c, false);
}
GridOccupancy blockOccupied = new GridOccupancy(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);
blockOccupied.markCells(c.cellX - left, c.cellY - top, c.spanX, c.spanY, true);
}
mCellLayout.mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
int[] tmpLocation = mCellLayout.findNearestArea(boundingRect.left, boundingRect.top,
boundingRect.width(), boundingRect.height(), direction,
mCellLayout.mTmpOccupied.cells, blockOccupied.cells, new int[2]);
// If we successfully found a location by pushing the block of views, we commit it
if (tmpLocation[0] >= 0 && tmpLocation[1] >= 0) {
int deltaX = tmpLocation[0] - boundingRect.left;
int deltaY = tmpLocation[1] - boundingRect.top;
for (View v : views) {
CellAndSpan c = currentState.map.get(v);
c.cellX += deltaX;
c.cellY += 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);
mCellLayout.mTmpOccupied.markCells(c, true);
}
return success;
}
/**
* Returns a "reorder" if there is empty space without rearranging anything.
*