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