[go: nahoru, domu]

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