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