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