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