ViewPager.java revision 71d54737b6d60ec99799aabdf9b48d2da26d8ccb
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    ItemInfo infoForChild(View child) {
1256        for (int i=0; i<mItems.size(); i++) {
1257            ItemInfo ii = mItems.get(i);
1258            if (mAdapter.isViewFromObject(child, ii.object)) {
1259                return ii;
1260            }
1261        }
1262        return null;
1263    }
1264
1265    ItemInfo infoForAnyChild(View child) {
1266        ViewParent parent;
1267        while ((parent=child.getParent()) != this) {
1268            if (parent == null || !(parent instanceof View)) {
1269                return null;
1270            }
1271            child = (View)parent;
1272        }
1273        return infoForChild(child);
1274    }
1275
1276    ItemInfo infoForPosition(int position) {
1277        for (int i = 0; i < mItems.size(); i++) {
1278            ItemInfo ii = mItems.get(i);
1279            if (ii.position == position) {
1280                return ii;
1281            }
1282        }
1283        return null;
1284    }
1285
1286    @Override
1287    protected void onAttachedToWindow() {
1288        super.onAttachedToWindow();
1289        mFirstLayout = true;
1290    }
1291
1292    @Override
1293    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1294        // For simple implementation, or internal size is always 0.
1295        // We depend on the container to specify the layout size of
1296        // our view.  We can't really know what it is since we will be
1297        // adding and removing different arbitrary views and do not
1298        // want the layout to change as this happens.
1299        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
1300                getDefaultSize(0, heightMeasureSpec));
1301
1302        final int measuredWidth = getMeasuredWidth();
1303        final int maxGutterSize = measuredWidth / 10;
1304        mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
1305
1306        // Children are just made to fill our space.
1307        int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
1308        int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
1309
1310        /*
1311         * Make sure all children have been properly measured. Decor views first.
1312         * Right now we cheat and make this less complicated by assuming decor
1313         * views won't intersect. We will pin to edges based on gravity.
1314         */
1315        int size = getChildCount();
1316        for (int i = 0; i < size; ++i) {
1317            final View child = getChildAt(i);
1318            if (child.getVisibility() != GONE) {
1319                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1320                if (lp != null && lp.isDecor) {
1321                    final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
1322                    final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
1323                    int widthMode = MeasureSpec.AT_MOST;
1324                    int heightMode = MeasureSpec.AT_MOST;
1325                    boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
1326                    boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;
1327
1328                    if (consumeVertical) {
1329                        widthMode = MeasureSpec.EXACTLY;
1330                    } else if (consumeHorizontal) {
1331                        heightMode = MeasureSpec.EXACTLY;
1332                    }
1333
1334                    int widthSize = childWidthSize;
1335                    int heightSize = childHeightSize;
1336                    if (lp.width != LayoutParams.WRAP_CONTENT) {
1337                        widthMode = MeasureSpec.EXACTLY;
1338                        if (lp.width != LayoutParams.FILL_PARENT) {
1339                            widthSize = lp.width;
1340                        }
1341                    }
1342                    if (lp.height != LayoutParams.WRAP_CONTENT) {
1343                        heightMode = MeasureSpec.EXACTLY;
1344                        if (lp.height != LayoutParams.FILL_PARENT) {
1345                            heightSize = lp.height;
1346                        }
1347                    }
1348                    final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
1349                    final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
1350                    child.measure(widthSpec, heightSpec);
1351
1352                    if (consumeVertical) {
1353                        childHeightSize -= child.getMeasuredHeight();
1354                    } else if (consumeHorizontal) {
1355                        childWidthSize -= child.getMeasuredWidth();
1356                    }
1357                }
1358            }
1359        }
1360
1361        mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
1362        mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
1363
1364        // Make sure we have created all fragments that we need to have shown.
1365        mInLayout = true;
1366        populate();
1367        mInLayout = false;
1368
1369        // Page views next.
1370        size = getChildCount();
1371        for (int i = 0; i < size; ++i) {
1372            final View child = getChildAt(i);
1373            if (child.getVisibility() != GONE) {
1374                if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
1375                        + ": " + mChildWidthMeasureSpec);
1376
1377                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1378                if (lp == null || !lp.isDecor) {
1379                    final int widthSpec = MeasureSpec.makeMeasureSpec(
1380                            (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
1381                    child.measure(widthSpec, mChildHeightMeasureSpec);
1382                }
1383            }
1384        }
1385    }
1386
1387    @Override
1388    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1389        super.onSizeChanged(w, h, oldw, oldh);
1390
1391        // Make sure scroll position is set correctly.
1392        if (w != oldw) {
1393            recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin);
1394        }
1395    }
1396
1397    private void recomputeScrollPosition(int width, int oldWidth, int margin, int oldMargin) {
1398        if (oldWidth > 0 && !mItems.isEmpty()) {
1399            final int widthWithMargin = width + margin;
1400            final int oldWidthWithMargin = oldWidth + oldMargin;
1401            final int xpos = getScrollX();
1402            final float pageOffset = (float) xpos / oldWidthWithMargin;
1403            final int newOffsetPixels = (int) (pageOffset * widthWithMargin);
1404
1405            scrollTo(newOffsetPixels, getScrollY());
1406            if (!mScroller.isFinished()) {
1407                // We now return to your regularly scheduled scroll, already in progress.
1408                final int newDuration = mScroller.getDuration() - mScroller.timePassed();
1409                ItemInfo targetInfo = infoForPosition(mCurItem);
1410                mScroller.startScroll(newOffsetPixels, 0,
1411                        (int) (targetInfo.offset * width), 0, newDuration);
1412            }
1413        } else {
1414            final ItemInfo ii = infoForPosition(mCurItem);
1415            final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;
1416            final int scrollPos = (int) (scrollOffset * width);
1417            if (scrollPos != getScrollX()) {
1418                completeScroll(false);
1419                scrollTo(scrollPos, getScrollY());
1420            }
1421        }
1422    }
1423
1424    @Override
1425    protected void onLayout(boolean changed, int l, int t, int r, int b) {
1426        mInLayout = true;
1427        populate();
1428        mInLayout = false;
1429
1430        final int count = getChildCount();
1431        int width = r - l;
1432        int height = b - t;
1433        int paddingLeft = getPaddingLeft();
1434        int paddingTop = getPaddingTop();
1435        int paddingRight = getPaddingRight();
1436        int paddingBottom = getPaddingBottom();
1437        final int scrollX = getScrollX();
1438
1439        int decorCount = 0;
1440
1441        // First pass - decor views. We need to do this in two passes so that
1442        // we have the proper offsets for non-decor views later.
1443        for (int i = 0; i < count; i++) {
1444            final View child = getChildAt(i);
1445            if (child.getVisibility() != GONE) {
1446                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1447                int childLeft = 0;
1448                int childTop = 0;
1449                if (lp.isDecor) {
1450                    final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
1451                    final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
1452                    switch (hgrav) {
1453                        default:
1454                            childLeft = paddingLeft;
1455                            break;
1456                        case Gravity.LEFT:
1457                            childLeft = paddingLeft;
1458                            paddingLeft += child.getMeasuredWidth();
1459                            break;
1460                        case Gravity.CENTER_HORIZONTAL:
1461                            childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
1462                                    paddingLeft);
1463                            break;
1464                        case Gravity.RIGHT:
1465                            childLeft = width - paddingRight - child.getMeasuredWidth();
1466                            paddingRight += child.getMeasuredWidth();
1467                            break;
1468                    }
1469                    switch (vgrav) {
1470                        default:
1471                            childTop = paddingTop;
1472                            break;
1473                        case Gravity.TOP:
1474                            childTop = paddingTop;
1475                            paddingTop += child.getMeasuredHeight();
1476                            break;
1477                        case Gravity.CENTER_VERTICAL:
1478                            childTop = Math.max((height - child.getMeasuredHeight()) / 2,
1479                                    paddingTop);
1480                            break;
1481                        case Gravity.BOTTOM:
1482                            childTop = height - paddingBottom - child.getMeasuredHeight();
1483                            paddingBottom += child.getMeasuredHeight();
1484                            break;
1485                    }
1486                    childLeft += scrollX;
1487                    child.layout(childLeft, childTop,
1488                            childLeft + child.getMeasuredWidth(),
1489                            childTop + child.getMeasuredHeight());
1490                    decorCount++;
1491                }
1492            }
1493        }
1494
1495        // Page views. Do this once we have the right padding offsets from above.
1496        for (int i = 0; i < count; i++) {
1497            final View child = getChildAt(i);
1498            if (child.getVisibility() != GONE) {
1499                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1500                ItemInfo ii;
1501                if (!lp.isDecor && (ii = infoForChild(child)) != null) {
1502                    int loff = (int) (width * ii.offset);
1503                    int childLeft = paddingLeft + loff;
1504                    int childTop = paddingTop;
1505                    if (lp.needsMeasure) {
1506                        // This was added during layout and needs measurement.
1507                        // Do it now that we know what we're working with.
1508                        lp.needsMeasure = false;
1509                        final int widthSpec = MeasureSpec.makeMeasureSpec(
1510                                (int) ((width - paddingLeft - paddingRight) * lp.widthFactor),
1511                                MeasureSpec.EXACTLY);
1512                        final int heightSpec = MeasureSpec.makeMeasureSpec(
1513                                (int) (height - paddingTop - paddingBottom),
1514                                MeasureSpec.EXACTLY);
1515                        child.measure(widthSpec, heightSpec);
1516                    }
1517                    if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
1518                            + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
1519                            + "x" + child.getMeasuredHeight());
1520                    child.layout(childLeft, childTop,
1521                            childLeft + child.getMeasuredWidth(),
1522                            childTop + child.getMeasuredHeight());
1523                }
1524            }
1525        }
1526        mTopPageBounds = paddingTop;
1527        mBottomPageBounds = height - paddingBottom;
1528        mDecorChildCount = decorCount;
1529        mFirstLayout = false;
1530    }
1531
1532    @Override
1533    public void computeScroll() {
1534        if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
1535            int oldX = getScrollX();
1536            int oldY = getScrollY();
1537            int x = mScroller.getCurrX();
1538            int y = mScroller.getCurrY();
1539
1540            if (oldX != x || oldY != y) {
1541                scrollTo(x, y);
1542                if (!pageScrolled(x)) {
1543                    mScroller.abortAnimation();
1544                    scrollTo(0, y);
1545                }
1546            }
1547
1548            // Keep on drawing until the animation has finished.
1549            ViewCompat.postInvalidateOnAnimation(this);
1550            return;
1551        }
1552
1553        // Done with scroll, clean up state.
1554        completeScroll(true);
1555    }
1556
1557    private boolean pageScrolled(int xpos) {
1558        if (mItems.size() == 0) {
1559            mCalledSuper = false;
1560            onPageScrolled(0, 0, 0);
1561            if (!mCalledSuper) {
1562                throw new IllegalStateException(
1563                        "onPageScrolled did not call superclass implementation");
1564            }
1565            return false;
1566        }
1567        final ItemInfo ii = infoForCurrentScrollPosition();
1568        final int width = getWidth();
1569        final int widthWithMargin = width + mPageMargin;
1570        final float marginOffset = (float) mPageMargin / width;
1571        final int currentPage = ii.position;
1572        final float pageOffset = (((float) xpos / width) - ii.offset) /
1573                (ii.widthFactor + marginOffset);
1574        final int offsetPixels = (int) (pageOffset * widthWithMargin);
1575
1576        mCalledSuper = false;
1577        onPageScrolled(currentPage, pageOffset, offsetPixels);
1578        if (!mCalledSuper) {
1579            throw new IllegalStateException(
1580                    "onPageScrolled did not call superclass implementation");
1581        }
1582        return true;
1583    }
1584
1585    /**
1586     * This method will be invoked when the current page is scrolled, either as part
1587     * of a programmatically initiated smooth scroll or a user initiated touch scroll.
1588     * If you override this method you must call through to the superclass implementation
1589     * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled
1590     * returns.
1591     *
1592     * @param position Position index of the first page currently being displayed.
1593     *                 Page position+1 will be visible if positionOffset is nonzero.
1594     * @param offset Value from [0, 1) indicating the offset from the page at position.
1595     * @param offsetPixels Value in pixels indicating the offset from position.
1596     */
1597    protected void onPageScrolled(int position, float offset, int offsetPixels) {
1598        // Offset any decor views if needed - keep them on-screen at all times.
1599        if (mDecorChildCount > 0) {
1600            final int scrollX = getScrollX();
1601            int paddingLeft = getPaddingLeft();
1602            int paddingRight = getPaddingRight();
1603            final int width = getWidth();
1604            final int childCount = getChildCount();
1605            for (int i = 0; i < childCount; i++) {
1606                final View child = getChildAt(i);
1607                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1608                if (!lp.isDecor) continue;
1609
1610                final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
1611                int childLeft = 0;
1612                switch (hgrav) {
1613                    default:
1614                        childLeft = paddingLeft;
1615                        break;
1616                    case Gravity.LEFT:
1617                        childLeft = paddingLeft;
1618                        paddingLeft += child.getWidth();
1619                        break;
1620                    case Gravity.CENTER_HORIZONTAL:
1621                        childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
1622                                paddingLeft);
1623                        break;
1624                    case Gravity.RIGHT:
1625                        childLeft = width - paddingRight - child.getMeasuredWidth();
1626                        paddingRight += child.getMeasuredWidth();
1627                        break;
1628                }
1629                childLeft += scrollX;
1630
1631                final int childOffset = childLeft - child.getLeft();
1632                if (childOffset != 0) {
1633                    child.offsetLeftAndRight(childOffset);
1634                }
1635            }
1636        }
1637
1638        if (mSeenPositionMin < 0 || position < mSeenPositionMin) {
1639            mSeenPositionMin = position;
1640        }
1641        if (mSeenPositionMax < 0 || FloatMath.ceil(position + offset) > mSeenPositionMax) {
1642            mSeenPositionMax = position + 1;
1643        }
1644
1645        if (mOnPageChangeListener != null) {
1646            mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
1647        }
1648        if (mInternalPageChangeListener != null) {
1649            mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
1650        }
1651
1652        if (mPageTransformer != null) {
1653            final int scrollX = getScrollX();
1654            final int childCount = getChildCount();
1655            for (int i = 0; i < childCount; i++) {
1656                final View child = getChildAt(i);
1657                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1658
1659                if (lp.isDecor) continue;
1660
1661                final float transformPos = (float) (child.getLeft() - scrollX) / getWidth();
1662                mPageTransformer.transformPage(child, transformPos);
1663            }
1664        }
1665
1666        mCalledSuper = true;
1667    }
1668
1669    private void completeScroll(boolean postEvents) {
1670        boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING;
1671        if (needPopulate) {
1672            // Done with scroll, no longer want to cache view drawing.
1673            setScrollingCacheEnabled(false);
1674            mScroller.abortAnimation();
1675            int oldX = getScrollX();
1676            int oldY = getScrollY();
1677            int x = mScroller.getCurrX();
1678            int y = mScroller.getCurrY();
1679            if (oldX != x || oldY != y) {
1680                scrollTo(x, y);
1681            }
1682        }
1683        mPopulatePending = false;
1684        for (int i=0; i<mItems.size(); i++) {
1685            ItemInfo ii = mItems.get(i);
1686            if (ii.scrolling) {
1687                needPopulate = true;
1688                ii.scrolling = false;
1689            }
1690        }
1691        if (needPopulate) {
1692            if (postEvents) {
1693                ViewCompat.postOnAnimation(this, mEndScrollRunnable);
1694            } else {
1695                mEndScrollRunnable.run();
1696            }
1697        }
1698    }
1699
1700    private boolean isGutterDrag(float x, float dx) {
1701        return (x < mGutterSize && dx > 0) || (x > getWidth() - mGutterSize && dx < 0);
1702    }
1703
1704    private void enableLayers(boolean enable) {
1705        final int childCount = getChildCount();
1706        for (int i = 0; i < childCount; i++) {
1707            final int layerType = enable ?
1708                    ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE;
1709            ViewCompat.setLayerType(getChildAt(i), layerType, null);
1710        }
1711    }
1712
1713    @Override
1714    public boolean onInterceptTouchEvent(MotionEvent ev) {
1715        /*
1716         * This method JUST determines whether we want to intercept the motion.
1717         * If we return true, onMotionEvent will be called and we do the actual
1718         * scrolling there.
1719         */
1720
1721        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
1722
1723        // Always take care of the touch gesture being complete.
1724        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
1725            // Release the drag.
1726            if (DEBUG) Log.v(TAG, "Intercept done!");
1727            mIsBeingDragged = false;
1728            mIsUnableToDrag = false;
1729            mActivePointerId = INVALID_POINTER;
1730            if (mVelocityTracker != null) {
1731                mVelocityTracker.recycle();
1732                mVelocityTracker = null;
1733            }
1734            return false;
1735        }
1736
1737        // Nothing more to do here if we have decided whether or not we
1738        // are dragging.
1739        if (action != MotionEvent.ACTION_DOWN) {
1740            if (mIsBeingDragged) {
1741                if (DEBUG) Log.v(TAG, "Intercept returning true!");
1742                return true;
1743            }
1744            if (mIsUnableToDrag) {
1745                if (DEBUG) Log.v(TAG, "Intercept returning false!");
1746                return false;
1747            }
1748        }
1749
1750        switch (action) {
1751            case MotionEvent.ACTION_MOVE: {
1752                /*
1753                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1754                 * whether the user has moved far enough from his original down touch.
1755                 */
1756
1757                /*
1758                * Locally do absolute value. mLastMotionY is set to the y value
1759                * of the down event.
1760                */
1761                final int activePointerId = mActivePointerId;
1762                if (activePointerId == INVALID_POINTER) {
1763                    // If we don't have a valid id, the touch down wasn't on content.
1764                    break;
1765                }
1766
1767                final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
1768                final float x = MotionEventCompat.getX(ev, pointerIndex);
1769                final float dx = x - mLastMotionX;
1770                final float xDiff = Math.abs(dx);
1771                final float y = MotionEventCompat.getY(ev, pointerIndex);
1772                final float yDiff = Math.abs(y - mLastMotionY);
1773                if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
1774
1775                if (dx != 0 && !isGutterDrag(mLastMotionX, dx) &&
1776                        canScroll(this, false, (int) dx, (int) x, (int) y)) {
1777                    // Nested view has scrollable area under this point. Let it be handled there.
1778                    mInitialMotionX = mLastMotionX = x;
1779                    mLastMotionY = y;
1780                    mIsUnableToDrag = true;
1781                    return false;
1782                }
1783                if (xDiff > mTouchSlop && xDiff > yDiff) {
1784                    if (DEBUG) Log.v(TAG, "Starting drag!");
1785                    mIsBeingDragged = true;
1786                    setScrollState(SCROLL_STATE_DRAGGING);
1787                    mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :
1788                            mInitialMotionX - mTouchSlop;
1789                    setScrollingCacheEnabled(true);
1790                } else {
1791                    if (yDiff > mTouchSlop) {
1792                        // The finger has moved enough in the vertical
1793                        // direction to be counted as a drag...  abort
1794                        // any attempt to drag horizontally, to work correctly
1795                        // with children that have scrolling containers.
1796                        if (DEBUG) Log.v(TAG, "Starting unable to drag!");
1797                        mIsUnableToDrag = true;
1798                    }
1799                }
1800                if (mIsBeingDragged) {
1801                    // Scroll to follow the motion event
1802                    if (performDrag(x)) {
1803                        ViewCompat.postInvalidateOnAnimation(this);
1804                    }
1805                }
1806                break;
1807            }
1808
1809            case MotionEvent.ACTION_DOWN: {
1810                /*
1811                 * Remember location of down touch.
1812                 * ACTION_DOWN always refers to pointer index 0.
1813                 */
1814                mLastMotionX = mInitialMotionX = ev.getX();
1815                mLastMotionY = ev.getY();
1816                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
1817                mIsUnableToDrag = false;
1818
1819                mScroller.computeScrollOffset();
1820                if (mScrollState == SCROLL_STATE_SETTLING &&
1821                        Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
1822                    // Let the user 'catch' the pager as it animates.
1823                    mScroller.abortAnimation();
1824                    mPopulatePending = false;
1825                    populate();
1826                    mIsBeingDragged = true;
1827                    setScrollState(SCROLL_STATE_DRAGGING);
1828                } else {
1829                    completeScroll(false);
1830                    mIsBeingDragged = false;
1831                }
1832
1833                if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
1834                        + " mIsBeingDragged=" + mIsBeingDragged
1835                        + "mIsUnableToDrag=" + mIsUnableToDrag);
1836                break;
1837            }
1838
1839            case MotionEventCompat.ACTION_POINTER_UP:
1840                onSecondaryPointerUp(ev);
1841                break;
1842        }
1843
1844        if (mVelocityTracker == null) {
1845            mVelocityTracker = VelocityTracker.obtain();
1846        }
1847        mVelocityTracker.addMovement(ev);
1848
1849        /*
1850         * The only time we want to intercept motion events is if we are in the
1851         * drag mode.
1852         */
1853        return mIsBeingDragged;
1854    }
1855
1856    @Override
1857    public boolean onTouchEvent(MotionEvent ev) {
1858        if (mFakeDragging) {
1859            // A fake drag is in progress already, ignore this real one
1860            // but still eat the touch events.
1861            // (It is likely that the user is multi-touching the screen.)
1862            return true;
1863        }
1864
1865        if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
1866            // Don't handle edge touches immediately -- they may actually belong to one of our
1867            // descendants.
1868            return false;
1869        }
1870
1871        if (mAdapter == null || mAdapter.getCount() == 0) {
1872            // Nothing to present or scroll; nothing to touch.
1873            return false;
1874        }
1875
1876        if (mVelocityTracker == null) {
1877            mVelocityTracker = VelocityTracker.obtain();
1878        }
1879        mVelocityTracker.addMovement(ev);
1880
1881        final int action = ev.getAction();
1882        boolean needsInvalidate = false;
1883
1884        switch (action & MotionEventCompat.ACTION_MASK) {
1885            case MotionEvent.ACTION_DOWN: {
1886                mScroller.abortAnimation();
1887                mPopulatePending = false;
1888                populate();
1889                mIsBeingDragged = true;
1890                setScrollState(SCROLL_STATE_DRAGGING);
1891
1892                // Remember where the motion event started
1893                mLastMotionX = mInitialMotionX = ev.getX();
1894                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
1895                break;
1896            }
1897            case MotionEvent.ACTION_MOVE:
1898                if (!mIsBeingDragged) {
1899                    final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
1900                    final float x = MotionEventCompat.getX(ev, pointerIndex);
1901                    final float xDiff = Math.abs(x - mLastMotionX);
1902                    final float y = MotionEventCompat.getY(ev, pointerIndex);
1903                    final float yDiff = Math.abs(y - mLastMotionY);
1904                    if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
1905                    if (xDiff > mTouchSlop && xDiff > yDiff) {
1906                        if (DEBUG) Log.v(TAG, "Starting drag!");
1907                        mIsBeingDragged = true;
1908                        mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
1909                                mInitialMotionX - mTouchSlop;
1910                        setScrollState(SCROLL_STATE_DRAGGING);
1911                        setScrollingCacheEnabled(true);
1912                    }
1913                }
1914                // Not else! Note that mIsBeingDragged can be set above.
1915                if (mIsBeingDragged) {
1916                    // Scroll to follow the motion event
1917                    final int activePointerIndex = MotionEventCompat.findPointerIndex(
1918                            ev, mActivePointerId);
1919                    final float x = MotionEventCompat.getX(ev, activePointerIndex);
1920                    needsInvalidate |= performDrag(x);
1921                }
1922                break;
1923            case MotionEvent.ACTION_UP:
1924                if (mIsBeingDragged) {
1925                    final VelocityTracker velocityTracker = mVelocityTracker;
1926                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1927                    int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
1928                            velocityTracker, mActivePointerId);
1929                    mPopulatePending = true;
1930                    final int width = getWidth();
1931                    final int scrollX = getScrollX();
1932                    final ItemInfo ii = infoForCurrentScrollPosition();
1933                    final int currentPage = ii.position;
1934                    final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
1935                    final int activePointerIndex =
1936                            MotionEventCompat.findPointerIndex(ev, mActivePointerId);
1937                    final float x = MotionEventCompat.getX(ev, activePointerIndex);
1938                    final int totalDelta = (int) (x - mInitialMotionX);
1939                    int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
1940                            totalDelta);
1941                    setCurrentItemInternal(nextPage, true, true, initialVelocity);
1942
1943                    mActivePointerId = INVALID_POINTER;
1944                    endDrag();
1945                    needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
1946                }
1947                break;
1948            case MotionEvent.ACTION_CANCEL:
1949                if (mIsBeingDragged) {
1950                    scrollToItem(mCurItem, true, 0, false);
1951                    mActivePointerId = INVALID_POINTER;
1952                    endDrag();
1953                    needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
1954                }
1955                break;
1956            case MotionEventCompat.ACTION_POINTER_DOWN: {
1957                final int index = MotionEventCompat.getActionIndex(ev);
1958                final float x = MotionEventCompat.getX(ev, index);
1959                mLastMotionX = x;
1960                mActivePointerId = MotionEventCompat.getPointerId(ev, index);
1961                break;
1962            }
1963            case MotionEventCompat.ACTION_POINTER_UP:
1964                onSecondaryPointerUp(ev);
1965                mLastMotionX = MotionEventCompat.getX(ev,
1966                        MotionEventCompat.findPointerIndex(ev, mActivePointerId));
1967                break;
1968        }
1969        if (needsInvalidate) {
1970            ViewCompat.postInvalidateOnAnimation(this);
1971        }
1972        return true;
1973    }
1974
1975    private boolean performDrag(float x) {
1976        boolean needsInvalidate = false;
1977
1978        final float deltaX = mLastMotionX - x;
1979        mLastMotionX = x;
1980
1981        float oldScrollX = getScrollX();
1982        float scrollX = oldScrollX + deltaX;
1983        final int width = getWidth();
1984
1985        float leftBound = width * mFirstOffset;
1986        float rightBound = width * mLastOffset;
1987        boolean leftAbsolute = true;
1988        boolean rightAbsolute = true;
1989
1990        final ItemInfo firstItem = mItems.get(0);
1991        final ItemInfo lastItem = mItems.get(mItems.size() - 1);
1992        if (firstItem.position != 0) {
1993            leftAbsolute = false;
1994            leftBound = firstItem.offset * width;
1995        }
1996        if (lastItem.position != mAdapter.getCount() - 1) {
1997            rightAbsolute = false;
1998            rightBound = lastItem.offset * width;
1999        }
2000
2001        if (scrollX < leftBound) {
2002            if (leftAbsolute) {
2003                float over = leftBound - scrollX;
2004                needsInvalidate = mLeftEdge.onPull(Math.abs(over) / width);
2005            }
2006            scrollX = leftBound;
2007        } else if (scrollX > rightBound) {
2008            if (rightAbsolute) {
2009                float over = scrollX - rightBound;
2010                needsInvalidate = mRightEdge.onPull(Math.abs(over) / width);
2011            }
2012            scrollX = rightBound;
2013        }
2014        // Don't lose the rounded component
2015        mLastMotionX += scrollX - (int) scrollX;
2016        scrollTo((int) scrollX, getScrollY());
2017        pageScrolled((int) scrollX);
2018
2019        return needsInvalidate;
2020    }
2021
2022    /**
2023     * @return Info about the page at the current scroll position.
2024     *         This can be synthetic for a missing middle page; the 'object' field can be null.
2025     */
2026    private ItemInfo infoForCurrentScrollPosition() {
2027        final int width = getWidth();
2028        final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0;
2029        final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
2030        int lastPos = -1;
2031        float lastOffset = 0.f;
2032        float lastWidth = 0.f;
2033        boolean first = true;
2034
2035        ItemInfo lastItem = null;
2036        for (int i = 0; i < mItems.size(); i++) {
2037            ItemInfo ii = mItems.get(i);
2038            float offset;
2039            if (!first && ii.position != lastPos + 1) {
2040                // Create a synthetic item for a missing page.
2041                ii = mTempItem;
2042                ii.offset = lastOffset + lastWidth + marginOffset;
2043                ii.position = lastPos + 1;
2044                ii.widthFactor = mAdapter.getPageWidth(ii.position);
2045                i--;
2046            }
2047            offset = ii.offset;
2048
2049            final float leftBound = offset;
2050            final float rightBound = offset + ii.widthFactor + marginOffset;
2051            if (first || scrollOffset >= leftBound) {
2052                if (scrollOffset < rightBound || i == mItems.size() - 1) {
2053                    return ii;
2054                }
2055            } else {
2056                return lastItem;
2057            }
2058            first = false;
2059            lastPos = ii.position;
2060            lastOffset = offset;
2061            lastWidth = ii.widthFactor;
2062            lastItem = ii;
2063        }
2064
2065        return lastItem;
2066    }
2067
2068    private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {
2069        int targetPage;
2070        if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
2071            targetPage = velocity > 0 ? currentPage : currentPage + 1;
2072        } else if (mSeenPositionMin >= 0 && mSeenPositionMin < currentPage && pageOffset < 0.5f) {
2073            targetPage = currentPage + 1;
2074        } else if (mSeenPositionMax >= 0 && mSeenPositionMax > currentPage + 1 &&
2075                pageOffset >= 0.5f) {
2076            targetPage = currentPage - 1;
2077        } else {
2078            targetPage = (int) (currentPage + pageOffset + 0.5f);
2079        }
2080
2081        if (mItems.size() > 0) {
2082            final ItemInfo firstItem = mItems.get(0);
2083            final ItemInfo lastItem = mItems.get(mItems.size() - 1);
2084
2085            // Only let the user target pages we have items for
2086            targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
2087        }
2088
2089        return targetPage;
2090    }
2091
2092    @Override
2093    public void draw(Canvas canvas) {
2094        super.draw(canvas);
2095        boolean needsInvalidate = false;
2096
2097        final int overScrollMode = ViewCompat.getOverScrollMode(this);
2098        if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
2099                (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&
2100                        mAdapter != null && mAdapter.getCount() > 1)) {
2101            if (!mLeftEdge.isFinished()) {
2102                final int restoreCount = canvas.save();
2103                final int height = getHeight() - getPaddingTop() - getPaddingBottom();
2104                final int width = getWidth();
2105
2106                canvas.rotate(270);
2107                canvas.translate(-height + getPaddingTop(), mFirstOffset * width);
2108                mLeftEdge.setSize(height, width);
2109                needsInvalidate |= mLeftEdge.draw(canvas);
2110                canvas.restoreToCount(restoreCount);
2111            }
2112            if (!mRightEdge.isFinished()) {
2113                final int restoreCount = canvas.save();
2114                final int width = getWidth();
2115                final int height = getHeight() - getPaddingTop() - getPaddingBottom();
2116
2117                canvas.rotate(90);
2118                canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width);
2119                mRightEdge.setSize(height, width);
2120                needsInvalidate |= mRightEdge.draw(canvas);
2121                canvas.restoreToCount(restoreCount);
2122            }
2123        } else {
2124            mLeftEdge.finish();
2125            mRightEdge.finish();
2126        }
2127
2128        if (needsInvalidate) {
2129            // Keep animating
2130            ViewCompat.postInvalidateOnAnimation(this);
2131        }
2132    }
2133
2134    @Override
2135    protected void onDraw(Canvas canvas) {
2136        super.onDraw(canvas);
2137
2138        // Draw the margin drawable between pages if needed.
2139        if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) {
2140            final int scrollX = getScrollX();
2141            final int width = getWidth();
2142
2143            final float marginOffset = (float) mPageMargin / width;
2144            int itemIndex = 0;
2145            ItemInfo ii = mItems.get(0);
2146            float offset = ii.offset;
2147            final int itemCount = mItems.size();
2148            final int firstPos = ii.position;
2149            final int lastPos = mItems.get(itemCount - 1).position;
2150            for (int pos = firstPos; pos < lastPos; pos++) {
2151                while (pos > ii.position && itemIndex < itemCount) {
2152                    ii = mItems.get(++itemIndex);
2153                }
2154
2155                float drawAt;
2156                if (pos == ii.position) {
2157                    drawAt = (ii.offset + ii.widthFactor) * width;
2158                    offset = ii.offset + ii.widthFactor + marginOffset;
2159                } else {
2160                    float widthFactor = mAdapter.getPageWidth(pos);
2161                    drawAt = (offset + widthFactor) * width;
2162                    offset += widthFactor + marginOffset;
2163                }
2164
2165                if (drawAt + mPageMargin > scrollX) {
2166                    mMarginDrawable.setBounds((int) drawAt, mTopPageBounds,
2167                            (int) (drawAt + mPageMargin + 0.5f), mBottomPageBounds);
2168                    mMarginDrawable.draw(canvas);
2169                }
2170
2171                if (drawAt > scrollX + width) {
2172                    break; // No more visible, no sense in continuing
2173                }
2174            }
2175        }
2176    }
2177
2178    /**
2179     * Start a fake drag of the pager.
2180     *
2181     * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
2182     * with the touch scrolling of another view, while still letting the ViewPager
2183     * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
2184     * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
2185     * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
2186     *
2187     * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
2188     * is already in progress, this method will return false.
2189     *
2190     * @return true if the fake drag began successfully, false if it could not be started.
2191     *
2192     * @see #fakeDragBy(float)
2193     * @see #endFakeDrag()
2194     */
2195    public boolean beginFakeDrag() {
2196        if (mIsBeingDragged) {
2197            return false;
2198        }
2199        mFakeDragging = true;
2200        setScrollState(SCROLL_STATE_DRAGGING);
2201        mInitialMotionX = mLastMotionX = 0;
2202        if (mVelocityTracker == null) {
2203            mVelocityTracker = VelocityTracker.obtain();
2204        } else {
2205            mVelocityTracker.clear();
2206        }
2207        final long time = SystemClock.uptimeMillis();
2208        final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
2209        mVelocityTracker.addMovement(ev);
2210        ev.recycle();
2211        mFakeDragBeginTime = time;
2212        return true;
2213    }
2214
2215    /**
2216     * End a fake drag of the pager.
2217     *
2218     * @see #beginFakeDrag()
2219     * @see #fakeDragBy(float)
2220     */
2221    public void endFakeDrag() {
2222        if (!mFakeDragging) {
2223            throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
2224        }
2225
2226        final VelocityTracker velocityTracker = mVelocityTracker;
2227        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
2228        int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
2229                velocityTracker, mActivePointerId);
2230        mPopulatePending = true;
2231        final int width = getWidth();
2232        final int scrollX = getScrollX();
2233        final ItemInfo ii = infoForCurrentScrollPosition();
2234        final int currentPage = ii.position;
2235        final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
2236        final int totalDelta = (int) (mLastMotionX - mInitialMotionX);
2237        int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
2238                totalDelta);
2239        setCurrentItemInternal(nextPage, true, true, initialVelocity);
2240        endDrag();
2241
2242        mFakeDragging = false;
2243    }
2244
2245    /**
2246     * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
2247     *
2248     * @param xOffset Offset in pixels to drag by.
2249     * @see #beginFakeDrag()
2250     * @see #endFakeDrag()
2251     */
2252    public void fakeDragBy(float xOffset) {
2253        if (!mFakeDragging) {
2254            throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
2255        }
2256
2257        mLastMotionX += xOffset;
2258
2259        float oldScrollX = getScrollX();
2260        float scrollX = oldScrollX - xOffset;
2261        final int width = getWidth();
2262
2263        float leftBound = width * mFirstOffset;
2264        float rightBound = width * mLastOffset;
2265
2266        final ItemInfo firstItem = mItems.get(0);
2267        final ItemInfo lastItem = mItems.get(mItems.size() - 1);
2268        if (firstItem.position != 0) {
2269            leftBound = firstItem.offset * width;
2270        }
2271        if (lastItem.position != mAdapter.getCount() - 1) {
2272            rightBound = lastItem.offset * width;
2273        }
2274
2275        if (scrollX < leftBound) {
2276            scrollX = leftBound;
2277        } else if (scrollX > rightBound) {
2278            scrollX = rightBound;
2279        }
2280        // Don't lose the rounded component
2281        mLastMotionX += scrollX - (int) scrollX;
2282        scrollTo((int) scrollX, getScrollY());
2283        pageScrolled((int) scrollX);
2284
2285        // Synthesize an event for the VelocityTracker.
2286        final long time = SystemClock.uptimeMillis();
2287        final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
2288                mLastMotionX, 0, 0);
2289        mVelocityTracker.addMovement(ev);
2290        ev.recycle();
2291    }
2292
2293    /**
2294     * Returns true if a fake drag is in progress.
2295     *
2296     * @return true if currently in a fake drag, false otherwise.
2297     *
2298     * @see #beginFakeDrag()
2299     * @see #fakeDragBy(float)
2300     * @see #endFakeDrag()
2301     */
2302    public boolean isFakeDragging() {
2303        return mFakeDragging;
2304    }
2305
2306    private void onSecondaryPointerUp(MotionEvent ev) {
2307        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
2308        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
2309        if (pointerId == mActivePointerId) {
2310            // This was our active pointer going up. Choose a new
2311            // active pointer and adjust accordingly.
2312            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
2313            mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);
2314            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
2315            if (mVelocityTracker != null) {
2316                mVelocityTracker.clear();
2317            }
2318        }
2319    }
2320
2321    private void endDrag() {
2322        mIsBeingDragged = false;
2323        mIsUnableToDrag = false;
2324
2325        if (mVelocityTracker != null) {
2326            mVelocityTracker.recycle();
2327            mVelocityTracker = null;
2328        }
2329    }
2330
2331    private void setScrollingCacheEnabled(boolean enabled) {
2332        if (mScrollingCacheEnabled != enabled) {
2333            mScrollingCacheEnabled = enabled;
2334            if (USE_CACHE) {
2335                final int size = getChildCount();
2336                for (int i = 0; i < size; ++i) {
2337                    final View child = getChildAt(i);
2338                    if (child.getVisibility() != GONE) {
2339                        child.setDrawingCacheEnabled(enabled);
2340                    }
2341                }
2342            }
2343        }
2344    }
2345
2346    /**
2347     * Tests scrollability within child views of v given a delta of dx.
2348     *
2349     * @param v View to test for horizontal scrollability
2350     * @param checkV Whether the view v passed should itself be checked for scrollability (true),
2351     *               or just its children (false).
2352     * @param dx Delta scrolled in pixels
2353     * @param x X coordinate of the active touch point
2354     * @param y Y coordinate of the active touch point
2355     * @return true if child views of v can be scrolled by delta of dx.
2356     */
2357    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
2358        if (v instanceof ViewGroup) {
2359            final ViewGroup group = (ViewGroup) v;
2360            final int scrollX = v.getScrollX();
2361            final int scrollY = v.getScrollY();
2362            final int count = group.getChildCount();
2363            // Count backwards - let topmost views consume scroll distance first.
2364            for (int i = count - 1; i >= 0; i--) {
2365                // TODO: Add versioned support here for transformed views.
2366                // This will not work for transformed views in Honeycomb+
2367                final View child = group.getChildAt(i);
2368                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
2369                        y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
2370                        canScroll(child, true, dx, x + scrollX - child.getLeft(),
2371                                y + scrollY - child.getTop())) {
2372                    return true;
2373                }
2374            }
2375        }
2376
2377        return checkV && ViewCompat.canScrollHorizontally(v, -dx);
2378    }
2379
2380    @Override
2381    public boolean dispatchKeyEvent(KeyEvent event) {
2382        // Let the focused view and/or our descendants get the key first
2383        return super.dispatchKeyEvent(event) || executeKeyEvent(event);
2384    }
2385
2386    /**
2387     * You can call this function yourself to have the scroll view perform
2388     * scrolling from a key event, just as if the event had been dispatched to
2389     * it by the view hierarchy.
2390     *
2391     * @param event The key event to execute.
2392     * @return Return true if the event was handled, else false.
2393     */
2394    public boolean executeKeyEvent(KeyEvent event) {
2395        boolean handled = false;
2396        if (event.getAction() == KeyEvent.ACTION_DOWN) {
2397            switch (event.getKeyCode()) {
2398                case KeyEvent.KEYCODE_DPAD_LEFT:
2399                    handled = arrowScroll(FOCUS_LEFT);
2400                    break;
2401                case KeyEvent.KEYCODE_DPAD_RIGHT:
2402                    handled = arrowScroll(FOCUS_RIGHT);
2403                    break;
2404                case KeyEvent.KEYCODE_TAB:
2405                    if (Build.VERSION.SDK_INT >= 11) {
2406                        // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD
2407                        // before Android 3.0. Ignore the tab key on those devices.
2408                        if (KeyEventCompat.hasNoModifiers(event)) {
2409                            handled = arrowScroll(FOCUS_FORWARD);
2410                        } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
2411                            handled = arrowScroll(FOCUS_BACKWARD);
2412                        }
2413                    }
2414                    break;
2415            }
2416        }
2417        return handled;
2418    }
2419
2420    public boolean arrowScroll(int direction) {
2421        View currentFocused = findFocus();
2422        if (currentFocused == this) currentFocused = null;
2423
2424        boolean handled = false;
2425
2426        View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
2427                direction);
2428        if (nextFocused != null && nextFocused != currentFocused) {
2429            if (direction == View.FOCUS_LEFT) {
2430                // If there is nothing to the left, or this is causing us to
2431                // jump to the right, then what we really want to do is page left.
2432                final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;
2433                final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;
2434                if (currentFocused != null && nextLeft >= currLeft) {
2435                    handled = pageLeft();
2436                } else {
2437                    handled = nextFocused.requestFocus();
2438                }
2439            } else if (direction == View.FOCUS_RIGHT) {
2440                // If there is nothing to the right, or this is causing us to
2441                // jump to the left, then what we really want to do is page right.
2442                final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;
2443                final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;
2444                if (currentFocused != null && nextLeft <= currLeft) {
2445                    handled = pageRight();
2446                } else {
2447                    handled = nextFocused.requestFocus();
2448                }
2449            }
2450        } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {
2451            // Trying to move left and nothing there; try to page.
2452            handled = pageLeft();
2453        } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {
2454            // Trying to move right and nothing there; try to page.
2455            handled = pageRight();
2456        }
2457        if (handled) {
2458            playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
2459        }
2460        return handled;
2461    }
2462
2463    private Rect getChildRectInPagerCoordinates(Rect outRect, View child) {
2464        if (outRect == null) {
2465            outRect = new Rect();
2466        }
2467        if (child == null) {
2468            outRect.set(0, 0, 0, 0);
2469            return outRect;
2470        }
2471        outRect.left = child.getLeft();
2472        outRect.right = child.getRight();
2473        outRect.top = child.getTop();
2474        outRect.bottom = child.getBottom();
2475
2476        ViewParent parent = child.getParent();
2477        while (parent instanceof ViewGroup && parent != this) {
2478            final ViewGroup group = (ViewGroup) parent;
2479            outRect.left += group.getLeft();
2480            outRect.right += group.getRight();
2481            outRect.top += group.getTop();
2482            outRect.bottom += group.getBottom();
2483
2484            parent = group.getParent();
2485        }
2486        return outRect;
2487    }
2488
2489    boolean pageLeft() {
2490        if (mCurItem > 0) {
2491            setCurrentItem(mCurItem-1, true);
2492            return true;
2493        }
2494        return false;
2495    }
2496
2497    boolean pageRight() {
2498        if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) {
2499            setCurrentItem(mCurItem+1, true);
2500            return true;
2501        }
2502        return false;
2503    }
2504
2505    /**
2506     * We only want the current page that is being shown to be focusable.
2507     */
2508    @Override
2509    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
2510        final int focusableCount = views.size();
2511
2512        final int descendantFocusability = getDescendantFocusability();
2513
2514        if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
2515            for (int i = 0; i < getChildCount(); i++) {
2516                final View child = getChildAt(i);
2517                if (child.getVisibility() == VISIBLE) {
2518                    ItemInfo ii = infoForChild(child);
2519                    if (ii != null && ii.position == mCurItem) {
2520                        child.addFocusables(views, direction, focusableMode);
2521                    }
2522                }
2523            }
2524        }
2525
2526        // we add ourselves (if focusable) in all cases except for when we are
2527        // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.  this is
2528        // to avoid the focus search finding layouts when a more precise search
2529        // among the focusable children would be more interesting.
2530        if (
2531            descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
2532                // No focusable descendants
2533                (focusableCount == views.size())) {
2534            // Note that we can't call the superclass here, because it will
2535            // add all views in.  So we need to do the same thing View does.
2536            if (!isFocusable()) {
2537                return;
2538            }
2539            if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
2540                    isInTouchMode() && !isFocusableInTouchMode()) {
2541                return;
2542            }
2543            if (views != null) {
2544                views.add(this);
2545            }
2546        }
2547    }
2548
2549    /**
2550     * We only want the current page that is being shown to be touchable.
2551     */
2552    @Override
2553    public void addTouchables(ArrayList<View> views) {
2554        // Note that we don't call super.addTouchables(), which means that
2555        // we don't call View.addTouchables().  This is okay because a ViewPager
2556        // is itself not touchable.
2557        for (int i = 0; i < getChildCount(); i++) {
2558            final View child = getChildAt(i);
2559            if (child.getVisibility() == VISIBLE) {
2560                ItemInfo ii = infoForChild(child);
2561                if (ii != null && ii.position == mCurItem) {
2562                    child.addTouchables(views);
2563                }
2564            }
2565        }
2566    }
2567
2568    /**
2569     * We only want the current page that is being shown to be focusable.
2570     */
2571    @Override
2572    protected boolean onRequestFocusInDescendants(int direction,
2573            Rect previouslyFocusedRect) {
2574        int index;
2575        int increment;
2576        int end;
2577        int count = getChildCount();
2578        if ((direction & FOCUS_FORWARD) != 0) {
2579            index = 0;
2580            increment = 1;
2581            end = count;
2582        } else {
2583            index = count - 1;
2584            increment = -1;
2585            end = -1;
2586        }
2587        for (int i = index; i != end; i += increment) {
2588            View child = getChildAt(i);
2589            if (child.getVisibility() == VISIBLE) {
2590                ItemInfo ii = infoForChild(child);
2591                if (ii != null && ii.position == mCurItem) {
2592                    if (child.requestFocus(direction, previouslyFocusedRect)) {
2593                        return true;
2594                    }
2595                }
2596            }
2597        }
2598        return false;
2599    }
2600
2601    @Override
2602    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
2603        // ViewPagers should only report accessibility info for the current page,
2604        // otherwise things get very confusing.
2605
2606        // TODO: Should this note something about the paging container?
2607
2608        final int childCount = getChildCount();
2609        for (int i = 0; i < childCount; i++) {
2610            final View child = getChildAt(i);
2611            if (child.getVisibility() == VISIBLE) {
2612                final ItemInfo ii = infoForChild(child);
2613                if (ii != null && ii.position == mCurItem &&
2614                        child.dispatchPopulateAccessibilityEvent(event)) {
2615                    return true;
2616                }
2617            }
2618        }
2619
2620        return false;
2621    }
2622
2623    @Override
2624    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
2625        return new LayoutParams();
2626    }
2627
2628    @Override
2629    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2630        return generateDefaultLayoutParams();
2631    }
2632
2633    @Override
2634    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2635        return p instanceof LayoutParams && super.checkLayoutParams(p);
2636    }
2637
2638    @Override
2639    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2640        return new LayoutParams(getContext(), attrs);
2641    }
2642
2643    class MyAccessibilityDelegate extends AccessibilityDelegateCompat {
2644
2645        @Override
2646        public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
2647            super.onInitializeAccessibilityEvent(host, event);
2648            event.setClassName(ViewPager.class.getName());
2649        }
2650
2651        @Override
2652        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
2653            super.onInitializeAccessibilityNodeInfo(host, info);
2654            info.setClassName(ViewPager.class.getName());
2655            info.setScrollable(mAdapter != null && mAdapter.getCount() > 1);
2656            if (mAdapter != null && mCurItem >= 0 && mCurItem < mAdapter.getCount() - 1) {
2657                info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
2658            }
2659            if (mAdapter != null && mCurItem > 0 && mCurItem < mAdapter.getCount()) {
2660                info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
2661            }
2662        }
2663
2664        @Override
2665        public boolean performAccessibilityAction(View host, int action, Bundle args) {
2666            if (super.performAccessibilityAction(host, action, args)) {
2667                return true;
2668            }
2669            switch (action) {
2670                case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {
2671                    if (mAdapter != null && mCurItem >= 0 && mCurItem < mAdapter.getCount() - 1) {
2672                        setCurrentItem(mCurItem + 1);
2673                        return true;
2674                    }
2675                } return false;
2676                case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {
2677                    if (mAdapter != null && mCurItem > 0 && mCurItem < mAdapter.getCount()) {
2678                        setCurrentItem(mCurItem - 1);
2679                        return true;
2680                    }
2681                } return false;
2682            }
2683            return false;
2684        }
2685    }
2686
2687    private class PagerObserver extends DataSetObserver {
2688        @Override
2689        public void onChanged() {
2690            dataSetChanged();
2691        }
2692        @Override
2693        public void onInvalidated() {
2694            dataSetChanged();
2695        }
2696    }
2697
2698    /**
2699     * Layout parameters that should be supplied for views added to a
2700     * ViewPager.
2701     */
2702    public static class LayoutParams extends ViewGroup.LayoutParams {
2703        /**
2704         * true if this view is a decoration on the pager itself and not
2705         * a view supplied by the adapter.
2706         */
2707        public boolean isDecor;
2708
2709        /**
2710         * Gravity setting for use on decor views only:
2711         * Where to position the view page within the overall ViewPager
2712         * container; constants are defined in {@link android.view.Gravity}.
2713         */
2714        public int gravity;
2715
2716        /**
2717         * Width as a 0-1 multiplier of the measured pager width
2718         */
2719        float widthFactor = 0.f;
2720
2721        /**
2722         * true if this view was added during layout and needs to be measured
2723         * before being positioned.
2724         */
2725        boolean needsMeasure;
2726
2727        /**
2728         * Adapter position this view is for if !isDecor
2729         */
2730        int position;
2731
2732        /**
2733         * Current child index within the ViewPager that this view occupies
2734         */
2735        int childIndex;
2736
2737        public LayoutParams() {
2738            super(FILL_PARENT, FILL_PARENT);
2739        }
2740
2741        public LayoutParams(Context context, AttributeSet attrs) {
2742            super(context, attrs);
2743
2744            final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
2745            gravity = a.getInteger(0, Gravity.TOP);
2746            a.recycle();
2747        }
2748    }
2749
2750    static class ViewPositionComparator implements Comparator<View> {
2751        @Override
2752        public int compare(View lhs, View rhs) {
2753            final LayoutParams llp = (LayoutParams) lhs.getLayoutParams();
2754            final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams();
2755            if (llp.isDecor != rlp.isDecor) {
2756                return llp.isDecor ? 1 : -1;
2757            }
2758            return llp.position - rlp.position;
2759        }
2760    }
2761}
2762