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