1 /* 2 * Copyright (C) 2015 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 17 package com.android.launcher3.folder; 18 19 import android.annotation.SuppressLint; 20 import android.content.Context; 21 import android.graphics.Canvas; 22 import android.graphics.drawable.Drawable; 23 import android.util.ArrayMap; 24 import android.util.AttributeSet; 25 import android.util.Log; 26 import android.view.Gravity; 27 import android.view.LayoutInflater; 28 import android.view.View; 29 import android.view.ViewDebug; 30 31 import com.android.launcher3.BubbleTextView; 32 import com.android.launcher3.CellLayout; 33 import com.android.launcher3.DeviceProfile; 34 import com.android.launcher3.InvariantDeviceProfile; 35 import com.android.launcher3.ItemInfo; 36 import com.android.launcher3.Launcher; 37 import com.android.launcher3.LauncherAppState; 38 import com.android.launcher3.PagedView; 39 import com.android.launcher3.R; 40 import com.android.launcher3.ShortcutAndWidgetContainer; 41 import com.android.launcher3.WorkspaceItemInfo; 42 import com.android.launcher3.Utilities; 43 import com.android.launcher3.Workspace.ItemOperator; 44 import com.android.launcher3.anim.Interpolators; 45 import com.android.launcher3.keyboard.ViewGroupFocusHelper; 46 import com.android.launcher3.pageindicators.PageIndicatorDots; 47 import com.android.launcher3.touch.ItemClickHandler; 48 import com.android.launcher3.util.Thunk; 49 50 import java.util.ArrayList; 51 import java.util.Iterator; 52 import java.util.Map; 53 54 public class FolderPagedView extends PagedView<PageIndicatorDots> { 55 56 private static final String TAG = "FolderPagedView"; 57 58 private static final int REORDER_ANIMATION_DURATION = 230; 59 private static final int START_VIEW_REORDER_DELAY = 30; 60 private static final float VIEW_REORDER_DELAY_FACTOR = 0.9f; 61 62 /** 63 * Fraction of the width to scroll when showing the next page hint. 64 */ 65 private static final float SCROLL_HINT_FRACTION = 0.07f; 66 67 private static final int[] sTmpArray = new int[2]; 68 69 public final boolean mIsRtl; 70 71 private final LayoutInflater mInflater; 72 private final ViewGroupFocusHelper mFocusIndicatorHelper; 73 74 @Thunk final ArrayMap<View, Runnable> mPendingAnimations = new ArrayMap<>(); 75 76 @ViewDebug.ExportedProperty(category = "launcher") 77 private final int mMaxCountX; 78 @ViewDebug.ExportedProperty(category = "launcher") 79 private final int mMaxCountY; 80 @ViewDebug.ExportedProperty(category = "launcher") 81 private final int mMaxItemsPerPage; 82 83 private int mAllocatedContentSize; 84 @ViewDebug.ExportedProperty(category = "launcher") 85 private int mGridCountX; 86 @ViewDebug.ExportedProperty(category = "launcher") 87 private int mGridCountY; 88 89 private Folder mFolder; 90 FolderPagedView(Context context, AttributeSet attrs)91 public FolderPagedView(Context context, AttributeSet attrs) { 92 super(context, attrs); 93 InvariantDeviceProfile profile = LauncherAppState.getIDP(context); 94 mMaxCountX = profile.numFolderColumns; 95 mMaxCountY = profile.numFolderRows; 96 97 mMaxItemsPerPage = mMaxCountX * mMaxCountY; 98 99 mInflater = LayoutInflater.from(context); 100 101 mIsRtl = Utilities.isRtl(getResources()); 102 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 103 104 mFocusIndicatorHelper = new ViewGroupFocusHelper(this); 105 } 106 setFolder(Folder folder)107 public void setFolder(Folder folder) { 108 mFolder = folder; 109 mPageIndicator = folder.findViewById(R.id.folder_page_indicator); 110 initParentViews(folder); 111 } 112 113 /** 114 * Calculates the grid size such that {@param count} items can fit in the grid. 115 * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while 116 * maintaining the restrictions of {@link #mMaxCountX} & {@link #mMaxCountY}. 117 */ calculateGridSize(int count, int countX, int countY, int maxCountX, int maxCountY, int maxItemsPerPage, int[] out)118 public static void calculateGridSize(int count, int countX, int countY, int maxCountX, 119 int maxCountY, int maxItemsPerPage, int[] out) { 120 boolean done; 121 int gridCountX = countX; 122 int gridCountY = countY; 123 124 if (count >= maxItemsPerPage) { 125 gridCountX = maxCountX; 126 gridCountY = maxCountY; 127 done = true; 128 } else { 129 done = false; 130 } 131 132 while (!done) { 133 int oldCountX = gridCountX; 134 int oldCountY = gridCountY; 135 if (gridCountX * gridCountY < count) { 136 // Current grid is too small, expand it 137 if ((gridCountX <= gridCountY || gridCountY == maxCountY) 138 && gridCountX < maxCountX) { 139 gridCountX++; 140 } else if (gridCountY < maxCountY) { 141 gridCountY++; 142 } 143 if (gridCountY == 0) gridCountY++; 144 } else if ((gridCountY - 1) * gridCountX >= count && gridCountY >= gridCountX) { 145 gridCountY = Math.max(0, gridCountY - 1); 146 } else if ((gridCountX - 1) * gridCountY >= count) { 147 gridCountX = Math.max(0, gridCountX - 1); 148 } 149 done = gridCountX == oldCountX && gridCountY == oldCountY; 150 } 151 152 out[0] = gridCountX; 153 out[1] = gridCountY; 154 } 155 156 /** 157 * Sets up the grid size such that {@param count} items can fit in the grid. 158 */ setupContentDimensions(int count)159 public void setupContentDimensions(int count) { 160 mAllocatedContentSize = count; 161 calculateGridSize(count, mGridCountX, mGridCountY, mMaxCountX, mMaxCountY, mMaxItemsPerPage, 162 sTmpArray); 163 mGridCountX = sTmpArray[0]; 164 mGridCountY = sTmpArray[1]; 165 166 // Update grid size 167 for (int i = getPageCount() - 1; i >= 0; i--) { 168 getPageAt(i).setGridSize(mGridCountX, mGridCountY); 169 } 170 } 171 172 @Override dispatchDraw(Canvas canvas)173 protected void dispatchDraw(Canvas canvas) { 174 mFocusIndicatorHelper.draw(canvas); 175 super.dispatchDraw(canvas); 176 } 177 178 /** 179 * Binds items to the layout. 180 */ bindItems(ArrayList<WorkspaceItemInfo> items)181 public void bindItems(ArrayList<WorkspaceItemInfo> items) { 182 ArrayList<View> icons = new ArrayList<>(); 183 for (WorkspaceItemInfo item : items) { 184 icons.add(createNewView(item)); 185 } 186 arrangeChildren(icons, icons.size(), false); 187 } 188 allocateSpaceForRank(int rank)189 public void allocateSpaceForRank(int rank) { 190 ArrayList<View> views = new ArrayList<>(mFolder.getItemsInReadingOrder()); 191 views.add(rank, null); 192 arrangeChildren(views, views.size(), false); 193 } 194 195 /** 196 * Create space for a new item at the end, and returns the rank for that item. 197 * Also sets the current page to the last page. 198 */ allocateRankForNewItem()199 public int allocateRankForNewItem() { 200 int rank = getItemCount(); 201 allocateSpaceForRank(rank); 202 setCurrentPage(rank / mMaxItemsPerPage); 203 return rank; 204 } 205 createAndAddViewForRank(WorkspaceItemInfo item, int rank)206 public View createAndAddViewForRank(WorkspaceItemInfo item, int rank) { 207 View icon = createNewView(item); 208 allocateSpaceForRank(rank); 209 addViewForRank(icon, item, rank); 210 return icon; 211 } 212 213 /** 214 * Adds the {@param view} to the layout based on {@param rank} and updated the position 215 * related attributes. It assumes that {@param item} is already attached to the view. 216 */ addViewForRank(View view, WorkspaceItemInfo item, int rank)217 public void addViewForRank(View view, WorkspaceItemInfo item, int rank) { 218 int pagePos = rank % mMaxItemsPerPage; 219 int pageNo = rank / mMaxItemsPerPage; 220 221 item.rank = rank; 222 item.cellX = pagePos % mGridCountX; 223 item.cellY = pagePos / mGridCountX; 224 225 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); 226 lp.cellX = item.cellX; 227 lp.cellY = item.cellY; 228 getPageAt(pageNo).addViewToCellLayout(view, -1, item.getViewId(), lp, true); 229 } 230 231 @SuppressLint("InflateParams") createNewView(WorkspaceItemInfo item)232 public View createNewView(WorkspaceItemInfo item) { 233 final BubbleTextView textView = (BubbleTextView) mInflater.inflate( 234 R.layout.folder_application, null, false); 235 textView.applyFromWorkspaceItem(item); 236 textView.setHapticFeedbackEnabled(false); 237 textView.setOnClickListener(ItemClickHandler.INSTANCE); 238 textView.setOnLongClickListener(mFolder); 239 textView.setOnFocusChangeListener(mFocusIndicatorHelper); 240 241 textView.setLayoutParams(new CellLayout.LayoutParams( 242 item.cellX, item.cellY, item.spanX, item.spanY)); 243 return textView; 244 } 245 246 @Override getPageAt(int index)247 public CellLayout getPageAt(int index) { 248 return (CellLayout) getChildAt(index); 249 } 250 getCurrentCellLayout()251 public CellLayout getCurrentCellLayout() { 252 return getPageAt(getNextPage()); 253 } 254 createAndAddNewPage()255 private CellLayout createAndAddNewPage() { 256 DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile(); 257 CellLayout page = (CellLayout) mInflater.inflate(R.layout.folder_page, this, false); 258 page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx); 259 page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false); 260 page.setInvertIfRtl(true); 261 page.setGridSize(mGridCountX, mGridCountY); 262 263 addView(page, -1, generateDefaultLayoutParams()); 264 return page; 265 } 266 267 @Override getChildGap()268 protected int getChildGap() { 269 return getPaddingLeft() + getPaddingRight(); 270 } 271 setFixedSize(int width, int height)272 public void setFixedSize(int width, int height) { 273 width -= (getPaddingLeft() + getPaddingRight()); 274 height -= (getPaddingTop() + getPaddingBottom()); 275 for (int i = getChildCount() - 1; i >= 0; i --) { 276 ((CellLayout) getChildAt(i)).setFixedSize(width, height); 277 } 278 } 279 removeItem(View v)280 public void removeItem(View v) { 281 for (int i = getChildCount() - 1; i >= 0; i --) { 282 getPageAt(i).removeView(v); 283 } 284 } 285 286 @Override onScrollChanged(int l, int t, int oldl, int oldt)287 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 288 super.onScrollChanged(l, t, oldl, oldt); 289 mPageIndicator.setScroll(l, mMaxScrollX); 290 } 291 292 /** 293 * Updates position and rank of all the children in the view. 294 * It essentially removes all views from all the pages and then adds them again in appropriate 295 * page. 296 * 297 * @param list the ordered list of children. 298 * @param itemCount if greater than the total children count, empty spaces are left 299 * at the end, otherwise it is ignored. 300 * 301 */ arrangeChildren(ArrayList<View> list, int itemCount)302 public void arrangeChildren(ArrayList<View> list, int itemCount) { 303 arrangeChildren(list, itemCount, true); 304 } 305 306 @SuppressLint("RtlHardcoded") arrangeChildren(ArrayList<View> list, int itemCount, boolean saveChanges)307 private void arrangeChildren(ArrayList<View> list, int itemCount, boolean saveChanges) { 308 ArrayList<CellLayout> pages = new ArrayList<>(); 309 for (int i = 0; i < getChildCount(); i++) { 310 CellLayout page = (CellLayout) getChildAt(i); 311 page.removeAllViews(); 312 pages.add(page); 313 } 314 setupContentDimensions(itemCount); 315 316 Iterator<CellLayout> pageItr = pages.iterator(); 317 CellLayout currentPage = null; 318 319 int position = 0; 320 int newX, newY, rank; 321 322 FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier( 323 Launcher.getLauncher(getContext()).getDeviceProfile().inv); 324 verifier.setFolderInfo(mFolder.getInfo()); 325 rank = 0; 326 for (int i = 0; i < itemCount; i++) { 327 View v = list.size() > i ? list.get(i) : null; 328 if (currentPage == null || position >= mMaxItemsPerPage) { 329 // Next page 330 if (pageItr.hasNext()) { 331 currentPage = pageItr.next(); 332 } else { 333 currentPage = createAndAddNewPage(); 334 } 335 position = 0; 336 } 337 338 if (v != null) { 339 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); 340 newX = position % mGridCountX; 341 newY = position / mGridCountX; 342 ItemInfo info = (ItemInfo) v.getTag(); 343 if (info.cellX != newX || info.cellY != newY || info.rank != rank) { 344 info.cellX = newX; 345 info.cellY = newY; 346 info.rank = rank; 347 if (saveChanges) { 348 mFolder.mLauncher.getModelWriter().addOrMoveItemInDatabase(info, 349 mFolder.mInfo.id, 0, info.cellX, info.cellY); 350 } 351 } 352 lp.cellX = info.cellX; 353 lp.cellY = info.cellY; 354 currentPage.addViewToCellLayout(v, -1, info.getViewId(), lp, true); 355 356 if (verifier.isItemInPreview(rank) && v instanceof BubbleTextView) { 357 ((BubbleTextView) v).verifyHighRes(); 358 } 359 } 360 361 rank ++; 362 position++; 363 } 364 365 // Remove extra views. 366 boolean removed = false; 367 while (pageItr.hasNext()) { 368 removeView(pageItr.next()); 369 removed = true; 370 } 371 if (removed) { 372 setCurrentPage(0); 373 } 374 375 setEnableOverscroll(getPageCount() > 1); 376 377 // Update footer 378 mPageIndicator.setVisibility(getPageCount() > 1 ? View.VISIBLE : View.GONE); 379 // Set the gravity as LEFT or RIGHT instead of START, as START depends on the actual text. 380 mFolder.mFolderName.setGravity(getPageCount() > 1 ? 381 (mIsRtl ? Gravity.RIGHT : Gravity.LEFT) : Gravity.CENTER_HORIZONTAL); 382 } 383 getDesiredWidth()384 public int getDesiredWidth() { 385 return getPageCount() > 0 ? 386 (getPageAt(0).getDesiredWidth() + getPaddingLeft() + getPaddingRight()) : 0; 387 } 388 getDesiredHeight()389 public int getDesiredHeight() { 390 return getPageCount() > 0 ? 391 (getPageAt(0).getDesiredHeight() + getPaddingTop() + getPaddingBottom()) : 0; 392 } 393 getItemCount()394 public int getItemCount() { 395 int lastPageIndex = getChildCount() - 1; 396 if (lastPageIndex < 0) { 397 // If there are no pages, nothing has yet been added to the folder. 398 return 0; 399 } 400 return getPageAt(lastPageIndex).getShortcutsAndWidgets().getChildCount() 401 + lastPageIndex * mMaxItemsPerPage; 402 } 403 404 /** 405 * @return the rank of the cell nearest to the provided pixel position. 406 */ findNearestArea(int pixelX, int pixelY)407 public int findNearestArea(int pixelX, int pixelY) { 408 int pageIndex = getNextPage(); 409 CellLayout page = getPageAt(pageIndex); 410 page.findNearestArea(pixelX, pixelY, 1, 1, sTmpArray); 411 if (mFolder.isLayoutRtl()) { 412 sTmpArray[0] = page.getCountX() - sTmpArray[0] - 1; 413 } 414 return Math.min(mAllocatedContentSize - 1, 415 pageIndex * mMaxItemsPerPage + sTmpArray[1] * mGridCountX + sTmpArray[0]); 416 } 417 getFirstItem()418 public View getFirstItem() { 419 if (getChildCount() < 1) { 420 return null; 421 } 422 ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets(); 423 if (mGridCountX > 0) { 424 return currContainer.getChildAt(0, 0); 425 } else { 426 return currContainer.getChildAt(0); 427 } 428 } 429 getLastItem()430 public View getLastItem() { 431 if (getChildCount() < 1) { 432 return null; 433 } 434 ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets(); 435 int lastRank = currContainer.getChildCount() - 1; 436 if (mGridCountX > 0) { 437 return currContainer.getChildAt(lastRank % mGridCountX, lastRank / mGridCountX); 438 } else { 439 return currContainer.getChildAt(lastRank); 440 } 441 } 442 443 /** 444 * Iterates over all its items in a reading order. 445 * @return the view for which the operator returned true. 446 */ iterateOverItems(ItemOperator op)447 public View iterateOverItems(ItemOperator op) { 448 for (int k = 0 ; k < getChildCount(); k++) { 449 CellLayout page = getPageAt(k); 450 for (int j = 0; j < page.getCountY(); j++) { 451 for (int i = 0; i < page.getCountX(); i++) { 452 View v = page.getChildAt(i, j); 453 if ((v != null) && op.evaluate((ItemInfo) v.getTag(), v)) { 454 return v; 455 } 456 } 457 } 458 } 459 return null; 460 } 461 getAccessibilityDescription()462 public String getAccessibilityDescription() { 463 return getContext().getString(R.string.folder_opened, mGridCountX, mGridCountY); 464 } 465 466 /** 467 * Sets the focus on the first visible child. 468 */ setFocusOnFirstChild()469 public void setFocusOnFirstChild() { 470 View firstChild = getCurrentCellLayout().getChildAt(0, 0); 471 if (firstChild != null) { 472 firstChild.requestFocus(); 473 } 474 } 475 476 @Override notifyPageSwitchListener(int prevPage)477 protected void notifyPageSwitchListener(int prevPage) { 478 super.notifyPageSwitchListener(prevPage); 479 if (mFolder != null) { 480 mFolder.updateTextViewFocus(); 481 } 482 } 483 484 /** 485 * Scrolls the current view by a fraction 486 */ showScrollHint(int direction)487 public void showScrollHint(int direction) { 488 float fraction = (direction == Folder.SCROLL_LEFT) ^ mIsRtl 489 ? -SCROLL_HINT_FRACTION : SCROLL_HINT_FRACTION; 490 int hint = (int) (fraction * getWidth()); 491 int scroll = getScrollForPage(getNextPage()) + hint; 492 int delta = scroll - getScrollX(); 493 if (delta != 0) { 494 mScroller.setInterpolator(Interpolators.DEACCEL); 495 mScroller.startScroll(getScrollX(), delta, Folder.SCROLL_HINT_DURATION); 496 invalidate(); 497 } 498 } 499 clearScrollHint()500 public void clearScrollHint() { 501 if (getScrollX() != getScrollForPage(getNextPage())) { 502 snapToPage(getNextPage()); 503 } 504 } 505 506 /** 507 * Finish animation all the views which are animating across pages 508 */ completePendingPageChanges()509 public void completePendingPageChanges() { 510 if (!mPendingAnimations.isEmpty()) { 511 ArrayMap<View, Runnable> pendingViews = new ArrayMap<>(mPendingAnimations); 512 for (Map.Entry<View, Runnable> e : pendingViews.entrySet()) { 513 e.getKey().animate().cancel(); 514 e.getValue().run(); 515 } 516 } 517 } 518 rankOnCurrentPage(int rank)519 public boolean rankOnCurrentPage(int rank) { 520 int p = rank / mMaxItemsPerPage; 521 return p == getNextPage(); 522 } 523 524 @Override onPageBeginTransition()525 protected void onPageBeginTransition() { 526 super.onPageBeginTransition(); 527 // Ensure that adjacent pages have high resolution icons 528 verifyVisibleHighResIcons(getCurrentPage() - 1); 529 verifyVisibleHighResIcons(getCurrentPage() + 1); 530 } 531 532 /** 533 * Ensures that all the icons on the given page are of high-res 534 */ verifyVisibleHighResIcons(int pageNo)535 public void verifyVisibleHighResIcons(int pageNo) { 536 CellLayout page = getPageAt(pageNo); 537 if (page != null) { 538 ShortcutAndWidgetContainer parent = page.getShortcutsAndWidgets(); 539 for (int i = parent.getChildCount() - 1; i >= 0; i--) { 540 BubbleTextView icon = ((BubbleTextView) parent.getChildAt(i)); 541 icon.verifyHighRes(); 542 // Set the callback back to the actual icon, in case 543 // it was captured by the FolderIcon 544 Drawable d = icon.getCompoundDrawables()[1]; 545 if (d != null) { 546 d.setCallback(icon); 547 } 548 } 549 } 550 } 551 getAllocatedContentSize()552 public int getAllocatedContentSize() { 553 return mAllocatedContentSize; 554 } 555 556 /** 557 * Reorders the items such that the {@param empty} spot moves to {@param target} 558 */ realTimeReorder(int empty, int target)559 public void realTimeReorder(int empty, int target) { 560 completePendingPageChanges(); 561 int delay = 0; 562 float delayAmount = START_VIEW_REORDER_DELAY; 563 564 // Animation only happens on the current page. 565 int pageToAnimate = getNextPage(); 566 567 int pageT = target / mMaxItemsPerPage; 568 int pagePosT = target % mMaxItemsPerPage; 569 570 if (pageT != pageToAnimate) { 571 Log.e(TAG, "Cannot animate when the target cell is invisible"); 572 } 573 int pagePosE = empty % mMaxItemsPerPage; 574 int pageE = empty / mMaxItemsPerPage; 575 576 int startPos, endPos; 577 int moveStart, moveEnd; 578 int direction; 579 580 if (target == empty) { 581 // No animation 582 return; 583 } else if (target > empty) { 584 // Items will move backwards to make room for the empty cell. 585 direction = 1; 586 587 // If empty cell is in a different page, move them instantly. 588 if (pageE < pageToAnimate) { 589 moveStart = empty; 590 // Instantly move the first item in the current page. 591 moveEnd = pageToAnimate * mMaxItemsPerPage; 592 // Animate the 2nd item in the current page, as the first item was already moved to 593 // the last page. 594 startPos = 0; 595 } else { 596 moveStart = moveEnd = -1; 597 startPos = pagePosE; 598 } 599 600 endPos = pagePosT; 601 } else { 602 // The items will move forward. 603 direction = -1; 604 605 if (pageE > pageToAnimate) { 606 // Move the items immediately. 607 moveStart = empty; 608 // Instantly move the last item in the current page. 609 moveEnd = (pageToAnimate + 1) * mMaxItemsPerPage - 1; 610 611 // Animations start with the second last item in the page 612 startPos = mMaxItemsPerPage - 1; 613 } else { 614 moveStart = moveEnd = -1; 615 startPos = pagePosE; 616 } 617 618 endPos = pagePosT; 619 } 620 621 // Instant moving views. 622 while (moveStart != moveEnd) { 623 int rankToMove = moveStart + direction; 624 int p = rankToMove / mMaxItemsPerPage; 625 int pagePos = rankToMove % mMaxItemsPerPage; 626 int x = pagePos % mGridCountX; 627 int y = pagePos / mGridCountX; 628 629 final CellLayout page = getPageAt(p); 630 final View v = page.getChildAt(x, y); 631 if (v != null) { 632 if (pageToAnimate != p) { 633 page.removeView(v); 634 addViewForRank(v, (WorkspaceItemInfo) v.getTag(), moveStart); 635 } else { 636 // Do a fake animation before removing it. 637 final int newRank = moveStart; 638 final float oldTranslateX = v.getTranslationX(); 639 640 Runnable endAction = new Runnable() { 641 642 @Override 643 public void run() { 644 mPendingAnimations.remove(v); 645 v.setTranslationX(oldTranslateX); 646 ((CellLayout) v.getParent().getParent()).removeView(v); 647 addViewForRank(v, (WorkspaceItemInfo) v.getTag(), newRank); 648 } 649 }; 650 v.animate() 651 .translationXBy((direction > 0 ^ mIsRtl) ? -v.getWidth() : v.getWidth()) 652 .setDuration(REORDER_ANIMATION_DURATION) 653 .setStartDelay(0) 654 .withEndAction(endAction); 655 mPendingAnimations.put(v, endAction); 656 } 657 } 658 moveStart = rankToMove; 659 } 660 661 if ((endPos - startPos) * direction <= 0) { 662 // No animation 663 return; 664 } 665 666 CellLayout page = getPageAt(pageToAnimate); 667 for (int i = startPos; i != endPos; i += direction) { 668 int nextPos = i + direction; 669 View v = page.getChildAt(nextPos % mGridCountX, nextPos / mGridCountX); 670 if (v != null) { 671 ((ItemInfo) v.getTag()).rank -= direction; 672 } 673 if (page.animateChildToPosition(v, i % mGridCountX, i / mGridCountX, 674 REORDER_ANIMATION_DURATION, delay, true, true)) { 675 delay += delayAmount; 676 delayAmount *= VIEW_REORDER_DELAY_FACTOR; 677 } 678 } 679 } 680 itemsPerPage()681 public int itemsPerPage() { 682 return mMaxItemsPerPage; 683 } 684 } 685