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