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