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