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