1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.celllayout; 17 18 import android.graphics.Rect; 19 import android.view.View; 20 21 import com.android.launcher3.CellLayout; 22 import com.android.launcher3.util.CellAndSpan; 23 import com.android.launcher3.util.GridOccupancy; 24 25 import java.util.ArrayList; 26 import java.util.Comparator; 27 import java.util.List; 28 import java.util.Map.Entry; 29 import java.util.stream.Collectors; 30 31 /** 32 * Contains the logic of a reorder. 33 * 34 * The content of this class was extracted from {@link CellLayout} and should mimic the exact 35 * same behaviour. 36 */ 37 public class ReorderAlgorithm { 38 39 CellLayout mCellLayout; 40 ReorderAlgorithm(CellLayout cellLayout)41 public ReorderAlgorithm(CellLayout cellLayout) { 42 mCellLayout = cellLayout; 43 } 44 45 /** 46 * This method differs from closestEmptySpaceReorder and dropInPlaceSolution because this method 47 * will move items around and will change the shape of the item if possible to try to find a 48 * solution. 49 * <p> 50 * When changing the size of the widget this method will try first subtracting -1 in the x 51 * dimension and then subtracting -1 in the y dimension until finding a possible solution or 52 * until it no longer can reduce the span. 53 * @param decX whether it will decrease the horizontal or vertical span if it can't find a 54 * solution for the current span. 55 * @return the same solution variable 56 */ findReorderSolution(ReorderParameters reorderParameters, boolean decX)57 public ItemConfiguration findReorderSolution(ReorderParameters reorderParameters, 58 boolean decX) { 59 return findReorderSolution(reorderParameters, mCellLayout.mDirectionVector, decX); 60 } 61 62 /** 63 * This method differs from closestEmptySpaceReorder and dropInPlaceSolution because this method 64 * will move items around and will change the shape of the item if possible to try to find a 65 * solution. 66 * <p> 67 * When changing the size of the widget this method will try first subtracting -1 in the x 68 * dimension and then subtracting -1 in the y dimension until finding a possible solution or 69 * until it no longer can reduce the span. 70 * @param direction Direction to attempt to push items if needed 71 * @param decX whether it will decrease the horizontal or vertical span if it can't find a 72 * solution for the current span. 73 * @return the same solution variable 74 */ findReorderSolution(ReorderParameters reorderParameters, int[] direction, boolean decX)75 public ItemConfiguration findReorderSolution(ReorderParameters reorderParameters, 76 int[] direction, boolean decX) { 77 return findReorderSolutionRecursive(reorderParameters.getPixelX(), 78 reorderParameters.getPixelY(), reorderParameters.getMinSpanX(), 79 reorderParameters.getMinSpanY(), reorderParameters.getSpanX(), 80 reorderParameters.getSpanY(), direction, 81 reorderParameters.getDragView(), decX, reorderParameters.getSolution()); 82 } 83 findReorderSolutionRecursive(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution)84 private ItemConfiguration findReorderSolutionRecursive(int pixelX, int pixelY, int minSpanX, 85 int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX, 86 ItemConfiguration solution) { 87 // Copy the current state into the solution. This solution will be manipulated as necessary. 88 mCellLayout.copyCurrentStateToSolution(solution); 89 // Copy the current occupied array into the temporary occupied array. This array will be 90 // manipulated as necessary to find a solution. 91 mCellLayout.getOccupied().copyTo(mCellLayout.mTmpOccupied); 92 93 // We find the nearest cell into which we would place the dragged item, assuming there's 94 // nothing in its way. 95 int[] result = new int[2]; 96 result = mCellLayout.findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY, result); 97 98 boolean success; 99 // First we try the exact nearest position of the item being dragged, 100 // we will then want to try to move this around to other neighbouring positions 101 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView, 102 solution); 103 104 if (!success) { 105 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in 106 // x, then 1 in y etc. 107 if (spanX > minSpanX && (minSpanY == spanY || decX)) { 108 return findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, 109 spanY, direction, dragView, false, solution); 110 } else if (spanY > minSpanY) { 111 return findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX, 112 spanY - 1, direction, dragView, true, solution); 113 } 114 solution.isSolution = false; 115 } else { 116 solution.isSolution = true; 117 solution.cellX = result[0]; 118 solution.cellY = result[1]; 119 solution.spanX = spanX; 120 solution.spanY = spanY; 121 } 122 return solution; 123 } 124 rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction, View ignoreView, ItemConfiguration solution)125 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction, 126 View ignoreView, ItemConfiguration solution) { 127 // Return early if get invalid cell positions 128 if (cellX < 0 || cellY < 0) return false; 129 130 ArrayList<View> intersectingViews = new ArrayList<>(); 131 Rect occupiedRect = new Rect(cellX, cellY, cellX + spanX, cellY + spanY); 132 133 // Mark the desired location of the view currently being dragged. 134 if (ignoreView != null) { 135 CellAndSpan c = solution.map.get(ignoreView); 136 if (c != null) { 137 c.cellX = cellX; 138 c.cellY = cellY; 139 } 140 } 141 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY); 142 Rect r1 = new Rect(); 143 // The views need to be sorted so that the results are deterministic on the views positions 144 // and not by the views hash which is "random". 145 // The views are sorted twice, once for the X position and a second time for the Y position 146 // to ensure same order everytime. 147 Comparator<View> comparator = Comparator.comparing( 148 (View view) -> ((CellLayoutLayoutParams) view.getLayoutParams()).getCellX() 149 ).thenComparing( 150 (View view) -> ((CellLayoutLayoutParams) view.getLayoutParams()).getCellY() 151 ); 152 List<View> views = solution.map.keySet().stream() 153 .sorted(comparator) 154 .collect(Collectors.toList()); 155 for (View child : views) { 156 if (child == ignoreView) continue; 157 CellAndSpan c = solution.map.get(child); 158 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams(); 159 r1.set(c.cellX, c.cellY, c.cellX + c.spanX, c.cellY + c.spanY); 160 if (Rect.intersects(r0, r1)) { 161 if (!lp.canReorder) { 162 return false; 163 } 164 intersectingViews.add(child); 165 } 166 } 167 168 solution.intersectingViews = intersectingViews; 169 170 // First we try to find a solution which respects the push mechanic. That is, 171 // we try to find a solution such that no displaced item travels through another item 172 // without also displacing that item. 173 if (attemptPushInDirection(intersectingViews, occupiedRect, direction, ignoreView, 174 solution)) { 175 return true; 176 } 177 178 // Next we try moving the views as a block, but without requiring the push mechanic. 179 if (addViewsToTempLocation(intersectingViews, occupiedRect, direction, ignoreView, 180 solution)) { 181 return true; 182 } 183 184 // Ok, they couldn't move as a block, let's move them individually 185 for (View v : intersectingViews) { 186 if (!addViewToTempLocation(v, occupiedRect, direction, solution)) { 187 return false; 188 } 189 } 190 return true; 191 } 192 addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop, int[] direction, ItemConfiguration currentState)193 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop, int[] direction, 194 ItemConfiguration currentState) { 195 CellAndSpan c = currentState.map.get(v); 196 boolean success = false; 197 mCellLayout.mTmpOccupied.markCells(c, false); 198 mCellLayout.mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true); 199 200 int[] tmpLocation = findNearestArea(c.cellX, c.cellY, c.spanX, c.spanY, direction, 201 mCellLayout.mTmpOccupied.cells, null, new int[2]); 202 203 if (tmpLocation[0] >= 0 && tmpLocation[1] >= 0) { 204 c.cellX = tmpLocation[0]; 205 c.cellY = tmpLocation[1]; 206 success = true; 207 } 208 mCellLayout.mTmpOccupied.markCells(c, true); 209 return success; 210 } 211 pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop, int[] direction, View dragView, ItemConfiguration currentState)212 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop, 213 int[] direction, View dragView, ItemConfiguration currentState) { 214 215 ViewCluster cluster = new ViewCluster(mCellLayout, views, currentState); 216 Rect clusterRect = cluster.getBoundingRect(); 217 int whichEdge; 218 int pushDistance; 219 boolean fail = false; 220 221 // Determine the edge of the cluster that will be leading the push and how far 222 // the cluster must be shifted. 223 if (direction[0] < 0) { 224 whichEdge = ViewCluster.LEFT; 225 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left; 226 } else if (direction[0] > 0) { 227 whichEdge = ViewCluster.RIGHT; 228 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left; 229 } else if (direction[1] < 0) { 230 whichEdge = ViewCluster.TOP; 231 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top; 232 } else { 233 whichEdge = ViewCluster.BOTTOM; 234 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top; 235 } 236 237 // Break early for invalid push distance. 238 if (pushDistance <= 0) { 239 return false; 240 } 241 242 // Mark the occupied state as false for the group of views we want to move. 243 for (View v : views) { 244 CellAndSpan c = currentState.map.get(v); 245 mCellLayout.mTmpOccupied.markCells(c, false); 246 } 247 248 // We save the current configuration -- if we fail to find a solution we will revert 249 // to the initial state. The process of finding a solution modifies the configuration 250 // in place, hence the need for revert in the failure case. 251 currentState.save(); 252 253 // The pushing algorithm is simplified by considering the views in the order in which 254 // they would be pushed by the cluster. For example, if the cluster is leading with its 255 // left edge, we consider sort the views by their right edge, from right to left. 256 cluster.sortConfigurationForEdgePush(whichEdge); 257 258 while (pushDistance > 0 && !fail) { 259 for (View v : currentState.sortedViews) { 260 // For each view that isn't in the cluster, we see if the leading edge of the 261 // cluster is contacting the edge of that view. If so, we add that view to the 262 // cluster. 263 if (!cluster.views.contains(v) && v != dragView) { 264 if (cluster.isViewTouchingEdge(v, whichEdge)) { 265 CellLayoutLayoutParams lp = (CellLayoutLayoutParams) v.getLayoutParams(); 266 if (!lp.canReorder) { 267 // The push solution includes the all apps button, this is not viable. 268 fail = true; 269 break; 270 } 271 cluster.addView(v); 272 CellAndSpan c = currentState.map.get(v); 273 274 // Adding view to cluster, mark it as not occupied. 275 mCellLayout.mTmpOccupied.markCells(c, false); 276 } 277 } 278 } 279 pushDistance--; 280 281 // The cluster has been completed, now we move the whole thing over in the appropriate 282 // direction. 283 cluster.shift(whichEdge, 1); 284 } 285 286 boolean foundSolution = false; 287 clusterRect = cluster.getBoundingRect(); 288 289 // Due to the nature of the algorithm, the only check required to verify a valid solution 290 // is to ensure that completed shifted cluster lies completely within the cell layout. 291 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCellLayout.getCountX() 292 && clusterRect.top >= 0 && clusterRect.bottom <= mCellLayout.getCountY()) { 293 foundSolution = true; 294 } else { 295 currentState.restore(); 296 } 297 298 // In either case, we set the occupied array as marked for the location of the views 299 for (View v : cluster.views) { 300 CellAndSpan c = currentState.map.get(v); 301 mCellLayout.mTmpOccupied.markCells(c, true); 302 } 303 304 return foundSolution; 305 } 306 revertDir(int[] direction)307 private void revertDir(int[] direction) { 308 direction[0] *= -1; 309 direction[1] *= -1; 310 } 311 312 // This method tries to find a reordering solution which satisfies the push mechanic by trying 313 // to push items in each of the cardinal directions, in an order based on the direction vector 314 // passed. attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied, int[] direction, View ignoreView, ItemConfiguration solution)315 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied, 316 int[] direction, View ignoreView, ItemConfiguration solution) { 317 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) { 318 // If the direction vector has two non-zero components, we try pushing 319 // separately in each of the components. 320 int temp; 321 for (int j = 0; j < 2; j++) { 322 for (int i = 1; i >= 0; i--) { 323 temp = direction[i]; 324 direction[i] = 0; 325 if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView, 326 solution)) { 327 return true; 328 } 329 direction[i] = temp; 330 } 331 revertDir(direction); 332 } 333 } else { 334 // If the direction vector has a single non-zero component, we push first in the 335 // direction of the vector 336 int temp; 337 for (int j = 0; j < 2; j++) { 338 for (int i = 0; i < 2; i++) { 339 if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView, 340 solution)) { 341 return true; 342 } 343 revertDir(direction); 344 } 345 // Swap the components 346 temp = direction[1]; 347 direction[1] = direction[0]; 348 direction[0] = temp; 349 } 350 } 351 return false; 352 } 353 addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop, int[] direction, View dragView, ItemConfiguration currentState)354 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop, 355 int[] direction, View dragView, ItemConfiguration currentState) { 356 if (views.isEmpty()) return true; 357 358 boolean success = false; 359 Rect boundingRect = new Rect(); 360 // We construct a rect which represents the entire group of views passed in 361 currentState.getBoundingRectForViews(views, boundingRect); 362 363 // Mark the occupied state as false for the group of views we want to move. 364 for (View v : views) { 365 CellAndSpan c = currentState.map.get(v); 366 mCellLayout.mTmpOccupied.markCells(c, false); 367 } 368 369 GridOccupancy blockOccupied = new GridOccupancy(boundingRect.width(), 370 boundingRect.height()); 371 int top = boundingRect.top; 372 int left = boundingRect.left; 373 // We mark more precisely which parts of the bounding rect are truly occupied, allowing 374 // for interlocking. 375 for (View v : views) { 376 CellAndSpan c = currentState.map.get(v); 377 blockOccupied.markCells(c.cellX - left, c.cellY - top, c.spanX, c.spanY, true); 378 } 379 380 mCellLayout.mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true); 381 382 int[] tmpLocation = findNearestArea(boundingRect.left, boundingRect.top, 383 boundingRect.width(), boundingRect.height(), direction, 384 mCellLayout.mTmpOccupied.cells, blockOccupied.cells, new int[2]); 385 386 // If we successfully found a location by pushing the block of views, we commit it 387 if (tmpLocation[0] >= 0 && tmpLocation[1] >= 0) { 388 int deltaX = tmpLocation[0] - boundingRect.left; 389 int deltaY = tmpLocation[1] - boundingRect.top; 390 for (View v : views) { 391 CellAndSpan c = currentState.map.get(v); 392 c.cellX += deltaX; 393 c.cellY += deltaY; 394 } 395 success = true; 396 } 397 398 // In either case, we set the occupied array as marked for the location of the views 399 for (View v : views) { 400 CellAndSpan c = currentState.map.get(v); 401 mCellLayout.mTmpOccupied.markCells(c, true); 402 } 403 return success; 404 } 405 406 /** 407 * Returns a "reorder" if there is empty space without rearranging anything. 408 * 409 * @return the configuration that represents the found reorder 410 */ dropInPlaceSolution(ReorderParameters reorderParameters)411 public ItemConfiguration dropInPlaceSolution(ReorderParameters reorderParameters) { 412 int[] result = mCellLayout.findNearestAreaIgnoreOccupied(reorderParameters.getPixelX(), 413 reorderParameters.getPixelY(), reorderParameters.getSpanX(), 414 reorderParameters.getSpanY(), new int[2]); 415 ItemConfiguration solution = new ItemConfiguration(); 416 mCellLayout.copyCurrentStateToSolution(solution); 417 418 solution.isSolution = !isConfigurationRegionOccupied( 419 new Rect(result[0], result[1], result[0] + reorderParameters.getSpanX(), 420 result[1] + reorderParameters.getSpanY()), solution, 421 reorderParameters.getDragView()); 422 if (!solution.isSolution) { 423 return solution; 424 } 425 solution.cellX = result[0]; 426 solution.cellY = result[1]; 427 solution.spanX = reorderParameters.getSpanX(); 428 solution.spanY = reorderParameters.getSpanY(); 429 return solution; 430 } 431 isConfigurationRegionOccupied(Rect region, ItemConfiguration configuration, View ignoreView)432 private boolean isConfigurationRegionOccupied(Rect region, ItemConfiguration configuration, 433 View ignoreView) { 434 return configuration.map 435 .entrySet() 436 .stream() 437 .filter(entry -> entry.getKey() != ignoreView) 438 .map(Entry::getValue) 439 .anyMatch(cellAndSpan -> region.intersect( 440 cellAndSpan.cellX, 441 cellAndSpan.cellY, 442 cellAndSpan.cellX + cellAndSpan.spanX, 443 cellAndSpan.cellY + cellAndSpan.spanY 444 ) 445 ); 446 } 447 448 /** 449 * Returns a "reorder" where we simply drop the item in the closest empty space, without moving 450 * any other item in the way. 451 * 452 * @return the configuration that represents the found reorder 453 */ closestEmptySpaceReorder(ReorderParameters reorderParameters)454 public ItemConfiguration closestEmptySpaceReorder(ReorderParameters reorderParameters) { 455 ItemConfiguration solution = new ItemConfiguration(); 456 int[] result = new int[2]; 457 int[] resultSpan = new int[2]; 458 mCellLayout.findNearestVacantArea(reorderParameters.getPixelX(), 459 reorderParameters.getPixelY(), reorderParameters.getMinSpanX(), 460 reorderParameters.getMinSpanY(), reorderParameters.getSpanX(), 461 reorderParameters.getSpanY(), result, resultSpan); 462 if (result[0] >= 0 && result[1] >= 0) { 463 mCellLayout.copyCurrentStateToSolution(solution); 464 solution.cellX = result[0]; 465 solution.cellY = result[1]; 466 solution.spanX = resultSpan[0]; 467 solution.spanY = resultSpan[1]; 468 solution.isSolution = true; 469 } else { 470 solution.isSolution = false; 471 } 472 return solution; 473 } 474 475 /** 476 * When the user drags an Item in the workspace sometimes we need to move the items already in 477 * the workspace to make space for the new item, this function return a solution for that 478 * reorder. 479 * 480 * @return returns a solution for the given parameters, the solution contains all the icons and 481 * the locations they should be in the given solution. 482 */ calculateReorder(ReorderParameters reorderParameters)483 public ItemConfiguration calculateReorder(ReorderParameters reorderParameters) { 484 getDirectionVectorForDrop(reorderParameters, mCellLayout.mDirectionVector); 485 486 ItemConfiguration dropInPlaceSolution = dropInPlaceSolution(reorderParameters); 487 488 // Find a solution involving pushing / displacing any items in the way 489 ItemConfiguration swapSolution = findReorderSolution(reorderParameters, true); 490 491 // We attempt the approach which doesn't shuffle views at all 492 ItemConfiguration closestSpaceSolution = closestEmptySpaceReorder(reorderParameters); 493 494 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead 495 // favor a solution in which the item is not resized, but 496 if (swapSolution.isSolution && swapSolution.area() >= closestSpaceSolution.area()) { 497 return swapSolution; 498 } else if (closestSpaceSolution.isSolution) { 499 return closestSpaceSolution; 500 } else if (dropInPlaceSolution.isSolution) { 501 return dropInPlaceSolution; 502 } 503 return null; 504 } 505 506 /* 507 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between 508 * the provided point and the provided cell 509 */ computeDirectionVector(float deltaX, float deltaY, int[] result)510 private void computeDirectionVector(float deltaX, float deltaY, int[] result) { 511 double angle = Math.atan(deltaY / deltaX); 512 513 result[0] = 0; 514 result[1] = 0; 515 if (Math.abs(Math.cos(angle)) > 0.5f) { 516 result[0] = (int) Math.signum(deltaX); 517 } 518 if (Math.abs(Math.sin(angle)) > 0.5f) { 519 result[1] = (int) Math.signum(deltaY); 520 } 521 } 522 523 /** 524 * This seems like it should be obvious and straight-forward, but when the direction vector 525 * needs to match with the notion of the dragView pushing other views, we have to employ 526 * a slightly more subtle notion of the direction vector. The question is what two points is 527 * the vector between? The center of the dragView and its desired destination? Not quite, as 528 * this doesn't necessarily coincide with the interaction of the dragView and items occupying 529 * those cells. Instead we use some heuristics to often lock the vector to up, down, left 530 * or right, which helps make pushing feel right. 531 */ getDirectionVectorForDrop(ReorderParameters reorderParameters, int[] resultDirection)532 public void getDirectionVectorForDrop(ReorderParameters reorderParameters, 533 int[] resultDirection) { 534 535 //TODO(adamcohen) b/151776141 use the items visual center for the direction vector 536 int[] targetDestination = new int[2]; 537 538 mCellLayout.findNearestAreaIgnoreOccupied(reorderParameters.getPixelX(), 539 reorderParameters.getPixelY(), reorderParameters.getSpanX(), 540 reorderParameters.getSpanY(), targetDestination); 541 Rect dragRect = new Rect(); 542 mCellLayout.cellToRect(targetDestination[0], targetDestination[1], 543 reorderParameters.getSpanX(), reorderParameters.getSpanY(), dragRect); 544 dragRect.offset(reorderParameters.getPixelX() - dragRect.centerX(), 545 reorderParameters.getPixelY() - dragRect.centerY()); 546 547 Rect region = new Rect(targetDestination[0], targetDestination[1], 548 targetDestination[0] + reorderParameters.getSpanX(), 549 targetDestination[1] + reorderParameters.getSpanY()); 550 Rect dropRegionRect = mCellLayout.getIntersectingRectanglesInRegion(region, 551 reorderParameters.getDragView()); 552 if (dropRegionRect == null) dropRegionRect = new Rect(region); 553 554 int dropRegionSpanX = dropRegionRect.width(); 555 int dropRegionSpanY = dropRegionRect.height(); 556 557 mCellLayout.cellToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(), 558 dropRegionRect.height(), dropRegionRect); 559 560 int deltaX = (dropRegionRect.centerX() - reorderParameters.getPixelX()) 561 / reorderParameters.getSpanX(); 562 int deltaY = (dropRegionRect.centerY() - reorderParameters.getPixelY()) 563 / reorderParameters.getSpanY(); 564 565 if (dropRegionSpanX == mCellLayout.getCountX() 566 || reorderParameters.getSpanX() == mCellLayout.getCountX()) { 567 deltaX = 0; 568 } 569 if (dropRegionSpanY == mCellLayout.getCountY() 570 || reorderParameters.getSpanY() == mCellLayout.getCountY()) { 571 deltaY = 0; 572 } 573 574 if (deltaX == 0 && deltaY == 0) { 575 // No idea what to do, give a random direction. 576 resultDirection[0] = 1; 577 resultDirection[1] = 0; 578 } else { 579 computeDirectionVector(deltaX, deltaY, resultDirection); 580 } 581 } 582 583 /** 584 * Find a vacant area that will fit the given bounds nearest the requested 585 * cell location, and will also weigh in a suggested direction vector of the 586 * desired location. This method computers distance based on unit grid distances, 587 * not pixel distances. 588 * 589 * @param cellX The X cell nearest to which you want to search for a vacant area. 590 * @param cellY The Y cell nearest which you want to search for a vacant area. 591 * @param spanX Horizontal span of the object. 592 * @param spanY Vertical span of the object. 593 * @param direction The favored direction in which the views should move from x, y 594 * @param occupied The array which represents which cells in the CellLayout are occupied 595 * @param blockOccupied The array which represents which cells in the specified block (cellX, 596 * cellY, spanX, spanY) are occupied. This is used when try to move a group 597 * of views. 598 * @param result Array in which to place the result, or null (in which case a new array 599 * will 600 * be allocated) 601 * @return The X, Y cell of a vacant area that can contain this object, 602 * nearest the requested location. 603 */ findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction, boolean[][] occupied, boolean[][] blockOccupied, int[] result)604 public int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction, 605 boolean[][] occupied, boolean[][] blockOccupied, int[] result) { 606 // Keep track of best-scoring drop area 607 final int[] bestXY = result != null ? result : new int[2]; 608 float bestDistance = Float.MAX_VALUE; 609 int bestDirectionScore = Integer.MIN_VALUE; 610 611 final int countX = mCellLayout.getCountX(); 612 final int countY = mCellLayout.getCountY(); 613 614 for (int y = 0; y < countY - (spanY - 1); y++) { 615 inner: 616 for (int x = 0; x < countX - (spanX - 1); x++) { 617 // First, let's see if this thing fits anywhere 618 for (int i = 0; i < spanX; i++) { 619 for (int j = 0; j < spanY; j++) { 620 if (occupied[x + i][y + j] && (blockOccupied == null 621 || blockOccupied[i][j])) { 622 continue inner; 623 } 624 } 625 } 626 627 float distance = (float) Math.hypot(x - cellX, y - cellY); 628 int[] curDirection = new int[2]; 629 computeDirectionVector(x - cellX, y - cellY, curDirection); 630 // The direction score is just the dot product of the two candidate direction 631 // and that passed in. 632 int curDirectionScore = 633 direction[0] * curDirection[0] + direction[1] * curDirection[1]; 634 if (Float.compare(distance, bestDistance) < 0 || (Float.compare(distance, 635 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) { 636 bestDistance = distance; 637 bestDirectionScore = curDirectionScore; 638 bestXY[0] = x; 639 bestXY[1] = y; 640 } 641 } 642 } 643 644 // Return -1, -1 if no suitable location found 645 if (bestDistance == Float.MAX_VALUE) { 646 bestXY[0] = -1; 647 bestXY[1] = -1; 648 } 649 return bestXY; 650 } 651 } 652