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