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