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