转自: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就能用了。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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 }