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