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