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