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