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