OSDN Git Service

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