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