FolderPagedView.java revision 1bce7fd342875be8f7c1f82c8cf21d0199c8d544
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.graphics.Canvas;
22import android.util.AttributeSet;
23import android.util.Log;
24import android.view.Gravity;
25import android.view.LayoutInflater;
26import android.view.View;
27import android.view.ViewDebug;
28import android.view.animation.DecelerateInterpolator;
29
30import com.android.launcher3.BubbleTextView;
31import com.android.launcher3.CellLayout;
32import com.android.launcher3.DeviceProfile;
33import com.android.launcher3.FocusHelper.PagedFolderKeyEventListener;
34import com.android.launcher3.IconCache;
35import com.android.launcher3.InvariantDeviceProfile;
36import com.android.launcher3.ItemInfo;
37import com.android.launcher3.Launcher;
38import com.android.launcher3.LauncherAppState;
39import com.android.launcher3.LauncherModel;
40import com.android.launcher3.PagedView;
41import com.android.launcher3.R;
42import com.android.launcher3.ShortcutAndWidgetContainer;
43import com.android.launcher3.ShortcutInfo;
44import com.android.launcher3.Utilities;
45import com.android.launcher3.Workspace.ItemOperator;
46import com.android.launcher3.dragndrop.DragController;
47import com.android.launcher3.pageindicators.PageIndicator;
48import com.android.launcher3.keyboard.ViewGroupFocusHelper;
49import com.android.launcher3.shortcuts.DeepShortcutManager;
50import com.android.launcher3.shortcuts.ShortcutsContainerListener;
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    /**
69     * Fraction of the width to scroll when showing the next page hint.
70     */
71    private static final float SCROLL_HINT_FRACTION = 0.07f;
72
73    private static final int[] sTempPosArray = new int[2];
74
75    public final boolean mIsRtl;
76
77    private final LayoutInflater mInflater;
78    private final IconCache mIconCache;
79    private final ViewGroupFocusHelper mFocusIndicatorHelper;
80
81    @Thunk final HashMap<View, Runnable> mPendingAnimations = new HashMap<>();
82
83    @ViewDebug.ExportedProperty(category = "launcher")
84    private final int mMaxCountX;
85    @ViewDebug.ExportedProperty(category = "launcher")
86    private final int mMaxCountY;
87    @ViewDebug.ExportedProperty(category = "launcher")
88    private final int mMaxItemsPerPage;
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    private PagedFolderKeyEventListener mKeyListener;
98
99    private PageIndicator mPageIndicator;
100
101    public FolderPagedView(Context context, AttributeSet attrs) {
102        super(context, attrs);
103        LauncherAppState app = LauncherAppState.getInstance();
104
105        InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
106        mMaxCountX = profile.numFolderColumns;
107        mMaxCountY = profile.numFolderRows;
108
109        mMaxItemsPerPage = mMaxCountX * mMaxCountY;
110
111        mInflater = LayoutInflater.from(context);
112        mIconCache = app.getIconCache();
113
114        mIsRtl = Utilities.isRtl(getResources());
115        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
116
117        setEdgeGlowColor(getResources().getColor(R.color.folder_edge_effect_color));
118        mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
119    }
120
121    public void setFolder(Folder folder) {
122        mFolder = folder;
123        mKeyListener = new PagedFolderKeyEventListener(folder);
124        mPageIndicator = (PageIndicator) folder.findViewById(R.id.folder_page_indicator);
125        initParentViews(folder);
126    }
127
128    /**
129     * Sets up the grid size such that {@param count} items can fit in the grid.
130     * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while
131     * maintaining the restrictions of {@link #mMaxCountX} &amp; {@link #mMaxCountY}.
132     */
133    private void setupContentDimensions(int count) {
134        mAllocatedContentSize = count;
135        boolean done;
136        if (count >= mMaxItemsPerPage) {
137            mGridCountX = mMaxCountX;
138            mGridCountY = mMaxCountY;
139            done = true;
140        } else {
141            done = false;
142        }
143
144        while (!done) {
145            int oldCountX = mGridCountX;
146            int oldCountY = mGridCountY;
147            if (mGridCountX * mGridCountY < count) {
148                // Current grid is too small, expand it
149                if ((mGridCountX <= mGridCountY || mGridCountY == mMaxCountY) && mGridCountX < mMaxCountX) {
150                    mGridCountX++;
151                } else if (mGridCountY < mMaxCountY) {
152                    mGridCountY++;
153                }
154                if (mGridCountY == 0) mGridCountY++;
155            } else if ((mGridCountY - 1) * mGridCountX >= count && mGridCountY >= mGridCountX) {
156                mGridCountY = Math.max(0, mGridCountY - 1);
157            } else if ((mGridCountX - 1) * mGridCountY >= count) {
158                mGridCountX = Math.max(0, mGridCountX - 1);
159            }
160            done = mGridCountX == oldCountX && mGridCountY == oldCountY;
161        }
162
163        // Update grid size
164        for (int i = getPageCount() - 1; i >= 0; i--) {
165            getPageAt(i).setGridSize(mGridCountX, mGridCountY);
166        }
167    }
168
169    @Override
170    protected void dispatchDraw(Canvas canvas) {
171        mFocusIndicatorHelper.draw(canvas);
172        super.dispatchDraw(canvas);
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        if (DeepShortcutManager.supportsShortcuts(item)) {
240            // TODO: only add this listener if the item has shortcuts associated with it.
241            textView.setOnTouchListener(new ShortcutsContainerListener(textView));
242        }
243        textView.setOnFocusChangeListener(mFocusIndicatorHelper);
244        textView.setOnKeyListener(mKeyListener);
245
246        textView.setLayoutParams(new CellLayout.LayoutParams(
247                item.cellX, item.cellY, item.spanX, item.spanY));
248        return textView;
249    }
250
251    @Override
252    public CellLayout getPageAt(int index) {
253        return (CellLayout) getChildAt(index);
254    }
255
256    public CellLayout getCurrentCellLayout() {
257        return getPageAt(getNextPage());
258    }
259
260    private CellLayout createAndAddNewPage() {
261        DeviceProfile grid = ((Launcher) getContext()).getDeviceProfile();
262        CellLayout page = new CellLayout(getContext());
263        page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
264        page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
265        page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
266        page.setInvertIfRtl(true);
267        page.setGridSize(mGridCountX, mGridCountY);
268
269        addView(page, -1, generateDefaultLayoutParams());
270        return page;
271    }
272
273    @Override
274    protected int getChildGap() {
275        return getPaddingLeft() + getPaddingRight();
276    }
277
278    public void setFixedSize(int width, int height) {
279        width -= (getPaddingLeft() + getPaddingRight());
280        height -= (getPaddingTop() + getPaddingBottom());
281        for (int i = getChildCount() - 1; i >= 0; i --) {
282            ((CellLayout) getChildAt(i)).setFixedSize(width, height);
283        }
284    }
285
286    public void removeItem(View v) {
287        for (int i = getChildCount() - 1; i >= 0; i --) {
288            getPageAt(i).removeView(v);
289        }
290    }
291
292    @Override
293    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
294        super.onScrollChanged(l, t, oldl, oldt);
295        mPageIndicator.setScroll(l, mMaxScrollX);
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    public boolean isFull() {
423        return !ALLOW_FOLDER_SCROLL && getItemCount() >= mMaxItemsPerPage;
424    }
425
426    public View getFirstItem() {
427        if (getChildCount() < 1) {
428            return null;
429        }
430        ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets();
431        if (mGridCountX > 0) {
432            return currContainer.getChildAt(0, 0);
433        } else {
434            return currContainer.getChildAt(0);
435        }
436    }
437
438    public View getLastItem() {
439        if (getChildCount() < 1) {
440            return null;
441        }
442        ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets();
443        int lastRank = currContainer.getChildCount() - 1;
444        if (mGridCountX > 0) {
445            return currContainer.getChildAt(lastRank % mGridCountX, lastRank / mGridCountX);
446        } else {
447            return currContainer.getChildAt(lastRank);
448        }
449    }
450
451    /**
452     * Iterates over all its items in a reading order.
453     * @return the view for which the operator returned true.
454     */
455    public View iterateOverItems(ItemOperator op) {
456        for (int k = 0 ; k < getChildCount(); k++) {
457            CellLayout page = getPageAt(k);
458            for (int j = 0; j < page.getCountY(); j++) {
459                for (int i = 0; i < page.getCountX(); i++) {
460                    View v = page.getChildAt(i, j);
461                    if ((v != null) && op.evaluate((ItemInfo) v.getTag(), v)) {
462                        return v;
463                    }
464                }
465            }
466        }
467        return null;
468    }
469
470    public String getAccessibilityDescription() {
471        return getContext().getString(R.string.folder_opened, mGridCountX, mGridCountY);
472    }
473
474    /**
475     * Sets the focus on the first visible child.
476     */
477    public void setFocusOnFirstChild() {
478        View firstChild = getCurrentCellLayout().getChildAt(0, 0);
479        if (firstChild != null) {
480            firstChild.requestFocus();
481        }
482    }
483
484    @Override
485    protected void notifyPageSwitchListener() {
486        super.notifyPageSwitchListener();
487        if (mFolder != null) {
488            mFolder.updateTextViewFocus();
489        }
490    }
491
492    /**
493     * Scrolls the current view by a fraction
494     */
495    public void showScrollHint(int direction) {
496        float fraction = (direction == DragController.SCROLL_LEFT) ^ mIsRtl
497                ? -SCROLL_HINT_FRACTION : SCROLL_HINT_FRACTION;
498        int hint = (int) (fraction * getWidth());
499        int scroll = getScrollForPage(getNextPage()) + hint;
500        int delta = scroll - getScrollX();
501        if (delta != 0) {
502            mScroller.setInterpolator(new DecelerateInterpolator());
503            mScroller.startScroll(getScrollX(), 0, delta, 0, Folder.SCROLL_HINT_DURATION);
504            invalidate();
505        }
506    }
507
508    public void clearScrollHint() {
509        if (getScrollX() != getScrollForPage(getNextPage())) {
510            snapToPage(getNextPage());
511        }
512    }
513
514    /**
515     * Finish animation all the views which are animating across pages
516     */
517    public void completePendingPageChanges() {
518        if (!mPendingAnimations.isEmpty()) {
519            HashMap<View, Runnable> pendingViews = new HashMap<>(mPendingAnimations);
520            for (Map.Entry<View, Runnable> e : pendingViews.entrySet()) {
521                e.getKey().animate().cancel();
522                e.getValue().run();
523            }
524        }
525    }
526
527    public boolean rankOnCurrentPage(int rank) {
528        int p = rank / mMaxItemsPerPage;
529        return p == getNextPage();
530    }
531
532    @Override
533    protected void onPageBeginMoving() {
534        super.onPageBeginMoving();
535        getVisiblePages(sTempPosArray);
536        for (int i = sTempPosArray[0]; i <= sTempPosArray[1]; i++) {
537            verifyVisibleHighResIcons(i);
538        }
539    }
540
541    /**
542     * Ensures that all the icons on the given page are of high-res
543     */
544    public void verifyVisibleHighResIcons(int pageNo) {
545        CellLayout page = getPageAt(pageNo);
546        if (page != null) {
547            ShortcutAndWidgetContainer parent = page.getShortcutsAndWidgets();
548            for (int i = parent.getChildCount() - 1; i >= 0; i--) {
549                ((BubbleTextView) parent.getChildAt(i)).verifyHighRes();
550            }
551        }
552    }
553
554    public int getAllocatedContentSize() {
555        return mAllocatedContentSize;
556    }
557
558    /**
559     * Reorders the items such that the {@param empty} spot moves to {@param target}
560     */
561    public void realTimeReorder(int empty, int target) {
562        completePendingPageChanges();
563        int delay = 0;
564        float delayAmount = START_VIEW_REORDER_DELAY;
565
566        // Animation only happens on the current page.
567        int pageToAnimate = getNextPage();
568
569        int pageT = target / mMaxItemsPerPage;
570        int pagePosT = target % mMaxItemsPerPage;
571
572        if (pageT != pageToAnimate) {
573            Log.e(TAG, "Cannot animate when the target cell is invisible");
574        }
575        int pagePosE = empty % mMaxItemsPerPage;
576        int pageE = empty / mMaxItemsPerPage;
577
578        int startPos, endPos;
579        int moveStart, moveEnd;
580        int direction;
581
582        if (target == empty) {
583            // No animation
584            return;
585        } else if (target > empty) {
586            // Items will move backwards to make room for the empty cell.
587            direction = 1;
588
589            // If empty cell is in a different page, move them instantly.
590            if (pageE < pageToAnimate) {
591                moveStart = empty;
592                // Instantly move the first item in the current page.
593                moveEnd = pageToAnimate * mMaxItemsPerPage;
594                // Animate the 2nd item in the current page, as the first item was already moved to
595                // the last page.
596                startPos = 0;
597            } else {
598                moveStart = moveEnd = -1;
599                startPos = pagePosE;
600            }
601
602            endPos = pagePosT;
603        } else {
604            // The items will move forward.
605            direction = -1;
606
607            if (pageE > pageToAnimate) {
608                // Move the items immediately.
609                moveStart = empty;
610                // Instantly move the last item in the current page.
611                moveEnd = (pageToAnimate + 1) * mMaxItemsPerPage - 1;
612
613                // Animations start with the second last item in the page
614                startPos = mMaxItemsPerPage - 1;
615            } else {
616                moveStart = moveEnd = -1;
617                startPos = pagePosE;
618            }
619
620            endPos = pagePosT;
621        }
622
623        // Instant moving views.
624        while (moveStart != moveEnd) {
625            int rankToMove = moveStart + direction;
626            int p = rankToMove / mMaxItemsPerPage;
627            int pagePos = rankToMove % mMaxItemsPerPage;
628            int x = pagePos % mGridCountX;
629            int y = pagePos / mGridCountX;
630
631            final CellLayout page = getPageAt(p);
632            final View v = page.getChildAt(x, y);
633            if (v != null) {
634                if (pageToAnimate != p) {
635                    page.removeView(v);
636                    addViewForRank(v, (ShortcutInfo) v.getTag(), moveStart);
637                } else {
638                    // Do a fake animation before removing it.
639                    final int newRank = moveStart;
640                    final float oldTranslateX = v.getTranslationX();
641
642                    Runnable endAction = new Runnable() {
643
644                        @Override
645                        public void run() {
646                            mPendingAnimations.remove(v);
647                            v.setTranslationX(oldTranslateX);
648                            ((CellLayout) v.getParent().getParent()).removeView(v);
649                            addViewForRank(v, (ShortcutInfo) v.getTag(), newRank);
650                        }
651                    };
652                    v.animate()
653                        .translationXBy((direction > 0 ^ mIsRtl) ? -v.getWidth() : v.getWidth())
654                        .setDuration(REORDER_ANIMATION_DURATION)
655                        .setStartDelay(0)
656                        .withEndAction(endAction);
657                    mPendingAnimations.put(v, endAction);
658                }
659            }
660            moveStart = rankToMove;
661        }
662
663        if ((endPos - startPos) * direction <= 0) {
664            // No animation
665            return;
666        }
667
668        CellLayout page = getPageAt(pageToAnimate);
669        for (int i = startPos; i != endPos; i += direction) {
670            int nextPos = i + direction;
671            View v = page.getChildAt(nextPos % mGridCountX, nextPos / mGridCountX);
672            if (v != null) {
673                ((ItemInfo) v.getTag()).rank -= direction;
674            }
675            if (page.animateChildToPosition(v, i % mGridCountX, i / mGridCountX,
676                    REORDER_ANIMATION_DURATION, delay, true, true)) {
677                delay += delayAmount;
678                delayAmount *= VIEW_REORDER_DELAY_FACTOR;
679            }
680        }
681    }
682
683    public int itemsPerPage() {
684        return mMaxItemsPerPage;
685    }
686
687    @Override
688    protected void getEdgeVerticalPostion(int[] pos) {
689        pos[0] = 0;
690        pos[1] = getViewportHeight();
691    }
692}
693