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