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