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                 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 
    361     public int getDesiredWidth() {
    362         return getPageCount() > 0 ?
    363                 (getPageAt(0).getDesiredWidth() + getPaddingLeft() + getPaddingRight()) : 0;
    364     }
    365 
    366     public int getDesiredHeight()  {
    367         return  getPageCount() > 0 ?
    368                 (getPageAt(0).getDesiredHeight() + getPaddingTop() + getPaddingBottom()) : 0;
    369     }
    370 
    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      */
    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
    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 
    401     public boolean isFull() {
    402         return !ALLOW_FOLDER_SCROLL && getItemCount() >= mMaxItemsPerPage;
    403     }
    404 
    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 
    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      */
    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 
    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      */
    457     public void setFocusOnFirstChild() {
    458         View firstChild = getCurrentCellLayout().getChildAt(0, 0);
    459         if (firstChild != null) {
    460             firstChild.requestFocus();
    461         }
    462     }
    463 
    464     @Override
    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      */
    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 
    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      */
    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 
    507     public boolean rankOnCurrentPage(int rank) {
    508         int p = rank / mMaxItemsPerPage;
    509         return p == getNextPage();
    510     }
    511 
    512     @Override
    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      */
    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 
    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      */
    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 
    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 
    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 
    685     public int itemsPerPage() {
    686         return mMaxItemsPerPage;
    687     }
    688 
    689     @Override
    690     protected void getEdgeVerticalPostion(int[] pos) {
    691         pos[0] = 0;
    692         pos[1] = getViewportHeight();
    693     }
    694 }
    695