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