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