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