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