Home | History | Annotate | Download | only in launcher3
      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 
     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 
    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} &amp; {@link #mMaxCountY}.
    115      */
    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      */
    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      */
    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 
    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      */
    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")
    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
    225     public CellLayout getPageAt(int index) {
    226         return (CellLayout) getChildAt(index);
    227     }
    228 
    229     public void removeCellLayoutView(View view) {
    230         for (int i = getChildCount() - 1; i >= 0; i --) {
    231             getPageAt(i).removeView(view);
    232         }
    233     }
    234 
    235     public CellLayout getCurrentCellLayout() {
    236         return getPageAt(getNextPage());
    237     }
    238 
    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
    253     protected int getChildGap() {
    254         return getPaddingLeft() + getPaddingRight();
    255     }
    256 
    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 
    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      */
    281     public void arrangeChildren(ArrayList<View> list, int itemCount) {
    282         arrangeChildren(list, itemCount, true);
    283     }
    284 
    285     @SuppressLint("RtlHardcoded")
    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 
    334             rank ++;
    335             position++;
    336         }
    337 
    338         // Remove extra views.
    339         boolean removed = false;
    340         while (pageItr.hasNext()) {
    341             removeView(pageItr.next());
    342             removed = true;
    343         }
    344         if (removed) {
    345             setCurrentPage(0);
    346         }
    347 
    348         setEnableOverscroll(getPageCount() > 1);
    349 
    350         // Update footer
    351         mPageIndicator.setVisibility(getPageCount() > 1 ? View.VISIBLE : View.GONE);
    352         // Set the gravity as LEFT or RIGHT instead of START, as START depends on the actual text.
    353         mFolder.mFolderName.setGravity(getPageCount() > 1 ?
    354                 (mIsRtl ? Gravity.RIGHT : Gravity.LEFT) : Gravity.CENTER_HORIZONTAL);
    355     }
    356 
    357     public int getDesiredWidth() {
    358         return getPageCount() > 0 ?
    359                 (getPageAt(0).getDesiredWidth() + getPaddingLeft() + getPaddingRight()) : 0;
    360     }
    361 
    362     public int getDesiredHeight()  {
    363         return  getPageCount() > 0 ?
    364                 (getPageAt(0).getDesiredHeight() + getPaddingTop() + getPaddingBottom()) : 0;
    365     }
    366 
    367     public int getItemCount() {
    368         int lastPageIndex = getChildCount() - 1;
    369         if (lastPageIndex < 0) {
    370             // If there are no pages, nothing has yet been added to the folder.
    371             return 0;
    372         }
    373         return getPageAt(lastPageIndex).getShortcutsAndWidgets().getChildCount()
    374                 + lastPageIndex * mMaxItemsPerPage;
    375     }
    376 
    377     /**
    378      * @return the rank of the cell nearest to the provided pixel position.
    379      */
    380     public int findNearestArea(int pixelX, int pixelY) {
    381         int pageIndex = getNextPage();
    382         CellLayout page = getPageAt(pageIndex);
    383         page.findNearestArea(pixelX, pixelY, 1, 1, sTempPosArray);
    384         if (mFolder.isLayoutRtl()) {
    385             sTempPosArray[0] = page.getCountX() - sTempPosArray[0] - 1;
    386         }
    387         return Math.min(mAllocatedContentSize - 1,
    388                 pageIndex * mMaxItemsPerPage + sTempPosArray[1] * mGridCountX + sTempPosArray[0]);
    389     }
    390 
    391     @Override
    392     protected PageMarkerResources getPageIndicatorMarker(int pageIndex) {
    393         return new PageMarkerResources(R.drawable.ic_pageindicator_current_folder,
    394                 R.drawable.ic_pageindicator_default_folder);
    395     }
    396 
    397     public boolean isFull() {
    398         return !ALLOW_FOLDER_SCROLL && getItemCount() >= mMaxItemsPerPage;
    399     }
    400 
    401     public View getLastItem() {
    402         if (getChildCount() < 1) {
    403             return null;
    404         }
    405         ShortcutAndWidgetContainer lastContainer = getCurrentCellLayout().getShortcutsAndWidgets();
    406         int lastRank = lastContainer.getChildCount() - 1;
    407         if (mGridCountX > 0) {
    408             return lastContainer.getChildAt(lastRank % mGridCountX, lastRank / mGridCountX);
    409         } else {
    410             return lastContainer.getChildAt(lastRank);
    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      */
    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, this)) {
    425                         return v;
    426                     }
    427                 }
    428             }
    429         }
    430         return null;
    431     }
    432 
    433     public String getAccessibilityDescription() {
    434         return String.format(getContext().getString(R.string.folder_opened),
    435                 mGridCountX, mGridCountY);
    436     }
    437 
    438     /**
    439      * Sets the focus on the first visible child.
    440      */
    441     public void setFocusOnFirstChild() {
    442         View firstChild = getCurrentCellLayout().getChildAt(0, 0);
    443         if (firstChild != null) {
    444             firstChild.requestFocus();
    445         }
    446     }
    447 
    448     @Override
    449     protected void notifyPageSwitchListener() {
    450         super.notifyPageSwitchListener();
    451         if (mFolder != null) {
    452             mFolder.updateTextViewFocus();
    453         }
    454     }
    455 
    456     /**
    457      * Scrolls the current view by a fraction
    458      */
    459     public void showScrollHint(int direction) {
    460         float fraction = (direction == DragController.SCROLL_LEFT) ^ mIsRtl
    461                 ? -SCROLL_HINT_FRACTION : SCROLL_HINT_FRACTION;
    462         int hint = (int) (fraction * getWidth());
    463         int scroll = getScrollForPage(getNextPage()) + hint;
    464         int delta = scroll - getScrollX();
    465         if (delta != 0) {
    466             mScroller.setInterpolator(new DecelerateInterpolator());
    467             mScroller.startScroll(getScrollX(), 0, delta, 0, Folder.SCROLL_HINT_DURATION);
    468             invalidate();
    469         }
    470     }
    471 
    472     public void clearScrollHint() {
    473         if (getScrollX() != getScrollForPage(getNextPage())) {
    474             snapToPage(getNextPage());
    475         }
    476     }
    477 
    478     /**
    479      * Finish animation all the views which are animating across pages
    480      */
    481     public void completePendingPageChanges() {
    482         if (!mPendingAnimations.isEmpty()) {
    483             HashMap<View, Runnable> pendingViews = new HashMap<>(mPendingAnimations);
    484             for (Map.Entry<View, Runnable> e : pendingViews.entrySet()) {
    485                 e.getKey().animate().cancel();
    486                 e.getValue().run();
    487             }
    488         }
    489     }
    490 
    491     public boolean rankOnCurrentPage(int rank) {
    492         int p = rank / mMaxItemsPerPage;
    493         return p == getNextPage();
    494     }
    495 
    496     @Override
    497     protected void onPageBeginMoving() {
    498         super.onPageBeginMoving();
    499         getVisiblePages(sTempPosArray);
    500         for (int i = sTempPosArray[0]; i <= sTempPosArray[1]; i++) {
    501             verifyVisibleHighResIcons(i);
    502         }
    503     }
    504 
    505     /**
    506      * Ensures that all the icons on the given page are of high-res
    507      */
    508     public void verifyVisibleHighResIcons(int pageNo) {
    509         CellLayout page = getPageAt(pageNo);
    510         if (page != null) {
    511             ShortcutAndWidgetContainer parent = page.getShortcutsAndWidgets();
    512             for (int i = parent.getChildCount() - 1; i >= 0; i--) {
    513                 ((BubbleTextView) parent.getChildAt(i)).verifyHighRes();
    514             }
    515         }
    516     }
    517 
    518     public int getAllocatedContentSize() {
    519         return mAllocatedContentSize;
    520     }
    521 
    522     /**
    523      * Reorders the items such that the {@param empty} spot moves to {@param target}
    524      */
    525     public void realTimeReorder(int empty, int target) {
    526         completePendingPageChanges();
    527         int delay = 0;
    528         float delayAmount = START_VIEW_REORDER_DELAY;
    529 
    530         // Animation only happens on the current page.
    531         int pageToAnimate = getNextPage();
    532 
    533         int pageT = target / mMaxItemsPerPage;
    534         int pagePosT = target % mMaxItemsPerPage;
    535 
    536         if (pageT != pageToAnimate) {
    537             Log.e(TAG, "Cannot animate when the target cell is invisible");
    538         }
    539         int pagePosE = empty % mMaxItemsPerPage;
    540         int pageE = empty / mMaxItemsPerPage;
    541 
    542         int startPos, endPos;
    543         int moveStart, moveEnd;
    544         int direction;
    545 
    546         if (target == empty) {
    547             // No animation
    548             return;
    549         } else if (target > empty) {
    550             // Items will move backwards to make room for the empty cell.
    551             direction = 1;
    552 
    553             // If empty cell is in a different page, move them instantly.
    554             if (pageE < pageToAnimate) {
    555                 moveStart = empty;
    556                 // Instantly move the first item in the current page.
    557                 moveEnd = pageToAnimate * mMaxItemsPerPage;
    558                 // Animate the 2nd item in the current page, as the first item was already moved to
    559                 // the last page.
    560                 startPos = 0;
    561             } else {
    562                 moveStart = moveEnd = -1;
    563                 startPos = pagePosE;
    564             }
    565 
    566             endPos = pagePosT;
    567         } else {
    568             // The items will move forward.
    569             direction = -1;
    570 
    571             if (pageE > pageToAnimate) {
    572                 // Move the items immediately.
    573                 moveStart = empty;
    574                 // Instantly move the last item in the current page.
    575                 moveEnd = (pageToAnimate + 1) * mMaxItemsPerPage - 1;
    576 
    577                 // Animations start with the second last item in the page
    578                 startPos = mMaxItemsPerPage - 1;
    579             } else {
    580                 moveStart = moveEnd = -1;
    581                 startPos = pagePosE;
    582             }
    583 
    584             endPos = pagePosT;
    585         }
    586 
    587         // Instant moving views.
    588         while (moveStart != moveEnd) {
    589             int rankToMove = moveStart + direction;
    590             int p = rankToMove / mMaxItemsPerPage;
    591             int pagePos = rankToMove % mMaxItemsPerPage;
    592             int x = pagePos % mGridCountX;
    593             int y = pagePos / mGridCountX;
    594 
    595             final CellLayout page = getPageAt(p);
    596             final View v = page.getChildAt(x, y);
    597             if (v != null) {
    598                 if (pageToAnimate != p) {
    599                     page.removeView(v);
    600                     addViewForRank(v, (ShortcutInfo) v.getTag(), moveStart);
    601                 } else {
    602                     // Do a fake animation before removing it.
    603                     final int newRank = moveStart;
    604                     final float oldTranslateX = v.getTranslationX();
    605 
    606                     Runnable endAction = new Runnable() {
    607 
    608                         @Override
    609                         public void run() {
    610                             mPendingAnimations.remove(v);
    611                             v.setTranslationX(oldTranslateX);
    612                             ((CellLayout) v.getParent().getParent()).removeView(v);
    613                             addViewForRank(v, (ShortcutInfo) v.getTag(), newRank);
    614                         }
    615                     };
    616                     v.animate()
    617                         .translationXBy((direction > 0 ^ mIsRtl) ? -v.getWidth() : v.getWidth())
    618                         .setDuration(REORDER_ANIMATION_DURATION)
    619                         .setStartDelay(0)
    620                         .withEndAction(endAction);
    621                     mPendingAnimations.put(v, endAction);
    622                 }
    623             }
    624             moveStart = rankToMove;
    625         }
    626 
    627         if ((endPos - startPos) * direction <= 0) {
    628             // No animation
    629             return;
    630         }
    631 
    632         CellLayout page = getPageAt(pageToAnimate);
    633         for (int i = startPos; i != endPos; i += direction) {
    634             int nextPos = i + direction;
    635             View v = page.getChildAt(nextPos % mGridCountX, nextPos / mGridCountX);
    636             if (v != null) {
    637                 ((ItemInfo) v.getTag()).rank -= direction;
    638             }
    639             if (page.animateChildToPosition(v, i % mGridCountX, i / mGridCountX,
    640                     REORDER_ANIMATION_DURATION, delay, true, true)) {
    641                 delay += delayAmount;
    642                 delayAmount *= VIEW_REORDER_DELAY_FACTOR;
    643             }
    644         }
    645     }
    646 
    647     public void setMarkerScale(float scale) {
    648         int count  = mPageIndicator.getChildCount();
    649         for (int i = 0; i < count; i++) {
    650             View marker = mPageIndicator.getChildAt(i);
    651             marker.animate().cancel();
    652             marker.setScaleX(scale);
    653             marker.setScaleY(scale);
    654         }
    655     }
    656 
    657     public void animateMarkers() {
    658         int count  = mPageIndicator.getChildCount();
    659         Interpolator interpolator = new OvershootInterpolator(PAGE_INDICATOR_OVERSHOOT_TENSION);
    660         for (int i = 0; i < count; i++) {
    661             mPageIndicator.getChildAt(i).animate().scaleX(1).scaleY(1)
    662                 .setInterpolator(interpolator)
    663                 .setDuration(PAGE_INDICATOR_ANIMATION_DURATION)
    664                 .setStartDelay(PAGE_INDICATOR_ANIMATION_STAGGERED_DELAY * i
    665                         + PAGE_INDICATOR_ANIMATION_START_DELAY);
    666         }
    667     }
    668 
    669     public int itemsPerPage() {
    670         return mMaxItemsPerPage;
    671     }
    672 
    673     @Override
    674     protected void getEdgeVerticalPostion(int[] pos) {
    675         pos[0] = 0;
    676         pos[1] = getViewportHeight();
    677     }
    678 }
    679