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