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