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