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