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