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