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