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