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