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