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