ViewPager.java revision d84ef6c972b75c0ddeee88db2d66d40fff7519a8
1/*
2 * Copyright (C) 2011 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 android.support.v4.view;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.database.DataSetObserver;
22import android.graphics.Canvas;
23import android.graphics.Rect;
24import android.graphics.drawable.Drawable;
25import android.os.Build;
26import android.os.Parcel;
27import android.os.Parcelable;
28import android.os.SystemClock;
29import android.support.v4.os.ParcelableCompat;
30import android.support.v4.os.ParcelableCompatCreatorCallbacks;
31import android.support.v4.widget.EdgeEffectCompat;
32import android.util.AttributeSet;
33import android.util.Log;
34import android.view.FocusFinder;
35import android.view.Gravity;
36import android.view.KeyEvent;
37import android.view.MotionEvent;
38import android.view.SoundEffectConstants;
39import android.view.VelocityTracker;
40import android.view.View;
41import android.view.ViewConfiguration;
42import android.view.ViewGroup;
43import android.view.ViewParent;
44import android.view.accessibility.AccessibilityEvent;
45import android.view.animation.Interpolator;
46import android.widget.Scroller;
47
48import java.util.ArrayList;
49import java.util.Collections;
50import java.util.Comparator;
51
52/**
53 * Layout manager that allows the user to flip left and right
54 * through pages of data.  You supply an implementation of a
55 * {@link PagerAdapter} to generate the pages that the view shows.
56 *
57 * <p>Note this class is currently under early design and
58 * development.  The API will likely change in later updates of
59 * the compatibility library, requiring changes to the source code
60 * of apps when they are compiled against the newer version.</p>
61 *
62 * <p>ViewPager is most often used in conjunction with {@link android.app.Fragment},
63 * which is a convenient way to supply and manage the lifecycle of each page.
64 * There are standard adapters implemented for using fragments with the ViewPager,
65 * which cover the most common use cases.  These are
66 * {@link android.support.v4.app.FragmentPagerAdapter},
67 * {@link android.support.v4.app.FragmentStatePagerAdapter},
68 * {@link android.support.v13.app.FragmentPagerAdapter}, and
69 * {@link android.support.v13.app.FragmentStatePagerAdapter}; each of these
70 * classes have simple code showing how to build a full user interface
71 * with them.
72 *
73 * <p>Here is a more complicated example of ViewPager, using it in conjuction
74 * with {@link android.app.ActionBar} tabs.  You can find other examples of using
75 * ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code.
76 *
77 * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java
78 *      complete}
79 */
80public class ViewPager extends ViewGroup {
81    private static final String TAG = "ViewPager";
82    private static final boolean DEBUG = false;
83
84    private static final boolean USE_CACHE = false;
85
86    private static final int DEFAULT_OFFSCREEN_PAGES = 1;
87    private static final int MAX_SETTLE_DURATION = 600; // ms
88    private static final int MIN_DISTANCE_FOR_FLING = 25; // dips
89
90    private static final int[] LAYOUT_ATTRS = new int[] {
91        android.R.attr.layout_gravity
92    };
93
94    static class ItemInfo {
95        Object object;
96        int position;
97        boolean scrolling;
98        float widthFactor;
99        float offset;
100    }
101
102    private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>(){
103        @Override
104        public int compare(ItemInfo lhs, ItemInfo rhs) {
105            return lhs.position - rhs.position;
106        }
107    };
108
109    private static final Interpolator sInterpolator = new Interpolator() {
110        public float getInterpolation(float t) {
111            t -= 1.0f;
112            return t * t * t * t * t + 1.0f;
113        }
114    };
115
116    private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
117    private final ItemInfo mTempItem = new ItemInfo();
118
119    private PagerAdapter mAdapter;
120    private int mCurItem;   // Index of currently displayed page.
121    private int mRestoredCurItem = -1;
122    private Parcelable mRestoredAdapterState = null;
123    private ClassLoader mRestoredClassLoader = null;
124    private Scroller mScroller;
125    private PagerObserver mObserver;
126
127    private int mPageMargin;
128    private Drawable mMarginDrawable;
129    private int mTopPageBounds;
130    private int mBottomPageBounds;
131
132    // Offsets of the first and last items, if known.
133    // Set during population, used to determine if we are at the beginning
134    // or end of the pager data set during touch scrolling.
135    private float mFirstOffset = -Float.MAX_VALUE;
136    private float mLastOffset = Float.MAX_VALUE;
137
138    private int mChildWidthMeasureSpec;
139    private int mChildHeightMeasureSpec;
140    private boolean mInLayout;
141
142    private boolean mScrollingCacheEnabled;
143
144    private boolean mPopulatePending;
145    private boolean mScrolling;
146    private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
147
148    private boolean mIsBeingDragged;
149    private boolean mIsUnableToDrag;
150    private int mTouchSlop;
151    private float mInitialMotionX;
152    /**
153     * Position of the last motion event.
154     */
155    private float mLastMotionX;
156    private float mLastMotionY;
157    /**
158     * ID of the active pointer. This is used to retain consistency during
159     * drags/flings if multiple pointers are used.
160     */
161    private int mActivePointerId = INVALID_POINTER;
162    /**
163     * Sentinel value for no current active pointer.
164     * Used by {@link #mActivePointerId}.
165     */
166    private static final int INVALID_POINTER = -1;
167
168    /**
169     * Determines speed during touch scrolling
170     */
171    private VelocityTracker mVelocityTracker;
172    private int mMinimumVelocity;
173    private int mMaximumVelocity;
174    private int mFlingDistance;
175    private int mCloseEnough;
176
177    // If the pager is at least this close to its final position, complete the scroll
178    // on touch down and let the user interact with the content inside instead of
179    // "catching" the flinging pager.
180    private static final int CLOSE_ENOUGH = 2; // dp
181
182    private boolean mFakeDragging;
183    private long mFakeDragBeginTime;
184
185    private EdgeEffectCompat mLeftEdge;
186    private EdgeEffectCompat mRightEdge;
187
188    private boolean mFirstLayout = true;
189    private boolean mNeedCalculatePageOffsets = false;
190    private boolean mCalledSuper;
191    private int mDecorChildCount;
192
193    private OnPageChangeListener mOnPageChangeListener;
194    private OnPageChangeListener mInternalPageChangeListener;
195    private OnAdapterChangeListener mAdapterChangeListener;
196
197    /**
198     * Indicates that the pager is in an idle, settled state. The current page
199     * is fully in view and no animation is in progress.
200     */
201    public static final int SCROLL_STATE_IDLE = 0;
202
203    /**
204     * Indicates that the pager is currently being dragged by the user.
205     */
206    public static final int SCROLL_STATE_DRAGGING = 1;
207
208    /**
209     * Indicates that the pager is in the process of settling to a final position.
210     */
211    public static final int SCROLL_STATE_SETTLING = 2;
212
213    private int mScrollState = SCROLL_STATE_IDLE;
214
215    /**
216     * Callback interface for responding to changing state of the selected page.
217     */
218    public interface OnPageChangeListener {
219
220        /**
221         * This method will be invoked when the current page is scrolled, either as part
222         * of a programmatically initiated smooth scroll or a user initiated touch scroll.
223         *
224         * @param position Position index of the first page currently being displayed.
225         *                 Page position+1 will be visible if positionOffset is nonzero.
226         * @param positionOffset Value from [0, 1) indicating the offset from the page at position.
227         * @param positionOffsetPixels Value in pixels indicating the offset from position.
228         */
229        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
230
231        /**
232         * This method will be invoked when a new page becomes selected. Animation is not
233         * necessarily complete.
234         *
235         * @param position Position index of the new selected page.
236         */
237        public void onPageSelected(int position);
238
239        /**
240         * Called when the scroll state changes. Useful for discovering when the user
241         * begins dragging, when the pager is automatically settling to the current page,
242         * or when it is fully stopped/idle.
243         *
244         * @param state The new scroll state.
245         * @see ViewPager#SCROLL_STATE_IDLE
246         * @see ViewPager#SCROLL_STATE_DRAGGING
247         * @see ViewPager#SCROLL_STATE_SETTLING
248         */
249        public void onPageScrollStateChanged(int state);
250    }
251
252    /**
253     * Simple implementation of the {@link OnPageChangeListener} interface with stub
254     * implementations of each method. Extend this if you do not intend to override
255     * every method of {@link OnPageChangeListener}.
256     */
257    public static class SimpleOnPageChangeListener implements OnPageChangeListener {
258        @Override
259        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
260            // This space for rent
261        }
262
263        @Override
264        public void onPageSelected(int position) {
265            // This space for rent
266        }
267
268        @Override
269        public void onPageScrollStateChanged(int state) {
270            // This space for rent
271        }
272    }
273
274    /**
275     * Used internally to monitor when adapters are switched.
276     */
277    interface OnAdapterChangeListener {
278        public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);
279    }
280
281    /**
282     * Used internally to tag special types of child views that should be added as
283     * pager decorations by default.
284     */
285    interface Decor {}
286
287    public ViewPager(Context context) {
288        super(context);
289        initViewPager();
290    }
291
292    public ViewPager(Context context, AttributeSet attrs) {
293        super(context, attrs);
294        initViewPager();
295    }
296
297    void initViewPager() {
298        setWillNotDraw(false);
299        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
300        setFocusable(true);
301        final Context context = getContext();
302        mScroller = new Scroller(context, sInterpolator);
303        final ViewConfiguration configuration = ViewConfiguration.get(context);
304        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
305        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
306        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
307        mLeftEdge = new EdgeEffectCompat(context);
308        mRightEdge = new EdgeEffectCompat(context);
309
310        final float density = context.getResources().getDisplayMetrics().density;
311        mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
312        mCloseEnough = (int) (CLOSE_ENOUGH * density);
313    }
314
315    private void setScrollState(int newState) {
316        if (mScrollState == newState) {
317            return;
318        }
319
320        mScrollState = newState;
321        if (mOnPageChangeListener != null) {
322            mOnPageChangeListener.onPageScrollStateChanged(newState);
323        }
324    }
325
326    /**
327     * Set a PagerAdapter that will supply views for this pager as needed.
328     *
329     * @param adapter Adapter to use
330     */
331    public void setAdapter(PagerAdapter adapter) {
332        if (mAdapter != null) {
333            mAdapter.unregisterDataSetObserver(mObserver);
334            mAdapter.startUpdate(this);
335            for (int i = 0; i < mItems.size(); i++) {
336                final ItemInfo ii = mItems.get(i);
337                mAdapter.destroyItem(this, ii.position, ii.object);
338            }
339            mAdapter.finishUpdate(this);
340            mItems.clear();
341            removeNonDecorViews();
342            mCurItem = 0;
343            scrollTo(0, 0);
344        }
345
346        final PagerAdapter oldAdapter = mAdapter;
347        mAdapter = adapter;
348
349        if (mAdapter != null) {
350            if (mObserver == null) {
351                mObserver = new PagerObserver();
352            }
353            mAdapter.registerDataSetObserver(mObserver);
354            mPopulatePending = false;
355            mFirstLayout = true;
356            if (mRestoredCurItem >= 0) {
357                mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
358                setCurrentItemInternal(mRestoredCurItem, false, true);
359                mRestoredCurItem = -1;
360                mRestoredAdapterState = null;
361                mRestoredClassLoader = null;
362            } else {
363                populate();
364            }
365        }
366
367        if (mAdapterChangeListener != null && oldAdapter != adapter) {
368            mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
369        }
370    }
371
372    private void removeNonDecorViews() {
373        for (int i = 0; i < getChildCount(); i++) {
374            final View child = getChildAt(i);
375            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
376            if (!lp.isDecor) {
377                removeViewAt(i);
378                i--;
379            }
380        }
381    }
382
383    /**
384     * Retrieve the current adapter supplying pages.
385     *
386     * @return The currently registered PagerAdapter
387     */
388    public PagerAdapter getAdapter() {
389        return mAdapter;
390    }
391
392    void setOnAdapterChangeListener(OnAdapterChangeListener listener) {
393        mAdapterChangeListener = listener;
394    }
395
396    /**
397     * Set the currently selected page. If the ViewPager has already been through its first
398     * layout with its current adapter there will be a smooth animated transition between
399     * the current item and the specified item.
400     *
401     * @param item Item index to select
402     */
403    public void setCurrentItem(int item) {
404        mPopulatePending = false;
405        setCurrentItemInternal(item, !mFirstLayout, false);
406    }
407
408    /**
409     * Set the currently selected page.
410     *
411     * @param item Item index to select
412     * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
413     */
414    public void setCurrentItem(int item, boolean smoothScroll) {
415        mPopulatePending = false;
416        setCurrentItemInternal(item, smoothScroll, false);
417    }
418
419    public int getCurrentItem() {
420        return mCurItem;
421    }
422
423    void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
424        setCurrentItemInternal(item, smoothScroll, always, 0);
425    }
426
427    void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
428        if (mAdapter == null || mAdapter.getCount() <= 0) {
429            setScrollingCacheEnabled(false);
430            return;
431        }
432        if (!always && mCurItem == item && mItems.size() != 0) {
433            setScrollingCacheEnabled(false);
434            return;
435        }
436        if (item < 0) {
437            item = 0;
438        } else if (item >= mAdapter.getCount()) {
439            item = mAdapter.getCount() - 1;
440        }
441        final int pageLimit = mOffscreenPageLimit;
442        if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
443            // We are doing a jump by more than one page.  To avoid
444            // glitches, we want to keep all current pages in the view
445            // until the scroll ends.
446            for (int i=0; i<mItems.size(); i++) {
447                mItems.get(i).scrolling = true;
448            }
449        }
450        final boolean dispatchSelected = mCurItem != item;
451        populate(item);
452        final ItemInfo curInfo = infoForPosition(item);
453        int destX = 0;
454        if (curInfo != null) {
455            final int width = getWidth();
456            destX = (int) (width * Math.max(mFirstOffset,
457                    Math.min(curInfo.offset, mLastOffset)));
458        }
459        if (smoothScroll) {
460            smoothScrollTo(destX, 0, velocity);
461            if (dispatchSelected && mOnPageChangeListener != null) {
462                mOnPageChangeListener.onPageSelected(item);
463            }
464            if (dispatchSelected && mInternalPageChangeListener != null) {
465                mInternalPageChangeListener.onPageSelected(item);
466            }
467        } else {
468            if (dispatchSelected && mOnPageChangeListener != null) {
469                mOnPageChangeListener.onPageSelected(item);
470            }
471            if (dispatchSelected && mInternalPageChangeListener != null) {
472                mInternalPageChangeListener.onPageSelected(item);
473            }
474            completeScroll();
475            scrollTo(destX, 0);
476        }
477    }
478
479    /**
480     * Set a listener that will be invoked whenever the page changes or is incrementally
481     * scrolled. See {@link OnPageChangeListener}.
482     *
483     * @param listener Listener to set
484     */
485    public void setOnPageChangeListener(OnPageChangeListener listener) {
486        mOnPageChangeListener = listener;
487    }
488
489    /**
490     * Set a separate OnPageChangeListener for internal use by the support library.
491     *
492     * @param listener Listener to set
493     * @return The old listener that was set, if any.
494     */
495    OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) {
496        OnPageChangeListener oldListener = mInternalPageChangeListener;
497        mInternalPageChangeListener = listener;
498        return oldListener;
499    }
500
501    /**
502     * Returns the number of pages that will be retained to either side of the
503     * current page in the view hierarchy in an idle state. Defaults to 1.
504     *
505     * @return How many pages will be kept offscreen on either side
506     * @see #setOffscreenPageLimit(int)
507     */
508    public int getOffscreenPageLimit() {
509        return mOffscreenPageLimit;
510    }
511
512    /**
513     * Set the number of pages that should be retained to either side of the
514     * current page in the view hierarchy in an idle state. Pages beyond this
515     * limit will be recreated from the adapter when needed.
516     *
517     * <p>This is offered as an optimization. If you know in advance the number
518     * of pages you will need to support or have lazy-loading mechanisms in place
519     * on your pages, tweaking this setting can have benefits in perceived smoothness
520     * of paging animations and interaction. If you have a small number of pages (3-4)
521     * that you can keep active all at once, less time will be spent in layout for
522     * newly created view subtrees as the user pages back and forth.</p>
523     *
524     * <p>You should keep this limit low, especially if your pages have complex layouts.
525     * This setting defaults to 1.</p>
526     *
527     * @param limit How many pages will be kept offscreen in an idle state.
528     */
529    public void setOffscreenPageLimit(int limit) {
530        if (limit < DEFAULT_OFFSCREEN_PAGES) {
531            Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
532                    DEFAULT_OFFSCREEN_PAGES);
533            limit = DEFAULT_OFFSCREEN_PAGES;
534        }
535        if (limit != mOffscreenPageLimit) {
536            mOffscreenPageLimit = limit;
537            populate();
538        }
539    }
540
541    /**
542     * Set the margin between pages.
543     *
544     * @param marginPixels Distance between adjacent pages in pixels
545     * @see #getPageMargin()
546     * @see #setPageMarginDrawable(Drawable)
547     * @see #setPageMarginDrawable(int)
548     */
549    public void setPageMargin(int marginPixels) {
550        final int oldMargin = mPageMargin;
551        mPageMargin = marginPixels;
552
553        final int width = getWidth();
554        recomputeScrollPosition(width, width, marginPixels, oldMargin);
555
556        requestLayout();
557    }
558
559    /**
560     * Return the margin between pages.
561     *
562     * @return The size of the margin in pixels
563     */
564    public int getPageMargin() {
565        return mPageMargin;
566    }
567
568    /**
569     * Set a drawable that will be used to fill the margin between pages.
570     *
571     * @param d Drawable to display between pages
572     */
573    public void setPageMarginDrawable(Drawable d) {
574        mMarginDrawable = d;
575        if (d != null) refreshDrawableState();
576        setWillNotDraw(d == null);
577        invalidate();
578    }
579
580    /**
581     * Set a drawable that will be used to fill the margin between pages.
582     *
583     * @param resId Resource ID of a drawable to display between pages
584     */
585    public void setPageMarginDrawable(int resId) {
586        setPageMarginDrawable(getContext().getResources().getDrawable(resId));
587    }
588
589    @Override
590    protected boolean verifyDrawable(Drawable who) {
591        return super.verifyDrawable(who) || who == mMarginDrawable;
592    }
593
594    @Override
595    protected void drawableStateChanged() {
596        super.drawableStateChanged();
597        final Drawable d = mMarginDrawable;
598        if (d != null && d.isStateful()) {
599            d.setState(getDrawableState());
600        }
601    }
602
603    // We want the duration of the page snap animation to be influenced by the distance that
604    // the screen has to travel, however, we don't want this duration to be effected in a
605    // purely linear fashion. Instead, we use this method to moderate the effect that the distance
606    // of travel has on the overall snap duration.
607    float distanceInfluenceForSnapDuration(float f) {
608        f -= 0.5f; // center the values about 0.
609        f *= 0.3f * Math.PI / 2.0f;
610        return (float) Math.sin(f);
611    }
612
613    /**
614     * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
615     *
616     * @param x the number of pixels to scroll by on the X axis
617     * @param y the number of pixels to scroll by on the Y axis
618     */
619    void smoothScrollTo(int x, int y) {
620        smoothScrollTo(x, y, 0);
621    }
622
623    /**
624     * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
625     *
626     * @param x the number of pixels to scroll by on the X axis
627     * @param y the number of pixels to scroll by on the Y axis
628     * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
629     */
630    void smoothScrollTo(int x, int y, int velocity) {
631        if (getChildCount() == 0) {
632            // Nothing to do.
633            setScrollingCacheEnabled(false);
634            return;
635        }
636        int sx = getScrollX();
637        int sy = getScrollY();
638        int dx = x - sx;
639        int dy = y - sy;
640        if (dx == 0 && dy == 0) {
641            completeScroll();
642            populate();
643            setScrollState(SCROLL_STATE_IDLE);
644            return;
645        }
646
647        setScrollingCacheEnabled(true);
648        mScrolling = true;
649        setScrollState(SCROLL_STATE_SETTLING);
650
651        final int width = getWidth();
652        final int halfWidth = width / 2;
653        final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
654        final float distance = halfWidth + halfWidth *
655                distanceInfluenceForSnapDuration(distanceRatio);
656
657        int duration = 0;
658        velocity = Math.abs(velocity);
659        if (velocity > 0) {
660            duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
661        } else {
662            final float pageWidth = width * mAdapter.getPageWidth(mCurItem);
663            final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin);
664            duration = (int) ((pageDelta + 1) * 100);
665        }
666        duration = Math.min(duration, MAX_SETTLE_DURATION);
667
668        mScroller.startScroll(sx, sy, dx, dy, duration);
669        ViewCompat.postInvalidateOnAnimation(this);
670    }
671
672    ItemInfo addNewItem(int position, int index) {
673        ItemInfo ii = new ItemInfo();
674        ii.position = position;
675        ii.object = mAdapter.instantiateItem(this, position);
676        ii.widthFactor = mAdapter.getPageWidth(position);
677        if (index < 0 || index >= mItems.size()) {
678            mItems.add(ii);
679        } else {
680            mItems.add(index, ii);
681        }
682        return ii;
683    }
684
685    void dataSetChanged() {
686        // This method only gets called if our observer is attached, so mAdapter is non-null.
687
688        boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
689                mItems.size() < mAdapter.getCount();
690        int newCurrItem = mCurItem;
691
692        boolean isUpdating = false;
693        for (int i = 0; i < mItems.size(); i++) {
694            final ItemInfo ii = mItems.get(i);
695            final int newPos = mAdapter.getItemPosition(ii.object);
696
697            if (newPos == PagerAdapter.POSITION_UNCHANGED) {
698                continue;
699            }
700
701            if (newPos == PagerAdapter.POSITION_NONE) {
702                mItems.remove(i);
703                i--;
704
705                if (!isUpdating) {
706                    mAdapter.startUpdate(this);
707                    isUpdating = true;
708                }
709
710                mAdapter.destroyItem(this, ii.position, ii.object);
711                needPopulate = true;
712
713                if (mCurItem == ii.position) {
714                    // Keep the current item in the valid range
715                    newCurrItem = Math.max(0, Math.min(mCurItem, mAdapter.getCount() - 1));
716                    needPopulate = true;
717                }
718                continue;
719            }
720
721            if (ii.position != newPos) {
722                if (ii.position == mCurItem) {
723                    // Our current item changed position. Follow it.
724                    newCurrItem = newPos;
725                }
726
727                ii.position = newPos;
728                needPopulate = true;
729            }
730        }
731
732        if (isUpdating) {
733            mAdapter.finishUpdate(this);
734        }
735
736        Collections.sort(mItems, COMPARATOR);
737
738        if (needPopulate) {
739            // Reset our known page widths; populate will recompute them.
740            final int childCount = getChildCount();
741            for (int i = 0; i < childCount; i++) {
742                final View child = getChildAt(i);
743                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
744                if (!lp.isDecor) {
745                    lp.widthFactor = 0.f;
746                }
747            }
748
749            setCurrentItemInternal(newCurrItem, false, true);
750            requestLayout();
751        }
752    }
753
754    void populate() {
755        populate(mCurItem);
756    }
757
758    void populate(int newCurrentItem) {
759        ItemInfo oldCurInfo = null;
760        if (mCurItem != newCurrentItem) {
761            oldCurInfo = infoForPosition(mCurItem);
762            mCurItem = newCurrentItem;
763        }
764
765        if (mAdapter == null) {
766            return;
767        }
768
769        // Bail now if we are waiting to populate.  This is to hold off
770        // on creating views from the time the user releases their finger to
771        // fling to a new position until we have finished the scroll to
772        // that position, avoiding glitches from happening at that point.
773        if (mPopulatePending) {
774            if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
775            return;
776        }
777
778        // Also, don't populate until we are attached to a window.  This is to
779        // avoid trying to populate before we have restored our view hierarchy
780        // state and conflicting with what is restored.
781        if (getWindowToken() == null) {
782            return;
783        }
784
785        mAdapter.startUpdate(this);
786
787        final int pageLimit = mOffscreenPageLimit;
788        final int startPos = Math.max(0, mCurItem - pageLimit);
789        final int N = mAdapter.getCount();
790        final int endPos = Math.min(N-1, mCurItem + pageLimit);
791
792        // Locate the currently focused item or add it if needed.
793        int curIndex = -1;
794        ItemInfo curItem = null;
795        for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
796            final ItemInfo ii = mItems.get(curIndex);
797            if (ii.position >= mCurItem) {
798                if (ii.position == mCurItem) curItem = ii;
799                break;
800            }
801        }
802
803        if (curItem == null && N > 0) {
804            curItem = addNewItem(mCurItem, curIndex);
805        }
806
807        // Fill 3x the available width or up to the number of offscreen
808        // pages requested to either side, whichever is larger.
809        // If we have no current item we have no work to do.
810        if (curItem != null) {
811            float extraWidthLeft = 0.f;
812            int itemIndex = curIndex - 1;
813            ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
814            final float leftWidthNeeded = 2.f - curItem.widthFactor;
815            for (int pos = mCurItem - 1; pos >= 0; pos--) {
816                if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
817                    if (ii == null) {
818                        break;
819                    }
820                    if (pos == ii.position && !ii.scrolling) {
821                        mItems.remove(itemIndex);
822                        mAdapter.destroyItem(this, pos, ii.object);
823                        itemIndex--;
824                        curIndex--;
825                        ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
826                    }
827                } else if (ii != null && pos == ii.position) {
828                    extraWidthLeft += ii.widthFactor;
829                    itemIndex--;
830                    ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
831                } else {
832                    ii = addNewItem(pos, itemIndex + 1);
833                    extraWidthLeft += ii.widthFactor;
834                    curIndex++;
835                    ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
836                }
837            }
838
839            float extraWidthRight = curItem.widthFactor;
840            itemIndex = curIndex + 1;
841            if (extraWidthRight < 2.f) {
842                ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
843                for (int pos = mCurItem + 1; pos < N; pos++) {
844                    if (extraWidthRight >= 2.f && pos > endPos) {
845                        if (ii == null) {
846                            break;
847                        }
848                        if (pos == ii.position && !ii.scrolling) {
849                            mItems.remove(itemIndex);
850                            mAdapter.destroyItem(this, pos, ii.object);
851                            ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
852                        }
853                    } else if (ii != null && pos == ii.position) {
854                        extraWidthRight += ii.widthFactor;
855                        itemIndex++;
856                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
857                    } else {
858                        ii = addNewItem(pos, itemIndex);
859                        itemIndex++;
860                        extraWidthRight += ii.widthFactor;
861                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
862                    }
863                }
864            }
865
866            calculatePageOffsets(curItem, curIndex, oldCurInfo);
867        }
868
869        if (DEBUG) {
870            Log.i(TAG, "Current page list:");
871            for (int i=0; i<mItems.size(); i++) {
872                Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
873            }
874        }
875
876        mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
877
878        mAdapter.finishUpdate(this);
879
880        // Check width measurement of current pages. Update LayoutParams as needed.
881        final int childCount = getChildCount();
882        for (int i = 0; i < childCount; i++) {
883            final View child = getChildAt(i);
884            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
885            if (!lp.isDecor && lp.widthFactor == 0.f) {
886                // 0 means requery the adapter for this, it doesn't have a valid width.
887                final ItemInfo ii = infoForChild(child);
888                if (ii != null) {
889                    lp.widthFactor = ii.widthFactor;
890                }
891            }
892        }
893
894        if (hasFocus()) {
895            View currentFocused = findFocus();
896            ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
897            if (ii == null || ii.position != mCurItem) {
898                for (int i=0; i<getChildCount(); i++) {
899                    View child = getChildAt(i);
900                    ii = infoForChild(child);
901                    if (ii != null && ii.position == mCurItem) {
902                        if (child.requestFocus(FOCUS_FORWARD)) {
903                            break;
904                        }
905                    }
906                }
907            }
908        }
909    }
910
911    private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
912        final int N = mAdapter.getCount();
913        final int width = getWidth();
914        final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
915        // Fix up offsets for later layout.
916        if (oldCurInfo != null) {
917            final int oldCurPosition = oldCurInfo.position;
918            // Base offsets off of oldCurInfo.
919            if (oldCurPosition < curItem.position) {
920                int itemIndex = 0;
921                ItemInfo ii = null;
922                float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset;
923                for (int pos = oldCurPosition + 1;
924                        pos <= curItem.position && itemIndex < mItems.size(); pos++) {
925                    ii = mItems.get(itemIndex);
926                    while (pos > ii.position && itemIndex < mItems.size() - 1) {
927                        itemIndex++;
928                        ii = mItems.get(itemIndex);
929                    }
930                    while (pos < ii.position) {
931                        // We don't have an item populated for this,
932                        // ask the adapter for an offset.
933                        offset += mAdapter.getPageWidth(pos) + marginOffset;
934                        pos++;
935                    }
936                    ii.offset = offset;
937                    offset += ii.widthFactor + marginOffset;
938                }
939            } else if (oldCurPosition > curItem.position) {
940                int itemIndex = mItems.size() - 1;
941                ItemInfo ii = null;
942                float offset = oldCurInfo.offset;
943                for (int pos = oldCurPosition - 1;
944                        pos >= curItem.position && itemIndex >= 0; pos--) {
945                    ii = mItems.get(itemIndex);
946                    while (pos < ii.position && itemIndex > 0) {
947                        itemIndex--;
948                        ii = mItems.get(itemIndex);
949                    }
950                    while (pos > ii.position) {
951                        // We don't have an item populated for this,
952                        // ask the adapter for an offset.
953                        offset -= mAdapter.getPageWidth(pos) + marginOffset;
954                        pos--;
955                    }
956                    offset -= ii.widthFactor + marginOffset;
957                    ii.offset = offset;
958                }
959            }
960        }
961
962        // Base all offsets off of curItem.
963        final int itemCount = mItems.size();
964        float offset = curItem.offset;
965        int pos = curItem.position - 1;
966        mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
967        mLastOffset = curItem.position == N - 1 ?
968                curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE;
969        // Previous pages
970        for (int i = curIndex - 1; i >= 0; i--, pos--) {
971            final ItemInfo ii = mItems.get(i);
972            while (pos > ii.position) {
973                offset -= mAdapter.getPageWidth(pos--) + marginOffset;
974            }
975            offset -= ii.widthFactor + marginOffset;
976            ii.offset = offset;
977            if (ii.position == 0) mFirstOffset = offset;
978        }
979        offset = curItem.offset + curItem.widthFactor + marginOffset;
980        pos = curItem.position + 1;
981        // Next pages
982        for (int i = curIndex + 1; i < itemCount; i++, pos++) {
983            final ItemInfo ii = mItems.get(i);
984            while (pos < ii.position) {
985                offset += mAdapter.getPageWidth(pos++) + marginOffset;
986            }
987            if (ii.position == N - 1) {
988                mLastOffset = offset + ii.widthFactor - 1;
989            }
990            ii.offset = offset;
991            offset += ii.widthFactor + marginOffset;
992        }
993
994        mNeedCalculatePageOffsets = false;
995    }
996
997    /**
998     * This is the persistent state that is saved by ViewPager.  Only needed
999     * if you are creating a sublass of ViewPager that must save its own
1000     * state, in which case it should implement a subclass of this which
1001     * contains that state.
1002     */
1003    public static class SavedState extends BaseSavedState {
1004        int position;
1005        Parcelable adapterState;
1006        ClassLoader loader;
1007
1008        public SavedState(Parcelable superState) {
1009            super(superState);
1010        }
1011
1012        @Override
1013        public void writeToParcel(Parcel out, int flags) {
1014            super.writeToParcel(out, flags);
1015            out.writeInt(position);
1016            out.writeParcelable(adapterState, flags);
1017        }
1018
1019        @Override
1020        public String toString() {
1021            return "FragmentPager.SavedState{"
1022                    + Integer.toHexString(System.identityHashCode(this))
1023                    + " position=" + position + "}";
1024        }
1025
1026        public static final Parcelable.Creator<SavedState> CREATOR
1027                = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
1028                    @Override
1029                    public SavedState createFromParcel(Parcel in, ClassLoader loader) {
1030                        return new SavedState(in, loader);
1031                    }
1032                    @Override
1033                    public SavedState[] newArray(int size) {
1034                        return new SavedState[size];
1035                    }
1036                });
1037
1038        SavedState(Parcel in, ClassLoader loader) {
1039            super(in);
1040            if (loader == null) {
1041                loader = getClass().getClassLoader();
1042            }
1043            position = in.readInt();
1044            adapterState = in.readParcelable(loader);
1045            this.loader = loader;
1046        }
1047    }
1048
1049    @Override
1050    public Parcelable onSaveInstanceState() {
1051        Parcelable superState = super.onSaveInstanceState();
1052        SavedState ss = new SavedState(superState);
1053        ss.position = mCurItem;
1054        if (mAdapter != null) {
1055            ss.adapterState = mAdapter.saveState();
1056        }
1057        return ss;
1058    }
1059
1060    @Override
1061    public void onRestoreInstanceState(Parcelable state) {
1062        if (!(state instanceof SavedState)) {
1063            super.onRestoreInstanceState(state);
1064            return;
1065        }
1066
1067        SavedState ss = (SavedState)state;
1068        super.onRestoreInstanceState(ss.getSuperState());
1069
1070        if (mAdapter != null) {
1071            mAdapter.restoreState(ss.adapterState, ss.loader);
1072            setCurrentItemInternal(ss.position, false, true);
1073        } else {
1074            mRestoredCurItem = ss.position;
1075            mRestoredAdapterState = ss.adapterState;
1076            mRestoredClassLoader = ss.loader;
1077        }
1078    }
1079
1080    @Override
1081    public void addView(View child, int index, ViewGroup.LayoutParams params) {
1082        if (!checkLayoutParams(params)) {
1083            params = generateLayoutParams(params);
1084        }
1085        final LayoutParams lp = (LayoutParams) params;
1086        lp.isDecor |= child instanceof Decor;
1087        if (mInLayout) {
1088            if (lp != null && lp.isDecor) {
1089                throw new IllegalStateException("Cannot add pager decor view during layout");
1090            }
1091            lp.needsMeasure = true;
1092            addViewInLayout(child, index, params);
1093        } else {
1094            super.addView(child, index, params);
1095        }
1096
1097        if (USE_CACHE) {
1098            if (child.getVisibility() != GONE) {
1099                child.setDrawingCacheEnabled(mScrollingCacheEnabled);
1100            } else {
1101                child.setDrawingCacheEnabled(false);
1102            }
1103        }
1104    }
1105
1106    ItemInfo infoForChild(View child) {
1107        for (int i=0; i<mItems.size(); i++) {
1108            ItemInfo ii = mItems.get(i);
1109            if (mAdapter.isViewFromObject(child, ii.object)) {
1110                return ii;
1111            }
1112        }
1113        return null;
1114    }
1115
1116    ItemInfo infoForAnyChild(View child) {
1117        ViewParent parent;
1118        while ((parent=child.getParent()) != this) {
1119            if (parent == null || !(parent instanceof View)) {
1120                return null;
1121            }
1122            child = (View)parent;
1123        }
1124        return infoForChild(child);
1125    }
1126
1127    ItemInfo infoForPosition(int position) {
1128        for (int i = 0; i < mItems.size(); i++) {
1129            ItemInfo ii = mItems.get(i);
1130            if (ii.position == position) {
1131                return ii;
1132            }
1133        }
1134        return null;
1135    }
1136
1137    @Override
1138    protected void onAttachedToWindow() {
1139        super.onAttachedToWindow();
1140        mFirstLayout = true;
1141    }
1142
1143    @Override
1144    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1145        // For simple implementation, or internal size is always 0.
1146        // We depend on the container to specify the layout size of
1147        // our view.  We can't really know what it is since we will be
1148        // adding and removing different arbitrary views and do not
1149        // want the layout to change as this happens.
1150        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
1151                getDefaultSize(0, heightMeasureSpec));
1152
1153        // Children are just made to fill our space.
1154        int childWidthSize = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
1155        int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
1156
1157        /*
1158         * Make sure all children have been properly measured. Decor views first.
1159         * Right now we cheat and make this less complicated by assuming decor
1160         * views won't intersect. We will pin to edges based on gravity.
1161         */
1162        int size = getChildCount();
1163        for (int i = 0; i < size; ++i) {
1164            final View child = getChildAt(i);
1165            if (child.getVisibility() != GONE) {
1166                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1167                if (lp != null && lp.isDecor) {
1168                    final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
1169                    final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
1170                    int widthMode = MeasureSpec.AT_MOST;
1171                    int heightMode = MeasureSpec.AT_MOST;
1172                    boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
1173                    boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;
1174
1175                    if (consumeVertical) {
1176                        widthMode = MeasureSpec.EXACTLY;
1177                    } else if (consumeHorizontal) {
1178                        heightMode = MeasureSpec.EXACTLY;
1179                    }
1180
1181                    int widthSize = childWidthSize;
1182                    int heightSize = childHeightSize;
1183                    if (lp.width != LayoutParams.WRAP_CONTENT) {
1184                        widthMode = MeasureSpec.EXACTLY;
1185                        if (lp.width != LayoutParams.FILL_PARENT) {
1186                            widthSize = lp.width;
1187                        }
1188                    }
1189                    if (lp.height != LayoutParams.WRAP_CONTENT) {
1190                        heightMode = MeasureSpec.EXACTLY;
1191                        if (lp.height != LayoutParams.FILL_PARENT) {
1192                            heightSize = lp.height;
1193                        }
1194                    }
1195                    final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
1196                    final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
1197                    child.measure(widthSpec, heightSpec);
1198
1199                    if (consumeVertical) {
1200                        childHeightSize -= child.getMeasuredHeight();
1201                    } else if (consumeHorizontal) {
1202                        childWidthSize -= child.getMeasuredWidth();
1203                    }
1204                }
1205            }
1206        }
1207
1208        mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
1209        mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
1210
1211        // Make sure we have created all fragments that we need to have shown.
1212        mInLayout = true;
1213        populate();
1214        mInLayout = false;
1215
1216        // Page views next.
1217        size = getChildCount();
1218        for (int i = 0; i < size; ++i) {
1219            final View child = getChildAt(i);
1220            if (child.getVisibility() != GONE) {
1221                if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
1222                        + ": " + mChildWidthMeasureSpec);
1223
1224                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1225                if (lp == null || !lp.isDecor) {
1226                    final int widthSpec = MeasureSpec.makeMeasureSpec(
1227                            (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
1228                    child.measure(widthSpec, mChildHeightMeasureSpec);
1229                }
1230            }
1231        }
1232    }
1233
1234    @Override
1235    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1236        super.onSizeChanged(w, h, oldw, oldh);
1237
1238        // Make sure scroll position is set correctly.
1239        if (w != oldw) {
1240            recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin);
1241        }
1242    }
1243
1244    private void recomputeScrollPosition(int width, int oldWidth, int margin, int oldMargin) {
1245        final int widthWithMargin = width + margin;
1246        if (oldWidth > 0 && !mItems.isEmpty()) {
1247            final int xpos = getScrollX();
1248            final int oldWidthWithMargin = oldWidth + oldMargin;
1249            final float pageOffset = (float) xpos / oldWidthWithMargin;
1250            final int newOffsetPixels = (int) (pageOffset * widthWithMargin);
1251
1252            scrollTo(newOffsetPixels, getScrollY());
1253            if (!mScroller.isFinished()) {
1254                // We now return to your regularly scheduled scroll, already in progress.
1255                final int newDuration = mScroller.getDuration() - mScroller.timePassed();
1256                ItemInfo targetInfo = infoForPosition(mCurItem);
1257                mScroller.startScroll(newOffsetPixels, 0,
1258                        (int) (targetInfo.offset * widthWithMargin), 0, newDuration);
1259            }
1260        } else {
1261            final ItemInfo ii = infoForPosition(mCurItem);
1262            final int scrollPos =
1263                    (int) ((ii != null ? Math.min(ii.offset, mLastOffset) : 0) * widthWithMargin);
1264            if (scrollPos != getScrollX()) {
1265                completeScroll();
1266                scrollTo(scrollPos, getScrollY());
1267            }
1268        }
1269    }
1270
1271    @Override
1272    protected void onLayout(boolean changed, int l, int t, int r, int b) {
1273        mInLayout = true;
1274        populate();
1275        mInLayout = false;
1276
1277        final int count = getChildCount();
1278        int width = r - l;
1279        int height = b - t;
1280        int paddingLeft = getPaddingLeft();
1281        int paddingTop = getPaddingTop();
1282        int paddingRight = getPaddingRight();
1283        int paddingBottom = getPaddingBottom();
1284        final int scrollX = getScrollX();
1285
1286        int decorCount = 0;
1287
1288        // First pass - decor views. We need to do this in two passes so that
1289        // we have the proper offsets for non-decor views later.
1290        for (int i = 0; i < count; i++) {
1291            final View child = getChildAt(i);
1292            if (child.getVisibility() != GONE) {
1293                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1294                int childLeft = 0;
1295                int childTop = 0;
1296                if (lp.isDecor) {
1297                    final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
1298                    final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
1299                    switch (hgrav) {
1300                        default:
1301                            childLeft = paddingLeft;
1302                            break;
1303                        case Gravity.LEFT:
1304                            childLeft = paddingLeft;
1305                            paddingLeft += child.getMeasuredWidth();
1306                            break;
1307                        case Gravity.CENTER_HORIZONTAL:
1308                            childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
1309                                    paddingLeft);
1310                            break;
1311                        case Gravity.RIGHT:
1312                            childLeft = width - paddingRight - child.getMeasuredWidth();
1313                            paddingRight += child.getMeasuredWidth();
1314                            break;
1315                    }
1316                    switch (vgrav) {
1317                        default:
1318                            childTop = paddingTop;
1319                            break;
1320                        case Gravity.TOP:
1321                            childTop = paddingTop;
1322                            paddingTop += child.getMeasuredHeight();
1323                            break;
1324                        case Gravity.CENTER_VERTICAL:
1325                            childTop = Math.max((height - child.getMeasuredHeight()) / 2,
1326                                    paddingTop);
1327                            break;
1328                        case Gravity.BOTTOM:
1329                            childTop = height - paddingBottom - child.getMeasuredHeight();
1330                            paddingBottom += child.getMeasuredHeight();
1331                            break;
1332                    }
1333                    childLeft += scrollX;
1334                    child.layout(childLeft, childTop,
1335                            childLeft + child.getMeasuredWidth(),
1336                            childTop + child.getMeasuredHeight());
1337                    decorCount++;
1338                }
1339            }
1340        }
1341
1342        // Page views. Do this once we have the right padding offsets from above.
1343        for (int i = 0; i < count; i++) {
1344            final View child = getChildAt(i);
1345            if (child.getVisibility() != GONE) {
1346                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1347                ItemInfo ii;
1348                if (!lp.isDecor && (ii = infoForChild(child)) != null) {
1349                    int loff = (int) (width * ii.offset);
1350                    int childLeft = paddingLeft + loff;
1351                    int childTop = paddingTop;
1352                    if (lp.needsMeasure) {
1353                        // This was added during layout and needs measurement.
1354                        // Do it now that we know what we're working with.
1355                        lp.needsMeasure = false;
1356                        final int widthSpec = MeasureSpec.makeMeasureSpec(
1357                                (int) ((width - paddingLeft - paddingRight) * lp.widthFactor),
1358                                MeasureSpec.EXACTLY);
1359                        final int heightSpec = MeasureSpec.makeMeasureSpec(
1360                                (int) (height - paddingTop - paddingBottom),
1361                                MeasureSpec.EXACTLY);
1362                        child.measure(widthSpec, heightSpec);
1363                    }
1364                    if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
1365                            + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
1366                            + "x" + child.getMeasuredHeight());
1367                    child.layout(childLeft, childTop,
1368                            childLeft + child.getMeasuredWidth(),
1369                            childTop + child.getMeasuredHeight());
1370                }
1371            }
1372        }
1373        mTopPageBounds = paddingTop;
1374        mBottomPageBounds = height - paddingBottom;
1375        mDecorChildCount = decorCount;
1376        mFirstLayout = false;
1377    }
1378
1379    @Override
1380    public void computeScroll() {
1381        if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
1382            int oldX = getScrollX();
1383            int oldY = getScrollY();
1384            int x = mScroller.getCurrX();
1385            int y = mScroller.getCurrY();
1386
1387            if (oldX != x || oldY != y) {
1388                scrollTo(x, y);
1389                if (!pageScrolled(x)) {
1390                    mScroller.abortAnimation();
1391                    scrollTo(0, y);
1392                }
1393            }
1394
1395            // Keep on drawing until the animation has finished.
1396            ViewCompat.postInvalidateOnAnimation(this);
1397            return;
1398        }
1399
1400        // Done with scroll, clean up state.
1401        completeScroll();
1402    }
1403
1404    private boolean pageScrolled(int xpos) {
1405        if (mItems.size() == 0) {
1406            mCalledSuper = false;
1407            onPageScrolled(0, 0, 0);
1408            if (!mCalledSuper) {
1409                throw new IllegalStateException(
1410                        "onPageScrolled did not call superclass implementation");
1411            }
1412            return false;
1413        }
1414        final ItemInfo ii = infoForCurrentScrollPosition();
1415        final int width = getWidth();
1416        final int widthWithMargin = width + mPageMargin;
1417        final float marginOffset = (float) mPageMargin / width;
1418        final int currentPage = ii.position;
1419        final float pageOffset = (((float) xpos / width) - ii.offset) /
1420                (ii.widthFactor + marginOffset);
1421        final int offsetPixels = (int) (pageOffset * widthWithMargin);
1422
1423        mCalledSuper = false;
1424        onPageScrolled(currentPage, pageOffset, offsetPixels);
1425        if (!mCalledSuper) {
1426            throw new IllegalStateException(
1427                    "onPageScrolled did not call superclass implementation");
1428        }
1429        return true;
1430    }
1431
1432    /**
1433     * This method will be invoked when the current page is scrolled, either as part
1434     * of a programmatically initiated smooth scroll or a user initiated touch scroll.
1435     * If you override this method you must call through to the superclass implementation
1436     * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled
1437     * returns.
1438     *
1439     * @param position Position index of the first page currently being displayed.
1440     *                 Page position+1 will be visible if positionOffset is nonzero.
1441     * @param offset Value from [0, 1) indicating the offset from the page at position.
1442     * @param offsetPixels Value in pixels indicating the offset from position.
1443     */
1444    protected void onPageScrolled(int position, float offset, int offsetPixels) {
1445        // Offset any decor views if needed - keep them on-screen at all times.
1446        if (mDecorChildCount > 0) {
1447            final int scrollX = getScrollX();
1448            int paddingLeft = getPaddingLeft();
1449            int paddingRight = getPaddingRight();
1450            final int width = getWidth();
1451            final int childCount = getChildCount();
1452            for (int i = 0; i < childCount; i++) {
1453                final View child = getChildAt(i);
1454                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1455                if (!lp.isDecor) continue;
1456
1457                final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
1458                int childLeft = 0;
1459                switch (hgrav) {
1460                    default:
1461                        childLeft = paddingLeft;
1462                        break;
1463                    case Gravity.LEFT:
1464                        childLeft = paddingLeft;
1465                        paddingLeft += child.getWidth();
1466                        break;
1467                    case Gravity.CENTER_HORIZONTAL:
1468                        childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
1469                                paddingLeft);
1470                        break;
1471                    case Gravity.RIGHT:
1472                        childLeft = width - paddingRight - child.getMeasuredWidth();
1473                        paddingRight += child.getMeasuredWidth();
1474                        break;
1475                }
1476                childLeft += scrollX;
1477
1478                final int childOffset = childLeft - child.getLeft();
1479                if (childOffset != 0) {
1480                    child.offsetLeftAndRight(childOffset);
1481                }
1482            }
1483        }
1484
1485        if (mOnPageChangeListener != null) {
1486            mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
1487        }
1488        if (mInternalPageChangeListener != null) {
1489            mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
1490        }
1491        mCalledSuper = true;
1492    }
1493
1494    private void completeScroll() {
1495        boolean needPopulate = mScrolling;
1496        if (needPopulate) {
1497            // Done with scroll, no longer want to cache view drawing.
1498            setScrollingCacheEnabled(false);
1499            mScroller.abortAnimation();
1500            int oldX = getScrollX();
1501            int oldY = getScrollY();
1502            int x = mScroller.getCurrX();
1503            int y = mScroller.getCurrY();
1504            if (oldX != x || oldY != y) {
1505                scrollTo(x, y);
1506            }
1507            setScrollState(SCROLL_STATE_IDLE);
1508        }
1509        mPopulatePending = false;
1510        mScrolling = false;
1511        for (int i=0; i<mItems.size(); i++) {
1512            ItemInfo ii = mItems.get(i);
1513            if (ii.scrolling) {
1514                needPopulate = true;
1515                ii.scrolling = false;
1516            }
1517        }
1518        if (needPopulate) {
1519            populate();
1520        }
1521    }
1522
1523    @Override
1524    public boolean onInterceptTouchEvent(MotionEvent ev) {
1525        /*
1526         * This method JUST determines whether we want to intercept the motion.
1527         * If we return true, onMotionEvent will be called and we do the actual
1528         * scrolling there.
1529         */
1530
1531        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
1532
1533        // Always take care of the touch gesture being complete.
1534        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
1535            // Release the drag.
1536            if (DEBUG) Log.v(TAG, "Intercept done!");
1537            mIsBeingDragged = false;
1538            mIsUnableToDrag = false;
1539            mActivePointerId = INVALID_POINTER;
1540            if (mVelocityTracker != null) {
1541                mVelocityTracker.recycle();
1542                mVelocityTracker = null;
1543            }
1544            return false;
1545        }
1546
1547        // Nothing more to do here if we have decided whether or not we
1548        // are dragging.
1549        if (action != MotionEvent.ACTION_DOWN) {
1550            if (mIsBeingDragged) {
1551                if (DEBUG) Log.v(TAG, "Intercept returning true!");
1552                return true;
1553            }
1554            if (mIsUnableToDrag) {
1555                if (DEBUG) Log.v(TAG, "Intercept returning false!");
1556                return false;
1557            }
1558        }
1559
1560        switch (action) {
1561            case MotionEvent.ACTION_MOVE: {
1562                /*
1563                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1564                 * whether the user has moved far enough from his original down touch.
1565                 */
1566
1567                /*
1568                * Locally do absolute value. mLastMotionY is set to the y value
1569                * of the down event.
1570                */
1571                final int activePointerId = mActivePointerId;
1572                if (activePointerId == INVALID_POINTER) {
1573                    // If we don't have a valid id, the touch down wasn't on content.
1574                    break;
1575                }
1576
1577                final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
1578                final float x = MotionEventCompat.getX(ev, pointerIndex);
1579                final float dx = x - mLastMotionX;
1580                final float xDiff = Math.abs(dx);
1581                final float y = MotionEventCompat.getY(ev, pointerIndex);
1582                final float yDiff = Math.abs(y - mLastMotionY);
1583                if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
1584
1585                if (canScroll(this, false, (int) dx, (int) x, (int) y)) {
1586                    // Nested view has scrollable area under this point. Let it be handled there.
1587                    mInitialMotionX = mLastMotionX = x;
1588                    mLastMotionY = y;
1589                    mIsUnableToDrag = true;
1590                    return false;
1591                }
1592                if (xDiff > mTouchSlop && xDiff > yDiff) {
1593                    if (DEBUG) Log.v(TAG, "Starting drag!");
1594                    mIsBeingDragged = true;
1595                    setScrollState(SCROLL_STATE_DRAGGING);
1596                    mLastMotionX = x;
1597                    setScrollingCacheEnabled(true);
1598                } else {
1599                    if (yDiff > mTouchSlop) {
1600                        // The finger has moved enough in the vertical
1601                        // direction to be counted as a drag...  abort
1602                        // any attempt to drag horizontally, to work correctly
1603                        // with children that have scrolling containers.
1604                        if (DEBUG) Log.v(TAG, "Starting unable to drag!");
1605                        mIsUnableToDrag = true;
1606                    }
1607                }
1608                break;
1609            }
1610
1611            case MotionEvent.ACTION_DOWN: {
1612                /*
1613                 * Remember location of down touch.
1614                 * ACTION_DOWN always refers to pointer index 0.
1615                 */
1616                mLastMotionX = mInitialMotionX = ev.getX();
1617                mLastMotionY = ev.getY();
1618                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
1619                mIsUnableToDrag = false;
1620
1621                if (mScrollState == SCROLL_STATE_SETTLING &&
1622                        Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
1623                    // Let the user 'catch' the pager as it animates.
1624                    mIsBeingDragged = true;
1625                    setScrollState(SCROLL_STATE_DRAGGING);
1626                } else {
1627                    completeScroll();
1628                    mIsBeingDragged = false;
1629                }
1630
1631                if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
1632                        + " mIsBeingDragged=" + mIsBeingDragged
1633                        + "mIsUnableToDrag=" + mIsUnableToDrag);
1634                break;
1635            }
1636
1637            case MotionEventCompat.ACTION_POINTER_UP:
1638                onSecondaryPointerUp(ev);
1639                break;
1640        }
1641
1642        if (!mIsBeingDragged) {
1643            // Track the velocity as long as we aren't dragging.
1644            // Once we start a real drag we will track in onTouchEvent.
1645            if (mVelocityTracker == null) {
1646                mVelocityTracker = VelocityTracker.obtain();
1647            }
1648            mVelocityTracker.addMovement(ev);
1649        }
1650
1651        /*
1652         * The only time we want to intercept motion events is if we are in the
1653         * drag mode.
1654         */
1655        return mIsBeingDragged;
1656    }
1657
1658    @Override
1659    public boolean onTouchEvent(MotionEvent ev) {
1660        if (mFakeDragging) {
1661            // A fake drag is in progress already, ignore this real one
1662            // but still eat the touch events.
1663            // (It is likely that the user is multi-touching the screen.)
1664            return true;
1665        }
1666
1667        if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
1668            // Don't handle edge touches immediately -- they may actually belong to one of our
1669            // descendants.
1670            return false;
1671        }
1672
1673        if (mAdapter == null || mAdapter.getCount() == 0) {
1674            // Nothing to present or scroll; nothing to touch.
1675            return false;
1676        }
1677
1678        if (mVelocityTracker == null) {
1679            mVelocityTracker = VelocityTracker.obtain();
1680        }
1681        mVelocityTracker.addMovement(ev);
1682
1683        final int action = ev.getAction();
1684        boolean needsInvalidate = false;
1685
1686        switch (action & MotionEventCompat.ACTION_MASK) {
1687            case MotionEvent.ACTION_DOWN: {
1688                /*
1689                 * If being flinged and user touches, stop the fling. isFinished
1690                 * will be false if being flinged.
1691                 */
1692                completeScroll();
1693
1694                // Remember where the motion event started
1695                mLastMotionX = mInitialMotionX = ev.getX();
1696                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
1697                break;
1698            }
1699            case MotionEvent.ACTION_MOVE:
1700                if (!mIsBeingDragged) {
1701                    final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
1702                    final float x = MotionEventCompat.getX(ev, pointerIndex);
1703                    final float xDiff = Math.abs(x - mLastMotionX);
1704                    final float y = MotionEventCompat.getY(ev, pointerIndex);
1705                    final float yDiff = Math.abs(y - mLastMotionY);
1706                    if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
1707                    if (xDiff > mTouchSlop && xDiff > yDiff) {
1708                        if (DEBUG) Log.v(TAG, "Starting drag!");
1709                        mIsBeingDragged = true;
1710                        mLastMotionX = x;
1711                        setScrollState(SCROLL_STATE_DRAGGING);
1712                        setScrollingCacheEnabled(true);
1713                    }
1714                }
1715                // Not else! Note that mIsBeingDragged can be set above.
1716                if (mIsBeingDragged) {
1717                    // Scroll to follow the motion event
1718                    final int activePointerIndex = MotionEventCompat.findPointerIndex(
1719                            ev, mActivePointerId);
1720                    final float x = MotionEventCompat.getX(ev, activePointerIndex);
1721                    final float deltaX = mLastMotionX - x;
1722                    mLastMotionX = x;
1723                    float oldScrollX = getScrollX();
1724                    float scrollX = oldScrollX + deltaX;
1725                    final int width = getWidth();
1726
1727                    float leftBound = width * mFirstOffset;
1728                    float rightBound = width * mLastOffset;
1729                    boolean leftAbsolute = true;
1730                    boolean rightAbsolute = true;
1731
1732                    final ItemInfo firstItem = mItems.get(0);
1733                    final ItemInfo lastItem = mItems.get(mItems.size() - 1);
1734                    if (firstItem.position != 0) {
1735                        leftAbsolute = false;
1736                        leftBound = firstItem.offset * width;
1737                    }
1738                    if (lastItem.position != mAdapter.getCount() - 1) {
1739                        rightAbsolute = false;
1740                        rightBound = lastItem.offset * width;
1741                    }
1742
1743                    if (scrollX < leftBound) {
1744                        if (leftAbsolute) {
1745                            float over = leftBound - scrollX;
1746                            needsInvalidate = mLeftEdge.onPull(Math.abs(over) / width);
1747                        }
1748                        scrollX = leftBound;
1749                    } else if (scrollX > rightBound) {
1750                        if (rightAbsolute) {
1751                            float over = scrollX - rightBound;
1752                            needsInvalidate = mRightEdge.onPull(Math.abs(over) / width);
1753                        }
1754                        scrollX = rightBound;
1755                    }
1756                    // Don't lose the rounded component
1757                    mLastMotionX += scrollX - (int) scrollX;
1758                    scrollTo((int) scrollX, getScrollY());
1759                    pageScrolled((int) scrollX);
1760                }
1761                break;
1762            case MotionEvent.ACTION_UP:
1763                if (mIsBeingDragged) {
1764                    final VelocityTracker velocityTracker = mVelocityTracker;
1765                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1766                    int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
1767                            velocityTracker, mActivePointerId);
1768                    mPopulatePending = true;
1769                    final int width = getWidth();
1770                    final int scrollX = getScrollX();
1771                    final ItemInfo ii = infoForCurrentScrollPosition();
1772                    final int currentPage = ii.position;
1773                    final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
1774                    final int activePointerIndex =
1775                            MotionEventCompat.findPointerIndex(ev, mActivePointerId);
1776                    final float x = MotionEventCompat.getX(ev, activePointerIndex);
1777                    final int totalDelta = (int) (x - mInitialMotionX);
1778                    int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
1779                            totalDelta);
1780                    setCurrentItemInternal(nextPage, true, true, initialVelocity);
1781
1782                    mActivePointerId = INVALID_POINTER;
1783                    endDrag();
1784                    needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
1785                }
1786                break;
1787            case MotionEvent.ACTION_CANCEL:
1788                if (mIsBeingDragged) {
1789                    setCurrentItemInternal(mCurItem, true, true);
1790                    mActivePointerId = INVALID_POINTER;
1791                    endDrag();
1792                    needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
1793                }
1794                break;
1795            case MotionEventCompat.ACTION_POINTER_DOWN: {
1796                final int index = MotionEventCompat.getActionIndex(ev);
1797                final float x = MotionEventCompat.getX(ev, index);
1798                mLastMotionX = x;
1799                mActivePointerId = MotionEventCompat.getPointerId(ev, index);
1800                break;
1801            }
1802            case MotionEventCompat.ACTION_POINTER_UP:
1803                onSecondaryPointerUp(ev);
1804                mLastMotionX = MotionEventCompat.getX(ev,
1805                        MotionEventCompat.findPointerIndex(ev, mActivePointerId));
1806                break;
1807        }
1808        if (needsInvalidate) {
1809            ViewCompat.postInvalidateOnAnimation(this);
1810        }
1811        return true;
1812    }
1813
1814    /**
1815     * @return Info about the page at the current scroll position.
1816     *         This can be synthetic for a missing middle page; the 'object' field can be null.
1817     */
1818    private ItemInfo infoForCurrentScrollPosition() {
1819        final int width = getWidth();
1820        final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0;
1821        final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
1822        int lastPos = -1;
1823        float lastOffset = 0.f;
1824        float lastWidth = 0.f;
1825        boolean first = true;
1826
1827        ItemInfo lastItem = null;
1828        for (int i = 0; i < mItems.size(); i++) {
1829            ItemInfo ii = mItems.get(i);
1830            float offset;
1831            if (!first && ii.position != lastPos + 1) {
1832                // Create a synthetic item for a missing page.
1833                ii = mTempItem;
1834                ii.offset = lastOffset + lastWidth + marginOffset;
1835                ii.position = lastPos + 1;
1836                ii.widthFactor = mAdapter.getPageWidth(ii.position);
1837                i--;
1838            }
1839            offset = ii.offset;
1840
1841            final float leftBound = offset - 0.0001f;
1842            final float rightBound = offset + ii.widthFactor + marginOffset + 0.0001f;
1843            if (first || scrollOffset >= leftBound) {
1844                if (scrollOffset < rightBound || i == mItems.size() - 1) {
1845                    return ii;
1846                }
1847            } else {
1848                return lastItem;
1849            }
1850            first = false;
1851            lastPos = ii.position;
1852            lastOffset = offset;
1853            lastWidth = ii.widthFactor;
1854            lastItem = ii;
1855        }
1856
1857        return lastItem;
1858    }
1859
1860    private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {
1861        int targetPage;
1862        if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
1863            targetPage = velocity > 0 ? currentPage : currentPage + 1;
1864        } else {
1865            targetPage = (int) (currentPage + pageOffset + 0.5f);
1866        }
1867
1868        if (mItems.size() > 0) {
1869            final ItemInfo firstItem = mItems.get(0);
1870            final ItemInfo lastItem = mItems.get(mItems.size() - 1);
1871
1872            // Only let the user target pages we have items for
1873            targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
1874        }
1875
1876        return targetPage;
1877    }
1878
1879    @Override
1880    public void draw(Canvas canvas) {
1881        super.draw(canvas);
1882        boolean needsInvalidate = false;
1883
1884        final int overScrollMode = ViewCompat.getOverScrollMode(this);
1885        if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
1886                (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&
1887                        mAdapter != null && mAdapter.getCount() > 1)) {
1888            if (!mLeftEdge.isFinished()) {
1889                final int restoreCount = canvas.save();
1890                final int height = getHeight() - getPaddingTop() - getPaddingBottom();
1891                final int width = getWidth();
1892
1893                canvas.rotate(270);
1894                canvas.translate(-height + getPaddingTop(), mFirstOffset * width);
1895                mLeftEdge.setSize(height, width);
1896                needsInvalidate |= mLeftEdge.draw(canvas);
1897                canvas.restoreToCount(restoreCount);
1898            }
1899            if (!mRightEdge.isFinished()) {
1900                final int restoreCount = canvas.save();
1901                final int width = getWidth();
1902                final int height = getHeight() - getPaddingTop() - getPaddingBottom();
1903
1904                canvas.rotate(90);
1905                canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width);
1906                mRightEdge.setSize(height, width);
1907                needsInvalidate |= mRightEdge.draw(canvas);
1908                canvas.restoreToCount(restoreCount);
1909            }
1910        } else {
1911            mLeftEdge.finish();
1912            mRightEdge.finish();
1913        }
1914
1915        if (needsInvalidate) {
1916            // Keep animating
1917            ViewCompat.postInvalidateOnAnimation(this);
1918        }
1919    }
1920
1921    @Override
1922    protected void onDraw(Canvas canvas) {
1923        super.onDraw(canvas);
1924
1925        // Draw the margin drawable between pages if needed.
1926        if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) {
1927            final int scrollX = getScrollX();
1928            final int width = getWidth();
1929
1930            final float marginOffset = (float) mPageMargin / width;
1931            int itemIndex = 0;
1932            ItemInfo ii = mItems.get(0);
1933            float offset = ii.offset;
1934            final int itemCount = mItems.size();
1935            final int firstPos = ii.position;
1936            final int lastPos = mItems.get(itemCount - 1).position;
1937            for (int pos = firstPos; pos < lastPos; pos++) {
1938                while (pos > ii.position && itemIndex < itemCount) {
1939                    ii = mItems.get(++itemIndex);
1940                }
1941
1942                float drawAt;
1943                if (pos == ii.position) {
1944                    drawAt = (ii.offset + ii.widthFactor) * width;
1945                    offset = ii.offset + ii.widthFactor + marginOffset;
1946                } else {
1947                    float widthFactor = mAdapter.getPageWidth(pos);
1948                    drawAt = (offset + widthFactor) * width;
1949                    offset += widthFactor + marginOffset;
1950                }
1951
1952                if (drawAt + mPageMargin > scrollX) {
1953                    mMarginDrawable.setBounds((int) (drawAt - 0.5f), mTopPageBounds,
1954                            (int) (drawAt + mPageMargin + 0.5f), mBottomPageBounds);
1955                    mMarginDrawable.draw(canvas);
1956                }
1957
1958                if (drawAt > scrollX + width) {
1959                    break; // No more visible, no sense in continuing
1960                }
1961            }
1962        }
1963    }
1964
1965    /**
1966     * Start a fake drag of the pager.
1967     *
1968     * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
1969     * with the touch scrolling of another view, while still letting the ViewPager
1970     * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
1971     * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
1972     * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
1973     *
1974     * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
1975     * is already in progress, this method will return false.
1976     *
1977     * @return true if the fake drag began successfully, false if it could not be started.
1978     *
1979     * @see #fakeDragBy(float)
1980     * @see #endFakeDrag()
1981     */
1982    public boolean beginFakeDrag() {
1983        if (mIsBeingDragged) {
1984            return false;
1985        }
1986        mFakeDragging = true;
1987        setScrollState(SCROLL_STATE_DRAGGING);
1988        mInitialMotionX = mLastMotionX = 0;
1989        if (mVelocityTracker == null) {
1990            mVelocityTracker = VelocityTracker.obtain();
1991        } else {
1992            mVelocityTracker.clear();
1993        }
1994        final long time = SystemClock.uptimeMillis();
1995        final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
1996        mVelocityTracker.addMovement(ev);
1997        ev.recycle();
1998        mFakeDragBeginTime = time;
1999        return true;
2000    }
2001
2002    /**
2003     * End a fake drag of the pager.
2004     *
2005     * @see #beginFakeDrag()
2006     * @see #fakeDragBy(float)
2007     */
2008    public void endFakeDrag() {
2009        if (!mFakeDragging) {
2010            throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
2011        }
2012
2013        final VelocityTracker velocityTracker = mVelocityTracker;
2014        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
2015        int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
2016                velocityTracker, mActivePointerId);
2017        mPopulatePending = true;
2018        final int width = getWidth();
2019        final int scrollX = getScrollX();
2020        final ItemInfo ii = infoForCurrentScrollPosition();
2021        final int currentPage = ii.position;
2022        final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
2023        final int totalDelta = (int) (mLastMotionX - mInitialMotionX);
2024        int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
2025                totalDelta);
2026        setCurrentItemInternal(nextPage, true, true, initialVelocity);
2027        endDrag();
2028
2029        mFakeDragging = false;
2030    }
2031
2032    /**
2033     * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
2034     *
2035     * @param xOffset Offset in pixels to drag by.
2036     * @see #beginFakeDrag()
2037     * @see #endFakeDrag()
2038     */
2039    public void fakeDragBy(float xOffset) {
2040        if (!mFakeDragging) {
2041            throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
2042        }
2043
2044        mLastMotionX += xOffset;
2045
2046        float oldScrollX = getScrollX();
2047        float scrollX = oldScrollX - xOffset;
2048        final int width = getWidth();
2049
2050        float leftBound = width * mFirstOffset;
2051        float rightBound = width * mLastOffset;
2052
2053        final ItemInfo firstItem = mItems.get(0);
2054        final ItemInfo lastItem = mItems.get(mItems.size() - 1);
2055        if (firstItem.position != 0) {
2056            leftBound = firstItem.offset * width;
2057        }
2058        if (lastItem.position != mAdapter.getCount() - 1) {
2059            rightBound = lastItem.offset * width;
2060        }
2061
2062        if (scrollX < leftBound) {
2063            scrollX = leftBound;
2064        } else if (scrollX > rightBound) {
2065            scrollX = rightBound;
2066        }
2067        // Don't lose the rounded component
2068        mLastMotionX += scrollX - (int) scrollX;
2069        scrollTo((int) scrollX, getScrollY());
2070        pageScrolled((int) scrollX);
2071
2072        // Synthesize an event for the VelocityTracker.
2073        final long time = SystemClock.uptimeMillis();
2074        final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
2075                mLastMotionX, 0, 0);
2076        mVelocityTracker.addMovement(ev);
2077        ev.recycle();
2078    }
2079
2080    /**
2081     * Returns true if a fake drag is in progress.
2082     *
2083     * @return true if currently in a fake drag, false otherwise.
2084     *
2085     * @see #beginFakeDrag()
2086     * @see #fakeDragBy(float)
2087     * @see #endFakeDrag()
2088     */
2089    public boolean isFakeDragging() {
2090        return mFakeDragging;
2091    }
2092
2093    private void onSecondaryPointerUp(MotionEvent ev) {
2094        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
2095        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
2096        if (pointerId == mActivePointerId) {
2097            // This was our active pointer going up. Choose a new
2098            // active pointer and adjust accordingly.
2099            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
2100            mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);
2101            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
2102            if (mVelocityTracker != null) {
2103                mVelocityTracker.clear();
2104            }
2105        }
2106    }
2107
2108    private void endDrag() {
2109        mIsBeingDragged = false;
2110        mIsUnableToDrag = false;
2111
2112        if (mVelocityTracker != null) {
2113            mVelocityTracker.recycle();
2114            mVelocityTracker = null;
2115        }
2116    }
2117
2118    private void setScrollingCacheEnabled(boolean enabled) {
2119        if (mScrollingCacheEnabled != enabled) {
2120            mScrollingCacheEnabled = enabled;
2121            if (USE_CACHE) {
2122                final int size = getChildCount();
2123                for (int i = 0; i < size; ++i) {
2124                    final View child = getChildAt(i);
2125                    if (child.getVisibility() != GONE) {
2126                        child.setDrawingCacheEnabled(enabled);
2127                    }
2128                }
2129            }
2130        }
2131    }
2132
2133    /**
2134     * Tests scrollability within child views of v given a delta of dx.
2135     *
2136     * @param v View to test for horizontal scrollability
2137     * @param checkV Whether the view v passed should itself be checked for scrollability (true),
2138     *               or just its children (false).
2139     * @param dx Delta scrolled in pixels
2140     * @param x X coordinate of the active touch point
2141     * @param y Y coordinate of the active touch point
2142     * @return true if child views of v can be scrolled by delta of dx.
2143     */
2144    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
2145        if (v instanceof ViewGroup) {
2146            final ViewGroup group = (ViewGroup) v;
2147            final int scrollX = v.getScrollX();
2148            final int scrollY = v.getScrollY();
2149            final int count = group.getChildCount();
2150            // Count backwards - let topmost views consume scroll distance first.
2151            for (int i = count - 1; i >= 0; i--) {
2152                // TODO: Add versioned support here for transformed views.
2153                // This will not work for transformed views in Honeycomb+
2154                final View child = group.getChildAt(i);
2155                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
2156                        y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
2157                        canScroll(child, true, dx, x + scrollX - child.getLeft(),
2158                                y + scrollY - child.getTop())) {
2159                    return true;
2160                }
2161            }
2162        }
2163
2164        return checkV && ViewCompat.canScrollHorizontally(v, -dx);
2165    }
2166
2167    @Override
2168    public boolean dispatchKeyEvent(KeyEvent event) {
2169        // Let the focused view and/or our descendants get the key first
2170        return super.dispatchKeyEvent(event) || executeKeyEvent(event);
2171    }
2172
2173    /**
2174     * You can call this function yourself to have the scroll view perform
2175     * scrolling from a key event, just as if the event had been dispatched to
2176     * it by the view hierarchy.
2177     *
2178     * @param event The key event to execute.
2179     * @return Return true if the event was handled, else false.
2180     */
2181    public boolean executeKeyEvent(KeyEvent event) {
2182        boolean handled = false;
2183        if (event.getAction() == KeyEvent.ACTION_DOWN) {
2184            switch (event.getKeyCode()) {
2185                case KeyEvent.KEYCODE_DPAD_LEFT:
2186                    handled = arrowScroll(FOCUS_LEFT);
2187                    break;
2188                case KeyEvent.KEYCODE_DPAD_RIGHT:
2189                    handled = arrowScroll(FOCUS_RIGHT);
2190                    break;
2191                case KeyEvent.KEYCODE_TAB:
2192                    if (Build.VERSION.SDK_INT >= 11) {
2193                        // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD
2194                        // before Android 3.0. Ignore the tab key on those devices.
2195                        if (KeyEventCompat.hasNoModifiers(event)) {
2196                            handled = arrowScroll(FOCUS_FORWARD);
2197                        } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
2198                            handled = arrowScroll(FOCUS_BACKWARD);
2199                        }
2200                    }
2201                    break;
2202            }
2203        }
2204        return handled;
2205    }
2206
2207    public boolean arrowScroll(int direction) {
2208        View currentFocused = findFocus();
2209        if (currentFocused == this) currentFocused = null;
2210
2211        boolean handled = false;
2212
2213        View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
2214                direction);
2215        if (nextFocused != null && nextFocused != currentFocused) {
2216            if (direction == View.FOCUS_LEFT) {
2217                // If there is nothing to the left, or this is causing us to
2218                // jump to the right, then what we really want to do is page left.
2219                if (currentFocused != null && nextFocused.getLeft() >= currentFocused.getLeft()) {
2220                    handled = pageLeft();
2221                } else {
2222                    handled = nextFocused.requestFocus();
2223                }
2224            } else if (direction == View.FOCUS_RIGHT) {
2225                // If there is nothing to the right, or this is causing us to
2226                // jump to the left, then what we really want to do is page right.
2227                if (currentFocused != null && nextFocused.getLeft() <= currentFocused.getLeft()) {
2228                    handled = pageRight();
2229                } else {
2230                    handled = nextFocused.requestFocus();
2231                }
2232            }
2233        } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {
2234            // Trying to move left and nothing there; try to page.
2235            handled = pageLeft();
2236        } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {
2237            // Trying to move right and nothing there; try to page.
2238            handled = pageRight();
2239        }
2240        if (handled) {
2241            playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
2242        }
2243        return handled;
2244    }
2245
2246    boolean pageLeft() {
2247        if (mCurItem > 0) {
2248            setCurrentItem(mCurItem-1, true);
2249            return true;
2250        }
2251        return false;
2252    }
2253
2254    boolean pageRight() {
2255        if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) {
2256            setCurrentItem(mCurItem+1, true);
2257            return true;
2258        }
2259        return false;
2260    }
2261
2262    /**
2263     * We only want the current page that is being shown to be focusable.
2264     */
2265    @Override
2266    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
2267        final int focusableCount = views.size();
2268
2269        final int descendantFocusability = getDescendantFocusability();
2270
2271        if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
2272            for (int i = 0; i < getChildCount(); i++) {
2273                final View child = getChildAt(i);
2274                if (child.getVisibility() == VISIBLE) {
2275                    ItemInfo ii = infoForChild(child);
2276                    if (ii != null && ii.position == mCurItem) {
2277                        child.addFocusables(views, direction, focusableMode);
2278                    }
2279                }
2280            }
2281        }
2282
2283        // we add ourselves (if focusable) in all cases except for when we are
2284        // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.  this is
2285        // to avoid the focus search finding layouts when a more precise search
2286        // among the focusable children would be more interesting.
2287        if (
2288            descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
2289                // No focusable descendants
2290                (focusableCount == views.size())) {
2291            // Note that we can't call the superclass here, because it will
2292            // add all views in.  So we need to do the same thing View does.
2293            if (!isFocusable()) {
2294                return;
2295            }
2296            if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
2297                    isInTouchMode() && !isFocusableInTouchMode()) {
2298                return;
2299            }
2300            if (views != null) {
2301                views.add(this);
2302            }
2303        }
2304    }
2305
2306    /**
2307     * We only want the current page that is being shown to be touchable.
2308     */
2309    @Override
2310    public void addTouchables(ArrayList<View> views) {
2311        // Note that we don't call super.addTouchables(), which means that
2312        // we don't call View.addTouchables().  This is okay because a ViewPager
2313        // is itself not touchable.
2314        for (int i = 0; i < getChildCount(); i++) {
2315            final View child = getChildAt(i);
2316            if (child.getVisibility() == VISIBLE) {
2317                ItemInfo ii = infoForChild(child);
2318                if (ii != null && ii.position == mCurItem) {
2319                    child.addTouchables(views);
2320                }
2321            }
2322        }
2323    }
2324
2325    /**
2326     * We only want the current page that is being shown to be focusable.
2327     */
2328    @Override
2329    protected boolean onRequestFocusInDescendants(int direction,
2330            Rect previouslyFocusedRect) {
2331        int index;
2332        int increment;
2333        int end;
2334        int count = getChildCount();
2335        if ((direction & FOCUS_FORWARD) != 0) {
2336            index = 0;
2337            increment = 1;
2338            end = count;
2339        } else {
2340            index = count - 1;
2341            increment = -1;
2342            end = -1;
2343        }
2344        for (int i = index; i != end; i += increment) {
2345            View child = getChildAt(i);
2346            if (child.getVisibility() == VISIBLE) {
2347                ItemInfo ii = infoForChild(child);
2348                if (ii != null && ii.position == mCurItem) {
2349                    if (child.requestFocus(direction, previouslyFocusedRect)) {
2350                        return true;
2351                    }
2352                }
2353            }
2354        }
2355        return false;
2356    }
2357
2358    @Override
2359    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
2360        // ViewPagers should only report accessibility info for the current page,
2361        // otherwise things get very confusing.
2362
2363        // TODO: Should this note something about the paging container?
2364
2365        final int childCount = getChildCount();
2366        for (int i = 0; i < childCount; i++) {
2367            final View child = getChildAt(i);
2368            if (child.getVisibility() == VISIBLE) {
2369                final ItemInfo ii = infoForChild(child);
2370                if (ii != null && ii.position == mCurItem &&
2371                        child.dispatchPopulateAccessibilityEvent(event)) {
2372                    return true;
2373                }
2374            }
2375        }
2376
2377        return false;
2378    }
2379
2380    @Override
2381    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
2382        return new LayoutParams();
2383    }
2384
2385    @Override
2386    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2387        return generateDefaultLayoutParams();
2388    }
2389
2390    @Override
2391    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2392        return p instanceof LayoutParams && super.checkLayoutParams(p);
2393    }
2394
2395    @Override
2396    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2397        return new LayoutParams(getContext(), attrs);
2398    }
2399
2400    private class PagerObserver extends DataSetObserver {
2401        @Override
2402        public void onChanged() {
2403            dataSetChanged();
2404        }
2405        @Override
2406        public void onInvalidated() {
2407            dataSetChanged();
2408        }
2409    }
2410
2411    /**
2412     * Layout parameters that should be supplied for views added to a
2413     * ViewPager.
2414     */
2415    public static class LayoutParams extends ViewGroup.LayoutParams {
2416        /**
2417         * true if this view is a decoration on the pager itself and not
2418         * a view supplied by the adapter.
2419         */
2420        public boolean isDecor;
2421
2422        /**
2423         * Gravity setting for use on decor views only:
2424         * Where to position the view page within the overall ViewPager
2425         * container; constants are defined in {@link android.view.Gravity}.
2426         */
2427        public int gravity;
2428
2429        /**
2430         * Width as a 0-1 multiplier of the measured pager width
2431         */
2432        public float widthFactor = 0.f;
2433
2434        /**
2435         * true if this view was added during layout and needs to be measured
2436         * before being positioned.
2437         */
2438        public boolean needsMeasure;
2439
2440        public LayoutParams() {
2441            super(FILL_PARENT, FILL_PARENT);
2442        }
2443
2444        public LayoutParams(Context context, AttributeSet attrs) {
2445            super(context, attrs);
2446
2447            final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
2448            gravity = a.getInteger(0, Gravity.TOP);
2449            a.recycle();
2450        }
2451    }
2452}
2453