ViewPager.java revision b469af6dc2f8cda4020a78fb4582c1483089fd6e
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() < 3 && mItems.size() < mAdapter.getCount();
689        int newCurrItem = -1;
690
691        boolean isUpdating = false;
692        for (int i = 0; i < mItems.size(); i++) {
693            final ItemInfo ii = mItems.get(i);
694            final int newPos = mAdapter.getItemPosition(ii.object);
695
696            if (newPos == PagerAdapter.POSITION_UNCHANGED) {
697                continue;
698            }
699
700            if (newPos == PagerAdapter.POSITION_NONE) {
701                mItems.remove(i);
702                i--;
703
704                if (!isUpdating) {
705                    mAdapter.startUpdate(this);
706                    isUpdating = true;
707                }
708
709                mAdapter.destroyItem(this, ii.position, ii.object);
710                needPopulate = true;
711
712                if (mCurItem == ii.position) {
713                    // Keep the current item in the valid range
714                    newCurrItem = Math.max(0, Math.min(mCurItem, mAdapter.getCount() - 1));
715                }
716                continue;
717            }
718
719            if (ii.position != newPos) {
720                if (ii.position == mCurItem) {
721                    // Our current item changed position. Follow it.
722                    newCurrItem = newPos;
723                }
724
725                ii.position = newPos;
726                needPopulate = true;
727            }
728        }
729
730        if (isUpdating) {
731            mAdapter.finishUpdate(this);
732        }
733
734        Collections.sort(mItems, COMPARATOR);
735
736        if (needPopulate) {
737            // Reset our known page widths; populate will recompute them.
738            final int childCount = getChildCount();
739            for (int i = 0; i < childCount; i++) {
740                final View child = getChildAt(i);
741                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
742                if (!lp.isDecor) {
743                    lp.widthFactor = 0.f;
744                }
745            }
746        }
747
748        if (newCurrItem >= 0) {
749            // TODO This currently causes a jump.
750            setCurrentItemInternal(newCurrItem, false, true);
751            needPopulate = true;
752        }
753        if (needPopulate) {
754            populate();
755            requestLayout();
756        }
757    }
758
759    void populate() {
760        populate(mCurItem);
761    }
762
763    void populate(int newCurrentItem) {
764        ItemInfo oldCurInfo = null;
765        if (mCurItem != newCurrentItem) {
766            oldCurInfo = infoForPosition(mCurItem);
767            mCurItem = newCurrentItem;
768        }
769
770        if (mAdapter == null) {
771            return;
772        }
773
774        // Bail now if we are waiting to populate.  This is to hold off
775        // on creating views from the time the user releases their finger to
776        // fling to a new position until we have finished the scroll to
777        // that position, avoiding glitches from happening at that point.
778        if (mPopulatePending) {
779            if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
780            return;
781        }
782
783        // Also, don't populate until we are attached to a window.  This is to
784        // avoid trying to populate before we have restored our view hierarchy
785        // state and conflicting with what is restored.
786        if (getWindowToken() == null) {
787            return;
788        }
789
790        mAdapter.startUpdate(this);
791
792        final int pageLimit = mOffscreenPageLimit;
793        final int startPos = Math.max(0, mCurItem - pageLimit);
794        final int N = mAdapter.getCount();
795        final int endPos = Math.min(N-1, mCurItem + pageLimit);
796
797        // Locate the currently focused item or add it if needed.
798        int curIndex = -1;
799        ItemInfo curItem = null;
800        for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
801            final ItemInfo ii = mItems.get(curIndex);
802            if (ii.position >= mCurItem) {
803                if (ii.position == mCurItem) curItem = ii;
804                break;
805            }
806        }
807
808        if (curItem == null && N > 0) {
809            curItem = addNewItem(mCurItem, curIndex);
810        }
811
812        // Fill 3x the available width or up to the number of offscreen
813        // pages requested to either side, whichever is larger.
814        // If we have no current item we have no work to do.
815        if (curItem != null) {
816            float extraWidthLeft = 0.f;
817            int itemIndex = curIndex - 1;
818            ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
819            final float leftWidthNeeded = 2.f - curItem.widthFactor;
820            for (int pos = mCurItem - 1; pos >= 0; pos--) {
821                if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
822                    if (ii == null) {
823                        break;
824                    }
825                    if (pos == ii.position && !ii.scrolling) {
826                        mItems.remove(itemIndex);
827                        mAdapter.destroyItem(this, pos, ii.object);
828                        itemIndex--;
829                        curIndex--;
830                        ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
831                    }
832                } else if (ii != null && pos == ii.position) {
833                    extraWidthLeft += ii.widthFactor;
834                    itemIndex--;
835                    ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
836                } else {
837                    ii = addNewItem(pos, itemIndex + 1);
838                    extraWidthLeft += ii.widthFactor;
839                    curIndex++;
840                    ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
841                }
842            }
843
844            float extraWidthRight = curItem.widthFactor;
845            itemIndex = curIndex + 1;
846            if (extraWidthRight < 2.f) {
847                ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
848                for (int pos = mCurItem + 1; pos < N; pos++) {
849                    if (extraWidthRight >= 2.f && pos > endPos) {
850                        if (ii == null) {
851                            break;
852                        }
853                        if (pos == ii.position && !ii.scrolling) {
854                            mItems.remove(itemIndex);
855                            mAdapter.destroyItem(this, pos, ii.object);
856                            ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
857                        }
858                    } else if (ii != null && pos == ii.position) {
859                        extraWidthRight += ii.widthFactor;
860                        itemIndex++;
861                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
862                    } else {
863                        ii = addNewItem(pos, itemIndex);
864                        itemIndex++;
865                        extraWidthRight += ii.widthFactor;
866                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
867                    }
868                }
869            }
870
871            calculatePageOffsets(curItem, curIndex, oldCurInfo);
872        }
873
874        if (DEBUG) {
875            Log.i(TAG, "Current page list:");
876            for (int i=0; i<mItems.size(); i++) {
877                Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
878            }
879        }
880
881        mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
882
883        mAdapter.finishUpdate(this);
884
885        // Check width measurement of current pages. Update LayoutParams as needed.
886        final int childCount = getChildCount();
887        for (int i = 0; i < childCount; i++) {
888            final View child = getChildAt(i);
889            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
890            if (!lp.isDecor && lp.widthFactor == 0.f) {
891                // 0 means requery the adapter for this, it doesn't have a valid width.
892                final ItemInfo ii = infoForChild(child);
893                if (ii != null) {
894                    lp.widthFactor = ii.widthFactor;
895                }
896            }
897        }
898
899        if (hasFocus()) {
900            View currentFocused = findFocus();
901            ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
902            if (ii == null || ii.position != mCurItem) {
903                for (int i=0; i<getChildCount(); i++) {
904                    View child = getChildAt(i);
905                    ii = infoForChild(child);
906                    if (ii != null && ii.position == mCurItem) {
907                        if (child.requestFocus(FOCUS_FORWARD)) {
908                            break;
909                        }
910                    }
911                }
912            }
913        }
914    }
915
916    private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
917        final int N = mAdapter.getCount();
918        final int width = getWidth();
919        final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
920        // Fix up offsets for later layout.
921        if (oldCurInfo != null) {
922            final int oldCurPosition = oldCurInfo.position;
923            // Base offsets off of oldCurInfo.
924            if (oldCurPosition < curItem.position) {
925                int itemIndex = 0;
926                ItemInfo ii = null;
927                float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset;
928                for (int pos = oldCurPosition + 1;
929                        pos <= curItem.position && itemIndex < mItems.size(); pos++) {
930                    ii = mItems.get(itemIndex);
931                    while (pos > ii.position && itemIndex < mItems.size() - 1) {
932                        itemIndex++;
933                        ii = mItems.get(itemIndex);
934                    }
935                    while (pos < ii.position) {
936                        // We don't have an item populated for this,
937                        // ask the adapter for an offset.
938                        offset += mAdapter.getPageWidth(pos) + marginOffset;
939                        pos++;
940                    }
941                    ii.offset = offset;
942                    offset += ii.widthFactor + marginOffset;
943                }
944            } else if (oldCurPosition > curItem.position) {
945                int itemIndex = mItems.size() - 1;
946                ItemInfo ii = null;
947                float offset = oldCurInfo.offset;
948                for (int pos = oldCurPosition - 1;
949                        pos >= curItem.position && itemIndex >= 0; pos--) {
950                    ii = mItems.get(itemIndex);
951                    while (pos < ii.position && itemIndex > 0) {
952                        itemIndex--;
953                        ii = mItems.get(itemIndex);
954                    }
955                    while (pos > ii.position) {
956                        // We don't have an item populated for this,
957                        // ask the adapter for an offset.
958                        offset -= mAdapter.getPageWidth(pos) + marginOffset;
959                        pos--;
960                    }
961                    offset -= ii.widthFactor + marginOffset;
962                    ii.offset = offset;
963                }
964            }
965        }
966
967        // Base all offsets off of curItem.
968        final int itemCount = mItems.size();
969        float offset = curItem.offset;
970        int pos = curItem.position - 1;
971        mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
972        mLastOffset = curItem.position == N - 1 ?
973                curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE;
974        // Previous pages
975        for (int i = curIndex - 1; i >= 0; i--, pos--) {
976            final ItemInfo ii = mItems.get(i);
977            while (pos > ii.position) {
978                offset -= mAdapter.getPageWidth(pos--) + marginOffset;
979            }
980            offset -= ii.widthFactor + marginOffset;
981            ii.offset = offset;
982            if (ii.position == 0) mFirstOffset = offset;
983        }
984        offset = curItem.offset + curItem.widthFactor + marginOffset;
985        pos = curItem.position + 1;
986        // Next pages
987        for (int i = curIndex + 1; i < itemCount; i++, pos++) {
988            final ItemInfo ii = mItems.get(i);
989            while (pos < ii.position) {
990                offset += mAdapter.getPageWidth(pos++) + marginOffset;
991            }
992            if (ii.position == N - 1) {
993                mLastOffset = offset + ii.widthFactor - 1;
994            }
995            ii.offset = offset;
996            offset += ii.widthFactor + marginOffset;
997        }
998
999        mNeedCalculatePageOffsets = false;
1000    }
1001
1002    /**
1003     * This is the persistent state that is saved by ViewPager.  Only needed
1004     * if you are creating a sublass of ViewPager that must save its own
1005     * state, in which case it should implement a subclass of this which
1006     * contains that state.
1007     */
1008    public static class SavedState extends BaseSavedState {
1009        int position;
1010        Parcelable adapterState;
1011        ClassLoader loader;
1012
1013        public SavedState(Parcelable superState) {
1014            super(superState);
1015        }
1016
1017        @Override
1018        public void writeToParcel(Parcel out, int flags) {
1019            super.writeToParcel(out, flags);
1020            out.writeInt(position);
1021            out.writeParcelable(adapterState, flags);
1022        }
1023
1024        @Override
1025        public String toString() {
1026            return "FragmentPager.SavedState{"
1027                    + Integer.toHexString(System.identityHashCode(this))
1028                    + " position=" + position + "}";
1029        }
1030
1031        public static final Parcelable.Creator<SavedState> CREATOR
1032                = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
1033                    @Override
1034                    public SavedState createFromParcel(Parcel in, ClassLoader loader) {
1035                        return new SavedState(in, loader);
1036                    }
1037                    @Override
1038                    public SavedState[] newArray(int size) {
1039                        return new SavedState[size];
1040                    }
1041                });
1042
1043        SavedState(Parcel in, ClassLoader loader) {
1044            super(in);
1045            if (loader == null) {
1046                loader = getClass().getClassLoader();
1047            }
1048            position = in.readInt();
1049            adapterState = in.readParcelable(loader);
1050            this.loader = loader;
1051        }
1052    }
1053
1054    @Override
1055    public Parcelable onSaveInstanceState() {
1056        Parcelable superState = super.onSaveInstanceState();
1057        SavedState ss = new SavedState(superState);
1058        ss.position = mCurItem;
1059        if (mAdapter != null) {
1060            ss.adapterState = mAdapter.saveState();
1061        }
1062        return ss;
1063    }
1064
1065    @Override
1066    public void onRestoreInstanceState(Parcelable state) {
1067        if (!(state instanceof SavedState)) {
1068            super.onRestoreInstanceState(state);
1069            return;
1070        }
1071
1072        SavedState ss = (SavedState)state;
1073        super.onRestoreInstanceState(ss.getSuperState());
1074
1075        if (mAdapter != null) {
1076            mAdapter.restoreState(ss.adapterState, ss.loader);
1077            setCurrentItemInternal(ss.position, false, true);
1078        } else {
1079            mRestoredCurItem = ss.position;
1080            mRestoredAdapterState = ss.adapterState;
1081            mRestoredClassLoader = ss.loader;
1082        }
1083    }
1084
1085    @Override
1086    public void addView(View child, int index, ViewGroup.LayoutParams params) {
1087        if (!checkLayoutParams(params)) {
1088            params = generateLayoutParams(params);
1089        }
1090        final LayoutParams lp = (LayoutParams) params;
1091        lp.isDecor |= child instanceof Decor;
1092        if (mInLayout) {
1093            if (lp != null && lp.isDecor) {
1094                throw new IllegalStateException("Cannot add pager decor view during layout");
1095            }
1096            lp.needsMeasure = true;
1097            addViewInLayout(child, index, params);
1098        } else {
1099            super.addView(child, index, params);
1100        }
1101
1102        if (USE_CACHE) {
1103            if (child.getVisibility() != GONE) {
1104                child.setDrawingCacheEnabled(mScrollingCacheEnabled);
1105            } else {
1106                child.setDrawingCacheEnabled(false);
1107            }
1108        }
1109    }
1110
1111    ItemInfo infoForChild(View child) {
1112        for (int i=0; i<mItems.size(); i++) {
1113            ItemInfo ii = mItems.get(i);
1114            if (mAdapter.isViewFromObject(child, ii.object)) {
1115                return ii;
1116            }
1117        }
1118        return null;
1119    }
1120
1121    ItemInfo infoForAnyChild(View child) {
1122        ViewParent parent;
1123        while ((parent=child.getParent()) != this) {
1124            if (parent == null || !(parent instanceof View)) {
1125                return null;
1126            }
1127            child = (View)parent;
1128        }
1129        return infoForChild(child);
1130    }
1131
1132    ItemInfo infoForPosition(int position) {
1133        for (int i = 0; i < mItems.size(); i++) {
1134            ItemInfo ii = mItems.get(i);
1135            if (ii.position == position) {
1136                return ii;
1137            }
1138        }
1139        return null;
1140    }
1141
1142    @Override
1143    protected void onAttachedToWindow() {
1144        super.onAttachedToWindow();
1145        mFirstLayout = true;
1146    }
1147
1148    @Override
1149    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1150        // For simple implementation, or internal size is always 0.
1151        // We depend on the container to specify the layout size of
1152        // our view.  We can't really know what it is since we will be
1153        // adding and removing different arbitrary views and do not
1154        // want the layout to change as this happens.
1155        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
1156                getDefaultSize(0, heightMeasureSpec));
1157
1158        // Children are just made to fill our space.
1159        int childWidthSize = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
1160        int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
1161
1162        /*
1163         * Make sure all children have been properly measured. Decor views first.
1164         * Right now we cheat and make this less complicated by assuming decor
1165         * views won't intersect. We will pin to edges based on gravity.
1166         */
1167        int size = getChildCount();
1168        for (int i = 0; i < size; ++i) {
1169            final View child = getChildAt(i);
1170            if (child.getVisibility() != GONE) {
1171                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1172                if (lp != null && lp.isDecor) {
1173                    final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
1174                    final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
1175                    int widthMode = MeasureSpec.AT_MOST;
1176                    int heightMode = MeasureSpec.AT_MOST;
1177                    boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
1178                    boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;
1179
1180                    if (consumeVertical) {
1181                        widthMode = MeasureSpec.EXACTLY;
1182                    } else if (consumeHorizontal) {
1183                        heightMode = MeasureSpec.EXACTLY;
1184                    }
1185
1186                    int widthSize = childWidthSize;
1187                    int heightSize = childHeightSize;
1188                    if (lp.width != LayoutParams.WRAP_CONTENT) {
1189                        widthMode = MeasureSpec.EXACTLY;
1190                        if (lp.width != LayoutParams.FILL_PARENT) {
1191                            widthSize = lp.width;
1192                        }
1193                    }
1194                    if (lp.height != LayoutParams.WRAP_CONTENT) {
1195                        heightMode = MeasureSpec.EXACTLY;
1196                        if (lp.height != LayoutParams.FILL_PARENT) {
1197                            heightSize = lp.height;
1198                        }
1199                    }
1200                    final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
1201                    final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
1202                    child.measure(widthSpec, heightSpec);
1203
1204                    if (consumeVertical) {
1205                        childHeightSize -= child.getMeasuredHeight();
1206                    } else if (consumeHorizontal) {
1207                        childWidthSize -= child.getMeasuredWidth();
1208                    }
1209                }
1210            }
1211        }
1212
1213        mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
1214        mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
1215
1216        // Make sure we have created all fragments that we need to have shown.
1217        mInLayout = true;
1218        populate();
1219        mInLayout = false;
1220
1221        // Page views next.
1222        size = getChildCount();
1223        for (int i = 0; i < size; ++i) {
1224            final View child = getChildAt(i);
1225            if (child.getVisibility() != GONE) {
1226                if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
1227                        + ": " + mChildWidthMeasureSpec);
1228
1229                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1230                if (lp == null || !lp.isDecor) {
1231                    final int widthSpec = MeasureSpec.makeMeasureSpec(
1232                            (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
1233                    child.measure(widthSpec, mChildHeightMeasureSpec);
1234                }
1235            }
1236        }
1237    }
1238
1239    @Override
1240    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1241        super.onSizeChanged(w, h, oldw, oldh);
1242
1243        // Make sure scroll position is set correctly.
1244        if (w != oldw) {
1245            recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin);
1246        }
1247    }
1248
1249    private void recomputeScrollPosition(int width, int oldWidth, int margin, int oldMargin) {
1250        if (oldWidth > 0 && !mItems.isEmpty()) {
1251            final int xpos = getScrollX();
1252            final ItemInfo ii = infoForCurrentScrollPosition();
1253            final float pageOffset = (((float) xpos / width) - ii.offset) / ii.widthFactor;
1254            final int offsetPixels = (int) (pageOffset * width);
1255
1256            scrollTo(offsetPixels, getScrollY());
1257            if (!mScroller.isFinished()) {
1258                // We now return to your regularly scheduled scroll, already in progress.
1259                final int newDuration = mScroller.getDuration() - mScroller.timePassed();
1260                ItemInfo targetInfo = infoForPosition(mCurItem);
1261                mScroller.startScroll(offsetPixels, 0, (int) (targetInfo.offset * width), 0,
1262                        newDuration);
1263            }
1264        } else {
1265            final ItemInfo ii = infoForPosition(mCurItem);
1266            final int scrollPos =
1267                    (int) ((ii != null ? Math.min(ii.offset, mLastOffset) : 0) * width);
1268            if (scrollPos != getScrollX()) {
1269                completeScroll();
1270                scrollTo(scrollPos, getScrollY());
1271            }
1272        }
1273    }
1274
1275    @Override
1276    protected void onLayout(boolean changed, int l, int t, int r, int b) {
1277        mInLayout = true;
1278        populate();
1279        mInLayout = false;
1280
1281        final int count = getChildCount();
1282        int width = r - l;
1283        int height = b - t;
1284        int paddingLeft = getPaddingLeft();
1285        int paddingTop = getPaddingTop();
1286        int paddingRight = getPaddingRight();
1287        int paddingBottom = getPaddingBottom();
1288        final int scrollX = getScrollX();
1289
1290        int decorCount = 0;
1291
1292        // First pass - decor views. We need to do this in two passes so that
1293        // we have the proper offsets for non-decor views later.
1294        for (int i = 0; i < count; i++) {
1295            final View child = getChildAt(i);
1296            if (child.getVisibility() != GONE) {
1297                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1298                int childLeft = 0;
1299                int childTop = 0;
1300                if (lp.isDecor) {
1301                    final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
1302                    final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
1303                    switch (hgrav) {
1304                        default:
1305                            childLeft = paddingLeft;
1306                            break;
1307                        case Gravity.LEFT:
1308                            childLeft = paddingLeft;
1309                            paddingLeft += child.getMeasuredWidth();
1310                            break;
1311                        case Gravity.CENTER_HORIZONTAL:
1312                            childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
1313                                    paddingLeft);
1314                            break;
1315                        case Gravity.RIGHT:
1316                            childLeft = width - paddingRight - child.getMeasuredWidth();
1317                            paddingRight += child.getMeasuredWidth();
1318                            break;
1319                    }
1320                    switch (vgrav) {
1321                        default:
1322                            childTop = paddingTop;
1323                            break;
1324                        case Gravity.TOP:
1325                            childTop = paddingTop;
1326                            paddingTop += child.getMeasuredHeight();
1327                            break;
1328                        case Gravity.CENTER_VERTICAL:
1329                            childTop = Math.max((height - child.getMeasuredHeight()) / 2,
1330                                    paddingTop);
1331                            break;
1332                        case Gravity.BOTTOM:
1333                            childTop = height - paddingBottom - child.getMeasuredHeight();
1334                            paddingBottom += child.getMeasuredHeight();
1335                            break;
1336                    }
1337                    childLeft += scrollX;
1338                    child.layout(childLeft, childTop,
1339                            childLeft + child.getMeasuredWidth(),
1340                            childTop + child.getMeasuredHeight());
1341                    decorCount++;
1342                }
1343            }
1344        }
1345
1346        // Page views. Do this once we have the right padding offsets from above.
1347        for (int i = 0; i < count; i++) {
1348            final View child = getChildAt(i);
1349            if (child.getVisibility() != GONE) {
1350                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1351                ItemInfo ii;
1352                if (!lp.isDecor && (ii = infoForChild(child)) != null) {
1353                    int loff = (int) (width * ii.offset);
1354                    int childLeft = paddingLeft + loff;
1355                    int childTop = paddingTop;
1356                    if (lp.needsMeasure) {
1357                        // This was added during layout and needs measurement.
1358                        // Do it now that we know what we're working with.
1359                        lp.needsMeasure = false;
1360                        final int widthSpec = MeasureSpec.makeMeasureSpec(
1361                                (int) ((width - paddingLeft - paddingRight) * lp.widthFactor),
1362                                MeasureSpec.EXACTLY);
1363                        final int heightSpec = MeasureSpec.makeMeasureSpec(
1364                                (int) (height - paddingTop - paddingBottom),
1365                                MeasureSpec.EXACTLY);
1366                        child.measure(widthSpec, heightSpec);
1367                    }
1368                    if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
1369                            + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
1370                            + "x" + child.getMeasuredHeight());
1371                    child.layout(childLeft, childTop,
1372                            childLeft + child.getMeasuredWidth(),
1373                            childTop + child.getMeasuredHeight());
1374                }
1375            }
1376        }
1377        mTopPageBounds = paddingTop;
1378        mBottomPageBounds = height - paddingBottom;
1379        mDecorChildCount = decorCount;
1380        mFirstLayout = false;
1381    }
1382
1383    @Override
1384    public void computeScroll() {
1385        if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
1386            int oldX = getScrollX();
1387            int oldY = getScrollY();
1388            int x = mScroller.getCurrX();
1389            int y = mScroller.getCurrY();
1390
1391            if (oldX != x || oldY != y) {
1392                scrollTo(x, y);
1393                if (!pageScrolled(x)) {
1394                    mScroller.abortAnimation();
1395                    scrollTo(0, y);
1396                }
1397            }
1398
1399            // Keep on drawing until the animation has finished.
1400            ViewCompat.postInvalidateOnAnimation(this);
1401            return;
1402        }
1403
1404        // Done with scroll, clean up state.
1405        completeScroll();
1406    }
1407
1408    private boolean pageScrolled(int xpos) {
1409        if (mItems.size() == 0) {
1410            mCalledSuper = false;
1411            onPageScrolled(0, 0, 0);
1412            if (!mCalledSuper) {
1413                throw new IllegalStateException(
1414                        "onPageScrolled did not call superclass implementation");
1415            }
1416            return false;
1417        }
1418        final ItemInfo ii = infoForCurrentScrollPosition();
1419        final int width = getWidth();
1420        final int widthWithMargin = width + mPageMargin;
1421        final float marginOffset = (float) mPageMargin / width;
1422        final int currentPage = ii.position;
1423        final float pageOffset = (((float) xpos / width) - ii.offset) /
1424                (ii.widthFactor + marginOffset);
1425        final int offsetPixels = (int) (pageOffset * widthWithMargin);
1426
1427        mCalledSuper = false;
1428        onPageScrolled(currentPage, pageOffset, offsetPixels);
1429        if (!mCalledSuper) {
1430            throw new IllegalStateException(
1431                    "onPageScrolled did not call superclass implementation");
1432        }
1433        return true;
1434    }
1435
1436    /**
1437     * This method will be invoked when the current page is scrolled, either as part
1438     * of a programmatically initiated smooth scroll or a user initiated touch scroll.
1439     * If you override this method you must call through to the superclass implementation
1440     * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled
1441     * returns.
1442     *
1443     * @param position Position index of the first page currently being displayed.
1444     *                 Page position+1 will be visible if positionOffset is nonzero.
1445     * @param offset Value from [0, 1) indicating the offset from the page at position.
1446     * @param offsetPixels Value in pixels indicating the offset from position.
1447     */
1448    protected void onPageScrolled(int position, float offset, int offsetPixels) {
1449        // Offset any decor views if needed - keep them on-screen at all times.
1450        if (mDecorChildCount > 0) {
1451            final int scrollX = getScrollX();
1452            int paddingLeft = getPaddingLeft();
1453            int paddingRight = getPaddingRight();
1454            final int width = getWidth();
1455            final int childCount = getChildCount();
1456            for (int i = 0; i < childCount; i++) {
1457                final View child = getChildAt(i);
1458                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1459                if (!lp.isDecor) continue;
1460
1461                final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
1462                int childLeft = 0;
1463                switch (hgrav) {
1464                    default:
1465                        childLeft = paddingLeft;
1466                        break;
1467                    case Gravity.LEFT:
1468                        childLeft = paddingLeft;
1469                        paddingLeft += child.getWidth();
1470                        break;
1471                    case Gravity.CENTER_HORIZONTAL:
1472                        childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
1473                                paddingLeft);
1474                        break;
1475                    case Gravity.RIGHT:
1476                        childLeft = width - paddingRight - child.getMeasuredWidth();
1477                        paddingRight += child.getMeasuredWidth();
1478                        break;
1479                }
1480                childLeft += scrollX;
1481
1482                final int childOffset = childLeft - child.getLeft();
1483                if (childOffset != 0) {
1484                    child.offsetLeftAndRight(childOffset);
1485                }
1486            }
1487        }
1488
1489        if (mOnPageChangeListener != null) {
1490            mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
1491        }
1492        if (mInternalPageChangeListener != null) {
1493            mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
1494        }
1495        mCalledSuper = true;
1496    }
1497
1498    private void completeScroll() {
1499        boolean needPopulate = mScrolling;
1500        if (needPopulate) {
1501            // Done with scroll, no longer want to cache view drawing.
1502            setScrollingCacheEnabled(false);
1503            mScroller.abortAnimation();
1504            int oldX = getScrollX();
1505            int oldY = getScrollY();
1506            int x = mScroller.getCurrX();
1507            int y = mScroller.getCurrY();
1508            if (oldX != x || oldY != y) {
1509                scrollTo(x, y);
1510            }
1511            setScrollState(SCROLL_STATE_IDLE);
1512        }
1513        mPopulatePending = false;
1514        mScrolling = false;
1515        for (int i=0; i<mItems.size(); i++) {
1516            ItemInfo ii = mItems.get(i);
1517            if (ii.scrolling) {
1518                needPopulate = true;
1519                ii.scrolling = false;
1520            }
1521        }
1522        if (needPopulate) {
1523            populate();
1524        }
1525    }
1526
1527    @Override
1528    public boolean onInterceptTouchEvent(MotionEvent ev) {
1529        /*
1530         * This method JUST determines whether we want to intercept the motion.
1531         * If we return true, onMotionEvent will be called and we do the actual
1532         * scrolling there.
1533         */
1534
1535        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
1536
1537        // Always take care of the touch gesture being complete.
1538        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
1539            // Release the drag.
1540            if (DEBUG) Log.v(TAG, "Intercept done!");
1541            mIsBeingDragged = false;
1542            mIsUnableToDrag = false;
1543            mActivePointerId = INVALID_POINTER;
1544            if (mVelocityTracker != null) {
1545                mVelocityTracker.recycle();
1546                mVelocityTracker = null;
1547            }
1548            return false;
1549        }
1550
1551        // Nothing more to do here if we have decided whether or not we
1552        // are dragging.
1553        if (action != MotionEvent.ACTION_DOWN) {
1554            if (mIsBeingDragged) {
1555                if (DEBUG) Log.v(TAG, "Intercept returning true!");
1556                return true;
1557            }
1558            if (mIsUnableToDrag) {
1559                if (DEBUG) Log.v(TAG, "Intercept returning false!");
1560                return false;
1561            }
1562        }
1563
1564        switch (action) {
1565            case MotionEvent.ACTION_MOVE: {
1566                /*
1567                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1568                 * whether the user has moved far enough from his original down touch.
1569                 */
1570
1571                /*
1572                * Locally do absolute value. mLastMotionY is set to the y value
1573                * of the down event.
1574                */
1575                final int activePointerId = mActivePointerId;
1576                if (activePointerId == INVALID_POINTER) {
1577                    // If we don't have a valid id, the touch down wasn't on content.
1578                    break;
1579                }
1580
1581                final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
1582                final float x = MotionEventCompat.getX(ev, pointerIndex);
1583                final float dx = x - mLastMotionX;
1584                final float xDiff = Math.abs(dx);
1585                final float y = MotionEventCompat.getY(ev, pointerIndex);
1586                final float yDiff = Math.abs(y - mLastMotionY);
1587                if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
1588
1589                if (canScroll(this, false, (int) dx, (int) x, (int) y)) {
1590                    // Nested view has scrollable area under this point. Let it be handled there.
1591                    mInitialMotionX = mLastMotionX = x;
1592                    mLastMotionY = y;
1593                    mIsUnableToDrag = true;
1594                    return false;
1595                }
1596                if (xDiff > mTouchSlop && xDiff > yDiff) {
1597                    if (DEBUG) Log.v(TAG, "Starting drag!");
1598                    mIsBeingDragged = true;
1599                    setScrollState(SCROLL_STATE_DRAGGING);
1600                    mLastMotionX = x;
1601                    setScrollingCacheEnabled(true);
1602                } else {
1603                    if (yDiff > mTouchSlop) {
1604                        // The finger has moved enough in the vertical
1605                        // direction to be counted as a drag...  abort
1606                        // any attempt to drag horizontally, to work correctly
1607                        // with children that have scrolling containers.
1608                        if (DEBUG) Log.v(TAG, "Starting unable to drag!");
1609                        mIsUnableToDrag = true;
1610                    }
1611                }
1612                break;
1613            }
1614
1615            case MotionEvent.ACTION_DOWN: {
1616                /*
1617                 * Remember location of down touch.
1618                 * ACTION_DOWN always refers to pointer index 0.
1619                 */
1620                mLastMotionX = mInitialMotionX = ev.getX();
1621                mLastMotionY = ev.getY();
1622                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
1623                mIsUnableToDrag = false;
1624
1625                if (mScrollState == SCROLL_STATE_SETTLING &&
1626                        Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
1627                    // Let the user 'catch' the pager as it animates.
1628                    mIsBeingDragged = true;
1629                    setScrollState(SCROLL_STATE_DRAGGING);
1630                } else {
1631                    completeScroll();
1632                    mIsBeingDragged = false;
1633                }
1634
1635                if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
1636                        + " mIsBeingDragged=" + mIsBeingDragged
1637                        + "mIsUnableToDrag=" + mIsUnableToDrag);
1638                break;
1639            }
1640
1641            case MotionEventCompat.ACTION_POINTER_UP:
1642                onSecondaryPointerUp(ev);
1643                break;
1644        }
1645
1646        if (!mIsBeingDragged) {
1647            // Track the velocity as long as we aren't dragging.
1648            // Once we start a real drag we will track in onTouchEvent.
1649            if (mVelocityTracker == null) {
1650                mVelocityTracker = VelocityTracker.obtain();
1651            }
1652            mVelocityTracker.addMovement(ev);
1653        }
1654
1655        /*
1656         * The only time we want to intercept motion events is if we are in the
1657         * drag mode.
1658         */
1659        return mIsBeingDragged;
1660    }
1661
1662    @Override
1663    public boolean onTouchEvent(MotionEvent ev) {
1664        if (mFakeDragging) {
1665            // A fake drag is in progress already, ignore this real one
1666            // but still eat the touch events.
1667            // (It is likely that the user is multi-touching the screen.)
1668            return true;
1669        }
1670
1671        if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
1672            // Don't handle edge touches immediately -- they may actually belong to one of our
1673            // descendants.
1674            return false;
1675        }
1676
1677        if (mAdapter == null || mAdapter.getCount() == 0) {
1678            // Nothing to present or scroll; nothing to touch.
1679            return false;
1680        }
1681
1682        if (mVelocityTracker == null) {
1683            mVelocityTracker = VelocityTracker.obtain();
1684        }
1685        mVelocityTracker.addMovement(ev);
1686
1687        final int action = ev.getAction();
1688        boolean needsInvalidate = false;
1689
1690        switch (action & MotionEventCompat.ACTION_MASK) {
1691            case MotionEvent.ACTION_DOWN: {
1692                /*
1693                 * If being flinged and user touches, stop the fling. isFinished
1694                 * will be false if being flinged.
1695                 */
1696                completeScroll();
1697
1698                // Remember where the motion event started
1699                mLastMotionX = mInitialMotionX = ev.getX();
1700                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
1701                break;
1702            }
1703            case MotionEvent.ACTION_MOVE:
1704                if (!mIsBeingDragged) {
1705                    final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
1706                    final float x = MotionEventCompat.getX(ev, pointerIndex);
1707                    final float xDiff = Math.abs(x - mLastMotionX);
1708                    final float y = MotionEventCompat.getY(ev, pointerIndex);
1709                    final float yDiff = Math.abs(y - mLastMotionY);
1710                    if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
1711                    if (xDiff > mTouchSlop && xDiff > yDiff) {
1712                        if (DEBUG) Log.v(TAG, "Starting drag!");
1713                        mIsBeingDragged = true;
1714                        mLastMotionX = x;
1715                        setScrollState(SCROLL_STATE_DRAGGING);
1716                        setScrollingCacheEnabled(true);
1717                    }
1718                }
1719                // Not else! Note that mIsBeingDragged can be set above.
1720                if (mIsBeingDragged) {
1721                    // Scroll to follow the motion event
1722                    final int activePointerIndex = MotionEventCompat.findPointerIndex(
1723                            ev, mActivePointerId);
1724                    final float x = MotionEventCompat.getX(ev, activePointerIndex);
1725                    final float deltaX = mLastMotionX - x;
1726                    mLastMotionX = x;
1727                    float oldScrollX = getScrollX();
1728                    float scrollX = oldScrollX + deltaX;
1729                    final int width = getWidth();
1730
1731                    float leftBound = width * mFirstOffset;
1732                    float rightBound = width * mLastOffset;
1733                    boolean leftAbsolute = true;
1734                    boolean rightAbsolute = true;
1735
1736                    final ItemInfo firstItem = mItems.get(0);
1737                    final ItemInfo lastItem = mItems.get(mItems.size() - 1);
1738                    if (firstItem.position != 0) {
1739                        leftAbsolute = false;
1740                        leftBound = firstItem.offset * width;
1741                    }
1742                    if (lastItem.position != mAdapter.getCount() - 1) {
1743                        rightAbsolute = false;
1744                        rightBound = lastItem.offset * width;
1745                    }
1746
1747                    if (scrollX < leftBound) {
1748                        if (leftAbsolute) {
1749                            float over = leftBound - scrollX;
1750                            needsInvalidate = mLeftEdge.onPull(Math.abs(over) / width);
1751                        }
1752                        scrollX = leftBound;
1753                    } else if (scrollX > rightBound) {
1754                        if (rightAbsolute) {
1755                            float over = scrollX - rightBound;
1756                            needsInvalidate = mRightEdge.onPull(Math.abs(over) / width);
1757                        }
1758                        scrollX = rightBound;
1759                    }
1760                    // Don't lose the rounded component
1761                    mLastMotionX += scrollX - (int) scrollX;
1762                    scrollTo((int) scrollX, getScrollY());
1763                    pageScrolled((int) scrollX);
1764                }
1765                break;
1766            case MotionEvent.ACTION_UP:
1767                if (mIsBeingDragged) {
1768                    final VelocityTracker velocityTracker = mVelocityTracker;
1769                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1770                    int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
1771                            velocityTracker, mActivePointerId);
1772                    mPopulatePending = true;
1773                    final int width = getWidth();
1774                    final int scrollX = getScrollX();
1775                    final ItemInfo ii = infoForCurrentScrollPosition();
1776                    final int currentPage = ii.position;
1777                    final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
1778                    final int activePointerIndex =
1779                            MotionEventCompat.findPointerIndex(ev, mActivePointerId);
1780                    final float x = MotionEventCompat.getX(ev, activePointerIndex);
1781                    final int totalDelta = (int) (x - mInitialMotionX);
1782                    int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
1783                            totalDelta);
1784                    setCurrentItemInternal(nextPage, true, true, initialVelocity);
1785
1786                    mActivePointerId = INVALID_POINTER;
1787                    endDrag();
1788                    needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
1789                }
1790                break;
1791            case MotionEvent.ACTION_CANCEL:
1792                if (mIsBeingDragged) {
1793                    setCurrentItemInternal(mCurItem, true, true);
1794                    mActivePointerId = INVALID_POINTER;
1795                    endDrag();
1796                    needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
1797                }
1798                break;
1799            case MotionEventCompat.ACTION_POINTER_DOWN: {
1800                final int index = MotionEventCompat.getActionIndex(ev);
1801                final float x = MotionEventCompat.getX(ev, index);
1802                mLastMotionX = x;
1803                mActivePointerId = MotionEventCompat.getPointerId(ev, index);
1804                break;
1805            }
1806            case MotionEventCompat.ACTION_POINTER_UP:
1807                onSecondaryPointerUp(ev);
1808                mLastMotionX = MotionEventCompat.getX(ev,
1809                        MotionEventCompat.findPointerIndex(ev, mActivePointerId));
1810                break;
1811        }
1812        if (needsInvalidate) {
1813            ViewCompat.postInvalidateOnAnimation(this);
1814        }
1815        return true;
1816    }
1817
1818    /**
1819     * @return Info about the page at the current scroll position.
1820     *         This can be synthetic for a missing middle page; the 'object' field can be null.
1821     */
1822    private ItemInfo infoForCurrentScrollPosition() {
1823        final int width = getWidth();
1824        final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0;
1825        final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
1826        int lastPos = -1;
1827        float lastOffset = 0.f;
1828        float lastWidth = 0.f;
1829        boolean first = true;
1830
1831        ItemInfo lastItem = null;
1832        for (int i = 0; i < mItems.size(); i++) {
1833            ItemInfo ii = mItems.get(i);
1834            float offset;
1835            if (!first && ii.position != lastPos + 1) {
1836                // Create a synthetic item for a missing page.
1837                ii = mTempItem;
1838                ii.offset = lastOffset + lastWidth + marginOffset;
1839                ii.position = lastPos + 1;
1840                ii.widthFactor = mAdapter.getPageWidth(ii.position);
1841                i--;
1842            }
1843            offset = ii.offset;
1844
1845            final float leftBound = offset - 0.0001f;
1846            final float rightBound = offset + ii.widthFactor + marginOffset + 0.0001f;
1847            if (first || scrollOffset >= leftBound) {
1848                if (scrollOffset < rightBound || i == mItems.size() - 1) {
1849                    return ii;
1850                }
1851            } else {
1852                return lastItem;
1853            }
1854            first = false;
1855            lastPos = ii.position;
1856            lastOffset = offset;
1857            lastWidth = ii.widthFactor;
1858            lastItem = ii;
1859        }
1860
1861        return lastItem;
1862    }
1863
1864    private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {
1865        int targetPage;
1866        if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
1867            targetPage = velocity > 0 ? currentPage : currentPage + 1;
1868        } else {
1869            targetPage = (int) (currentPage + pageOffset + 0.5f);
1870        }
1871
1872        if (mItems.size() > 0) {
1873            final ItemInfo firstItem = mItems.get(0);
1874            final ItemInfo lastItem = mItems.get(mItems.size() - 1);
1875
1876            // Only let the user target pages we have items for
1877            targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
1878        }
1879
1880        return targetPage;
1881    }
1882
1883    @Override
1884    public void draw(Canvas canvas) {
1885        super.draw(canvas);
1886        boolean needsInvalidate = false;
1887
1888        final int overScrollMode = ViewCompat.getOverScrollMode(this);
1889        if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
1890                (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&
1891                        mAdapter != null && mAdapter.getCount() > 1)) {
1892            if (!mLeftEdge.isFinished()) {
1893                final int restoreCount = canvas.save();
1894                final int height = getHeight() - getPaddingTop() - getPaddingBottom();
1895                final int width = getWidth();
1896
1897                canvas.rotate(270);
1898                canvas.translate(-height + getPaddingTop(), mFirstOffset * width);
1899                mLeftEdge.setSize(height, width);
1900                needsInvalidate |= mLeftEdge.draw(canvas);
1901                canvas.restoreToCount(restoreCount);
1902            }
1903            if (!mRightEdge.isFinished()) {
1904                final int restoreCount = canvas.save();
1905                final int width = getWidth();
1906                final int height = getHeight() - getPaddingTop() - getPaddingBottom();
1907
1908                canvas.rotate(90);
1909                canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width);
1910                mRightEdge.setSize(height, width);
1911                needsInvalidate |= mRightEdge.draw(canvas);
1912                canvas.restoreToCount(restoreCount);
1913            }
1914        } else {
1915            mLeftEdge.finish();
1916            mRightEdge.finish();
1917        }
1918
1919        if (needsInvalidate) {
1920            // Keep animating
1921            ViewCompat.postInvalidateOnAnimation(this);
1922        }
1923    }
1924
1925    @Override
1926    protected void onDraw(Canvas canvas) {
1927        super.onDraw(canvas);
1928
1929        // Draw the margin drawable between pages if needed.
1930        if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) {
1931            final int scrollX = getScrollX();
1932            final int width = getWidth();
1933
1934            final float marginOffset = (float) mPageMargin / width;
1935            int itemIndex = 0;
1936            ItemInfo ii = mItems.get(0);
1937            float offset = ii.offset;
1938            final int itemCount = mItems.size();
1939            final int firstPos = ii.position;
1940            final int lastPos = mItems.get(itemCount - 1).position;
1941            for (int pos = firstPos; pos < lastPos; pos++) {
1942                while (pos > ii.position && itemIndex < itemCount) {
1943                    ii = mItems.get(++itemIndex);
1944                }
1945
1946                float drawAt;
1947                if (pos == ii.position) {
1948                    drawAt = (ii.offset + ii.widthFactor) * width;
1949                    offset = ii.offset + ii.widthFactor + marginOffset;
1950                } else {
1951                    float widthFactor = mAdapter.getPageWidth(pos);
1952                    drawAt = (offset + widthFactor) * width;
1953                    offset += widthFactor + marginOffset;
1954                }
1955
1956                if (drawAt + mPageMargin > scrollX) {
1957                    mMarginDrawable.setBounds((int) (drawAt - 0.5f), mTopPageBounds,
1958                            (int) (drawAt + mPageMargin + 0.5f), mBottomPageBounds);
1959                    mMarginDrawable.draw(canvas);
1960                }
1961
1962                if (drawAt > scrollX + width) {
1963                    break; // No more visible, no sense in continuing
1964                }
1965            }
1966        }
1967    }
1968
1969    /**
1970     * Start a fake drag of the pager.
1971     *
1972     * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
1973     * with the touch scrolling of another view, while still letting the ViewPager
1974     * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
1975     * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
1976     * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
1977     *
1978     * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
1979     * is already in progress, this method will return false.
1980     *
1981     * @return true if the fake drag began successfully, false if it could not be started.
1982     *
1983     * @see #fakeDragBy(float)
1984     * @see #endFakeDrag()
1985     */
1986    public boolean beginFakeDrag() {
1987        if (mIsBeingDragged) {
1988            return false;
1989        }
1990        mFakeDragging = true;
1991        setScrollState(SCROLL_STATE_DRAGGING);
1992        mInitialMotionX = mLastMotionX = 0;
1993        if (mVelocityTracker == null) {
1994            mVelocityTracker = VelocityTracker.obtain();
1995        } else {
1996            mVelocityTracker.clear();
1997        }
1998        final long time = SystemClock.uptimeMillis();
1999        final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
2000        mVelocityTracker.addMovement(ev);
2001        ev.recycle();
2002        mFakeDragBeginTime = time;
2003        return true;
2004    }
2005
2006    /**
2007     * End a fake drag of the pager.
2008     *
2009     * @see #beginFakeDrag()
2010     * @see #fakeDragBy(float)
2011     */
2012    public void endFakeDrag() {
2013        if (!mFakeDragging) {
2014            throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
2015        }
2016
2017        final VelocityTracker velocityTracker = mVelocityTracker;
2018        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
2019        int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
2020                velocityTracker, mActivePointerId);
2021        mPopulatePending = true;
2022        final int width = getWidth();
2023        final int scrollX = getScrollX();
2024        final ItemInfo ii = infoForCurrentScrollPosition();
2025        final int currentPage = ii.position;
2026        final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
2027        final int totalDelta = (int) (mLastMotionX - mInitialMotionX);
2028        int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
2029                totalDelta);
2030        setCurrentItemInternal(nextPage, true, true, initialVelocity);
2031        endDrag();
2032
2033        mFakeDragging = false;
2034    }
2035
2036    /**
2037     * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
2038     *
2039     * @param xOffset Offset in pixels to drag by.
2040     * @see #beginFakeDrag()
2041     * @see #endFakeDrag()
2042     */
2043    public void fakeDragBy(float xOffset) {
2044        if (!mFakeDragging) {
2045            throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
2046        }
2047
2048        mLastMotionX += xOffset;
2049
2050        float oldScrollX = getScrollX();
2051        float scrollX = oldScrollX - xOffset;
2052        final int width = getWidth();
2053
2054        float leftBound = width * mFirstOffset;
2055        float rightBound = width * mLastOffset;
2056
2057        final ItemInfo firstItem = mItems.get(0);
2058        final ItemInfo lastItem = mItems.get(mItems.size() - 1);
2059        if (firstItem.position != 0) {
2060            leftBound = firstItem.offset * width;
2061        }
2062        if (lastItem.position != mAdapter.getCount() - 1) {
2063            rightBound = lastItem.offset * width;
2064        }
2065
2066        if (scrollX < leftBound) {
2067            scrollX = leftBound;
2068        } else if (scrollX > rightBound) {
2069            scrollX = rightBound;
2070        }
2071        // Don't lose the rounded component
2072        mLastMotionX += scrollX - (int) scrollX;
2073        scrollTo((int) scrollX, getScrollY());
2074        pageScrolled((int) scrollX);
2075
2076        // Synthesize an event for the VelocityTracker.
2077        final long time = SystemClock.uptimeMillis();
2078        final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
2079                mLastMotionX, 0, 0);
2080        mVelocityTracker.addMovement(ev);
2081        ev.recycle();
2082    }
2083
2084    /**
2085     * Returns true if a fake drag is in progress.
2086     *
2087     * @return true if currently in a fake drag, false otherwise.
2088     *
2089     * @see #beginFakeDrag()
2090     * @see #fakeDragBy(float)
2091     * @see #endFakeDrag()
2092     */
2093    public boolean isFakeDragging() {
2094        return mFakeDragging;
2095    }
2096
2097    private void onSecondaryPointerUp(MotionEvent ev) {
2098        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
2099        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
2100        if (pointerId == mActivePointerId) {
2101            // This was our active pointer going up. Choose a new
2102            // active pointer and adjust accordingly.
2103            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
2104            mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);
2105            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
2106            if (mVelocityTracker != null) {
2107                mVelocityTracker.clear();
2108            }
2109        }
2110    }
2111
2112    private void endDrag() {
2113        mIsBeingDragged = false;
2114        mIsUnableToDrag = false;
2115
2116        if (mVelocityTracker != null) {
2117            mVelocityTracker.recycle();
2118            mVelocityTracker = null;
2119        }
2120    }
2121
2122    private void setScrollingCacheEnabled(boolean enabled) {
2123        if (mScrollingCacheEnabled != enabled) {
2124            mScrollingCacheEnabled = enabled;
2125            if (USE_CACHE) {
2126                final int size = getChildCount();
2127                for (int i = 0; i < size; ++i) {
2128                    final View child = getChildAt(i);
2129                    if (child.getVisibility() != GONE) {
2130                        child.setDrawingCacheEnabled(enabled);
2131                    }
2132                }
2133            }
2134        }
2135    }
2136
2137    /**
2138     * Tests scrollability within child views of v given a delta of dx.
2139     *
2140     * @param v View to test for horizontal scrollability
2141     * @param checkV Whether the view v passed should itself be checked for scrollability (true),
2142     *               or just its children (false).
2143     * @param dx Delta scrolled in pixels
2144     * @param x X coordinate of the active touch point
2145     * @param y Y coordinate of the active touch point
2146     * @return true if child views of v can be scrolled by delta of dx.
2147     */
2148    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
2149        if (v instanceof ViewGroup) {
2150            final ViewGroup group = (ViewGroup) v;
2151            final int scrollX = v.getScrollX();
2152            final int scrollY = v.getScrollY();
2153            final int count = group.getChildCount();
2154            // Count backwards - let topmost views consume scroll distance first.
2155            for (int i = count - 1; i >= 0; i--) {
2156                // TODO: Add versioned support here for transformed views.
2157                // This will not work for transformed views in Honeycomb+
2158                final View child = group.getChildAt(i);
2159                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
2160                        y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
2161                        canScroll(child, true, dx, x + scrollX - child.getLeft(),
2162                                y + scrollY - child.getTop())) {
2163                    return true;
2164                }
2165            }
2166        }
2167
2168        return checkV && ViewCompat.canScrollHorizontally(v, -dx);
2169    }
2170
2171    @Override
2172    public boolean dispatchKeyEvent(KeyEvent event) {
2173        // Let the focused view and/or our descendants get the key first
2174        return super.dispatchKeyEvent(event) || executeKeyEvent(event);
2175    }
2176
2177    /**
2178     * You can call this function yourself to have the scroll view perform
2179     * scrolling from a key event, just as if the event had been dispatched to
2180     * it by the view hierarchy.
2181     *
2182     * @param event The key event to execute.
2183     * @return Return true if the event was handled, else false.
2184     */
2185    public boolean executeKeyEvent(KeyEvent event) {
2186        boolean handled = false;
2187        if (event.getAction() == KeyEvent.ACTION_DOWN) {
2188            switch (event.getKeyCode()) {
2189                case KeyEvent.KEYCODE_DPAD_LEFT:
2190                    handled = arrowScroll(FOCUS_LEFT);
2191                    break;
2192                case KeyEvent.KEYCODE_DPAD_RIGHT:
2193                    handled = arrowScroll(FOCUS_RIGHT);
2194                    break;
2195                case KeyEvent.KEYCODE_TAB:
2196                    if (Build.VERSION.SDK_INT >= 11) {
2197                        // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD
2198                        // before Android 3.0. Ignore the tab key on those devices.
2199                        if (KeyEventCompat.hasNoModifiers(event)) {
2200                            handled = arrowScroll(FOCUS_FORWARD);
2201                        } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
2202                            handled = arrowScroll(FOCUS_BACKWARD);
2203                        }
2204                    }
2205                    break;
2206            }
2207        }
2208        return handled;
2209    }
2210
2211    public boolean arrowScroll(int direction) {
2212        View currentFocused = findFocus();
2213        if (currentFocused == this) currentFocused = null;
2214
2215        boolean handled = false;
2216
2217        View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
2218                direction);
2219        if (nextFocused != null && nextFocused != currentFocused) {
2220            if (direction == View.FOCUS_LEFT) {
2221                // If there is nothing to the left, or this is causing us to
2222                // jump to the right, then what we really want to do is page left.
2223                if (currentFocused != null && nextFocused.getLeft() >= currentFocused.getLeft()) {
2224                    handled = pageLeft();
2225                } else {
2226                    handled = nextFocused.requestFocus();
2227                }
2228            } else if (direction == View.FOCUS_RIGHT) {
2229                // If there is nothing to the right, or this is causing us to
2230                // jump to the left, then what we really want to do is page right.
2231                if (currentFocused != null && nextFocused.getLeft() <= currentFocused.getLeft()) {
2232                    handled = pageRight();
2233                } else {
2234                    handled = nextFocused.requestFocus();
2235                }
2236            }
2237        } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {
2238            // Trying to move left and nothing there; try to page.
2239            handled = pageLeft();
2240        } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {
2241            // Trying to move right and nothing there; try to page.
2242            handled = pageRight();
2243        }
2244        if (handled) {
2245            playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
2246        }
2247        return handled;
2248    }
2249
2250    boolean pageLeft() {
2251        if (mCurItem > 0) {
2252            setCurrentItem(mCurItem-1, true);
2253            return true;
2254        }
2255        return false;
2256    }
2257
2258    boolean pageRight() {
2259        if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) {
2260            setCurrentItem(mCurItem+1, true);
2261            return true;
2262        }
2263        return false;
2264    }
2265
2266    /**
2267     * We only want the current page that is being shown to be focusable.
2268     */
2269    @Override
2270    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
2271        final int focusableCount = views.size();
2272
2273        final int descendantFocusability = getDescendantFocusability();
2274
2275        if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
2276            for (int i = 0; i < getChildCount(); i++) {
2277                final View child = getChildAt(i);
2278                if (child.getVisibility() == VISIBLE) {
2279                    ItemInfo ii = infoForChild(child);
2280                    if (ii != null && ii.position == mCurItem) {
2281                        child.addFocusables(views, direction, focusableMode);
2282                    }
2283                }
2284            }
2285        }
2286
2287        // we add ourselves (if focusable) in all cases except for when we are
2288        // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.  this is
2289        // to avoid the focus search finding layouts when a more precise search
2290        // among the focusable children would be more interesting.
2291        if (
2292            descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
2293                // No focusable descendants
2294                (focusableCount == views.size())) {
2295            // Note that we can't call the superclass here, because it will
2296            // add all views in.  So we need to do the same thing View does.
2297            if (!isFocusable()) {
2298                return;
2299            }
2300            if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
2301                    isInTouchMode() && !isFocusableInTouchMode()) {
2302                return;
2303            }
2304            if (views != null) {
2305                views.add(this);
2306            }
2307        }
2308    }
2309
2310    /**
2311     * We only want the current page that is being shown to be touchable.
2312     */
2313    @Override
2314    public void addTouchables(ArrayList<View> views) {
2315        // Note that we don't call super.addTouchables(), which means that
2316        // we don't call View.addTouchables().  This is okay because a ViewPager
2317        // is itself not touchable.
2318        for (int i = 0; i < getChildCount(); i++) {
2319            final View child = getChildAt(i);
2320            if (child.getVisibility() == VISIBLE) {
2321                ItemInfo ii = infoForChild(child);
2322                if (ii != null && ii.position == mCurItem) {
2323                    child.addTouchables(views);
2324                }
2325            }
2326        }
2327    }
2328
2329    /**
2330     * We only want the current page that is being shown to be focusable.
2331     */
2332    @Override
2333    protected boolean onRequestFocusInDescendants(int direction,
2334            Rect previouslyFocusedRect) {
2335        int index;
2336        int increment;
2337        int end;
2338        int count = getChildCount();
2339        if ((direction & FOCUS_FORWARD) != 0) {
2340            index = 0;
2341            increment = 1;
2342            end = count;
2343        } else {
2344            index = count - 1;
2345            increment = -1;
2346            end = -1;
2347        }
2348        for (int i = index; i != end; i += increment) {
2349            View child = getChildAt(i);
2350            if (child.getVisibility() == VISIBLE) {
2351                ItemInfo ii = infoForChild(child);
2352                if (ii != null && ii.position == mCurItem) {
2353                    if (child.requestFocus(direction, previouslyFocusedRect)) {
2354                        return true;
2355                    }
2356                }
2357            }
2358        }
2359        return false;
2360    }
2361
2362    @Override
2363    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
2364        // ViewPagers should only report accessibility info for the current page,
2365        // otherwise things get very confusing.
2366
2367        // TODO: Should this note something about the paging container?
2368
2369        final int childCount = getChildCount();
2370        for (int i = 0; i < childCount; i++) {
2371            final View child = getChildAt(i);
2372            if (child.getVisibility() == VISIBLE) {
2373                final ItemInfo ii = infoForChild(child);
2374                if (ii != null && ii.position == mCurItem &&
2375                        child.dispatchPopulateAccessibilityEvent(event)) {
2376                    return true;
2377                }
2378            }
2379        }
2380
2381        return false;
2382    }
2383
2384    @Override
2385    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
2386        return new LayoutParams();
2387    }
2388
2389    @Override
2390    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2391        return generateDefaultLayoutParams();
2392    }
2393
2394    @Override
2395    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2396        return p instanceof LayoutParams && super.checkLayoutParams(p);
2397    }
2398
2399    @Override
2400    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2401        return new LayoutParams(getContext(), attrs);
2402    }
2403
2404    private class PagerObserver extends DataSetObserver {
2405        @Override
2406        public void onChanged() {
2407            dataSetChanged();
2408        }
2409        @Override
2410        public void onInvalidated() {
2411            dataSetChanged();
2412        }
2413    }
2414
2415    /**
2416     * Layout parameters that should be supplied for views added to a
2417     * ViewPager.
2418     */
2419    public static class LayoutParams extends ViewGroup.LayoutParams {
2420        /**
2421         * true if this view is a decoration on the pager itself and not
2422         * a view supplied by the adapter.
2423         */
2424        public boolean isDecor;
2425
2426        /**
2427         * Gravity setting for use on decor views only:
2428         * Where to position the view page within the overall ViewPager
2429         * container; constants are defined in {@link android.view.Gravity}.
2430         */
2431        public int gravity;
2432
2433        /**
2434         * Width as a 0-1 multiplier of the measured pager width
2435         */
2436        public float widthFactor = 0.f;
2437
2438        /**
2439         * true if this view was added during layout and needs to be measured
2440         * before being positioned.
2441         */
2442        public boolean needsMeasure;
2443
2444        public LayoutParams() {
2445            super(FILL_PARENT, FILL_PARENT);
2446        }
2447
2448        public LayoutParams(Context context, AttributeSet attrs) {
2449            super(context, attrs);
2450
2451            final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
2452            gravity = a.getInteger(0, Gravity.TOP);
2453            a.recycle();
2454        }
2455    }
2456}
2457