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