ViewPager.java revision 1cbf48f28f6ef523af2a4aafa98c5437964cb48a
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.graphics.Canvas;
21import android.graphics.Rect;
22import android.graphics.drawable.Drawable;
23import android.os.Parcel;
24import android.os.Parcelable;
25import android.os.SystemClock;
26import android.support.v4.os.ParcelableCompat;
27import android.support.v4.os.ParcelableCompatCreatorCallbacks;
28import android.support.v4.widget.EdgeEffectCompat;
29import android.util.AttributeSet;
30import android.util.Log;
31import android.view.FocusFinder;
32import android.view.KeyEvent;
33import android.view.MotionEvent;
34import android.view.SoundEffectConstants;
35import android.view.VelocityTracker;
36import android.view.View;
37import android.view.ViewConfiguration;
38import android.view.ViewGroup;
39import android.view.ViewParent;
40import android.view.accessibility.AccessibilityEvent;
41import android.view.animation.Interpolator;
42import android.widget.Scroller;
43
44import java.util.ArrayList;
45import java.util.Collections;
46import java.util.Comparator;
47
48/**
49 * Layout manager that allows the user to flip left and right
50 * through pages of data.  You supply an implementation of a
51 * {@link PagerAdapter} to generate the pages that the view shows.
52 *
53 * <p>Note this class is currently under early design and
54 * development.  The API will likely change in later updates of
55 * the compatibility library, requiring changes to the source code
56 * of apps when they are compiled against the newer version.</p>
57 */
58public class ViewPager extends ViewGroup {
59    private static final String TAG = "ViewPager";
60    private static final boolean DEBUG = false;
61
62    private static final boolean USE_CACHE = false;
63
64    private static final int DEFAULT_OFFSCREEN_PAGES = 1;
65    private static final int MAX_SETTLE_DURATION = 600; // ms
66
67    static class ItemInfo {
68        Object object;
69        int position;
70        boolean scrolling;
71    }
72
73    private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>(){
74        @Override
75        public int compare(ItemInfo lhs, ItemInfo rhs) {
76            return lhs.position - rhs.position;
77        }};
78
79    private static final Interpolator sInterpolator = new Interpolator() {
80        public float getInterpolation(float t) {
81            // _o(t) = t * t * ((tension + 1) * t + tension)
82            // o(t) = _o(t - 1) + 1
83            t -= 1.0f;
84            return t * t * t + 1.0f;
85        }
86    };
87
88    private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
89
90    private PagerAdapter mAdapter;
91    private int mCurItem;   // Index of currently displayed page.
92    private int mRestoredCurItem = -1;
93    private Parcelable mRestoredAdapterState = null;
94    private ClassLoader mRestoredClassLoader = null;
95    private Scroller mScroller;
96    private PagerAdapter.DataSetObserver mObserver;
97
98    private int mPageMargin;
99    private Drawable mMarginDrawable;
100
101    private int mChildWidthMeasureSpec;
102    private int mChildHeightMeasureSpec;
103    private boolean mInLayout;
104
105    private boolean mScrollingCacheEnabled;
106
107    private boolean mPopulatePending;
108    private boolean mScrolling;
109    private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
110
111    private boolean mIsBeingDragged;
112    private boolean mIsUnableToDrag;
113    private int mTouchSlop;
114    private float mInitialMotionX;
115    /**
116     * Position of the last motion event.
117     */
118    private float mLastMotionX;
119    private float mLastMotionY;
120    /**
121     * ID of the active pointer. This is used to retain consistency during
122     * drags/flings if multiple pointers are used.
123     */
124    private int mActivePointerId = INVALID_POINTER;
125    /**
126     * Sentinel value for no current active pointer.
127     * Used by {@link #mActivePointerId}.
128     */
129    private static final int INVALID_POINTER = -1;
130
131    /**
132     * Determines speed during touch scrolling
133     */
134    private VelocityTracker mVelocityTracker;
135    private int mMinimumVelocity;
136    private int mMaximumVelocity;
137    private float mBaseLineFlingVelocity;
138    private float mFlingVelocityInfluence;
139
140    private boolean mFakeDragging;
141    private long mFakeDragBeginTime;
142
143    private EdgeEffectCompat mLeftEdge;
144    private EdgeEffectCompat mRightEdge;
145
146    private boolean mFirstLayout = true;
147
148    private OnPageChangeListener mOnPageChangeListener;
149
150    /**
151     * Indicates that the pager is in an idle, settled state. The current page
152     * is fully in view and no animation is in progress.
153     */
154    public static final int SCROLL_STATE_IDLE = 0;
155
156    /**
157     * Indicates that the pager is currently being dragged by the user.
158     */
159    public static final int SCROLL_STATE_DRAGGING = 1;
160
161    /**
162     * Indicates that the pager is in the process of settling to a final position.
163     */
164    public static final int SCROLL_STATE_SETTLING = 2;
165
166    private int mScrollState = SCROLL_STATE_IDLE;
167
168    /**
169     * Callback interface for responding to changing state of the selected page.
170     */
171    public interface OnPageChangeListener {
172
173        /**
174         * This method will be invoked when the current page is scrolled, either as part
175         * of a programmatically initiated smooth scroll or a user initiated touch scroll.
176         *
177         * @param position Position index of the first page currently being displayed.
178         *                 Page position+1 will be visible if positionOffset is nonzero.
179         * @param positionOffset Value from [0, 1) indicating the offset from the page at position.
180         * @param positionOffsetPixels Value in pixels indicating the offset from position.
181         */
182        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
183
184        /**
185         * This method will be invoked when a new page becomes selected. Animation is not
186         * necessarily complete.
187         *
188         * @param position Position index of the new selected page.
189         */
190        public void onPageSelected(int position);
191
192        /**
193         * Called when the scroll state changes. Useful for discovering when the user
194         * begins dragging, when the pager is automatically settling to the current page,
195         * or when it is fully stopped/idle.
196         *
197         * @param state The new scroll state.
198         * @see ViewPager#SCROLL_STATE_IDLE
199         * @see ViewPager#SCROLL_STATE_DRAGGING
200         * @see ViewPager#SCROLL_STATE_SETTLING
201         */
202        public void onPageScrollStateChanged(int state);
203    }
204
205    /**
206     * Simple implementation of the {@link OnPageChangeListener} interface with stub
207     * implementations of each method. Extend this if you do not intend to override
208     * every method of {@link OnPageChangeListener}.
209     */
210    public static class SimpleOnPageChangeListener implements OnPageChangeListener {
211        @Override
212        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
213            // This space for rent
214        }
215
216        @Override
217        public void onPageSelected(int position) {
218            // This space for rent
219        }
220
221        @Override
222        public void onPageScrollStateChanged(int state) {
223            // This space for rent
224        }
225    }
226
227    public ViewPager(Context context) {
228        super(context);
229        initViewPager();
230    }
231
232    public ViewPager(Context context, AttributeSet attrs) {
233        super(context, attrs);
234        initViewPager();
235    }
236
237    void initViewPager() {
238        setWillNotDraw(false);
239        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
240        setFocusable(true);
241        final Context context = getContext();
242        mScroller = new Scroller(context, sInterpolator);
243        final ViewConfiguration configuration = ViewConfiguration.get(context);
244        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
245        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
246        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
247        mLeftEdge = new EdgeEffectCompat(context);
248        mRightEdge = new EdgeEffectCompat(context);
249
250        float density = context.getResources().getDisplayMetrics().density;
251        mBaseLineFlingVelocity = 2500.0f * density;
252        mFlingVelocityInfluence = 0.4f;
253    }
254
255    private void setScrollState(int newState) {
256        if (mScrollState == newState) {
257            return;
258        }
259
260        mScrollState = newState;
261        if (mOnPageChangeListener != null) {
262            mOnPageChangeListener.onPageScrollStateChanged(newState);
263        }
264    }
265
266    public void setAdapter(PagerAdapter adapter) {
267        if (mAdapter != null) {
268            mAdapter.setDataSetObserver(null);
269            mAdapter.startUpdate(this);
270            for (int i = 0; i < mItems.size(); i++) {
271                final ItemInfo ii = mItems.get(i);
272                mAdapter.destroyItem(this, ii.position, ii.object);
273            }
274            mAdapter.finishUpdate(this);
275            mItems.clear();
276            removeAllViews();
277            mCurItem = 0;
278            scrollTo(0, 0);
279        }
280
281        mAdapter = adapter;
282
283        if (mAdapter != null) {
284            if (mObserver == null) {
285                mObserver = new DataSetObserver();
286            }
287            mAdapter.setDataSetObserver(mObserver);
288            mPopulatePending = false;
289            if (mRestoredCurItem >= 0) {
290                mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
291                setCurrentItemInternal(mRestoredCurItem, false, true);
292                mRestoredCurItem = -1;
293                mRestoredAdapterState = null;
294                mRestoredClassLoader = null;
295            } else {
296                populate();
297            }
298        }
299    }
300
301    public PagerAdapter getAdapter() {
302        return mAdapter;
303    }
304
305    /**
306     * Set the currently selected page. If the ViewPager has already been through its first
307     * layout there will be a smooth animated transition between the current item and the
308     * specified item.
309     *
310     * @param item Item index to select
311     */
312    public void setCurrentItem(int item) {
313        mPopulatePending = false;
314        setCurrentItemInternal(item, !mFirstLayout, false);
315    }
316
317    /**
318     * Set the currently selected page.
319     *
320     * @param item Item index to select
321     * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
322     */
323    public void setCurrentItem(int item, boolean smoothScroll) {
324        mPopulatePending = false;
325        setCurrentItemInternal(item, smoothScroll, false);
326    }
327
328    public int getCurrentItem() {
329        return mCurItem;
330    }
331
332    void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
333        setCurrentItemInternal(item, smoothScroll, always, 0);
334    }
335
336    void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
337        if (mAdapter == null || mAdapter.getCount() <= 0) {
338            setScrollingCacheEnabled(false);
339            return;
340        }
341        if (!always && mCurItem == item && mItems.size() != 0) {
342            setScrollingCacheEnabled(false);
343            return;
344        }
345        if (item < 0) {
346            item = 0;
347        } else if (item >= mAdapter.getCount()) {
348            item = mAdapter.getCount() - 1;
349        }
350        final int pageLimit = mOffscreenPageLimit;
351        if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
352            // We are doing a jump by more than one page.  To avoid
353            // glitches, we want to keep all current pages in the view
354            // until the scroll ends.
355            for (int i=0; i<mItems.size(); i++) {
356                mItems.get(i).scrolling = true;
357            }
358        }
359        final boolean dispatchSelected = mCurItem != item;
360        mCurItem = item;
361        populate();
362        final int destX = (getWidth() + mPageMargin) * item;
363        if (smoothScroll) {
364            smoothScrollTo(destX, 0, velocity);
365            if (dispatchSelected && mOnPageChangeListener != null) {
366                mOnPageChangeListener.onPageSelected(item);
367            }
368        } else {
369            if (dispatchSelected && mOnPageChangeListener != null) {
370                mOnPageChangeListener.onPageSelected(item);
371            }
372            completeScroll();
373            scrollTo(destX, 0);
374        }
375    }
376
377    public void setOnPageChangeListener(OnPageChangeListener listener) {
378        mOnPageChangeListener = listener;
379    }
380
381    /**
382     * Returns the number of pages that will be retained to either side of the
383     * current page in the view hierarchy in an idle state. Defaults to 1.
384     *
385     * @return How many pages will be kept offscreen on either side
386     * @see #setOffscreenPageLimit(int)
387     */
388    public int getOffscreenPageLimit() {
389        return mOffscreenPageLimit;
390    }
391
392    /**
393     * Set the number of pages that should be retained to either side of the
394     * current page in the view hierarchy in an idle state. Pages beyond this
395     * limit will be recreated from the adapter when needed.
396     *
397     * <p>This is offered as an optimization. If you know in advance the number
398     * of pages you will need to support or have lazy-loading mechanisms in place
399     * on your pages, tweaking this setting can have benefits in perceived smoothness
400     * of paging animations and interaction. If you have a small number of pages (3-4)
401     * that you can keep active all at once, less time will be spent in layout for
402     * newly created view subtrees as the user pages back and forth.</p>
403     *
404     * <p>You should keep this limit low, especially if your pages have complex layouts.
405     * This setting defaults to 1.</p>
406     *
407     * @param limit How many pages will be kept offscreen in an idle state.
408     */
409    public void setOffscreenPageLimit(int limit) {
410        if (limit < DEFAULT_OFFSCREEN_PAGES) {
411            Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
412                    DEFAULT_OFFSCREEN_PAGES);
413            limit = DEFAULT_OFFSCREEN_PAGES;
414        }
415        if (limit != mOffscreenPageLimit) {
416            mOffscreenPageLimit = limit;
417            populate();
418        }
419    }
420
421    /**
422     * Set the margin between pages.
423     *
424     * @param marginPixels Distance between adjacent pages in pixels
425     * @see #getPageMargin()
426     * @see #setPageMarginDrawable(Drawable)
427     * @see #setPageMarginDrawable(int)
428     */
429    public void setPageMargin(int marginPixels) {
430        final int oldMargin = mPageMargin;
431        mPageMargin = marginPixels;
432
433        final int width = getWidth();
434        recomputeScrollPosition(width, width, marginPixels, oldMargin);
435
436        requestLayout();
437    }
438
439    /**
440     * Return the margin between pages.
441     *
442     * @return The size of the margin in pixels
443     */
444    public int getPageMargin() {
445        return mPageMargin;
446    }
447
448    /**
449     * Set a drawable that will be used to fill the margin between pages.
450     *
451     * @param d Drawable to display between pages
452     */
453    public void setPageMarginDrawable(Drawable d) {
454        mMarginDrawable = d;
455        if (d != null) refreshDrawableState();
456        setWillNotDraw(d == null);
457        invalidate();
458    }
459
460    /**
461     * Set a drawable that will be used to fill the margin between pages.
462     *
463     * @param resId Resource ID of a drawable to display between pages
464     */
465    public void setPageMarginDrawable(int resId) {
466        setPageMarginDrawable(getContext().getResources().getDrawable(resId));
467    }
468
469    @Override
470    protected boolean verifyDrawable(Drawable who) {
471        return super.verifyDrawable(who) || who == mMarginDrawable;
472    }
473
474    @Override
475    protected void drawableStateChanged() {
476        super.drawableStateChanged();
477        final Drawable d = mMarginDrawable;
478        if (d != null && d.isStateful()) {
479            d.setState(getDrawableState());
480        }
481    }
482
483    // We want the duration of the page snap animation to be influenced by the distance that
484    // the screen has to travel, however, we don't want this duration to be effected in a
485    // purely linear fashion. Instead, we use this method to moderate the effect that the distance
486    // of travel has on the overall snap duration.
487    float distanceInfluenceForSnapDuration(float f) {
488        f -= 0.5f; // center the values about 0.
489        f *= 0.3f * Math.PI / 2.0f;
490        return (float) Math.sin(f);
491    }
492
493    /**
494     * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
495     *
496     * @param x the number of pixels to scroll by on the X axis
497     * @param y the number of pixels to scroll by on the Y axis
498     */
499    void smoothScrollTo(int x, int y) {
500        smoothScrollTo(x, y, 0);
501    }
502
503    /**
504     * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
505     *
506     * @param x the number of pixels to scroll by on the X axis
507     * @param y the number of pixels to scroll by on the Y axis
508     * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
509     */
510    void smoothScrollTo(int x, int y, int velocity) {
511        if (getChildCount() == 0) {
512            // Nothing to do.
513            setScrollingCacheEnabled(false);
514            return;
515        }
516        int sx = getScrollX();
517        int sy = getScrollY();
518        int dx = x - sx;
519        int dy = y - sy;
520        if (dx == 0 && dy == 0) {
521            completeScroll();
522            setScrollState(SCROLL_STATE_IDLE);
523            return;
524        }
525
526        setScrollingCacheEnabled(true);
527        mScrolling = true;
528        setScrollState(SCROLL_STATE_SETTLING);
529
530        final float pageDelta = (float) Math.abs(dx) / (getWidth() + mPageMargin);
531        int duration = (int) (pageDelta * 100);
532
533        velocity = Math.abs(velocity);
534        if (velocity > 0) {
535            duration += (duration / (velocity / mBaseLineFlingVelocity)) * mFlingVelocityInfluence;
536        } else {
537            duration += 100;
538        }
539        duration = Math.min(duration, MAX_SETTLE_DURATION);
540
541        mScroller.startScroll(sx, sy, dx, dy, duration);
542        invalidate();
543    }
544
545    void addNewItem(int position, int index) {
546        ItemInfo ii = new ItemInfo();
547        ii.position = position;
548        ii.object = mAdapter.instantiateItem(this, position);
549        if (index < 0) {
550            mItems.add(ii);
551        } else {
552            mItems.add(index, ii);
553        }
554    }
555
556    void dataSetChanged() {
557        // This method only gets called if our observer is attached, so mAdapter is non-null.
558
559        boolean needPopulate = mItems.size() < 3 && mItems.size() < mAdapter.getCount();
560        int newCurrItem = -1;
561
562        for (int i = 0; i < mItems.size(); i++) {
563            final ItemInfo ii = mItems.get(i);
564            final int newPos = mAdapter.getItemPosition(ii.object);
565
566            if (newPos == PagerAdapter.POSITION_UNCHANGED) {
567                continue;
568            }
569
570            if (newPos == PagerAdapter.POSITION_NONE) {
571                mItems.remove(i);
572                i--;
573                mAdapter.destroyItem(this, ii.position, ii.object);
574                needPopulate = true;
575
576                if (mCurItem == ii.position) {
577                    // Keep the current item in the valid range
578                    newCurrItem = Math.max(0, Math.min(mCurItem, mAdapter.getCount() - 1));
579                }
580                continue;
581            }
582
583            if (ii.position != newPos) {
584                if (ii.position == mCurItem) {
585                    // Our current item changed position. Follow it.
586                    newCurrItem = newPos;
587                }
588
589                ii.position = newPos;
590                needPopulate = true;
591            }
592        }
593
594        Collections.sort(mItems, COMPARATOR);
595
596        if (newCurrItem >= 0) {
597            // TODO This currently causes a jump.
598            setCurrentItemInternal(newCurrItem, false, true);
599            needPopulate = true;
600        }
601        if (needPopulate) {
602            populate();
603            requestLayout();
604        }
605    }
606
607    void populate() {
608        if (mAdapter == null) {
609            return;
610        }
611
612        // Bail now if we are waiting to populate.  This is to hold off
613        // on creating views from the time the user releases their finger to
614        // fling to a new position until we have finished the scroll to
615        // that position, avoiding glitches from happening at that point.
616        if (mPopulatePending) {
617            if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
618            return;
619        }
620
621        // Also, don't populate until we are attached to a window.  This is to
622        // avoid trying to populate before we have restored our view hierarchy
623        // state and conflicting with what is restored.
624        if (getWindowToken() == null) {
625            return;
626        }
627
628        mAdapter.startUpdate(this);
629
630        final int pageLimit = mOffscreenPageLimit;
631        final int startPos = Math.max(0, mCurItem - pageLimit);
632        final int N = mAdapter.getCount();
633        final int endPos = Math.min(N-1, mCurItem + pageLimit);
634
635        if (DEBUG) Log.v(TAG, "populating: startPos=" + startPos + " endPos=" + endPos);
636
637        // Add and remove pages in the existing list.
638        int lastPos = -1;
639        for (int i=0; i<mItems.size(); i++) {
640            ItemInfo ii = mItems.get(i);
641            if ((ii.position < startPos || ii.position > endPos) && !ii.scrolling) {
642                if (DEBUG) Log.i(TAG, "removing: " + ii.position + " @ " + i);
643                mItems.remove(i);
644                i--;
645                mAdapter.destroyItem(this, ii.position, ii.object);
646            } else if (lastPos < endPos && ii.position > startPos) {
647                // The next item is outside of our range, but we have a gap
648                // between it and the last item where we want to have a page
649                // shown.  Fill in the gap.
650                lastPos++;
651                if (lastPos < startPos) {
652                    lastPos = startPos;
653                }
654                while (lastPos <= endPos && lastPos < ii.position) {
655                    if (DEBUG) Log.i(TAG, "inserting: " + lastPos + " @ " + i);
656                    addNewItem(lastPos, i);
657                    lastPos++;
658                    i++;
659                }
660            }
661            lastPos = ii.position;
662        }
663
664        // Add any new pages we need at the end.
665        lastPos = mItems.size() > 0 ? mItems.get(mItems.size()-1).position : -1;
666        if (lastPos < endPos) {
667            lastPos++;
668            lastPos = lastPos > startPos ? lastPos : startPos;
669            while (lastPos <= endPos) {
670                if (DEBUG) Log.i(TAG, "appending: " + lastPos);
671                addNewItem(lastPos, -1);
672                lastPos++;
673            }
674        }
675
676        if (DEBUG) {
677            Log.i(TAG, "Current page list:");
678            for (int i=0; i<mItems.size(); i++) {
679                Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
680            }
681        }
682
683        ItemInfo curItem = null;
684        for (int i=0; i<mItems.size(); i++) {
685            if (mItems.get(i).position == mCurItem) {
686                curItem = mItems.get(i);
687                break;
688            }
689        }
690        mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
691
692        mAdapter.finishUpdate(this);
693
694        if (hasFocus()) {
695            View currentFocused = findFocus();
696            ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
697            if (ii == null || ii.position != mCurItem) {
698                for (int i=0; i<getChildCount(); i++) {
699                    View child = getChildAt(i);
700                    ii = infoForChild(child);
701                    if (ii != null && ii.position == mCurItem) {
702                        if (child.requestFocus(FOCUS_FORWARD)) {
703                            break;
704                        }
705                    }
706                }
707            }
708        }
709    }
710
711    public static class SavedState extends BaseSavedState {
712        int position;
713        Parcelable adapterState;
714        ClassLoader loader;
715
716        public SavedState(Parcelable superState) {
717            super(superState);
718        }
719
720        @Override
721        public void writeToParcel(Parcel out, int flags) {
722            super.writeToParcel(out, flags);
723            out.writeInt(position);
724            out.writeParcelable(adapterState, flags);
725        }
726
727        @Override
728        public String toString() {
729            return "FragmentPager.SavedState{"
730                    + Integer.toHexString(System.identityHashCode(this))
731                    + " position=" + position + "}";
732        }
733
734        public static final Parcelable.Creator<SavedState> CREATOR
735                = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
736                    @Override
737                    public SavedState createFromParcel(Parcel in, ClassLoader loader) {
738                        return new SavedState(in, loader);
739                    }
740                    @Override
741                    public SavedState[] newArray(int size) {
742                        return new SavedState[size];
743                    }
744                });
745
746        SavedState(Parcel in, ClassLoader loader) {
747            super(in);
748            if (loader == null) {
749                loader = getClass().getClassLoader();
750            }
751            position = in.readInt();
752            adapterState = in.readParcelable(loader);
753            this.loader = loader;
754        }
755    }
756
757    @Override
758    public Parcelable onSaveInstanceState() {
759        Parcelable superState = super.onSaveInstanceState();
760        SavedState ss = new SavedState(superState);
761        ss.position = mCurItem;
762        if (mAdapter != null) {
763            ss.adapterState = mAdapter.saveState();
764        }
765        return ss;
766    }
767
768    @Override
769    public void onRestoreInstanceState(Parcelable state) {
770        if (!(state instanceof SavedState)) {
771            super.onRestoreInstanceState(state);
772            return;
773        }
774
775        SavedState ss = (SavedState)state;
776        super.onRestoreInstanceState(ss.getSuperState());
777
778        if (mAdapter != null) {
779            mAdapter.restoreState(ss.adapterState, ss.loader);
780            setCurrentItemInternal(ss.position, false, true);
781        } else {
782            mRestoredCurItem = ss.position;
783            mRestoredAdapterState = ss.adapterState;
784            mRestoredClassLoader = ss.loader;
785        }
786    }
787
788    @Override
789    public void addView(View child, int index, LayoutParams params) {
790        if (mInLayout) {
791            addViewInLayout(child, index, params);
792            child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
793        } else {
794            super.addView(child, index, params);
795        }
796
797        if (USE_CACHE) {
798            if (child.getVisibility() != GONE) {
799                child.setDrawingCacheEnabled(mScrollingCacheEnabled);
800            } else {
801                child.setDrawingCacheEnabled(false);
802            }
803        }
804    }
805
806    ItemInfo infoForChild(View child) {
807        for (int i=0; i<mItems.size(); i++) {
808            ItemInfo ii = mItems.get(i);
809            if (mAdapter.isViewFromObject(child, ii.object)) {
810                return ii;
811            }
812        }
813        return null;
814    }
815
816    ItemInfo infoForAnyChild(View child) {
817        ViewParent parent;
818        while ((parent=child.getParent()) != this) {
819            if (parent == null || !(parent instanceof View)) {
820                return null;
821            }
822            child = (View)parent;
823        }
824        return infoForChild(child);
825    }
826
827    @Override
828    protected void onAttachedToWindow() {
829        super.onAttachedToWindow();
830        mFirstLayout = true;
831    }
832
833    @Override
834    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
835        // For simple implementation, or internal size is always 0.
836        // We depend on the container to specify the layout size of
837        // our view.  We can't really know what it is since we will be
838        // adding and removing different arbitrary views and do not
839        // want the layout to change as this happens.
840        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
841                getDefaultSize(0, heightMeasureSpec));
842
843        // Children are just made to fill our space.
844        mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
845                getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY);
846        mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
847                getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY);
848
849        // Make sure we have created all fragments that we need to have shown.
850        mInLayout = true;
851        populate();
852        mInLayout = false;
853
854        // Make sure all children have been properly measured.
855        final int size = getChildCount();
856        for (int i = 0; i < size; ++i) {
857            final View child = getChildAt(i);
858            if (child.getVisibility() != GONE) {
859                if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
860		        + ": " + mChildWidthMeasureSpec);
861                child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
862            }
863        }
864    }
865
866    @Override
867    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
868        super.onSizeChanged(w, h, oldw, oldh);
869
870        // Make sure scroll position is set correctly.
871        if (w != oldw) {
872            recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin);
873        }
874    }
875
876    private void recomputeScrollPosition(int width, int oldWidth, int margin, int oldMargin) {
877        final int widthWithMargin = width + margin;
878        if (oldWidth > 0) {
879            final int oldScrollPos = getScrollX();
880            final int oldwwm = oldWidth + oldMargin;
881            final int oldScrollItem = oldScrollPos / oldwwm;
882            final float scrollOffset = (float) (oldScrollPos % oldwwm) / oldwwm;
883            final int scrollPos = (int) ((oldScrollItem + scrollOffset) * widthWithMargin);
884            scrollTo(scrollPos, getScrollY());
885            if (!mScroller.isFinished()) {
886                // We now return to your regularly scheduled scroll, already in progress.
887                final int newDuration = mScroller.getDuration() - mScroller.timePassed();
888                mScroller.startScroll(scrollPos, 0, mCurItem * widthWithMargin, 0, newDuration);
889            }
890        } else {
891            int scrollPos = mCurItem * widthWithMargin;
892            if (scrollPos != getScrollX()) {
893                completeScroll();
894                scrollTo(scrollPos, getScrollY());
895            }
896        }
897    }
898
899    @Override
900    protected void onLayout(boolean changed, int l, int t, int r, int b) {
901        mInLayout = true;
902        populate();
903        mInLayout = false;
904
905        final int count = getChildCount();
906        final int width = r-l;
907
908        for (int i = 0; i < count; i++) {
909            View child = getChildAt(i);
910            ItemInfo ii;
911            if (child.getVisibility() != GONE && (ii=infoForChild(child)) != null) {
912                int loff = (width + mPageMargin) * ii.position;
913                int childLeft = getPaddingLeft() + loff;
914                int childTop = getPaddingTop();
915                if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
916		        + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
917		        + "x" + child.getMeasuredHeight());
918                child.layout(childLeft, childTop,
919                        childLeft + child.getMeasuredWidth(),
920                        childTop + child.getMeasuredHeight());
921            }
922        }
923        mFirstLayout = false;
924    }
925
926    @Override
927    public void computeScroll() {
928        if (DEBUG) Log.i(TAG, "computeScroll: finished=" + mScroller.isFinished());
929        if (!mScroller.isFinished()) {
930            if (mScroller.computeScrollOffset()) {
931                if (DEBUG) Log.i(TAG, "computeScroll: still scrolling");
932                int oldX = getScrollX();
933                int oldY = getScrollY();
934                int x = mScroller.getCurrX();
935                int y = mScroller.getCurrY();
936
937                if (oldX != x || oldY != y) {
938                    scrollTo(x, y);
939                }
940
941                if (mOnPageChangeListener != null) {
942                    final int widthWithMargin = getWidth() + mPageMargin;
943                    final int position = x / widthWithMargin;
944                    final int offsetPixels = x % widthWithMargin;
945                    final float offset = (float) offsetPixels / widthWithMargin;
946                    mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
947                }
948
949                // Keep on drawing until the animation has finished.
950                invalidate();
951                return;
952            }
953        }
954
955        // Done with scroll, clean up state.
956        completeScroll();
957    }
958
959    private void completeScroll() {
960        boolean needPopulate = mScrolling;
961        if (needPopulate) {
962            // Done with scroll, no longer want to cache view drawing.
963            setScrollingCacheEnabled(false);
964            mScroller.abortAnimation();
965            int oldX = getScrollX();
966            int oldY = getScrollY();
967            int x = mScroller.getCurrX();
968            int y = mScroller.getCurrY();
969            if (oldX != x || oldY != y) {
970                scrollTo(x, y);
971            }
972            setScrollState(SCROLL_STATE_IDLE);
973        }
974        mPopulatePending = false;
975        mScrolling = false;
976        for (int i=0; i<mItems.size(); i++) {
977            ItemInfo ii = mItems.get(i);
978            if (ii.scrolling) {
979                needPopulate = true;
980                ii.scrolling = false;
981            }
982        }
983        if (needPopulate) {
984            populate();
985        }
986    }
987
988    @Override
989    public boolean onInterceptTouchEvent(MotionEvent ev) {
990        /*
991         * This method JUST determines whether we want to intercept the motion.
992         * If we return true, onMotionEvent will be called and we do the actual
993         * scrolling there.
994         */
995
996        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
997
998        // Always take care of the touch gesture being complete.
999        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
1000            // Release the drag.
1001            if (DEBUG) Log.v(TAG, "Intercept done!");
1002            mIsBeingDragged = false;
1003            mIsUnableToDrag = false;
1004            mActivePointerId = INVALID_POINTER;
1005            return false;
1006        }
1007
1008        // Nothing more to do here if we have decided whether or not we
1009        // are dragging.
1010        if (action != MotionEvent.ACTION_DOWN) {
1011            if (mIsBeingDragged) {
1012                if (DEBUG) Log.v(TAG, "Intercept returning true!");
1013                return true;
1014            }
1015            if (mIsUnableToDrag) {
1016                if (DEBUG) Log.v(TAG, "Intercept returning false!");
1017                return false;
1018            }
1019        }
1020
1021        switch (action) {
1022            case MotionEvent.ACTION_MOVE: {
1023                /*
1024                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1025                 * whether the user has moved far enough from his original down touch.
1026                 */
1027
1028                /*
1029                * Locally do absolute value. mLastMotionY is set to the y value
1030                * of the down event.
1031                */
1032                final int activePointerId = mActivePointerId;
1033                if (activePointerId == INVALID_POINTER) {
1034                    // If we don't have a valid id, the touch down wasn't on content.
1035                    break;
1036                }
1037
1038                final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
1039                final float x = MotionEventCompat.getX(ev, pointerIndex);
1040                final float dx = x - mLastMotionX;
1041                final float xDiff = Math.abs(dx);
1042                final float y = MotionEventCompat.getY(ev, pointerIndex);
1043                final float yDiff = Math.abs(y - mLastMotionY);
1044                final int scrollX = getScrollX();
1045                final boolean atEdge = (dx > 0 && scrollX == 0) || (dx < 0 && mAdapter != null &&
1046                        scrollX >= (mAdapter.getCount() - 1) * getWidth() - 1);
1047                if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
1048
1049                if (canScroll(this, false, (int) dx, (int) x, (int) y)) {
1050                    // Nested view has scrollable area under this point. Let it be handled there.
1051                    mInitialMotionX = mLastMotionX = x;
1052                    mLastMotionY = y;
1053                    return false;
1054                }
1055                if (xDiff > mTouchSlop && xDiff > yDiff) {
1056                    if (DEBUG) Log.v(TAG, "Starting drag!");
1057                    mIsBeingDragged = true;
1058                    setScrollState(SCROLL_STATE_DRAGGING);
1059                    mLastMotionX = x;
1060                    setScrollingCacheEnabled(true);
1061                } else {
1062                    if (yDiff > mTouchSlop) {
1063                        // The finger has moved enough in the vertical
1064                        // direction to be counted as a drag...  abort
1065                        // any attempt to drag horizontally, to work correctly
1066                        // with children that have scrolling containers.
1067                        if (DEBUG) Log.v(TAG, "Starting unable to drag!");
1068                        mIsUnableToDrag = true;
1069                    }
1070                }
1071                break;
1072            }
1073
1074            case MotionEvent.ACTION_DOWN: {
1075                /*
1076                 * Remember location of down touch.
1077                 * ACTION_DOWN always refers to pointer index 0.
1078                 */
1079                mLastMotionX = mInitialMotionX = ev.getX();
1080                mLastMotionY = ev.getY();
1081                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
1082
1083                if (mScrollState == SCROLL_STATE_SETTLING) {
1084                    // Let the user 'catch' the pager as it animates.
1085                    mIsBeingDragged = true;
1086                    mIsUnableToDrag = false;
1087                    setScrollState(SCROLL_STATE_DRAGGING);
1088                } else {
1089                    completeScroll();
1090                    mIsBeingDragged = false;
1091                    mIsUnableToDrag = false;
1092                }
1093
1094                if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
1095                        + " mIsBeingDragged=" + mIsBeingDragged
1096                        + "mIsUnableToDrag=" + mIsUnableToDrag);
1097                break;
1098            }
1099
1100            case MotionEventCompat.ACTION_POINTER_UP:
1101                onSecondaryPointerUp(ev);
1102                break;
1103        }
1104
1105        /*
1106        * The only time we want to intercept motion events is if we are in the
1107        * drag mode.
1108        */
1109        return mIsBeingDragged;
1110    }
1111
1112    @Override
1113    public boolean onTouchEvent(MotionEvent ev) {
1114        if (mFakeDragging) {
1115            // A fake drag is in progress already, ignore this real one
1116            // but still eat the touch events.
1117            // (It is likely that the user is multi-touching the screen.)
1118            return true;
1119        }
1120
1121        if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
1122            // Don't handle edge touches immediately -- they may actually belong to one of our
1123            // descendants.
1124            return false;
1125        }
1126
1127        if (mAdapter == null || mAdapter.getCount() == 0) {
1128            // Nothing to present or scroll; nothing to touch.
1129            return false;
1130        }
1131
1132        if (mVelocityTracker == null) {
1133            mVelocityTracker = VelocityTracker.obtain();
1134        }
1135        mVelocityTracker.addMovement(ev);
1136
1137        final int action = ev.getAction();
1138        boolean needsInvalidate = false;
1139
1140        switch (action & MotionEventCompat.ACTION_MASK) {
1141            case MotionEvent.ACTION_DOWN: {
1142                /*
1143                 * If being flinged and user touches, stop the fling. isFinished
1144                 * will be false if being flinged.
1145                 */
1146                completeScroll();
1147
1148                // Remember where the motion event started
1149                mLastMotionX = mInitialMotionX = ev.getX();
1150                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
1151                break;
1152            }
1153            case MotionEvent.ACTION_MOVE:
1154                if (!mIsBeingDragged) {
1155                    final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
1156                    final float x = MotionEventCompat.getX(ev, pointerIndex);
1157                    final float xDiff = Math.abs(x - mLastMotionX);
1158                    final float y = MotionEventCompat.getY(ev, pointerIndex);
1159                    final float yDiff = Math.abs(y - mLastMotionY);
1160                    if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
1161                    if (xDiff > mTouchSlop && xDiff > yDiff) {
1162                        if (DEBUG) Log.v(TAG, "Starting drag!");
1163                        mIsBeingDragged = true;
1164                        mLastMotionX = x;
1165                        setScrollState(SCROLL_STATE_DRAGGING);
1166                        setScrollingCacheEnabled(true);
1167                    }
1168                }
1169                if (mIsBeingDragged) {
1170                    // Scroll to follow the motion event
1171                    final int activePointerIndex = MotionEventCompat.findPointerIndex(
1172                            ev, mActivePointerId);
1173                    final float x = MotionEventCompat.getX(ev, activePointerIndex);
1174                    final float deltaX = mLastMotionX - x;
1175                    mLastMotionX = x;
1176                    float oldScrollX = getScrollX();
1177                    float scrollX = oldScrollX + deltaX;
1178                    final int width = getWidth();
1179                    final int widthWithMargin = width + mPageMargin;
1180
1181                    final int lastItemIndex = mAdapter.getCount() - 1;
1182                    final float leftBound = Math.max(0, (mCurItem - 1) * widthWithMargin);
1183                    final float rightBound =
1184                            Math.min(mCurItem + 1, lastItemIndex) * widthWithMargin;
1185                    if (scrollX < leftBound) {
1186                        if (leftBound == 0) {
1187                            float over = -scrollX;
1188                            needsInvalidate = mLeftEdge.onPull(over / width);
1189                        }
1190                        scrollX = leftBound;
1191                    } else if (scrollX > rightBound) {
1192                        if (rightBound == lastItemIndex * widthWithMargin) {
1193                            float over = scrollX - rightBound;
1194                            needsInvalidate = mRightEdge.onPull(over / width);
1195                        }
1196                        scrollX = rightBound;
1197                    }
1198                    // Don't lose the rounded component
1199                    mLastMotionX += scrollX - (int) scrollX;
1200                    scrollTo((int) scrollX, getScrollY());
1201                    if (mOnPageChangeListener != null) {
1202                        final int position = (int) scrollX / widthWithMargin;
1203                        final int positionOffsetPixels = (int) scrollX % widthWithMargin;
1204                        final float positionOffset = (float) positionOffsetPixels / widthWithMargin;
1205                        mOnPageChangeListener.onPageScrolled(position, positionOffset,
1206                                positionOffsetPixels);
1207                    }
1208                }
1209                break;
1210            case MotionEvent.ACTION_UP:
1211                if (mIsBeingDragged) {
1212                    final VelocityTracker velocityTracker = mVelocityTracker;
1213                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1214                    int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
1215                            velocityTracker, mActivePointerId);
1216                    mPopulatePending = true;
1217                    final int widthWithMargin = getWidth() + mPageMargin;
1218                    final int scrollX = getScrollX();
1219                    final int currentPage = scrollX / widthWithMargin;
1220                    int nextPage = initialVelocity > 0 ? currentPage : currentPage + 1;
1221                    setCurrentItemInternal(nextPage, true, true, initialVelocity);
1222
1223                    mActivePointerId = INVALID_POINTER;
1224                    endDrag();
1225                    needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
1226                }
1227                break;
1228            case MotionEvent.ACTION_CANCEL:
1229                if (mIsBeingDragged) {
1230                    setCurrentItemInternal(mCurItem, true, true);
1231                    mActivePointerId = INVALID_POINTER;
1232                    endDrag();
1233                    needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
1234                }
1235                break;
1236            case MotionEventCompat.ACTION_POINTER_DOWN: {
1237                final int index = MotionEventCompat.getActionIndex(ev);
1238                final float x = MotionEventCompat.getX(ev, index);
1239                mLastMotionX = x;
1240                mActivePointerId = MotionEventCompat.getPointerId(ev, index);
1241                break;
1242            }
1243            case MotionEventCompat.ACTION_POINTER_UP:
1244                onSecondaryPointerUp(ev);
1245                mLastMotionX = MotionEventCompat.getX(ev,
1246                        MotionEventCompat.findPointerIndex(ev, mActivePointerId));
1247                break;
1248        }
1249        if (needsInvalidate) {
1250            invalidate();
1251        }
1252        return true;
1253    }
1254
1255    @Override
1256    public void draw(Canvas canvas) {
1257        super.draw(canvas);
1258        boolean needsInvalidate = false;
1259
1260        final int overScrollMode = ViewCompat.getOverScrollMode(this);
1261        if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
1262                (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&
1263                        mAdapter != null && mAdapter.getCount() > 1)) {
1264            if (!mLeftEdge.isFinished()) {
1265                final int restoreCount = canvas.save();
1266                final int height = getHeight() - getPaddingTop() - getPaddingBottom();
1267
1268                canvas.rotate(270);
1269                canvas.translate(-height + getPaddingTop(), 0);
1270                mLeftEdge.setSize(height, getWidth());
1271                needsInvalidate |= mLeftEdge.draw(canvas);
1272                canvas.restoreToCount(restoreCount);
1273            }
1274            if (!mRightEdge.isFinished()) {
1275                final int restoreCount = canvas.save();
1276                final int width = getWidth();
1277                final int height = getHeight() - getPaddingTop() - getPaddingBottom();
1278                final int itemCount = mAdapter != null ? mAdapter.getCount() : 1;
1279
1280                canvas.rotate(90);
1281                canvas.translate(-getPaddingTop(),
1282                        -itemCount * (width + mPageMargin) + mPageMargin);
1283                mRightEdge.setSize(height, width);
1284                needsInvalidate |= mRightEdge.draw(canvas);
1285                canvas.restoreToCount(restoreCount);
1286            }
1287        } else {
1288            mLeftEdge.finish();
1289            mRightEdge.finish();
1290        }
1291
1292        if (needsInvalidate) {
1293            // Keep animating
1294            invalidate();
1295        }
1296    }
1297
1298    @Override
1299    protected void onDraw(Canvas canvas) {
1300        super.onDraw(canvas);
1301
1302        // Draw the margin drawable if needed.
1303        if (mPageMargin > 0 && mMarginDrawable != null) {
1304            final int scrollX = getScrollX();
1305            final int width = getWidth();
1306            final int offset = scrollX % (width + mPageMargin);
1307            if (offset != 0) {
1308                // Pages fit completely when settled; we only need to draw when in between
1309                final int left = scrollX - offset + width;
1310                mMarginDrawable.setBounds(left, 0, left + mPageMargin, getHeight());
1311                mMarginDrawable.draw(canvas);
1312            }
1313        }
1314    }
1315
1316    /**
1317     * Start a fake drag of the pager.
1318     *
1319     * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
1320     * with the touch scrolling of another view, while still letting the ViewPager
1321     * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
1322     * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
1323     * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
1324     *
1325     * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
1326     * is already in progress, this method will return false.
1327     *
1328     * @return true if the fake drag began successfully, false if it could not be started.
1329     *
1330     * @see #fakeDragBy(float)
1331     * @see #endFakeDrag()
1332     */
1333    public boolean beginFakeDrag() {
1334        if (mIsBeingDragged) {
1335            return false;
1336        }
1337        mFakeDragging = true;
1338        setScrollState(SCROLL_STATE_DRAGGING);
1339        mInitialMotionX = mLastMotionX = 0;
1340        if (mVelocityTracker == null) {
1341            mVelocityTracker = VelocityTracker.obtain();
1342        } else {
1343            mVelocityTracker.clear();
1344        }
1345        final long time = SystemClock.uptimeMillis();
1346        final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
1347        mVelocityTracker.addMovement(ev);
1348        ev.recycle();
1349        mFakeDragBeginTime = time;
1350        return true;
1351    }
1352
1353    /**
1354     * End a fake drag of the pager.
1355     *
1356     * @see #beginFakeDrag()
1357     * @see #fakeDragBy(float)
1358     */
1359    public void endFakeDrag() {
1360        if (!mFakeDragging) {
1361            throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
1362        }
1363
1364        final VelocityTracker velocityTracker = mVelocityTracker;
1365        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1366        int initialVelocity = (int)VelocityTrackerCompat.getYVelocity(
1367                velocityTracker, mActivePointerId);
1368        mPopulatePending = true;
1369        if ((Math.abs(initialVelocity) > mMinimumVelocity)
1370                || Math.abs(mInitialMotionX-mLastMotionX) >= (getWidth()/3)) {
1371            if (mLastMotionX > mInitialMotionX) {
1372                setCurrentItemInternal(mCurItem-1, true, true);
1373            } else {
1374                setCurrentItemInternal(mCurItem+1, true, true);
1375            }
1376        } else {
1377            setCurrentItemInternal(mCurItem, true, true);
1378        }
1379        endDrag();
1380
1381        mFakeDragging = false;
1382    }
1383
1384    /**
1385     * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
1386     *
1387     * @param xOffset Offset in pixels to drag by.
1388     * @see #beginFakeDrag()
1389     * @see #endFakeDrag()
1390     */
1391    public void fakeDragBy(float xOffset) {
1392        if (!mFakeDragging) {
1393            throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
1394        }
1395
1396        mLastMotionX += xOffset;
1397        float scrollX = getScrollX() - xOffset;
1398        final int width = getWidth();
1399        final int widthWithMargin = width + mPageMargin;
1400
1401        final float leftBound = Math.max(0, (mCurItem - 1) * widthWithMargin);
1402        final float rightBound =
1403                Math.min(mCurItem + 1, mAdapter.getCount() - 1) * widthWithMargin;
1404        if (scrollX < leftBound) {
1405            scrollX = leftBound;
1406        } else if (scrollX > rightBound) {
1407            scrollX = rightBound;
1408        }
1409        // Don't lose the rounded component
1410        mLastMotionX += scrollX - (int) scrollX;
1411        scrollTo((int) scrollX, getScrollY());
1412        if (mOnPageChangeListener != null) {
1413            final int position = (int) scrollX / widthWithMargin;
1414            final int positionOffsetPixels = (int) scrollX % widthWithMargin;
1415            final float positionOffset = (float) positionOffsetPixels / widthWithMargin;
1416            mOnPageChangeListener.onPageScrolled(position, positionOffset,
1417                    positionOffsetPixels);
1418        }
1419
1420        // Synthesize an event for the VelocityTracker.
1421        final long time = SystemClock.uptimeMillis();
1422        final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
1423                mLastMotionX, 0, 0);
1424        mVelocityTracker.addMovement(ev);
1425        ev.recycle();
1426    }
1427
1428    /**
1429     * Returns true if a fake drag is in progress.
1430     *
1431     * @return true if currently in a fake drag, false otherwise.
1432     *
1433     * @see #beginFakeDrag()
1434     * @see #fakeDragBy(float)
1435     * @see #endFakeDrag()
1436     */
1437    public boolean isFakeDragging() {
1438        return mFakeDragging;
1439    }
1440
1441    private void onSecondaryPointerUp(MotionEvent ev) {
1442        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
1443        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
1444        if (pointerId == mActivePointerId) {
1445            // This was our active pointer going up. Choose a new
1446            // active pointer and adjust accordingly.
1447            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1448            mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);
1449            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
1450            if (mVelocityTracker != null) {
1451                mVelocityTracker.clear();
1452            }
1453        }
1454    }
1455
1456    private void endDrag() {
1457        mIsBeingDragged = false;
1458        mIsUnableToDrag = false;
1459
1460        if (mVelocityTracker != null) {
1461            mVelocityTracker.recycle();
1462            mVelocityTracker = null;
1463        }
1464    }
1465
1466    private void setScrollingCacheEnabled(boolean enabled) {
1467        if (mScrollingCacheEnabled != enabled) {
1468            mScrollingCacheEnabled = enabled;
1469            if (USE_CACHE) {
1470                final int size = getChildCount();
1471                for (int i = 0; i < size; ++i) {
1472                    final View child = getChildAt(i);
1473                    if (child.getVisibility() != GONE) {
1474                        child.setDrawingCacheEnabled(enabled);
1475                    }
1476                }
1477            }
1478        }
1479    }
1480
1481    /**
1482     * Tests scrollability within child views of v given a delta of dx.
1483     *
1484     * @param v View to test for horizontal scrollability
1485     * @param checkV Whether the view v passed should itself be checked for scrollability (true),
1486     *               or just its children (false).
1487     * @param dx Delta scrolled in pixels
1488     * @param x X coordinate of the active touch point
1489     * @param y Y coordinate of the active touch point
1490     * @return true if child views of v can be scrolled by delta of dx.
1491     */
1492    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
1493        if (v instanceof ViewGroup) {
1494            final ViewGroup group = (ViewGroup) v;
1495            final int scrollX = v.getScrollX();
1496            final int scrollY = v.getScrollY();
1497            final int count = group.getChildCount();
1498            // Count backwards - let topmost views consume scroll distance first.
1499            for (int i = count - 1; i >= 0; i--) {
1500                // TODO: Add versioned support here for transformed views.
1501                // This will not work for transformed views in Honeycomb+
1502                final View child = group.getChildAt(i);
1503                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
1504                        y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
1505                        canScroll(child, true, dx, x + scrollX - child.getLeft(),
1506                                y + scrollY - child.getTop())) {
1507                    return true;
1508                }
1509            }
1510        }
1511
1512        return checkV && ViewCompat.canScrollHorizontally(v, -dx);
1513    }
1514
1515    @Override
1516    public boolean dispatchKeyEvent(KeyEvent event) {
1517        // Let the focused view and/or our descendants get the key first
1518        return super.dispatchKeyEvent(event) || executeKeyEvent(event);
1519    }
1520
1521    /**
1522     * You can call this function yourself to have the scroll view perform
1523     * scrolling from a key event, just as if the event had been dispatched to
1524     * it by the view hierarchy.
1525     *
1526     * @param event The key event to execute.
1527     * @return Return true if the event was handled, else false.
1528     */
1529    public boolean executeKeyEvent(KeyEvent event) {
1530        boolean handled = false;
1531        if (event.getAction() == KeyEvent.ACTION_DOWN) {
1532            switch (event.getKeyCode()) {
1533                case KeyEvent.KEYCODE_DPAD_LEFT:
1534                    handled = arrowScroll(FOCUS_LEFT);
1535                    break;
1536                case KeyEvent.KEYCODE_DPAD_RIGHT:
1537                    handled = arrowScroll(FOCUS_RIGHT);
1538                    break;
1539                case KeyEvent.KEYCODE_TAB:
1540                    if (KeyEventCompat.hasNoModifiers(event)) {
1541                        handled = arrowScroll(FOCUS_FORWARD);
1542                    } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
1543                        handled = arrowScroll(FOCUS_BACKWARD);
1544                    }
1545                    break;
1546            }
1547        }
1548        return handled;
1549    }
1550
1551    public boolean arrowScroll(int direction) {
1552        View currentFocused = findFocus();
1553        if (currentFocused == this) currentFocused = null;
1554
1555        boolean handled = false;
1556
1557        View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
1558                direction);
1559        if (nextFocused != null && nextFocused != currentFocused) {
1560            if (direction == View.FOCUS_LEFT) {
1561                // If there is nothing to the left, or this is causing us to
1562                // jump to the right, then what we really want to do is page left.
1563                if (currentFocused != null && nextFocused.getLeft() >= currentFocused.getLeft()) {
1564                    handled = pageLeft();
1565                } else {
1566                    handled = nextFocused.requestFocus();
1567                }
1568            } else if (direction == View.FOCUS_RIGHT) {
1569                // If there is nothing to the right, or this is causing us to
1570                // jump to the left, then what we really want to do is page right.
1571                if (currentFocused != null && nextFocused.getLeft() <= currentFocused.getLeft()) {
1572                    handled = pageRight();
1573                } else {
1574                    handled = nextFocused.requestFocus();
1575                }
1576            }
1577        } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {
1578            // Trying to move left and nothing there; try to page.
1579            handled = pageLeft();
1580        } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {
1581            // Trying to move right and nothing there; try to page.
1582            handled = pageRight();
1583        }
1584        if (handled) {
1585            playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
1586        }
1587        return handled;
1588    }
1589
1590    boolean pageLeft() {
1591        if (mCurItem > 0) {
1592            setCurrentItem(mCurItem-1, true);
1593            return true;
1594        }
1595        return false;
1596    }
1597
1598    boolean pageRight() {
1599        if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) {
1600            setCurrentItem(mCurItem+1, true);
1601            return true;
1602        }
1603        return false;
1604    }
1605
1606    /**
1607     * We only want the current page that is being shown to be focusable.
1608     */
1609    @Override
1610    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1611        final int focusableCount = views.size();
1612
1613        final int descendantFocusability = getDescendantFocusability();
1614
1615        if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
1616            for (int i = 0; i < getChildCount(); i++) {
1617                final View child = getChildAt(i);
1618                if (child.getVisibility() == VISIBLE) {
1619                    ItemInfo ii = infoForChild(child);
1620                    if (ii != null && ii.position == mCurItem) {
1621                        child.addFocusables(views, direction, focusableMode);
1622                    }
1623                }
1624            }
1625        }
1626
1627        // we add ourselves (if focusable) in all cases except for when we are
1628        // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.  this is
1629        // to avoid the focus search finding layouts when a more precise search
1630        // among the focusable children would be more interesting.
1631        if (
1632            descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
1633                // No focusable descendants
1634                (focusableCount == views.size())) {
1635            // Note that we can't call the superclass here, because it will
1636            // add all views in.  So we need to do the same thing View does.
1637            if (!isFocusable()) {
1638                return;
1639            }
1640            if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
1641                    isInTouchMode() && !isFocusableInTouchMode()) {
1642                return;
1643            }
1644            if (views != null) {
1645                views.add(this);
1646            }
1647        }
1648    }
1649
1650    /**
1651     * We only want the current page that is being shown to be touchable.
1652     */
1653    @Override
1654    public void addTouchables(ArrayList<View> views) {
1655        // Note that we don't call super.addTouchables(), which means that
1656        // we don't call View.addTouchables().  This is okay because a ViewPager
1657        // is itself not touchable.
1658        for (int i = 0; i < getChildCount(); i++) {
1659            final View child = getChildAt(i);
1660            if (child.getVisibility() == VISIBLE) {
1661                ItemInfo ii = infoForChild(child);
1662                if (ii != null && ii.position == mCurItem) {
1663                    child.addTouchables(views);
1664                }
1665            }
1666        }
1667    }
1668
1669    /**
1670     * We only want the current page that is being shown to be focusable.
1671     */
1672    @Override
1673    protected boolean onRequestFocusInDescendants(int direction,
1674            Rect previouslyFocusedRect) {
1675        int index;
1676        int increment;
1677        int end;
1678        int count = getChildCount();
1679        if ((direction & FOCUS_FORWARD) != 0) {
1680            index = 0;
1681            increment = 1;
1682            end = count;
1683        } else {
1684            index = count - 1;
1685            increment = -1;
1686            end = -1;
1687        }
1688        for (int i = index; i != end; i += increment) {
1689            View child = getChildAt(i);
1690            if (child.getVisibility() == VISIBLE) {
1691                ItemInfo ii = infoForChild(child);
1692                if (ii != null && ii.position == mCurItem) {
1693                    if (child.requestFocus(direction, previouslyFocusedRect)) {
1694                        return true;
1695                    }
1696                }
1697            }
1698        }
1699        return false;
1700    }
1701
1702    @Override
1703    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
1704        // ViewPagers should only report accessibility info for the current page,
1705        // otherwise things get very confusing.
1706
1707        // TODO: Should this note something about the paging container?
1708
1709        final int childCount = getChildCount();
1710        for (int i = 0; i < childCount; i++) {
1711            final View child = getChildAt(i);
1712            if (child.getVisibility() == VISIBLE) {
1713                final ItemInfo ii = infoForChild(child);
1714                if (ii != null && ii.position == mCurItem &&
1715                        child.dispatchPopulateAccessibilityEvent(event)) {
1716                    return true;
1717                }
1718            }
1719        }
1720
1721        return false;
1722    }
1723
1724    private class DataSetObserver implements PagerAdapter.DataSetObserver {
1725        @Override
1726        public void onDataSetChanged() {
1727            dataSetChanged();
1728        }
1729    }
1730}
1731