转自: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。对,就是这种解决方案!!但是!!
但是!!!但是什么呢?但是我试过,没用。为什么呢?接下来就是本文的重点了。
因为现在都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 *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.
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 ComparatorCOMPARATOR = new Comparator (){ 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 mItems = new ArrayList (); 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 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. 395 * 396 * You should keep this limit low, especially if your pages have complex layouts. 397 * This setting defaults to 1.
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; iendPos) && !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 CREATOR 727 = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks () { 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 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 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 vertical1056 // direction to be counted as a drag... abort1057 // any attempt to drag horizontally, to work correctly1058 // 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 + "," + mLastMotionY1087 + " mIsBeingDragged=" + mIsBeingDragged1088 + "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 the1099 * drag mode.1100 */1101 return mIsBeingDragged;1102 }1103 1104 @Override1105 public boolean onTouchEvent(MotionEvent ev) {1106 if (mFakeDragging) {1107 // A fake drag is in progress already, ignore this real one1108 // 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 our1115 // 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. isFinished1136 * will be false if being flinged.1137 */1138 completeScroll();1139 1140 // Remember where the motion event started1141 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 event1163 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 component1191 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 @Override1248 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 animating1286 invalidate();1287 }1288 }1289 1290 @Override1291 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 between1301 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 * A fake drag can be useful if you want to synchronize the motion of the ViewPager1312 * with the touch scrolling of another view, while still letting the ViewPager1313 * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)1314 * Call { @link #fakeDragBy(float)} to simulate the actual drag motion. Call1315 * { @link #endFakeDrag()} to complete the fake drag and fling as necessary.1316 *1317 *
During a fake drag the ViewPager will ignore all touch events. If a real drag1318 * 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 component1402 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 new1438 // 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 scrollability1477 * @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 pixels1480 * @param x X coordinate of the active touch point1481 * @param y Y coordinate of the active touch point1482 * @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 @Override1508 public boolean dispatchKeyEvent(KeyEvent event) {1509 // Let the focused view and/or our descendants get the key first1510 return super.dispatchKeyEvent(event) || executeKeyEvent(event);1511 }1512 1513 /**1514 * You can call this function yourself to have the scroll view perform1515 * scrolling from a key event, just as if the event had been dispatched to1516 * 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 to1554 // 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 to1562 // 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 @Override1602 public void addFocusables(ArrayList
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 are1620 // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is1621 // to avoid the focus search finding layouts when a more precise search1622 // among the focusable children would be more interesting.1623 if (1624 descendantFocusability != FOCUS_AFTER_DESCENDANTS ||1625 // No focusable descendants1626 (focusableCount == views.size())) {1627 // Note that we can't call the superclass here, because it will1628 // 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 @Override1646 public void addTouchables(ArrayList views) {1647 // Note that we don't call super.addTouchables(), which means that1648 // we don't call View.addTouchables(). This is okay because a ViewPager1649 // 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 @Override1665 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 @Override1695 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 @Override1719 public void onChanged() {1720 dataSetChanged();1721 }1722 1723 @Override1724 public void onInvalidated() {1725 dataSetChanged();1726 }1727 }1728 }