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