• 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.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.WorkspaceItemInfo;
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 
FolderPagedView(Context context, AttributeSet attrs)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 
setFolder(Folder folder)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      */
calculateGridSize(int count, int countX, int countY, int maxCountX, int maxCountY, int maxItemsPerPage, int[] out)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      */
setupContentDimensions(int count)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
dispatchDraw(Canvas canvas)173     protected void dispatchDraw(Canvas canvas) {
174         mFocusIndicatorHelper.draw(canvas);
175         super.dispatchDraw(canvas);
176     }
177 
178     /**
179      * Binds items to the layout.
180      */
bindItems(ArrayList<WorkspaceItemInfo> items)181     public void bindItems(ArrayList<WorkspaceItemInfo> items) {
182         ArrayList<View> icons = new ArrayList<>();
183         for (WorkspaceItemInfo item : items) {
184             icons.add(createNewView(item));
185         }
186         arrangeChildren(icons, icons.size(), false);
187     }
188 
allocateSpaceForRank(int rank)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      */
allocateRankForNewItem()199     public int allocateRankForNewItem() {
200         int rank = getItemCount();
201         allocateSpaceForRank(rank);
202         setCurrentPage(rank / mMaxItemsPerPage);
203         return rank;
204     }
205 
createAndAddViewForRank(WorkspaceItemInfo item, int rank)206     public View createAndAddViewForRank(WorkspaceItemInfo 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      */
addViewForRank(View view, WorkspaceItemInfo item, int rank)217     public void addViewForRank(View view, WorkspaceItemInfo 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(view, -1, item.getViewId(), lp, true);
229     }
230 
231     @SuppressLint("InflateParams")
createNewView(WorkspaceItemInfo item)232     public View createNewView(WorkspaceItemInfo item) {
233         final BubbleTextView textView = (BubbleTextView) mInflater.inflate(
234                 R.layout.folder_application, null, false);
235         textView.applyFromWorkspaceItem(item);
236         textView.setHapticFeedbackEnabled(false);
237         textView.setOnClickListener(ItemClickHandler.INSTANCE);
238         textView.setOnLongClickListener(mFolder);
239         textView.setOnFocusChangeListener(mFocusIndicatorHelper);
240 
241         textView.setLayoutParams(new CellLayout.LayoutParams(
242                 item.cellX, item.cellY, item.spanX, item.spanY));
243         return textView;
244     }
245 
246     @Override
getPageAt(int index)247     public CellLayout getPageAt(int index) {
248         return (CellLayout) getChildAt(index);
249     }
250 
getCurrentCellLayout()251     public CellLayout getCurrentCellLayout() {
252         return getPageAt(getNextPage());
253     }
254 
createAndAddNewPage()255     private CellLayout createAndAddNewPage() {
256         DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
257         CellLayout page = (CellLayout) mInflater.inflate(R.layout.folder_page, this, false);
258         page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
259         page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
260         page.setInvertIfRtl(true);
261         page.setGridSize(mGridCountX, mGridCountY);
262 
263         addView(page, -1, generateDefaultLayoutParams());
264         return page;
265     }
266 
267     @Override
getChildGap()268     protected int getChildGap() {
269         return getPaddingLeft() + getPaddingRight();
270     }
271 
setFixedSize(int width, int height)272     public void setFixedSize(int width, int height) {
273         width -= (getPaddingLeft() + getPaddingRight());
274         height -= (getPaddingTop() + getPaddingBottom());
275         for (int i = getChildCount() - 1; i >= 0; i --) {
276             ((CellLayout) getChildAt(i)).setFixedSize(width, height);
277         }
278     }
279 
removeItem(View v)280     public void removeItem(View v) {
281         for (int i = getChildCount() - 1; i >= 0; i --) {
282             getPageAt(i).removeView(v);
283         }
284     }
285 
286     @Override
onScrollChanged(int l, int t, int oldl, int oldt)287     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
288         super.onScrollChanged(l, t, oldl, oldt);
289         mPageIndicator.setScroll(l, mMaxScrollX);
290     }
291 
292     /**
293      * Updates position and rank of all the children in the view.
294      * It essentially removes all views from all the pages and then adds them again in appropriate
295      * page.
296      *
297      * @param list the ordered list of children.
298      * @param itemCount if greater than the total children count, empty spaces are left
299      * at the end, otherwise it is ignored.
300      *
301      */
arrangeChildren(ArrayList<View> list, int itemCount)302     public void arrangeChildren(ArrayList<View> list, int itemCount) {
303         arrangeChildren(list, itemCount, true);
304     }
305 
306     @SuppressLint("RtlHardcoded")
arrangeChildren(ArrayList<View> list, int itemCount, boolean saveChanges)307     private void arrangeChildren(ArrayList<View> list, int itemCount, boolean saveChanges) {
308         ArrayList<CellLayout> pages = new ArrayList<>();
309         for (int i = 0; i < getChildCount(); i++) {
310             CellLayout page = (CellLayout) getChildAt(i);
311             page.removeAllViews();
312             pages.add(page);
313         }
314         setupContentDimensions(itemCount);
315 
316         Iterator<CellLayout> pageItr = pages.iterator();
317         CellLayout currentPage = null;
318 
319         int position = 0;
320         int newX, newY, rank;
321 
322         FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier(
323                 Launcher.getLauncher(getContext()).getDeviceProfile().inv);
324         verifier.setFolderInfo(mFolder.getInfo());
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(v, -1, info.getViewId(), lp, true);
355 
356                 if (verifier.isItemInPreview(rank) && v instanceof BubbleTextView) {
357                     ((BubbleTextView) v).verifyHighRes();
358                 }
359             }
360 
361             rank ++;
362             position++;
363         }
364 
365         // Remove extra views.
366         boolean removed = false;
367         while (pageItr.hasNext()) {
368             removeView(pageItr.next());
369             removed = true;
370         }
371         if (removed) {
372             setCurrentPage(0);
373         }
374 
375         setEnableOverscroll(getPageCount() > 1);
376 
377         // Update footer
378         mPageIndicator.setVisibility(getPageCount() > 1 ? View.VISIBLE : View.GONE);
379         // Set the gravity as LEFT or RIGHT instead of START, as START depends on the actual text.
380         mFolder.mFolderName.setGravity(getPageCount() > 1 ?
381                 (mIsRtl ? Gravity.RIGHT : Gravity.LEFT) : Gravity.CENTER_HORIZONTAL);
382     }
383 
getDesiredWidth()384     public int getDesiredWidth() {
385         return getPageCount() > 0 ?
386                 (getPageAt(0).getDesiredWidth() + getPaddingLeft() + getPaddingRight()) : 0;
387     }
388 
getDesiredHeight()389     public int getDesiredHeight()  {
390         return  getPageCount() > 0 ?
391                 (getPageAt(0).getDesiredHeight() + getPaddingTop() + getPaddingBottom()) : 0;
392     }
393 
getItemCount()394     public int getItemCount() {
395         int lastPageIndex = getChildCount() - 1;
396         if (lastPageIndex < 0) {
397             // If there are no pages, nothing has yet been added to the folder.
398             return 0;
399         }
400         return getPageAt(lastPageIndex).getShortcutsAndWidgets().getChildCount()
401                 + lastPageIndex * mMaxItemsPerPage;
402     }
403 
404     /**
405      * @return the rank of the cell nearest to the provided pixel position.
406      */
findNearestArea(int pixelX, int pixelY)407     public int findNearestArea(int pixelX, int pixelY) {
408         int pageIndex = getNextPage();
409         CellLayout page = getPageAt(pageIndex);
410         page.findNearestArea(pixelX, pixelY, 1, 1, sTmpArray);
411         if (mFolder.isLayoutRtl()) {
412             sTmpArray[0] = page.getCountX() - sTmpArray[0] - 1;
413         }
414         return Math.min(mAllocatedContentSize - 1,
415                 pageIndex * mMaxItemsPerPage + sTmpArray[1] * mGridCountX + sTmpArray[0]);
416     }
417 
getFirstItem()418     public View getFirstItem() {
419         if (getChildCount() < 1) {
420             return null;
421         }
422         ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets();
423         if (mGridCountX > 0) {
424             return currContainer.getChildAt(0, 0);
425         } else {
426             return currContainer.getChildAt(0);
427         }
428     }
429 
getLastItem()430     public View getLastItem() {
431         if (getChildCount() < 1) {
432             return null;
433         }
434         ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets();
435         int lastRank = currContainer.getChildCount() - 1;
436         if (mGridCountX > 0) {
437             return currContainer.getChildAt(lastRank % mGridCountX, lastRank / mGridCountX);
438         } else {
439             return currContainer.getChildAt(lastRank);
440         }
441     }
442 
443     /**
444      * Iterates over all its items in a reading order.
445      * @return the view for which the operator returned true.
446      */
iterateOverItems(ItemOperator op)447     public View iterateOverItems(ItemOperator op) {
448         for (int k = 0 ; k < getChildCount(); k++) {
449             CellLayout page = getPageAt(k);
450             for (int j = 0; j < page.getCountY(); j++) {
451                 for (int i = 0; i < page.getCountX(); i++) {
452                     View v = page.getChildAt(i, j);
453                     if ((v != null) && op.evaluate((ItemInfo) v.getTag(), v)) {
454                         return v;
455                     }
456                 }
457             }
458         }
459         return null;
460     }
461 
getAccessibilityDescription()462     public String getAccessibilityDescription() {
463         return getContext().getString(R.string.folder_opened, mGridCountX, mGridCountY);
464     }
465 
466     /**
467      * Sets the focus on the first visible child.
468      */
setFocusOnFirstChild()469     public void setFocusOnFirstChild() {
470         View firstChild = getCurrentCellLayout().getChildAt(0, 0);
471         if (firstChild != null) {
472             firstChild.requestFocus();
473         }
474     }
475 
476     @Override
notifyPageSwitchListener(int prevPage)477     protected void notifyPageSwitchListener(int prevPage) {
478         super.notifyPageSwitchListener(prevPage);
479         if (mFolder != null) {
480             mFolder.updateTextViewFocus();
481         }
482     }
483 
484     /**
485      * Scrolls the current view by a fraction
486      */
showScrollHint(int direction)487     public void showScrollHint(int direction) {
488         float fraction = (direction == Folder.SCROLL_LEFT) ^ mIsRtl
489                 ? -SCROLL_HINT_FRACTION : SCROLL_HINT_FRACTION;
490         int hint = (int) (fraction * getWidth());
491         int scroll = getScrollForPage(getNextPage()) + hint;
492         int delta = scroll - getScrollX();
493         if (delta != 0) {
494             mScroller.setInterpolator(Interpolators.DEACCEL);
495             mScroller.startScroll(getScrollX(), delta, Folder.SCROLL_HINT_DURATION);
496             invalidate();
497         }
498     }
499 
clearScrollHint()500     public void clearScrollHint() {
501         if (getScrollX() != getScrollForPage(getNextPage())) {
502             snapToPage(getNextPage());
503         }
504     }
505 
506     /**
507      * Finish animation all the views which are animating across pages
508      */
completePendingPageChanges()509     public void completePendingPageChanges() {
510         if (!mPendingAnimations.isEmpty()) {
511             ArrayMap<View, Runnable> pendingViews = new ArrayMap<>(mPendingAnimations);
512             for (Map.Entry<View, Runnable> e : pendingViews.entrySet()) {
513                 e.getKey().animate().cancel();
514                 e.getValue().run();
515             }
516         }
517     }
518 
rankOnCurrentPage(int rank)519     public boolean rankOnCurrentPage(int rank) {
520         int p = rank / mMaxItemsPerPage;
521         return p == getNextPage();
522     }
523 
524     @Override
onPageBeginTransition()525     protected void onPageBeginTransition() {
526         super.onPageBeginTransition();
527         // Ensure that adjacent pages have high resolution icons
528         verifyVisibleHighResIcons(getCurrentPage() - 1);
529         verifyVisibleHighResIcons(getCurrentPage() + 1);
530     }
531 
532     /**
533      * Ensures that all the icons on the given page are of high-res
534      */
verifyVisibleHighResIcons(int pageNo)535     public void verifyVisibleHighResIcons(int pageNo) {
536         CellLayout page = getPageAt(pageNo);
537         if (page != null) {
538             ShortcutAndWidgetContainer parent = page.getShortcutsAndWidgets();
539             for (int i = parent.getChildCount() - 1; i >= 0; i--) {
540                 BubbleTextView icon = ((BubbleTextView) parent.getChildAt(i));
541                 icon.verifyHighRes();
542                 // Set the callback back to the actual icon, in case
543                 // it was captured by the FolderIcon
544                 Drawable d = icon.getCompoundDrawables()[1];
545                 if (d != null) {
546                     d.setCallback(icon);
547                 }
548             }
549         }
550     }
551 
getAllocatedContentSize()552     public int getAllocatedContentSize() {
553         return mAllocatedContentSize;
554     }
555 
556     /**
557      * Reorders the items such that the {@param empty} spot moves to {@param target}
558      */
realTimeReorder(int empty, int target)559     public void realTimeReorder(int empty, int target) {
560         completePendingPageChanges();
561         int delay = 0;
562         float delayAmount = START_VIEW_REORDER_DELAY;
563 
564         // Animation only happens on the current page.
565         int pageToAnimate = getNextPage();
566 
567         int pageT = target / mMaxItemsPerPage;
568         int pagePosT = target % mMaxItemsPerPage;
569 
570         if (pageT != pageToAnimate) {
571             Log.e(TAG, "Cannot animate when the target cell is invisible");
572         }
573         int pagePosE = empty % mMaxItemsPerPage;
574         int pageE = empty / mMaxItemsPerPage;
575 
576         int startPos, endPos;
577         int moveStart, moveEnd;
578         int direction;
579 
580         if (target == empty) {
581             // No animation
582             return;
583         } else if (target > empty) {
584             // Items will move backwards to make room for the empty cell.
585             direction = 1;
586 
587             // If empty cell is in a different page, move them instantly.
588             if (pageE < pageToAnimate) {
589                 moveStart = empty;
590                 // Instantly move the first item in the current page.
591                 moveEnd = pageToAnimate * mMaxItemsPerPage;
592                 // Animate the 2nd item in the current page, as the first item was already moved to
593                 // the last page.
594                 startPos = 0;
595             } else {
596                 moveStart = moveEnd = -1;
597                 startPos = pagePosE;
598             }
599 
600             endPos = pagePosT;
601         } else {
602             // The items will move forward.
603             direction = -1;
604 
605             if (pageE > pageToAnimate) {
606                 // Move the items immediately.
607                 moveStart = empty;
608                 // Instantly move the last item in the current page.
609                 moveEnd = (pageToAnimate + 1) * mMaxItemsPerPage - 1;
610 
611                 // Animations start with the second last item in the page
612                 startPos = mMaxItemsPerPage - 1;
613             } else {
614                 moveStart = moveEnd = -1;
615                 startPos = pagePosE;
616             }
617 
618             endPos = pagePosT;
619         }
620 
621         // Instant moving views.
622         while (moveStart != moveEnd) {
623             int rankToMove = moveStart + direction;
624             int p = rankToMove / mMaxItemsPerPage;
625             int pagePos = rankToMove % mMaxItemsPerPage;
626             int x = pagePos % mGridCountX;
627             int y = pagePos / mGridCountX;
628 
629             final CellLayout page = getPageAt(p);
630             final View v = page.getChildAt(x, y);
631             if (v != null) {
632                 if (pageToAnimate != p) {
633                     page.removeView(v);
634                     addViewForRank(v, (WorkspaceItemInfo) v.getTag(), moveStart);
635                 } else {
636                     // Do a fake animation before removing it.
637                     final int newRank = moveStart;
638                     final float oldTranslateX = v.getTranslationX();
639 
640                     Runnable endAction = new Runnable() {
641 
642                         @Override
643                         public void run() {
644                             mPendingAnimations.remove(v);
645                             v.setTranslationX(oldTranslateX);
646                             ((CellLayout) v.getParent().getParent()).removeView(v);
647                             addViewForRank(v, (WorkspaceItemInfo) v.getTag(), newRank);
648                         }
649                     };
650                     v.animate()
651                         .translationXBy((direction > 0 ^ mIsRtl) ? -v.getWidth() : v.getWidth())
652                         .setDuration(REORDER_ANIMATION_DURATION)
653                         .setStartDelay(0)
654                         .withEndAction(endAction);
655                     mPendingAnimations.put(v, endAction);
656                 }
657             }
658             moveStart = rankToMove;
659         }
660 
661         if ((endPos - startPos) * direction <= 0) {
662             // No animation
663             return;
664         }
665 
666         CellLayout page = getPageAt(pageToAnimate);
667         for (int i = startPos; i != endPos; i += direction) {
668             int nextPos = i + direction;
669             View v = page.getChildAt(nextPos % mGridCountX, nextPos / mGridCountX);
670             if (v != null) {
671                 ((ItemInfo) v.getTag()).rank -= direction;
672             }
673             if (page.animateChildToPosition(v, i % mGridCountX, i / mGridCountX,
674                     REORDER_ANIMATION_DURATION, delay, true, true)) {
675                 delay += delayAmount;
676                 delayAmount *= VIEW_REORDER_DELAY_FACTOR;
677             }
678         }
679     }
680 
itemsPerPage()681     public int itemsPerPage() {
682         return mMaxItemsPerPage;
683     }
684 }
685