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