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