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