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