关于禁止ViewPager预加载问题【转】

转自:http://blog.csdn.net/qq_21898059/article/details/51453938#comments

我最近上班又遇到一个小难题了,就是如题所述:ViewPager预加载的问题。相信用过ViewPager的人大抵都有遇到过这种情况,网上的解决办法也就那么几个,终于在我自己不断试验之下,完美解决了(禁止了)ViewPager的预加载。

好了,首先来说明一下,什么是ViewPager的预加载:ViewPager有一个 “预加载”的机制,默认会把ViewPager当前位置的左右相邻页面预先初始化(俗称的预加载),它的默认值是 1,这样做的好处就是ViewPager左右滑动会更加流畅。

可是我的情况很特殊,因为我 5 个Fragment里有一个Fragment是有SurfaceView的,这样造成的问题就是,我ViewPager滑动到其相邻页面时,含有SurfaceView的页面就会被预先初始化,然后SurfaceView就开始预览了,只是我们看不到而已。同样的,当我们从含有SurfaceView的页面滑倒其相邻的页面时,SurfaceView并不会回调其surfaceDestory方法。于是这给我造成了极大的困扰。

ok,下面言归正传,到底该怎么禁止ViewPager的这个预加载问题呢?

方案1:网上大多数说法是 懒加载,即让ViewPager预加载初始化UI,而具体一些数据,网络访问请求等延迟加载。这是靠Fragment里有一个setUserVisibleHint(boolean isVisibleToUser)的方法,我们可以在这个方法里做判断,当其True可见时(即切换到某一个具体Fragment)时才去加载数据,这样可以省流量。但这里并不满足我的需求,因为某一个Fragment并不会在ViewPager滑动到其相邻的Fragment时销毁。这个只可以解决部分人问题。

首先我们来深入了解下ViewPager的预加载机制:

上文提到过,ViewPager默认预加载的数量是1,这一点我们可以在ViewPager源码里看到。

DEFAULT_OFFSCREEN_PAGES 这里就定义了默认值是1, 所以网上 有种解决方案 说调用ViewPager的setOffscreenPageLimit(int limit),来设置ViewPager预加载的数量,但是这里很明确的告诉你,这种方案是不可行的,如下图ViewPager源码:

我们可以看到,如果你调用该方法传进来的值小于1是无效的,会被强行的拽回1。而且DEFAULT_OFFSCREEN_PAGES 这个值是private的,子类继承ViewPager也是不可见的。

方案2:然后网上有第二种说法,自定义一个ViewPager,把原生ViewPager全拷过来,修改这个DEFAULT_OFFSCREEN_PAGES 值为0。对,就是这种解决方案!!但是!!

但是!!!但是什么呢?但是我试过,没用。为什么呢?接下来就是本文的重点了。

因为现在Android都6.0了,版本都老高了,其实android虽然每个版本都有v4包,但是这些v4包是有差异的。这就造成高版本v4包里的ViewPager,即使你Copy它,将其DEFAULT_OFFSCREEN_PAGES的值改为0,还是不起作用的,其中的逻辑变了。具体哪里变了导致无效我也没有继续研究了,毕竟公司不会花时间让我研究这些啊。偷笑

完美解决方案:ok,所以关于禁止ViewPager预加载的完美解决方案就是,使用低版本v4包里的ViewPager,完全copy一份,将其中的DEFAULT_OFFSCREEN_PAGES值改为0即可。博主亲测 API 14 即 Android 4.0的v4包里ViewPager 有效。

当然,谷歌既然有这么一种ViewPager的机制肯定有它的道理,所以一般还是预加载的好。

最后,因为低版本的源码越来越少的人会去下载,这里直接把这个禁止了预加载的ViewPager贴上来,需要的人就拿去吧。copy就能用了。

   1 package com.winstars.petclient.widget;
   2 
   3 import android.content.Context;
   4 import android.database.DataSetObserver;
   5 import android.graphics.Canvas;
   6 import android.graphics.Rect;
   7 import android.graphics.drawable.Drawable;
   8 import android.os.Parcel;
   9 import android.os.Parcelable;
  10 import android.os.SystemClock;
  11 import android.support.v4.os.ParcelableCompat;
  12 import android.support.v4.os.ParcelableCompatCreatorCallbacks;
  13 import android.support.v4.view.KeyEventCompat;
  14 import android.support.v4.view.MotionEventCompat;
  15 import android.support.v4.view.PagerAdapter;
  16 import android.support.v4.view.VelocityTrackerCompat;
  17 import android.support.v4.view.ViewCompat;
  18 import android.support.v4.view.ViewConfigurationCompat;
  19 import android.support.v4.widget.EdgeEffectCompat;
  20 import android.util.AttributeSet;
  21 import android.util.Log;
  22 import android.view.FocusFinder;
  23 import android.view.KeyEvent;
  24 import android.view.MotionEvent;
  25 import android.view.SoundEffectConstants;
  26 import android.view.VelocityTracker;
  27 import android.view.View;
  28 import android.view.ViewConfiguration;
  29 import android.view.ViewGroup;
  30 import android.view.ViewParent;
  31 import android.view.accessibility.AccessibilityEvent;
  32 import android.view.animation.Interpolator;
  33 import android.widget.Scroller;
  34 
  35 import java.util.ArrayList;
  36 import java.util.Collections;
  37 import java.util.Comparator;
  38 
  39 /**
  40  * Layout manager that allows the user to flip left and right
  41  * through pages of data.  You supply an implementation of a
  42  * {@link android.support.v4.view.PagerAdapter} to generate the pages that the view shows.
  43  *
  44  * <p>Note this class is currently under early design and
  45  * development.  The API will likely change in later updates of
  46  * the compatibility library, requiring changes to the source code
  47  * of apps when they are compiled against the newer version.</p>
  48  */
  49 public class NoPreloadViewPager extends ViewGroup {
  50     private static final String TAG = "NoPreloadViewPager";
  51     private static final boolean DEBUG = false;
  52 
  53     private static final boolean USE_CACHE = false;
  54 
  55     private static final int DEFAULT_OFFSCREEN_PAGES = 0;//默认是1
  56     private static final int MAX_SETTLE_DURATION = 600; // ms
  57 
  58     static class ItemInfo {
  59         Object object;
  60         int position;
  61         boolean scrolling;
  62     }
  63 
  64     private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>(){
  65         @Override
  66         public int compare(ItemInfo lhs, ItemInfo rhs) {
  67             return lhs.position - rhs.position;
  68         }};
  69 
  70     private static final Interpolator sInterpolator = new Interpolator() {
  71         public float getInterpolation(float t) {
  72             // _o(t) = t * t * ((tension + 1) * t + tension)
  73             // o(t) = _o(t - 1) + 1
  74             t -= 1.0f;
  75             return t * t * t + 1.0f;
  76         }
  77     };
  78 
  79     private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
  80 
  81     private PagerAdapter mAdapter;
  82     private int mCurItem;   // Index of currently displayed page.
  83     private int mRestoredCurItem = -1;
  84     private Parcelable mRestoredAdapterState = null;
  85     private ClassLoader mRestoredClassLoader = null;
  86     private Scroller mScroller;
  87     private PagerObserver mObserver;
  88 
  89     private int mPageMargin;
  90     private Drawable mMarginDrawable;
  91 
  92     private int mChildWidthMeasureSpec;
  93     private int mChildHeightMeasureSpec;
  94     private boolean mInLayout;
  95 
  96     private boolean mScrollingCacheEnabled;
  97 
  98     private boolean mPopulatePending;
  99     private boolean mScrolling;
 100     private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
 101 
 102     private boolean mIsBeingDragged;
 103     private boolean mIsUnableToDrag;
 104     private int mTouchSlop;
 105     private float mInitialMotionX;
 106     /**
 107      * Position of the last motion event.
 108      */
 109     private float mLastMotionX;
 110     private float mLastMotionY;
 111     /**
 112      * ID of the active pointer. This is used to retain consistency during
 113      * drags/flings if multiple pointers are used.
 114      */
 115     private int mActivePointerId = INVALID_POINTER;
 116     /**
 117      * Sentinel value for no current active pointer.
 118      * Used by {@link #mActivePointerId}.
 119      */
 120     private static final int INVALID_POINTER = -1;
 121 
 122     /**
 123      * Determines speed during touch scrolling
 124      */
 125     private VelocityTracker mVelocityTracker;
 126     private int mMinimumVelocity;
 127     private int mMaximumVelocity;
 128     private float mBaseLineFlingVelocity;
 129     private float mFlingVelocityInfluence;
 130 
 131     private boolean mFakeDragging;
 132     private long mFakeDragBeginTime;
 133 
 134     private EdgeEffectCompat mLeftEdge;
 135     private EdgeEffectCompat mRightEdge;
 136 
 137     private boolean mFirstLayout = true;
 138 
 139     private OnPageChangeListener mOnPageChangeListener;
 140 
 141     /**
 142      * Indicates that the pager is in an idle, settled state. The current page
 143      * is fully in view and no animation is in progress.
 144      */
 145     public static final int SCROLL_STATE_IDLE = 0;
 146 
 147     /**
 148      * Indicates that the pager is currently being dragged by the user.
 149      */
 150     public static final int SCROLL_STATE_DRAGGING = 1;
 151 
 152     /**
 153      * Indicates that the pager is in the process of settling to a final position.
 154      */
 155     public static final int SCROLL_STATE_SETTLING = 2;
 156 
 157     private int mScrollState = SCROLL_STATE_IDLE;
 158 
 159     /**
 160      * Callback interface for responding to changing state of the selected page.
 161      */
 162     public interface OnPageChangeListener {
 163 
 164         /**
 165          * This method will be invoked when the current page is scrolled, either as part
 166          * of a programmatically initiated smooth scroll or a user initiated touch scroll.
 167          *
 168          * @param position Position index of the first page currently being displayed.
 169          *                 Page position+1 will be visible if positionOffset is nonzero.
 170          * @param positionOffset Value from [0, 1) indicating the offset from the page at position.
 171          * @param positionOffsetPixels Value in pixels indicating the offset from position.
 172          */
 173         public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
 174 
 175         /**
 176          * This method will be invoked when a new page becomes selected. Animation is not
 177          * necessarily complete.
 178          *
 179          * @param position Position index of the new selected page.
 180          */
 181         public void onPageSelected(int position);
 182 
 183         /**
 184          * Called when the scroll state changes. Useful for discovering when the user
 185          * begins dragging, when the pager is automatically settling to the current page,
 186          * or when it is fully stopped/idle.
 187          *
 188          * @param state The new scroll state.
 189          * @see android.support.v4.view.ViewPager#SCROLL_STATE_IDLE
 190          * @see android.support.v4.view.ViewPager#SCROLL_STATE_DRAGGING
 191          * @see android.support.v4.view.ViewPager#SCROLL_STATE_SETTLING
 192          */
 193         public void onPageScrollStateChanged(int state);
 194     }
 195 
 196     /**
 197      * Simple implementation of the {@link android.support.v4.view.LazyViewPager.OnPageChangeListener} interface with stub
 198      * implementations of each method. Extend this if you do not intend to override
 199      * every method of {@link android.support.v4.view.LazyViewPager.OnPageChangeListener}.
 200      */
 201     public static class SimpleOnPageChangeListener implements OnPageChangeListener {
 202         @Override
 203         public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
 204             // This space for rent
 205         }
 206 
 207         @Override
 208         public void onPageSelected(int position) {
 209             // This space for rent
 210         }
 211 
 212         @Override
 213         public void onPageScrollStateChanged(int state) {
 214             // This space for rent
 215         }
 216     }
 217 
 218     public NoPreloadViewPager(Context context) {
 219         super(context);
 220         initViewPager();
 221     }
 222 
 223     public NoPreloadViewPager(Context context, AttributeSet attrs) {
 224         super(context, attrs);
 225         initViewPager();
 226     }
 227 
 228     void initViewPager() {
 229         setWillNotDraw(false);
 230         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
 231         setFocusable(true);
 232         final Context context = getContext();
 233         mScroller = new Scroller(context, sInterpolator);
 234         final ViewConfiguration configuration = ViewConfiguration.get(context);
 235         mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
 236         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
 237         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
 238         mLeftEdge = new EdgeEffectCompat(context);
 239         mRightEdge = new EdgeEffectCompat(context);
 240 
 241         float density = context.getResources().getDisplayMetrics().density;
 242         mBaseLineFlingVelocity = 2500.0f * density;
 243         mFlingVelocityInfluence = 0.4f;
 244     }
 245 
 246     private void setScrollState(int newState) {
 247         if (mScrollState == newState) {
 248             return;
 249         }
 250 
 251         mScrollState = newState;
 252         if (mOnPageChangeListener != null) {
 253             mOnPageChangeListener.onPageScrollStateChanged(newState);
 254         }
 255     }
 256 
 257     public void setAdapter(PagerAdapter adapter) {
 258         if (mAdapter != null) {
 259 //            mAdapter.unregisterDataSetObserver(mObserver);
 260             mAdapter.startUpdate(this);
 261             for (int i = 0; i < mItems.size(); i++) {
 262                 final ItemInfo ii = mItems.get(i);
 263                 mAdapter.destroyItem(this, ii.position, ii.object);
 264             }
 265             mAdapter.finishUpdate(this);
 266             mItems.clear();
 267             removeAllViews();
 268             mCurItem = 0;
 269             scrollTo(0, 0);
 270         }
 271 
 272         mAdapter = adapter;
 273 
 274         if (mAdapter != null) {
 275             if (mObserver == null) {
 276                 mObserver = new PagerObserver();
 277             }
 278 //            mAdapter.registerDataSetObserver(mObserver);
 279             mPopulatePending = false;
 280             if (mRestoredCurItem >= 0) {
 281                 mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
 282                 setCurrentItemInternal(mRestoredCurItem, false, true);
 283                 mRestoredCurItem = -1;
 284                 mRestoredAdapterState = null;
 285                 mRestoredClassLoader = null;
 286             } else {
 287                 populate();
 288             }
 289         }
 290     }
 291 
 292     public PagerAdapter getAdapter() {
 293         return mAdapter;
 294     }
 295 
 296     /**
 297      * Set the currently selected page. If the ViewPager has already been through its first
 298      * layout there will be a smooth animated transition between the current item and the
 299      * specified item.
 300      *
 301      * @param item Item index to select
 302      */
 303     public void setCurrentItem(int item) {
 304         mPopulatePending = false;
 305         setCurrentItemInternal(item, !mFirstLayout, false);
 306     }
 307 
 308     /**
 309      * Set the currently selected page.
 310      *
 311      * @param item Item index to select
 312      * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
 313      */
 314     public void setCurrentItem(int item, boolean smoothScroll) {
 315         mPopulatePending = false;
 316         setCurrentItemInternal(item, smoothScroll, false);
 317     }
 318 
 319     public int getCurrentItem() {
 320         return mCurItem;
 321     }
 322 
 323     void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
 324         setCurrentItemInternal(item, smoothScroll, always, 0);
 325     }
 326 
 327     void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
 328         if (mAdapter == null || mAdapter.getCount() <= 0) {
 329             setScrollingCacheEnabled(false);
 330             return;
 331         }
 332         if (!always && mCurItem == item && mItems.size() != 0) {
 333             setScrollingCacheEnabled(false);
 334             return;
 335         }
 336         if (item < 0) {
 337             item = 0;
 338         } else if (item >= mAdapter.getCount()) {
 339             item = mAdapter.getCount() - 1;
 340         }
 341         final int pageLimit = mOffscreenPageLimit;
 342         if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
 343             // We are doing a jump by more than one page.  To avoid
 344             // glitches, we want to keep all current pages in the view
 345             // until the scroll ends.
 346             for (int i=0; i<mItems.size(); i++) {
 347                 mItems.get(i).scrolling = true;
 348             }
 349         }
 350 
 351         final boolean dispatchSelected = mCurItem != item;
 352         mCurItem = item;
 353         populate();
 354         final int destX = (getWidth() + mPageMargin) * item;
 355         if (smoothScroll) {
 356             smoothScrollTo(destX, 0, velocity);
 357             if (dispatchSelected && mOnPageChangeListener != null) {
 358                 mOnPageChangeListener.onPageSelected(item);
 359             }
 360         } else {
 361             if (dispatchSelected && mOnPageChangeListener != null) {
 362                 mOnPageChangeListener.onPageSelected(item);
 363             }
 364             completeScroll();
 365             scrollTo(destX, 0);
 366         }
 367     }
 368 
 369     public void setOnPageChangeListener(OnPageChangeListener listener) {
 370         mOnPageChangeListener = listener;
 371     }
 372 
 373     /**
 374      * Returns the number of pages that will be retained to either side of the
 375      * current page in the view hierarchy in an idle state. Defaults to 1.
 376      *
 377      * @return How many pages will be kept offscreen on either side
 378      * @see #setOffscreenPageLimit(int)
 379      */
 380     public int getOffscreenPageLimit() {
 381         return mOffscreenPageLimit;
 382     }
 383 
 384     /**
 385      * Set the number of pages that should be retained to either side of the
 386      * current page in the view hierarchy in an idle state. Pages beyond this
 387      * limit will be recreated from the adapter when needed.
 388      *
 389      * <p>This is offered as an optimization. If you know in advance the number
 390      * of pages you will need to support or have lazy-loading mechanisms in place
 391      * on your pages, tweaking this setting can have benefits in perceived smoothness
 392      * of paging animations and interaction. If you have a small number of pages (3-4)
 393      * that you can keep active all at once, less time will be spent in layout for
 394      * newly created view subtrees as the user pages back and forth.</p>
 395      *
 396      * <p>You should keep this limit low, especially if your pages have complex layouts.
 397      * This setting defaults to 1.</p>
 398      *
 399      * @param limit How many pages will be kept offscreen in an idle state.
 400      */
 401     public void setOffscreenPageLimit(int limit) {
 402         if (limit < DEFAULT_OFFSCREEN_PAGES) {
 403             Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
 404                     DEFAULT_OFFSCREEN_PAGES);
 405             limit = DEFAULT_OFFSCREEN_PAGES;
 406         }
 407         if (limit != mOffscreenPageLimit) {
 408             mOffscreenPageLimit = limit;
 409             populate();
 410         }
 411     }
 412 
 413     /**
 414      * Set the margin between pages.
 415      *
 416      * @param marginPixels Distance between adjacent pages in pixels
 417      * @see #getPageMargin()
 418      * @see #setPageMarginDrawable(android.graphics.drawable.Drawable)
 419      * @see #setPageMarginDrawable(int)
 420      */
 421     public void setPageMargin(int marginPixels) {
 422         final int oldMargin = mPageMargin;
 423         mPageMargin = marginPixels;
 424 
 425         final int width = getWidth();
 426         recomputeScrollPosition(width, width, marginPixels, oldMargin);
 427 
 428         requestLayout();
 429     }
 430 
 431     /**
 432      * Return the margin between pages.
 433      *
 434      * @return The size of the margin in pixels
 435      */
 436     public int getPageMargin() {
 437         return mPageMargin;
 438     }
 439 
 440     /**
 441      * Set a drawable that will be used to fill the margin between pages.
 442      *
 443      * @param d Drawable to display between pages
 444      */
 445     public void setPageMarginDrawable(Drawable d) {
 446         mMarginDrawable = d;
 447         if (d != null) refreshDrawableState();
 448         setWillNotDraw(d == null);
 449         invalidate();
 450     }
 451 
 452     /**
 453      * Set a drawable that will be used to fill the margin between pages.
 454      *
 455      * @param resId Resource ID of a drawable to display between pages
 456      */
 457     public void setPageMarginDrawable(int resId) {
 458         setPageMarginDrawable(getContext().getResources().getDrawable(resId));
 459     }
 460 
 461     @Override
 462     protected boolean verifyDrawable(Drawable who) {
 463         return super.verifyDrawable(who) || who == mMarginDrawable;
 464     }
 465 
 466     @Override
 467     protected void drawableStateChanged() {
 468         super.drawableStateChanged();
 469         final Drawable d = mMarginDrawable;
 470         if (d != null && d.isStateful()) {
 471             d.setState(getDrawableState());
 472         }
 473     }
 474 
 475     // We want the duration of the page snap animation to be influenced by the distance that
 476     // the screen has to travel, however, we don't want this duration to be effected in a
 477     // purely linear fashion. Instead, we use this method to moderate the effect that the distance
 478     // of travel has on the overall snap duration.
 479     float distanceInfluenceForSnapDuration(float f) {
 480         f -= 0.5f; // center the values about 0.
 481         f *= 0.3f * Math.PI / 2.0f;
 482         return (float) Math.sin(f);
 483     }
 484 
 485     /**
 486      * Like {@link android.view.View#scrollBy}, but scroll smoothly instead of immediately.
 487      *
 488      * @param x the number of pixels to scroll by on the X axis
 489      * @param y the number of pixels to scroll by on the Y axis
 490      */
 491     void smoothScrollTo(int x, int y) {
 492         smoothScrollTo(x, y, 0);
 493     }
 494 
 495     /**
 496      * Like {@link android.view.View#scrollBy}, but scroll smoothly instead of immediately.
 497      *
 498      * @param x the number of pixels to scroll by on the X axis
 499      * @param y the number of pixels to scroll by on the Y axis
 500      * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
 501      */
 502     void smoothScrollTo(int x, int y, int velocity) {
 503         if (getChildCount() == 0) {
 504             // Nothing to do.
 505             setScrollingCacheEnabled(false);
 506             return;
 507         }
 508         int sx = getScrollX();
 509         int sy = getScrollY();
 510         int dx = x - sx;
 511         int dy = y - sy;
 512         if (dx == 0 && dy == 0) {
 513             completeScroll();
 514             setScrollState(SCROLL_STATE_IDLE);
 515             return;
 516         }
 517 
 518         setScrollingCacheEnabled(true);
 519         mScrolling = true;
 520         setScrollState(SCROLL_STATE_SETTLING);
 521 
 522         final float pageDelta = (float) Math.abs(dx) / (getWidth() + mPageMargin);
 523         int duration = (int) (pageDelta * 100);
 524 
 525         velocity = Math.abs(velocity);
 526         if (velocity > 0) {
 527             duration += (duration / (velocity / mBaseLineFlingVelocity)) * mFlingVelocityInfluence;
 528         } else {
 529             duration += 100;
 530         }
 531         duration = Math.min(duration, MAX_SETTLE_DURATION);
 532 
 533         mScroller.startScroll(sx, sy, dx, dy, duration);
 534         invalidate();
 535     }
 536 
 537     void addNewItem(int position, int index) {
 538         ItemInfo ii = new ItemInfo();
 539         ii.position = position;
 540         ii.object = mAdapter.instantiateItem(this, position);
 541         if (index < 0) {
 542             mItems.add(ii);
 543         } else {
 544             mItems.add(index, ii);
 545         }
 546     }
 547 
 548     void dataSetChanged() {
 549         // This method only gets called if our observer is attached, so mAdapter is non-null.
 550 
 551         boolean needPopulate = mItems.size() < 3 && mItems.size() < mAdapter.getCount();
 552         int newCurrItem = -1;
 553 
 554         for (int i = 0; i < mItems.size(); i++) {
 555             final ItemInfo ii = mItems.get(i);
 556             final int newPos = mAdapter.getItemPosition(ii.object);
 557 
 558             if (newPos == PagerAdapter.POSITION_UNCHANGED) {
 559                 continue;
 560             }
 561 
 562             if (newPos == PagerAdapter.POSITION_NONE) {
 563                 mItems.remove(i);
 564                 i--;
 565                 mAdapter.destroyItem(this, ii.position, ii.object);
 566                 needPopulate = true;
 567 
 568                 if (mCurItem == ii.position) {
 569                     // Keep the current item in the valid range
 570                     newCurrItem = Math.max(0, Math.min(mCurItem, mAdapter.getCount() - 1));
 571                 }
 572                 continue;
 573             }
 574 
 575             if (ii.position != newPos) {
 576                 if (ii.position == mCurItem) {
 577                     // Our current item changed position. Follow it.
 578                     newCurrItem = newPos;
 579                 }
 580 
 581                 ii.position = newPos;
 582                 needPopulate = true;
 583             }
 584         }
 585 
 586         Collections.sort(mItems, COMPARATOR);
 587 
 588         if (newCurrItem >= 0) {
 589             // TODO This currently causes a jump.
 590             setCurrentItemInternal(newCurrItem, false, true);
 591             needPopulate = true;
 592         }
 593         if (needPopulate) {
 594             populate();
 595             requestLayout();
 596         }
 597     }
 598 
 599     void populate() {
 600         if (mAdapter == null) {
 601             return;
 602         }
 603 
 604         // Bail now if we are waiting to populate.  This is to hold off
 605         // on creating views from the time the user releases their finger to
 606         // fling to a new position until we have finished the scroll to
 607         // that position, avoiding glitches from happening at that point.
 608         if (mPopulatePending) {
 609             if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
 610             return;
 611         }
 612 
 613         // Also, don't populate until we are attached to a window.  This is to
 614         // avoid trying to populate before we have restored our view hierarchy
 615         // state and conflicting with what is restored.
 616         if (getWindowToken() == null) {
 617             return;
 618         }
 619 
 620         mAdapter.startUpdate(this);
 621 
 622         final int pageLimit = mOffscreenPageLimit;
 623         final int startPos = Math.max(0, mCurItem - pageLimit);
 624         final int N = mAdapter.getCount();
 625         final int endPos = Math.min(N-1, mCurItem + pageLimit);
 626 
 627         if (DEBUG) Log.v(TAG, "populating: startPos=" + startPos + " endPos=" + endPos);
 628 
 629         // Add and remove pages in the existing list.
 630         int lastPos = -1;
 631         for (int i=0; i<mItems.size(); i++) {
 632             ItemInfo ii = mItems.get(i);
 633             if ((ii.position < startPos || ii.position > endPos) && !ii.scrolling) {
 634                 if (DEBUG) Log.i(TAG, "removing: " + ii.position + " @ " + i);
 635                 mItems.remove(i);
 636                 i--;
 637                 mAdapter.destroyItem(this, ii.position, ii.object);
 638             } else if (lastPos < endPos && ii.position > startPos) {
 639                 // The next item is outside of our range, but we have a gap
 640                 // between it and the last item where we want to have a page
 641                 // shown.  Fill in the gap.
 642                 lastPos++;
 643                 if (lastPos < startPos) {
 644                     lastPos = startPos;
 645                 }
 646                 while (lastPos <= endPos && lastPos < ii.position) {
 647                     if (DEBUG) Log.i(TAG, "inserting: " + lastPos + " @ " + i);
 648                     addNewItem(lastPos, i);
 649                     lastPos++;
 650                     i++;
 651                 }
 652             }
 653             lastPos = ii.position;
 654         }
 655 
 656         // Add any new pages we need at the end.
 657         lastPos = mItems.size() > 0 ? mItems.get(mItems.size()-1).position : -1;
 658         if (lastPos < endPos) {
 659             lastPos++;
 660             lastPos = lastPos > startPos ? lastPos : startPos;
 661             while (lastPos <= endPos) {
 662                 if (DEBUG) Log.i(TAG, "appending: " + lastPos);
 663                 addNewItem(lastPos, -1);
 664                 lastPos++;
 665             }
 666         }
 667 
 668         if (DEBUG) {
 669             Log.i(TAG, "Current page list:");
 670             for (int i=0; i<mItems.size(); i++) {
 671                 Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
 672             }
 673         }
 674 
 675         ItemInfo curItem = null;
 676         for (int i=0; i<mItems.size(); i++) {
 677             if (mItems.get(i).position == mCurItem) {
 678                 curItem = mItems.get(i);
 679                 break;
 680             }
 681         }
 682         mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
 683 
 684         mAdapter.finishUpdate(this);
 685 
 686         if (hasFocus()) {
 687             View currentFocused = findFocus();
 688             ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
 689             if (ii == null || ii.position != mCurItem) {
 690                 for (int i=0; i<getChildCount(); i++) {
 691                     View child = getChildAt(i);
 692                     ii = infoForChild(child);
 693                     if (ii != null && ii.position == mCurItem) {
 694                         if (child.requestFocus(FOCUS_FORWARD)) {
 695                             break;
 696                         }
 697                     }
 698                 }
 699             }
 700         }
 701     }
 702 
 703     public static class SavedState extends BaseSavedState {
 704         int position;
 705         Parcelable adapterState;
 706         ClassLoader loader;
 707 
 708         public SavedState(Parcelable superState) {
 709             super(superState);
 710         }
 711 
 712         @Override
 713         public void writeToParcel(Parcel out, int flags) {
 714             super.writeToParcel(out, flags);
 715             out.writeInt(position);
 716             out.writeParcelable(adapterState, flags);
 717         }
 718 
 719         @Override
 720         public String toString() {
 721             return "FragmentPager.SavedState{"
 722                     + Integer.toHexString(System.identityHashCode(this))
 723                     + " position=" + position + "}";
 724         }
 725 
 726         public static final Creator<SavedState> CREATOR
 727                 = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
 728             @Override
 729             public SavedState createFromParcel(Parcel in, ClassLoader loader) {
 730                 return new SavedState(in, loader);
 731             }
 732             @Override
 733             public SavedState[] newArray(int size) {
 734                 return new SavedState[size];
 735             }
 736         });
 737 
 738         SavedState(Parcel in, ClassLoader loader) {
 739             super(in);
 740             if (loader == null) {
 741                 loader = getClass().getClassLoader();
 742             }
 743             position = in.readInt();
 744             adapterState = in.readParcelable(loader);
 745             this.loader = loader;
 746         }
 747     }
 748 
 749     @Override
 750     public Parcelable onSaveInstanceState() {
 751         Parcelable superState = super.onSaveInstanceState();
 752         SavedState ss = new SavedState(superState);
 753         ss.position = mCurItem;
 754         if (mAdapter != null) {
 755             ss.adapterState = mAdapter.saveState();
 756         }
 757         return ss;
 758     }
 759 
 760     @Override
 761     public void onRestoreInstanceState(Parcelable state) {
 762         if (!(state instanceof SavedState)) {
 763             super.onRestoreInstanceState(state);
 764             return;
 765         }
 766 
 767         SavedState ss = (SavedState)state;
 768         super.onRestoreInstanceState(ss.getSuperState());
 769 
 770         if (mAdapter != null) {
 771             mAdapter.restoreState(ss.adapterState, ss.loader);
 772             setCurrentItemInternal(ss.position, false, true);
 773         } else {
 774             mRestoredCurItem = ss.position;
 775             mRestoredAdapterState = ss.adapterState;
 776             mRestoredClassLoader = ss.loader;
 777         }
 778     }
 779 
 780     @Override
 781     public void addView(View child, int index, LayoutParams params) {
 782         if (mInLayout) {
 783             addViewInLayout(child, index, params);
 784             child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
 785         } else {
 786             super.addView(child, index, params);
 787         }
 788 
 789         if (USE_CACHE) {
 790             if (child.getVisibility() != GONE) {
 791                 child.setDrawingCacheEnabled(mScrollingCacheEnabled);
 792             } else {
 793                 child.setDrawingCacheEnabled(false);
 794             }
 795         }
 796     }
 797 
 798     ItemInfo infoForChild(View child) {
 799         for (int i=0; i<mItems.size(); i++) {
 800             ItemInfo ii = mItems.get(i);
 801             if (mAdapter.isViewFromObject(child, ii.object)) {
 802                 return ii;
 803             }
 804         }
 805         return null;
 806     }
 807 
 808     ItemInfo infoForAnyChild(View child) {
 809         ViewParent parent;
 810         while ((parent=child.getParent()) != this) {
 811             if (parent == null || !(parent instanceof View)) {
 812                 return null;
 813             }
 814             child = (View)parent;
 815         }
 816         return infoForChild(child);
 817     }
 818 
 819     @Override
 820     protected void onAttachedToWindow() {
 821         super.onAttachedToWindow();
 822         mFirstLayout = true;
 823     }
 824 
 825     @Override
 826     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 827         // For simple implementation, or internal size is always 0.
 828         // We depend on the container to specify the layout size of
 829         // our view.  We can't really know what it is since we will be
 830         // adding and removing different arbitrary views and do not
 831         // want the layout to change as this happens.
 832         setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
 833                 getDefaultSize(0, heightMeasureSpec));
 834 
 835         // Children are just made to fill our space.
 836         mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
 837                 getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY);
 838         mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
 839                 getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY);
 840 
 841         // Make sure we have created all fragments that we need to have shown.
 842         mInLayout = true;
 843         populate();
 844         mInLayout = false;
 845 
 846         // Make sure all children have been properly measured.
 847         final int size = getChildCount();
 848         for (int i = 0; i < size; ++i) {
 849             final View child = getChildAt(i);
 850             if (child.getVisibility() != GONE) {
 851                 if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
 852                         + ": " + mChildWidthMeasureSpec);
 853                 child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
 854             }
 855         }
 856     }
 857 
 858     @Override
 859     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
 860         super.onSizeChanged(w, h, oldw, oldh);
 861 
 862         // Make sure scroll position is set correctly.
 863         if (w != oldw) {
 864             recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin);
 865         }
 866     }
 867 
 868     private void recomputeScrollPosition(int width, int oldWidth, int margin, int oldMargin) {
 869         final int widthWithMargin = width + margin;
 870         if (oldWidth > 0) {
 871             final int oldScrollPos = getScrollX();
 872             final int oldwwm = oldWidth + oldMargin;
 873             final int oldScrollItem = oldScrollPos / oldwwm;
 874             final float scrollOffset = (float) (oldScrollPos % oldwwm) / oldwwm;
 875             final int scrollPos = (int) ((oldScrollItem + scrollOffset) * widthWithMargin);
 876             scrollTo(scrollPos, getScrollY());
 877             if (!mScroller.isFinished()) {
 878                 // We now return to your regularly scheduled scroll, already in progress.
 879                 final int newDuration = mScroller.getDuration() - mScroller.timePassed();
 880                 mScroller.startScroll(scrollPos, 0, mCurItem * widthWithMargin, 0, newDuration);
 881             }
 882         } else {
 883             int scrollPos = mCurItem * widthWithMargin;
 884             if (scrollPos != getScrollX()) {
 885                 completeScroll();
 886                 scrollTo(scrollPos, getScrollY());
 887             }
 888         }
 889     }
 890 
 891     @Override
 892     protected void onLayout(boolean changed, int l, int t, int r, int b) {
 893         mInLayout = true;
 894         populate();
 895         mInLayout = false;
 896 
 897         final int count = getChildCount();
 898         final int width = r-l;
 899 
 900         for (int i = 0; i < count; i++) {
 901             View child = getChildAt(i);
 902             ItemInfo ii;
 903             if (child.getVisibility() != GONE && (ii=infoForChild(child)) != null) {
 904                 int loff = (width + mPageMargin) * ii.position;
 905                 int childLeft = getPaddingLeft() + loff;
 906                 int childTop = getPaddingTop();
 907                 if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
 908                         + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
 909                         + "x" + child.getMeasuredHeight());
 910                 child.layout(childLeft, childTop,
 911                         childLeft + child.getMeasuredWidth(),
 912                         childTop + child.getMeasuredHeight());
 913             }
 914         }
 915         mFirstLayout = false;
 916     }
 917 
 918     @Override
 919     public void computeScroll() {
 920         if (DEBUG) Log.i(TAG, "computeScroll: finished=" + mScroller.isFinished());
 921         if (!mScroller.isFinished()) {
 922             if (mScroller.computeScrollOffset()) {
 923                 if (DEBUG) Log.i(TAG, "computeScroll: still scrolling");
 924                 int oldX = getScrollX();
 925                 int oldY = getScrollY();
 926                 int x = mScroller.getCurrX();
 927                 int y = mScroller.getCurrY();
 928 
 929                 if (oldX != x || oldY != y) {
 930                     scrollTo(x, y);
 931                 }
 932 
 933                 if (mOnPageChangeListener != null) {
 934                     final int widthWithMargin = getWidth() + mPageMargin;
 935                     final int position = x / widthWithMargin;
 936                     final int offsetPixels = x % widthWithMargin;
 937                     final float offset = (float) offsetPixels / widthWithMargin;
 938                     mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
 939                 }
 940 
 941                 // Keep on drawing until the animation has finished.
 942                 invalidate();
 943                 return;
 944             }
 945         }
 946 
 947         // Done with scroll, clean up state.
 948         completeScroll();
 949     }
 950 
 951     private void completeScroll() {
 952         boolean needPopulate = mScrolling;
 953         if (needPopulate) {
 954             // Done with scroll, no longer want to cache view drawing.
 955             setScrollingCacheEnabled(false);
 956             mScroller.abortAnimation();
 957             int oldX = getScrollX();
 958             int oldY = getScrollY();
 959             int x = mScroller.getCurrX();
 960             int y = mScroller.getCurrY();
 961             if (oldX != x || oldY != y) {
 962                 scrollTo(x, y);
 963             }
 964             setScrollState(SCROLL_STATE_IDLE);
 965         }
 966         mPopulatePending = false;
 967         mScrolling = false;
 968         for (int i=0; i<mItems.size(); i++) {
 969             ItemInfo ii = mItems.get(i);
 970             if (ii.scrolling) {
 971                 needPopulate = true;
 972                 ii.scrolling = false;
 973             }
 974         }
 975         if (needPopulate) {
 976             populate();
 977         }
 978     }
 979 
 980     @Override
 981     public boolean onInterceptTouchEvent(MotionEvent ev) {
 982         /*
 983          * This method JUST determines whether we want to intercept the motion.
 984          * If we return true, onMotionEvent will be called and we do the actual
 985          * scrolling there.
 986          */
 987 
 988         final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
 989 
 990         // Always take care of the touch gesture being complete.
 991         if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
 992             // Release the drag.
 993             if (DEBUG) Log.v(TAG, "Intercept done!");
 994             mIsBeingDragged = false;
 995             mIsUnableToDrag = false;
 996             mActivePointerId = INVALID_POINTER;
 997             return false;
 998         }
 999 
1000         // Nothing more to do here if we have decided whether or not we
1001         // are dragging.
1002         if (action != MotionEvent.ACTION_DOWN) {
1003             if (mIsBeingDragged) {
1004                 if (DEBUG) Log.v(TAG, "Intercept returning true!");
1005                 return true;
1006             }
1007             if (mIsUnableToDrag) {
1008                 if (DEBUG) Log.v(TAG, "Intercept returning false!");
1009                 return false;
1010             }
1011         }
1012 
1013         switch (action) {
1014             case MotionEvent.ACTION_MOVE: {
1015                 /*
1016                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1017                  * whether the user has moved far enough from his original down touch.
1018                  */
1019 
1020                 /*
1021                 * Locally do absolute value. mLastMotionY is set to the y value
1022                 * of the down event.
1023                 */
1024                 final int activePointerId = mActivePointerId;
1025                 if (activePointerId == INVALID_POINTER) {
1026                     // If we don't have a valid id, the touch down wasn't on content.
1027                     break;
1028                 }
1029 
1030                 final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
1031                 final float x = MotionEventCompat.getX(ev, pointerIndex);
1032                 final float dx = x - mLastMotionX;
1033                 final float xDiff = Math.abs(dx);
1034                 final float y = MotionEventCompat.getY(ev, pointerIndex);
1035                 final float yDiff = Math.abs(y - mLastMotionY);
1036                 final int scrollX = getScrollX();
1037                 final boolean atEdge = (dx > 0 && scrollX == 0) || (dx < 0 && mAdapter != null &&
1038                         scrollX >= (mAdapter.getCount() - 1) * getWidth() - 1);
1039                 if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
1040 
1041                 if (canScroll(this, false, (int) dx, (int) x, (int) y)) {
1042                     // Nested view has scrollable area under this point. Let it be handled there.
1043                     mInitialMotionX = mLastMotionX = x;
1044                     mLastMotionY = y;
1045                     return false;
1046                 }
1047                 if (xDiff > mTouchSlop && xDiff > yDiff) {
1048                     if (DEBUG) Log.v(TAG, "Starting drag!");
1049                     mIsBeingDragged = true;
1050                     setScrollState(SCROLL_STATE_DRAGGING);
1051                     mLastMotionX = x;
1052                     setScrollingCacheEnabled(true);
1053                 } else {
1054                     if (yDiff > mTouchSlop) {
1055                         // The finger has moved enough in the vertical
1056                         // direction to be counted as a drag...  abort
1057                         // any attempt to drag horizontally, to work correctly
1058                         // with children that have scrolling containers.
1059                         if (DEBUG) Log.v(TAG, "Starting unable to drag!");
1060                         mIsUnableToDrag = true;
1061                     }
1062                 }
1063                 break;
1064             }
1065 
1066             case MotionEvent.ACTION_DOWN: {
1067                 /*
1068                  * Remember location of down touch.
1069                  * ACTION_DOWN always refers to pointer index 0.
1070                  */
1071                 mLastMotionX = mInitialMotionX = ev.getX();
1072                 mLastMotionY = ev.getY();
1073                 mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
1074 
1075                 if (mScrollState == SCROLL_STATE_SETTLING) {
1076                     // Let the user 'catch' the pager as it animates.
1077                     mIsBeingDragged = true;
1078                     mIsUnableToDrag = false;
1079                     setScrollState(SCROLL_STATE_DRAGGING);
1080                 } else {
1081                     completeScroll();
1082                     mIsBeingDragged = false;
1083                     mIsUnableToDrag = false;
1084                 }
1085 
1086                 if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
1087                         + " mIsBeingDragged=" + mIsBeingDragged
1088                         + "mIsUnableToDrag=" + mIsUnableToDrag);
1089                 break;
1090             }
1091 
1092             case MotionEventCompat.ACTION_POINTER_UP:
1093                 onSecondaryPointerUp(ev);
1094                 break;
1095         }
1096 
1097         /*
1098         * The only time we want to intercept motion events is if we are in the
1099         * drag mode.
1100         */
1101         return mIsBeingDragged;
1102     }
1103 
1104     @Override
1105     public boolean onTouchEvent(MotionEvent ev) {
1106         if (mFakeDragging) {
1107             // A fake drag is in progress already, ignore this real one
1108             // but still eat the touch events.
1109             // (It is likely that the user is multi-touching the screen.)
1110             return true;
1111         }
1112 
1113         if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
1114             // Don't handle edge touches immediately -- they may actually belong to one of our
1115             // descendants.
1116             return false;
1117         }
1118 
1119         if (mAdapter == null || mAdapter.getCount() == 0) {
1120             // Nothing to present or scroll; nothing to touch.
1121             return false;
1122         }
1123 
1124         if (mVelocityTracker == null) {
1125             mVelocityTracker = VelocityTracker.obtain();
1126         }
1127         mVelocityTracker.addMovement(ev);
1128 
1129         final int action = ev.getAction();
1130         boolean needsInvalidate = false;
1131 
1132         switch (action & MotionEventCompat.ACTION_MASK) {
1133             case MotionEvent.ACTION_DOWN: {
1134                 /*
1135                  * If being flinged and user touches, stop the fling. isFinished
1136                  * will be false if being flinged.
1137                  */
1138                 completeScroll();
1139 
1140                 // Remember where the motion event started
1141                 mLastMotionX = mInitialMotionX = ev.getX();
1142                 mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
1143                 break;
1144             }
1145             case MotionEvent.ACTION_MOVE:
1146                 if (!mIsBeingDragged) {
1147                     final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
1148                     final float x = MotionEventCompat.getX(ev, pointerIndex);
1149                     final float xDiff = Math.abs(x - mLastMotionX);
1150                     final float y = MotionEventCompat.getY(ev, pointerIndex);
1151                     final float yDiff = Math.abs(y - mLastMotionY);
1152                     if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
1153                     if (xDiff > mTouchSlop && xDiff > yDiff) {
1154                         if (DEBUG) Log.v(TAG, "Starting drag!");
1155                         mIsBeingDragged = true;
1156                         mLastMotionX = x;
1157                         setScrollState(SCROLL_STATE_DRAGGING);
1158                         setScrollingCacheEnabled(true);
1159                     }
1160                 }
1161                 if (mIsBeingDragged) {
1162                     // Scroll to follow the motion event
1163                     final int activePointerIndex = MotionEventCompat.findPointerIndex(
1164                             ev, mActivePointerId);
1165                     final float x = MotionEventCompat.getX(ev, activePointerIndex);
1166                     final float deltaX = mLastMotionX - x;
1167                     mLastMotionX = x;
1168                     float oldScrollX = getScrollX();
1169                     float scrollX = oldScrollX + deltaX;
1170                     final int width = getWidth();
1171                     final int widthWithMargin = width + mPageMargin;
1172 
1173                     final int lastItemIndex = mAdapter.getCount() - 1;
1174                     final float leftBound = Math.max(0, (mCurItem - 1) * widthWithMargin);
1175                     final float rightBound =
1176                             Math.min(mCurItem + 1, lastItemIndex) * widthWithMargin;
1177                     if (scrollX < leftBound) {
1178                         if (leftBound == 0) {
1179                             float over = -scrollX;
1180                             needsInvalidate = mLeftEdge.onPull(over / width);
1181                         }
1182                         scrollX = leftBound;
1183                     } else if (scrollX > rightBound) {
1184                         if (rightBound == lastItemIndex * widthWithMargin) {
1185                             float over = scrollX - rightBound;
1186                             needsInvalidate = mRightEdge.onPull(over / width);
1187                         }
1188                         scrollX = rightBound;
1189                     }
1190                     // Don't lose the rounded component
1191                     mLastMotionX += scrollX - (int) scrollX;
1192                     scrollTo((int) scrollX, getScrollY());
1193                     if (mOnPageChangeListener != null) {
1194                         final int position = (int) scrollX / widthWithMargin;
1195                         final int positionOffsetPixels = (int) scrollX % widthWithMargin;
1196                         final float positionOffset = (float) positionOffsetPixels / widthWithMargin;
1197                         mOnPageChangeListener.onPageScrolled(position, positionOffset,
1198                                 positionOffsetPixels);
1199                     }
1200                 }
1201                 break;
1202             case MotionEvent.ACTION_UP:
1203                 if (mIsBeingDragged) {
1204                     final VelocityTracker velocityTracker = mVelocityTracker;
1205                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1206                     int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
1207                             velocityTracker, mActivePointerId);
1208                     mPopulatePending = true;
1209                     final int widthWithMargin = getWidth() + mPageMargin;
1210                     final int scrollX = getScrollX();
1211                     final int currentPage = scrollX / widthWithMargin;
1212                     int nextPage = initialVelocity > 0 ? currentPage : currentPage + 1;
1213                     setCurrentItemInternal(nextPage, true, true, initialVelocity);
1214 
1215                     mActivePointerId = INVALID_POINTER;
1216                     endDrag();
1217                     needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
1218                 }
1219                 break;
1220             case MotionEvent.ACTION_CANCEL:
1221                 if (mIsBeingDragged) {
1222                     setCurrentItemInternal(mCurItem, true, true);
1223                     mActivePointerId = INVALID_POINTER;
1224                     endDrag();
1225                     needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
1226                 }
1227                 break;
1228             case MotionEventCompat.ACTION_POINTER_DOWN: {
1229                 final int index = MotionEventCompat.getActionIndex(ev);
1230                 final float x = MotionEventCompat.getX(ev, index);
1231                 mLastMotionX = x;
1232                 mActivePointerId = MotionEventCompat.getPointerId(ev, index);
1233                 break;
1234             }
1235             case MotionEventCompat.ACTION_POINTER_UP:
1236                 onSecondaryPointerUp(ev);
1237                 mLastMotionX = MotionEventCompat.getX(ev,
1238                         MotionEventCompat.findPointerIndex(ev, mActivePointerId));
1239                 break;
1240         }
1241         if (needsInvalidate) {
1242             invalidate();
1243         }
1244         return true;
1245     }
1246 
1247     @Override
1248     public void draw(Canvas canvas) {
1249         super.draw(canvas);
1250         boolean needsInvalidate = false;
1251 
1252         final int overScrollMode = ViewCompat.getOverScrollMode(this);
1253         if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
1254                 (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&
1255                         mAdapter != null && mAdapter.getCount() > 1)) {
1256             if (!mLeftEdge.isFinished()) {
1257                 final int restoreCount = canvas.save();
1258                 final int height = getHeight() - getPaddingTop() - getPaddingBottom();
1259 
1260                 canvas.rotate(270);
1261                 canvas.translate(-height + getPaddingTop(), 0);
1262                 mLeftEdge.setSize(height, getWidth());
1263                 needsInvalidate |= mLeftEdge.draw(canvas);
1264                 canvas.restoreToCount(restoreCount);
1265             }
1266             if (!mRightEdge.isFinished()) {
1267                 final int restoreCount = canvas.save();
1268                 final int width = getWidth();
1269                 final int height = getHeight() - getPaddingTop() - getPaddingBottom();
1270                 final int itemCount = mAdapter != null ? mAdapter.getCount() : 1;
1271 
1272                 canvas.rotate(90);
1273                 canvas.translate(-getPaddingTop(),
1274                         -itemCount * (width + mPageMargin) + mPageMargin);
1275                 mRightEdge.setSize(height, width);
1276                 needsInvalidate |= mRightEdge.draw(canvas);
1277                 canvas.restoreToCount(restoreCount);
1278             }
1279         } else {
1280             mLeftEdge.finish();
1281             mRightEdge.finish();
1282         }
1283 
1284         if (needsInvalidate) {
1285             // Keep animating
1286             invalidate();
1287         }
1288     }
1289 
1290     @Override
1291     protected void onDraw(Canvas canvas) {
1292         super.onDraw(canvas);
1293 
1294         // Draw the margin drawable if needed.
1295         if (mPageMargin > 0 && mMarginDrawable != null) {
1296             final int scrollX = getScrollX();
1297             final int width = getWidth();
1298             final int offset = scrollX % (width + mPageMargin);
1299             if (offset != 0) {
1300                 // Pages fit completely when settled; we only need to draw when in between
1301                 final int left = scrollX - offset + width;
1302                 mMarginDrawable.setBounds(left, 0, left + mPageMargin, getHeight());
1303                 mMarginDrawable.draw(canvas);
1304             }
1305         }
1306     }
1307 
1308     /**
1309      * Start a fake drag of the pager.
1310      *
1311      * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
1312      * with the touch scrolling of another view, while still letting the ViewPager
1313      * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
1314      * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
1315      * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
1316      *
1317      * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
1318      * is already in progress, this method will return false.
1319      *
1320      * @return true if the fake drag began successfully, false if it could not be started.
1321      *
1322      * @see #fakeDragBy(float)
1323      * @see #endFakeDrag()
1324      */
1325     public boolean beginFakeDrag() {
1326         if (mIsBeingDragged) {
1327             return false;
1328         }
1329         mFakeDragging = true;
1330         setScrollState(SCROLL_STATE_DRAGGING);
1331         mInitialMotionX = mLastMotionX = 0;
1332         if (mVelocityTracker == null) {
1333             mVelocityTracker = VelocityTracker.obtain();
1334         } else {
1335             mVelocityTracker.clear();
1336         }
1337         final long time = SystemClock.uptimeMillis();
1338         final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
1339         mVelocityTracker.addMovement(ev);
1340         ev.recycle();
1341         mFakeDragBeginTime = time;
1342         return true;
1343     }
1344 
1345     /**
1346      * End a fake drag of the pager.
1347      *
1348      * @see #beginFakeDrag()
1349      * @see #fakeDragBy(float)
1350      */
1351     public void endFakeDrag() {
1352         if (!mFakeDragging) {
1353             throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
1354         }
1355 
1356         final VelocityTracker velocityTracker = mVelocityTracker;
1357         velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1358         int initialVelocity = (int)VelocityTrackerCompat.getYVelocity(
1359                 velocityTracker, mActivePointerId);
1360         mPopulatePending = true;
1361         if ((Math.abs(initialVelocity) > mMinimumVelocity)
1362                 || Math.abs(mInitialMotionX-mLastMotionX) >= (getWidth()/3)) {
1363             if (mLastMotionX > mInitialMotionX) {
1364                 setCurrentItemInternal(mCurItem-1, true, true);
1365             } else {
1366                 setCurrentItemInternal(mCurItem+1, true, true);
1367             }
1368         } else {
1369             setCurrentItemInternal(mCurItem, true, true);
1370         }
1371         endDrag();
1372 
1373         mFakeDragging = false;
1374     }
1375 
1376     /**
1377      * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
1378      *
1379      * @param xOffset Offset in pixels to drag by.
1380      * @see #beginFakeDrag()
1381      * @see #endFakeDrag()
1382      */
1383     public void fakeDragBy(float xOffset) {
1384         if (!mFakeDragging) {
1385             throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
1386         }
1387 
1388         mLastMotionX += xOffset;
1389         float scrollX = getScrollX() - xOffset;
1390         final int width = getWidth();
1391         final int widthWithMargin = width + mPageMargin;
1392 
1393         final float leftBound = Math.max(0, (mCurItem - 1) * widthWithMargin);
1394         final float rightBound =
1395                 Math.min(mCurItem + 1, mAdapter.getCount() - 1) * widthWithMargin;
1396         if (scrollX < leftBound) {
1397             scrollX = leftBound;
1398         } else if (scrollX > rightBound) {
1399             scrollX = rightBound;
1400         }
1401         // Don't lose the rounded component
1402         mLastMotionX += scrollX - (int) scrollX;
1403         scrollTo((int) scrollX, getScrollY());
1404         if (mOnPageChangeListener != null) {
1405             final int position = (int) scrollX / widthWithMargin;
1406             final int positionOffsetPixels = (int) scrollX % widthWithMargin;
1407             final float positionOffset = (float) positionOffsetPixels / widthWithMargin;
1408             mOnPageChangeListener.onPageScrolled(position, positionOffset,
1409                     positionOffsetPixels);
1410         }
1411 
1412         // Synthesize an event for the VelocityTracker.
1413         final long time = SystemClock.uptimeMillis();
1414         final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
1415                 mLastMotionX, 0, 0);
1416         mVelocityTracker.addMovement(ev);
1417         ev.recycle();
1418     }
1419 
1420     /**
1421      * Returns true if a fake drag is in progress.
1422      *
1423      * @return true if currently in a fake drag, false otherwise.
1424      *
1425      * @see #beginFakeDrag()
1426      * @see #fakeDragBy(float)
1427      * @see #endFakeDrag()
1428      */
1429     public boolean isFakeDragging() {
1430         return mFakeDragging;
1431     }
1432 
1433     private void onSecondaryPointerUp(MotionEvent ev) {
1434         final int pointerIndex = MotionEventCompat.getActionIndex(ev);
1435         final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
1436         if (pointerId == mActivePointerId) {
1437             // This was our active pointer going up. Choose a new
1438             // active pointer and adjust accordingly.
1439             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1440             mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);
1441             mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
1442             if (mVelocityTracker != null) {
1443                 mVelocityTracker.clear();
1444             }
1445         }
1446     }
1447 
1448     private void endDrag() {
1449         mIsBeingDragged = false;
1450         mIsUnableToDrag = false;
1451 
1452         if (mVelocityTracker != null) {
1453             mVelocityTracker.recycle();
1454             mVelocityTracker = null;
1455         }
1456     }
1457 
1458     private void setScrollingCacheEnabled(boolean enabled) {
1459         if (mScrollingCacheEnabled != enabled) {
1460             mScrollingCacheEnabled = enabled;
1461             if (USE_CACHE) {
1462                 final int size = getChildCount();
1463                 for (int i = 0; i < size; ++i) {
1464                     final View child = getChildAt(i);
1465                     if (child.getVisibility() != GONE) {
1466                         child.setDrawingCacheEnabled(enabled);
1467                     }
1468                 }
1469             }
1470         }
1471     }
1472 
1473     /**
1474      * Tests scrollability within child views of v given a delta of dx.
1475      *
1476      * @param v View to test for horizontal scrollability
1477      * @param checkV Whether the view v passed should itself be checked for scrollability (true),
1478      *               or just its children (false).
1479      * @param dx Delta scrolled in pixels
1480      * @param x X coordinate of the active touch point
1481      * @param y Y coordinate of the active touch point
1482      * @return true if child views of v can be scrolled by delta of dx.
1483      */
1484     protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
1485         if (v instanceof ViewGroup) {
1486             final ViewGroup group = (ViewGroup) v;
1487             final int scrollX = v.getScrollX();
1488             final int scrollY = v.getScrollY();
1489             final int count = group.getChildCount();
1490             // Count backwards - let topmost views consume scroll distance first.
1491             for (int i = count - 1; i >= 0; i--) {
1492                 // TODO: Add versioned support here for transformed views.
1493                 // This will not work for transformed views in Honeycomb+
1494                 final View child = group.getChildAt(i);
1495                 if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
1496                         y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
1497                         canScroll(child, true, dx, x + scrollX - child.getLeft(),
1498                                 y + scrollY - child.getTop())) {
1499                     return true;
1500                 }
1501             }
1502         }
1503 
1504         return checkV && ViewCompat.canScrollHorizontally(v, -dx);
1505     }
1506 
1507     @Override
1508     public boolean dispatchKeyEvent(KeyEvent event) {
1509         // Let the focused view and/or our descendants get the key first
1510         return super.dispatchKeyEvent(event) || executeKeyEvent(event);
1511     }
1512 
1513     /**
1514      * You can call this function yourself to have the scroll view perform
1515      * scrolling from a key event, just as if the event had been dispatched to
1516      * it by the view hierarchy.
1517      *
1518      * @param event The key event to execute.
1519      * @return Return true if the event was handled, else false.
1520      */
1521     public boolean executeKeyEvent(KeyEvent event) {
1522         boolean handled = false;
1523         if (event.getAction() == KeyEvent.ACTION_DOWN) {
1524             switch (event.getKeyCode()) {
1525                 case KeyEvent.KEYCODE_DPAD_LEFT:
1526                     handled = arrowScroll(FOCUS_LEFT);
1527                     break;
1528                 case KeyEvent.KEYCODE_DPAD_RIGHT:
1529                     handled = arrowScroll(FOCUS_RIGHT);
1530                     break;
1531                 case KeyEvent.KEYCODE_TAB:
1532                     if (KeyEventCompat.hasNoModifiers(event)) {
1533                         handled = arrowScroll(FOCUS_FORWARD);
1534                     } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
1535                         handled = arrowScroll(FOCUS_BACKWARD);
1536                     }
1537                     break;
1538             }
1539         }
1540         return handled;
1541     }
1542 
1543     public boolean arrowScroll(int direction) {
1544         View currentFocused = findFocus();
1545         if (currentFocused == this) currentFocused = null;
1546 
1547         boolean handled = false;
1548 
1549         View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
1550                 direction);
1551         if (nextFocused != null && nextFocused != currentFocused) {
1552             if (direction == View.FOCUS_LEFT) {
1553                 // If there is nothing to the left, or this is causing us to
1554                 // jump to the right, then what we really want to do is page left.
1555                 if (currentFocused != null && nextFocused.getLeft() >= currentFocused.getLeft()) {
1556                     handled = pageLeft();
1557                 } else {
1558                     handled = nextFocused.requestFocus();
1559                 }
1560             } else if (direction == View.FOCUS_RIGHT) {
1561                 // If there is nothing to the right, or this is causing us to
1562                 // jump to the left, then what we really want to do is page right.
1563                 if (currentFocused != null && nextFocused.getLeft() <= currentFocused.getLeft()) {
1564                     handled = pageRight();
1565                 } else {
1566                     handled = nextFocused.requestFocus();
1567                 }
1568             }
1569         } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {
1570             // Trying to move left and nothing there; try to page.
1571             handled = pageLeft();
1572         } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {
1573             // Trying to move right and nothing there; try to page.
1574             handled = pageRight();
1575         }
1576         if (handled) {
1577             playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
1578         }
1579         return handled;
1580     }
1581 
1582     boolean pageLeft() {
1583         if (mCurItem > 0) {
1584             setCurrentItem(mCurItem-1, true);
1585             return true;
1586         }
1587         return false;
1588     }
1589 
1590     boolean pageRight() {
1591         if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) {
1592             setCurrentItem(mCurItem+1, true);
1593             return true;
1594         }
1595         return false;
1596     }
1597 
1598     /**
1599      * We only want the current page that is being shown to be focusable.
1600      */
1601     @Override
1602     public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1603         final int focusableCount = views.size();
1604 
1605         final int descendantFocusability = getDescendantFocusability();
1606 
1607         if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
1608             for (int i = 0; i < getChildCount(); i++) {
1609                 final View child = getChildAt(i);
1610                 if (child.getVisibility() == VISIBLE) {
1611                     ItemInfo ii = infoForChild(child);
1612                     if (ii != null && ii.position == mCurItem) {
1613                         child.addFocusables(views, direction, focusableMode);
1614                     }
1615                 }
1616             }
1617         }
1618 
1619         // we add ourselves (if focusable) in all cases except for when we are
1620         // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.  this is
1621         // to avoid the focus search finding layouts when a more precise search
1622         // among the focusable children would be more interesting.
1623         if (
1624                 descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
1625                         // No focusable descendants
1626                         (focusableCount == views.size())) {
1627             // Note that we can't call the superclass here, because it will
1628             // add all views in.  So we need to do the same thing View does.
1629             if (!isFocusable()) {
1630                 return;
1631             }
1632             if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
1633                     isInTouchMode() && !isFocusableInTouchMode()) {
1634                 return;
1635             }
1636             if (views != null) {
1637                 views.add(this);
1638             }
1639         }
1640     }
1641 
1642     /**
1643      * We only want the current page that is being shown to be touchable.
1644      */
1645     @Override
1646     public void addTouchables(ArrayList<View> views) {
1647         // Note that we don't call super.addTouchables(), which means that
1648         // we don't call View.addTouchables().  This is okay because a ViewPager
1649         // is itself not touchable.
1650         for (int i = 0; i < getChildCount(); i++) {
1651             final View child = getChildAt(i);
1652             if (child.getVisibility() == VISIBLE) {
1653                 ItemInfo ii = infoForChild(child);
1654                 if (ii != null && ii.position == mCurItem) {
1655                     child.addTouchables(views);
1656                 }
1657             }
1658         }
1659     }
1660 
1661     /**
1662      * We only want the current page that is being shown to be focusable.
1663      */
1664     @Override
1665     protected boolean onRequestFocusInDescendants(int direction,
1666                                                   Rect previouslyFocusedRect) {
1667         int index;
1668         int increment;
1669         int end;
1670         int count = getChildCount();
1671         if ((direction & FOCUS_FORWARD) != 0) {
1672             index = 0;
1673             increment = 1;
1674             end = count;
1675         } else {
1676             index = count - 1;
1677             increment = -1;
1678             end = -1;
1679         }
1680         for (int i = index; i != end; i += increment) {
1681             View child = getChildAt(i);
1682             if (child.getVisibility() == VISIBLE) {
1683                 ItemInfo ii = infoForChild(child);
1684                 if (ii != null && ii.position == mCurItem) {
1685                     if (child.requestFocus(direction, previouslyFocusedRect)) {
1686                         return true;
1687                     }
1688                 }
1689             }
1690         }
1691         return false;
1692     }
1693 
1694     @Override
1695     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
1696         // ViewPagers should only report accessibility info for the current page,
1697         // otherwise things get very confusing.
1698 
1699         // TODO: Should this note something about the paging container?
1700 
1701         final int childCount = getChildCount();
1702         for (int i = 0; i < childCount; i++) {
1703             final View child = getChildAt(i);
1704             if (child.getVisibility() == VISIBLE) {
1705                 final ItemInfo ii = infoForChild(child);
1706                 if (ii != null && ii.position == mCurItem &&
1707                         child.dispatchPopulateAccessibilityEvent(event)) {
1708                     return true;
1709                 }
1710             }
1711         }
1712 
1713         return false;
1714     }
1715 
1716     private class PagerObserver extends DataSetObserver {
1717 
1718         @Override
1719         public void onChanged() {
1720             dataSetChanged();
1721         }
1722 
1723         @Override
1724         public void onInvalidated() {
1725             dataSetChanged();
1726         }
1727     }
1728 }
NoPreloadViewPager
原文地址:https://www.cnblogs.com/Sharley/p/6061010.html