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