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