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