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