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