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