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