ViewPager.java revision 3040c142491acd4a09e7d0add073be0067aec2d5
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 setScrollState(SCROLL_STATE_IDLE); 361 return; 362 } 363 364 setScrollingCacheEnabled(true); 365 mScrolling = true; 366 setScrollState(SCROLL_STATE_SETTLING); 367 mScroller.startScroll(sx, sy, dx, dy); 368 invalidate(); 369 } 370 371 void addNewItem(int position, int index) { 372 ItemInfo ii = new ItemInfo(); 373 ii.position = position; 374 ii.object = mAdapter.instantiateItem(this, position); 375 if (index < 0) { 376 mItems.add(ii); 377 } else { 378 mItems.add(index, ii); 379 } 380 } 381 382 void dataSetChanged() { 383 // This method only gets called if our observer is attached, so mAdapter is non-null. 384 385 boolean needPopulate = mItems.size() < 3 && mItems.size() < mAdapter.getCount(); 386 int newCurrItem = -1; 387 388 for (int i = 0; i < mItems.size(); i++) { 389 final ItemInfo ii = mItems.get(i); 390 final int newPos = mAdapter.getItemPosition(ii.object); 391 392 if (newPos == PagerAdapter.POSITION_UNCHANGED) { 393 continue; 394 } 395 396 if (newPos == PagerAdapter.POSITION_NONE) { 397 mItems.remove(i); 398 i--; 399 mAdapter.destroyItem(this, ii.position, ii.object); 400 needPopulate = true; 401 402 if (mCurItem == ii.position) { 403 // Keep the current item in the valid range 404 newCurrItem = Math.max(0, Math.min(mCurItem, mAdapter.getCount() - 1)); 405 } 406 continue; 407 } 408 409 if (ii.position != newPos) { 410 if (ii.position == mCurItem) { 411 // Our current item changed position. Follow it. 412 newCurrItem = newPos; 413 } 414 415 ii.position = newPos; 416 needPopulate = true; 417 } 418 } 419 420 Collections.sort(mItems, COMPARATOR); 421 422 if (newCurrItem >= 0) { 423 // TODO This currently causes a jump. 424 setCurrentItemInternal(newCurrItem, false, true); 425 needPopulate = true; 426 } 427 if (needPopulate) { 428 populate(); 429 requestLayout(); 430 } 431 } 432 433 void populate() { 434 if (mAdapter == null) { 435 return; 436 } 437 438 // Bail now if we are waiting to populate. This is to hold off 439 // on creating views from the time the user releases their finger to 440 // fling to a new position until we have finished the scroll to 441 // that position, avoiding glitches from happening at that point. 442 if (mPopulatePending) { 443 if (DEBUG) Log.i(TAG, "populate is pending, skipping for now..."); 444 return; 445 } 446 447 // Also, don't populate until we are attached to a window. This is to 448 // avoid trying to populate before we have restored our view hierarchy 449 // state and conflicting with what is restored. 450 if (getWindowToken() == null) { 451 return; 452 } 453 454 mAdapter.startUpdate(this); 455 456 final int startPos = mCurItem > 0 ? mCurItem - 1 : mCurItem; 457 final int N = mAdapter.getCount(); 458 final int endPos = mCurItem < (N-1) ? mCurItem+1 : N-1; 459 460 if (DEBUG) Log.v(TAG, "populating: startPos=" + startPos + " endPos=" + endPos); 461 462 // Add and remove pages in the existing list. 463 int lastPos = -1; 464 for (int i=0; i<mItems.size(); i++) { 465 ItemInfo ii = mItems.get(i); 466 if ((ii.position < startPos || ii.position > endPos) && !ii.scrolling) { 467 if (DEBUG) Log.i(TAG, "removing: " + ii.position + " @ " + i); 468 mItems.remove(i); 469 i--; 470 mAdapter.destroyItem(this, ii.position, ii.object); 471 } else if (lastPos < endPos && ii.position > startPos) { 472 // The next item is outside of our range, but we have a gap 473 // between it and the last item where we want to have a page 474 // shown. Fill in the gap. 475 lastPos++; 476 if (lastPos < startPos) { 477 lastPos = startPos; 478 } 479 while (lastPos <= endPos && lastPos < ii.position) { 480 if (DEBUG) Log.i(TAG, "inserting: " + lastPos + " @ " + i); 481 addNewItem(lastPos, i); 482 lastPos++; 483 i++; 484 } 485 } 486 lastPos = ii.position; 487 } 488 489 // Add any new pages we need at the end. 490 lastPos = mItems.size() > 0 ? mItems.get(mItems.size()-1).position : -1; 491 if (lastPos < endPos) { 492 lastPos++; 493 lastPos = lastPos > startPos ? lastPos : startPos; 494 while (lastPos <= endPos) { 495 if (DEBUG) Log.i(TAG, "appending: " + lastPos); 496 addNewItem(lastPos, -1); 497 lastPos++; 498 } 499 } 500 501 if (DEBUG) { 502 Log.i(TAG, "Current page list:"); 503 for (int i=0; i<mItems.size(); i++) { 504 Log.i(TAG, "#" + i + ": page " + mItems.get(i).position); 505 } 506 } 507 508 ItemInfo curItem = null; 509 for (int i=0; i<mItems.size(); i++) { 510 if (mItems.get(i).position == mCurItem) { 511 curItem = mItems.get(i); 512 break; 513 } 514 } 515 mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null); 516 517 mAdapter.finishUpdate(this); 518 519 if (hasFocus()) { 520 View currentFocused = findFocus(); 521 ItemInfo ii = currentFocused != null ? infoForChild(currentFocused) : null; 522 if (ii == null || ii.position != mCurItem) { 523 for (int i=0; i<getChildCount(); i++) { 524 View child = getChildAt(i); 525 ii = infoForChild(child); 526 if (ii != null && ii.position == mCurItem) { 527 if (child.requestFocus(FOCUS_FORWARD)) { 528 break; 529 } 530 } 531 } 532 } 533 } 534 } 535 536 public static class SavedState extends BaseSavedState { 537 int position; 538 Parcelable adapterState; 539 ClassLoader loader; 540 541 public SavedState(Parcelable superState) { 542 super(superState); 543 } 544 545 @Override 546 public void writeToParcel(Parcel out, int flags) { 547 super.writeToParcel(out, flags); 548 out.writeInt(position); 549 out.writeParcelable(adapterState, flags); 550 } 551 552 @Override 553 public String toString() { 554 return "FragmentPager.SavedState{" 555 + Integer.toHexString(System.identityHashCode(this)) 556 + " position=" + position + "}"; 557 } 558 559 public static final Parcelable.Creator<SavedState> CREATOR 560 = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() { 561 @Override 562 public SavedState createFromParcel(Parcel in, ClassLoader loader) { 563 return new SavedState(in, loader); 564 } 565 @Override 566 public SavedState[] newArray(int size) { 567 return new SavedState[size]; 568 } 569 }); 570 571 SavedState(Parcel in, ClassLoader loader) { 572 super(in); 573 if (loader == null) { 574 loader = getClass().getClassLoader(); 575 } 576 position = in.readInt(); 577 adapterState = in.readParcelable(loader); 578 this.loader = loader; 579 } 580 } 581 582 @Override 583 public Parcelable onSaveInstanceState() { 584 Parcelable superState = super.onSaveInstanceState(); 585 SavedState ss = new SavedState(superState); 586 ss.position = mCurItem; 587 if (mAdapter != null) { 588 ss.adapterState = mAdapter.saveState(); 589 } 590 return ss; 591 } 592 593 @Override 594 public void onRestoreInstanceState(Parcelable state) { 595 if (!(state instanceof SavedState)) { 596 super.onRestoreInstanceState(state); 597 return; 598 } 599 600 SavedState ss = (SavedState)state; 601 super.onRestoreInstanceState(ss.getSuperState()); 602 603 if (mAdapter != null) { 604 mAdapter.restoreState(ss.adapterState, ss.loader); 605 setCurrentItemInternal(ss.position, false, true); 606 } else { 607 mRestoredCurItem = ss.position; 608 mRestoredAdapterState = ss.adapterState; 609 mRestoredClassLoader = ss.loader; 610 } 611 } 612 613 @Override 614 public void addView(View child, int index, LayoutParams params) { 615 if (mInLayout) { 616 addViewInLayout(child, index, params); 617 child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec); 618 } else { 619 super.addView(child, index, params); 620 } 621 622 if (USE_CACHE) { 623 if (child.getVisibility() != GONE) { 624 child.setDrawingCacheEnabled(mScrollingCacheEnabled); 625 } else { 626 child.setDrawingCacheEnabled(false); 627 } 628 } 629 } 630 631 ItemInfo infoForChild(View child) { 632 for (int i=0; i<mItems.size(); i++) { 633 ItemInfo ii = mItems.get(i); 634 if (mAdapter.isViewFromObject(child, ii.object)) { 635 return ii; 636 } 637 } 638 return null; 639 } 640 641 @Override 642 protected void onAttachedToWindow() { 643 super.onAttachedToWindow(); 644 mFirstLayout = true; 645 if (mAdapter != null) { 646 populate(); 647 } 648 } 649 650 @Override 651 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 652 // For simple implementation, or internal size is always 0. 653 // We depend on the container to specify the layout size of 654 // our view. We can't really know what it is since we will be 655 // adding and removing different arbitrary views and do not 656 // want the layout to change as this happens. 657 setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), 658 getDefaultSize(0, heightMeasureSpec)); 659 660 // Children are just made to fill our space. 661 mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - 662 getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY); 663 mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - 664 getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY); 665 666 // Make sure we have created all fragments that we need to have shown. 667 mInLayout = true; 668 populate(); 669 mInLayout = false; 670 671 // Make sure all children have been properly measured. 672 final int size = getChildCount(); 673 for (int i = 0; i < size; ++i) { 674 final View child = getChildAt(i); 675 if (child.getVisibility() != GONE) { 676 if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child 677 + ": " + mChildWidthMeasureSpec); 678 child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec); 679 } 680 } 681 } 682 683 @Override 684 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 685 super.onSizeChanged(w, h, oldw, oldh); 686 687 // Make sure scroll position is set correctly. 688 if (w != oldw) { 689 if (oldw > 0) { 690 final int oldScrollPos = getScrollX(); 691 final int oldScrollItem = oldScrollPos / oldw; 692 final float scrollOffset = (float) (oldScrollPos % oldw) / oldw; 693 final int scrollPos = (int) ((oldScrollItem + scrollOffset) * w); 694 scrollTo(scrollPos, getScrollY()); 695 if (!mScroller.isFinished()) { 696 // We now return to your regularly scheduled scroll, already in progress. 697 final int newDuration = mScroller.getDuration() - mScroller.timePassed(); 698 mScroller.startScroll(scrollPos, 0, mCurItem * w, 0, newDuration); 699 } 700 } else { 701 int scrollPos = mCurItem * w; 702 if (scrollPos != getScrollX()) { 703 completeScroll(); 704 scrollTo(scrollPos, getScrollY()); 705 } 706 } 707 } 708 } 709 710 @Override 711 protected void onLayout(boolean changed, int l, int t, int r, int b) { 712 mInLayout = true; 713 populate(); 714 mInLayout = false; 715 716 final int count = getChildCount(); 717 final int width = r-l; 718 719 for (int i = 0; i < count; i++) { 720 View child = getChildAt(i); 721 ItemInfo ii; 722 if (child.getVisibility() != GONE && (ii=infoForChild(child)) != null) { 723 int loff = width*ii.position; 724 int childLeft = getPaddingLeft() + loff; 725 int childTop = getPaddingTop(); 726 if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object 727 + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth() 728 + "x" + child.getMeasuredHeight()); 729 child.layout(childLeft, childTop, 730 childLeft + child.getMeasuredWidth(), 731 childTop + child.getMeasuredHeight()); 732 } 733 } 734 mFirstLayout = false; 735 } 736 737 @Override 738 public void computeScroll() { 739 if (DEBUG) Log.i(TAG, "computeScroll: finished=" + mScroller.isFinished()); 740 if (!mScroller.isFinished()) { 741 if (mScroller.computeScrollOffset()) { 742 if (DEBUG) Log.i(TAG, "computeScroll: still scrolling"); 743 int oldX = getScrollX(); 744 int oldY = getScrollY(); 745 int x = mScroller.getCurrX(); 746 int y = mScroller.getCurrY(); 747 748 if (oldX != x || oldY != y) { 749 scrollTo(x, y); 750 } 751 752 if (mOnPageChangeListener != null) { 753 final int width = getWidth(); 754 final int position = x / width; 755 final int offsetPixels = x % width; 756 final float offset = (float) offsetPixels / width; 757 mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels); 758 } 759 760 // Keep on drawing until the animation has finished. 761 invalidate(); 762 return; 763 } 764 } 765 766 // Done with scroll, clean up state. 767 completeScroll(); 768 } 769 770 private void completeScroll() { 771 boolean needPopulate = mScrolling; 772 if (needPopulate) { 773 // Done with scroll, no longer want to cache view drawing. 774 setScrollingCacheEnabled(false); 775 mScroller.abortAnimation(); 776 int oldX = getScrollX(); 777 int oldY = getScrollY(); 778 int x = mScroller.getCurrX(); 779 int y = mScroller.getCurrY(); 780 if (oldX != x || oldY != y) { 781 scrollTo(x, y); 782 } 783 setScrollState(SCROLL_STATE_IDLE); 784 } 785 mPopulatePending = false; 786 mScrolling = false; 787 for (int i=0; i<mItems.size(); i++) { 788 ItemInfo ii = mItems.get(i); 789 if (ii.scrolling) { 790 needPopulate = true; 791 ii.scrolling = false; 792 } 793 } 794 if (needPopulate) { 795 populate(); 796 } 797 } 798 799 @Override 800 public boolean onInterceptTouchEvent(MotionEvent ev) { 801 /* 802 * This method JUST determines whether we want to intercept the motion. 803 * If we return true, onMotionEvent will be called and we do the actual 804 * scrolling there. 805 */ 806 807 final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; 808 809 // Always take care of the touch gesture being complete. 810 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 811 // Release the drag. 812 if (DEBUG) Log.v(TAG, "Intercept done!"); 813 mIsBeingDragged = false; 814 mIsUnableToDrag = false; 815 mActivePointerId = INVALID_POINTER; 816 return false; 817 } 818 819 // Nothing more to do here if we have decided whether or not we 820 // are dragging. 821 if (action != MotionEvent.ACTION_DOWN) { 822 if (mIsBeingDragged) { 823 if (DEBUG) Log.v(TAG, "Intercept returning true!"); 824 return true; 825 } 826 if (mIsUnableToDrag) { 827 if (DEBUG) Log.v(TAG, "Intercept returning false!"); 828 return false; 829 } 830 } 831 832 switch (action) { 833 case MotionEvent.ACTION_MOVE: { 834 /* 835 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 836 * whether the user has moved far enough from his original down touch. 837 */ 838 839 /* 840 * Locally do absolute value. mLastMotionY is set to the y value 841 * of the down event. 842 */ 843 final int activePointerId = mActivePointerId; 844 if (activePointerId == INVALID_POINTER) { 845 // If we don't have a valid id, the touch down wasn't on content. 846 break; 847 } 848 849 final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId); 850 final float x = MotionEventCompat.getX(ev, pointerIndex); 851 final float dx = x - mLastMotionX; 852 final float xDiff = Math.abs(dx); 853 final float y = MotionEventCompat.getY(ev, pointerIndex); 854 final float yDiff = Math.abs(y - mLastMotionY); 855 final int scrollX = getScrollX(); 856 final boolean atEdge = (dx > 0 && scrollX == 0) || (dx < 0 && mAdapter != null && 857 scrollX >= (mAdapter.getCount() - 1) * getWidth() - 1); 858 if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); 859 860 if (atEdge || canScroll(this, false, (int) dx, (int) x, (int) y)) { 861 // Nested view has scrollable area under this point. Let it be handled there. 862 mInitialMotionX = mLastMotionX = x; 863 mLastMotionY = y; 864 return false; 865 } 866 if (xDiff > mTouchSlop && xDiff > yDiff) { 867 if (DEBUG) Log.v(TAG, "Starting drag!"); 868 mIsBeingDragged = true; 869 setScrollState(SCROLL_STATE_DRAGGING); 870 mLastMotionX = x; 871 setScrollingCacheEnabled(true); 872 } else { 873 if (yDiff > mTouchSlop) { 874 // The finger has moved enough in the vertical 875 // direction to be counted as a drag... abort 876 // any attempt to drag horizontally, to work correctly 877 // with children that have scrolling containers. 878 if (DEBUG) Log.v(TAG, "Starting unable to drag!"); 879 mIsUnableToDrag = true; 880 } 881 } 882 break; 883 } 884 885 case MotionEvent.ACTION_DOWN: { 886 /* 887 * Remember location of down touch. 888 * ACTION_DOWN always refers to pointer index 0. 889 */ 890 mLastMotionX = mInitialMotionX = ev.getX(); 891 mLastMotionY = ev.getY(); 892 mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 893 894 if (mScrollState == SCROLL_STATE_SETTLING) { 895 // Let the user 'catch' the pager as it animates. 896 mIsBeingDragged = true; 897 mIsUnableToDrag = false; 898 setScrollState(SCROLL_STATE_DRAGGING); 899 } else { 900 completeScroll(); 901 mIsBeingDragged = false; 902 mIsUnableToDrag = false; 903 } 904 905 if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY 906 + " mIsBeingDragged=" + mIsBeingDragged 907 + "mIsUnableToDrag=" + mIsUnableToDrag); 908 break; 909 } 910 911 case MotionEventCompat.ACTION_POINTER_UP: 912 onSecondaryPointerUp(ev); 913 break; 914 } 915 916 /* 917 * The only time we want to intercept motion events is if we are in the 918 * drag mode. 919 */ 920 return mIsBeingDragged; 921 } 922 923 @Override 924 public boolean onTouchEvent(MotionEvent ev) { 925 if (mFakeDragging) { 926 // A fake drag is in progress already, ignore this real one 927 // but still eat the touch events. 928 // (It is likely that the user is multi-touching the screen.) 929 return true; 930 } 931 932 if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) { 933 // Don't handle edge touches immediately -- they may actually belong to one of our 934 // descendants. 935 return false; 936 } 937 938 if (mAdapter == null || mAdapter.getCount() == 0) { 939 // Nothing to present or scroll; nothing to touch. 940 return false; 941 } 942 943 if (mVelocityTracker == null) { 944 mVelocityTracker = VelocityTracker.obtain(); 945 } 946 mVelocityTracker.addMovement(ev); 947 948 final int action = ev.getAction(); 949 950 switch (action & MotionEventCompat.ACTION_MASK) { 951 case MotionEvent.ACTION_DOWN: { 952 /* 953 * If being flinged and user touches, stop the fling. isFinished 954 * will be false if being flinged. 955 */ 956 completeScroll(); 957 958 // Remember where the motion event started 959 mLastMotionX = mInitialMotionX = ev.getX(); 960 mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 961 break; 962 } 963 case MotionEvent.ACTION_MOVE: 964 if (!mIsBeingDragged) { 965 final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 966 final float x = MotionEventCompat.getX(ev, pointerIndex); 967 final float xDiff = Math.abs(x - mLastMotionX); 968 final float y = MotionEventCompat.getY(ev, pointerIndex); 969 final float yDiff = Math.abs(y - mLastMotionY); 970 if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); 971 if (xDiff > mTouchSlop && xDiff > yDiff) { 972 if (DEBUG) Log.v(TAG, "Starting drag!"); 973 mIsBeingDragged = true; 974 mLastMotionX = x; 975 setScrollState(SCROLL_STATE_DRAGGING); 976 setScrollingCacheEnabled(true); 977 } 978 } 979 if (mIsBeingDragged) { 980 // Scroll to follow the motion event 981 final int activePointerIndex = MotionEventCompat.findPointerIndex( 982 ev, mActivePointerId); 983 final float x = MotionEventCompat.getX(ev, activePointerIndex); 984 final float deltaX = mLastMotionX - x; 985 mLastMotionX = x; 986 float scrollX = getScrollX() + deltaX; 987 final int width = getWidth(); 988 989 final float leftBound = Math.max(0, (mCurItem - 1) * width); 990 final float rightBound = 991 Math.min(mCurItem + 1, mAdapter.getCount() - 1) * width; 992 if (scrollX < leftBound) { 993 scrollX = leftBound; 994 } else if (scrollX > rightBound) { 995 scrollX = rightBound; 996 } 997 // Don't lose the rounded component 998 mLastMotionX += scrollX - (int) scrollX; 999 scrollTo((int) scrollX, getScrollY()); 1000 if (mOnPageChangeListener != null) { 1001 final int position = (int) scrollX / width; 1002 final int positionOffsetPixels = (int) scrollX % width; 1003 final float positionOffset = (float) positionOffsetPixels / width; 1004 mOnPageChangeListener.onPageScrolled(position, positionOffset, 1005 positionOffsetPixels); 1006 } 1007 } 1008 break; 1009 case MotionEvent.ACTION_UP: 1010 if (mIsBeingDragged) { 1011 final VelocityTracker velocityTracker = mVelocityTracker; 1012 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1013 int initialVelocity = (int)VelocityTrackerCompat.getYVelocity( 1014 velocityTracker, mActivePointerId); 1015 mPopulatePending = true; 1016 if ((Math.abs(initialVelocity) > mMinimumVelocity) 1017 || Math.abs(mInitialMotionX-mLastMotionX) >= (getWidth()/3)) { 1018 if (mLastMotionX > mInitialMotionX) { 1019 setCurrentItemInternal(mCurItem-1, true, true); 1020 } else { 1021 setCurrentItemInternal(mCurItem+1, true, true); 1022 } 1023 } else { 1024 setCurrentItemInternal(mCurItem, true, true); 1025 } 1026 1027 mActivePointerId = INVALID_POINTER; 1028 endDrag(); 1029 } 1030 break; 1031 case MotionEvent.ACTION_CANCEL: 1032 if (mIsBeingDragged) { 1033 setCurrentItemInternal(mCurItem, true, true); 1034 mActivePointerId = INVALID_POINTER; 1035 endDrag(); 1036 } 1037 break; 1038 case MotionEventCompat.ACTION_POINTER_DOWN: { 1039 final int index = MotionEventCompat.getActionIndex(ev); 1040 final float x = MotionEventCompat.getX(ev, index); 1041 mLastMotionX = x; 1042 mActivePointerId = MotionEventCompat.getPointerId(ev, index); 1043 break; 1044 } 1045 case MotionEventCompat.ACTION_POINTER_UP: 1046 onSecondaryPointerUp(ev); 1047 mLastMotionX = MotionEventCompat.getX(ev, 1048 MotionEventCompat.findPointerIndex(ev, mActivePointerId)); 1049 break; 1050 } 1051 return true; 1052 } 1053 1054 /** 1055 * Start a fake drag of the pager. 1056 * 1057 * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager 1058 * with the touch scrolling of another view, while still letting the ViewPager 1059 * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.) 1060 * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call 1061 * {@link #endFakeDrag()} to complete the fake drag and fling as necessary. 1062 * 1063 * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag 1064 * is already in progress, this method will return false. 1065 * 1066 * @return true if the fake drag began successfully, false if it could not be started. 1067 * 1068 * @see #fakeDragBy(float) 1069 * @see #endFakeDrag() 1070 */ 1071 public boolean beginFakeDrag() { 1072 if (mIsBeingDragged) { 1073 return false; 1074 } 1075 mFakeDragging = true; 1076 setScrollState(SCROLL_STATE_DRAGGING); 1077 mInitialMotionX = mLastMotionX = 0; 1078 if (mVelocityTracker == null) { 1079 mVelocityTracker = VelocityTracker.obtain(); 1080 } else { 1081 mVelocityTracker.clear(); 1082 } 1083 final long time = SystemClock.uptimeMillis(); 1084 final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0); 1085 mVelocityTracker.addMovement(ev); 1086 ev.recycle(); 1087 mFakeDragBeginTime = time; 1088 return true; 1089 } 1090 1091 /** 1092 * End a fake drag of the pager. 1093 * 1094 * @see #beginFakeDrag() 1095 * @see #fakeDragBy(float) 1096 */ 1097 public void endFakeDrag() { 1098 if (!mFakeDragging) { 1099 throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); 1100 } 1101 1102 final VelocityTracker velocityTracker = mVelocityTracker; 1103 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1104 int initialVelocity = (int)VelocityTrackerCompat.getYVelocity( 1105 velocityTracker, mActivePointerId); 1106 mPopulatePending = true; 1107 if ((Math.abs(initialVelocity) > mMinimumVelocity) 1108 || Math.abs(mInitialMotionX-mLastMotionX) >= (getWidth()/3)) { 1109 if (mLastMotionX > mInitialMotionX) { 1110 setCurrentItemInternal(mCurItem-1, true, true); 1111 } else { 1112 setCurrentItemInternal(mCurItem+1, true, true); 1113 } 1114 } else { 1115 setCurrentItemInternal(mCurItem, true, true); 1116 } 1117 endDrag(); 1118 1119 mFakeDragging = false; 1120 } 1121 1122 /** 1123 * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first. 1124 * 1125 * @param xOffset Offset in pixels to drag by. 1126 * @see #beginFakeDrag() 1127 * @see #endFakeDrag() 1128 */ 1129 public void fakeDragBy(float xOffset) { 1130 if (!mFakeDragging) { 1131 throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); 1132 } 1133 1134 mLastMotionX += xOffset; 1135 float scrollX = getScrollX() - xOffset; 1136 final int width = getWidth(); 1137 1138 final float leftBound = Math.max(0, (mCurItem - 1) * width); 1139 final float rightBound = 1140 Math.min(mCurItem + 1, mAdapter.getCount() - 1) * width; 1141 if (scrollX < leftBound) { 1142 scrollX = leftBound; 1143 } else if (scrollX > rightBound) { 1144 scrollX = rightBound; 1145 } 1146 // Don't lose the rounded component 1147 mLastMotionX += scrollX - (int) scrollX; 1148 scrollTo((int) scrollX, getScrollY()); 1149 if (mOnPageChangeListener != null) { 1150 final int position = (int) scrollX / width; 1151 final int positionOffsetPixels = (int) scrollX % width; 1152 final float positionOffset = (float) positionOffsetPixels / width; 1153 mOnPageChangeListener.onPageScrolled(position, positionOffset, 1154 positionOffsetPixels); 1155 } 1156 1157 // Synthesize an event for the VelocityTracker. 1158 final long time = SystemClock.uptimeMillis(); 1159 final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE, 1160 mLastMotionX, 0, 0); 1161 mVelocityTracker.addMovement(ev); 1162 ev.recycle(); 1163 } 1164 1165 /** 1166 * Returns true if a fake drag is in progress. 1167 * 1168 * @return true if currently in a fake drag, false otherwise. 1169 * 1170 * @see #beginFakeDrag() 1171 * @see #fakeDragBy(float) 1172 * @see #endFakeDrag() 1173 */ 1174 public boolean isFakeDragging() { 1175 return mFakeDragging; 1176 } 1177 1178 private void onSecondaryPointerUp(MotionEvent ev) { 1179 final int pointerIndex = MotionEventCompat.getActionIndex(ev); 1180 final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 1181 if (pointerId == mActivePointerId) { 1182 // This was our active pointer going up. Choose a new 1183 // active pointer and adjust accordingly. 1184 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 1185 mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex); 1186 mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); 1187 if (mVelocityTracker != null) { 1188 mVelocityTracker.clear(); 1189 } 1190 } 1191 } 1192 1193 private void endDrag() { 1194 mIsBeingDragged = false; 1195 mIsUnableToDrag = false; 1196 1197 if (mVelocityTracker != null) { 1198 mVelocityTracker.recycle(); 1199 mVelocityTracker = null; 1200 } 1201 } 1202 1203 private void setScrollingCacheEnabled(boolean enabled) { 1204 if (mScrollingCacheEnabled != enabled) { 1205 mScrollingCacheEnabled = enabled; 1206 if (USE_CACHE) { 1207 final int size = getChildCount(); 1208 for (int i = 0; i < size; ++i) { 1209 final View child = getChildAt(i); 1210 if (child.getVisibility() != GONE) { 1211 child.setDrawingCacheEnabled(enabled); 1212 } 1213 } 1214 } 1215 } 1216 } 1217 1218 /** 1219 * Tests scrollability within child views of v given a delta of dx. 1220 * 1221 * @param v View to test for horizontal scrollability 1222 * @param checkV Whether the view v passed should itself be checked for scrollability (true), 1223 * or just its children (false). 1224 * @param dx Delta scrolled in pixels 1225 * @param x X coordinate of the active touch point 1226 * @param y Y coordinate of the active touch point 1227 * @return true if child views of v can be scrolled by delta of dx. 1228 */ 1229 protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { 1230 if (v instanceof ViewGroup) { 1231 final ViewGroup group = (ViewGroup) v; 1232 final int scrollX = v.getScrollX(); 1233 final int scrollY = v.getScrollY(); 1234 final int count = group.getChildCount(); 1235 // Count backwards - let topmost views consume scroll distance first. 1236 for (int i = count - 1; i >= 0; i--) { 1237 // TODO: Add versioned support here for transformed views. 1238 // This will not work for transformed views in Honeycomb+ 1239 final View child = group.getChildAt(i); 1240 if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && 1241 y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && 1242 canScroll(child, true, dx, x + scrollX - child.getLeft(), 1243 y + scrollY - child.getTop())) { 1244 return true; 1245 } 1246 } 1247 } 1248 1249 return checkV && ViewCompat.canScrollHorizontally(v, -dx); 1250 } 1251 1252 @Override 1253 public boolean dispatchKeyEvent(KeyEvent event) { 1254 // Let the focused view and/or our descendants get the key first 1255 return super.dispatchKeyEvent(event) || executeKeyEvent(event); 1256 } 1257 1258 /** 1259 * You can call this function yourself to have the scroll view perform 1260 * scrolling from a key event, just as if the event had been dispatched to 1261 * it by the view hierarchy. 1262 * 1263 * @param event The key event to execute. 1264 * @return Return true if the event was handled, else false. 1265 */ 1266 public boolean executeKeyEvent(KeyEvent event) { 1267 boolean handled = false; 1268 if (event.getAction() == KeyEvent.ACTION_DOWN) { 1269 switch (event.getKeyCode()) { 1270 case KeyEvent.KEYCODE_DPAD_LEFT: 1271 handled = arrowScroll(FOCUS_LEFT); 1272 break; 1273 case KeyEvent.KEYCODE_DPAD_RIGHT: 1274 handled = arrowScroll(FOCUS_RIGHT); 1275 break; 1276 case KeyEvent.KEYCODE_TAB: 1277 if (KeyEventCompat.hasNoModifiers(event)) { 1278 handled = arrowScroll(FOCUS_FORWARD); 1279 } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) { 1280 handled = arrowScroll(FOCUS_BACKWARD); 1281 } 1282 break; 1283 } 1284 } 1285 return handled; 1286 } 1287 1288 public boolean arrowScroll(int direction) { 1289 View currentFocused = findFocus(); 1290 if (currentFocused == this) currentFocused = null; 1291 1292 boolean handled = false; 1293 1294 View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, 1295 direction); 1296 if (nextFocused != null && nextFocused != currentFocused) { 1297 if (direction == View.FOCUS_LEFT) { 1298 // If there is nothing to the left, or this is causing us to 1299 // jump to the right, then what we really want to do is page left. 1300 if (currentFocused != null && nextFocused.getLeft() >= currentFocused.getLeft()) { 1301 handled = pageLeft(); 1302 } else { 1303 handled = nextFocused.requestFocus(); 1304 } 1305 } else if (direction == View.FOCUS_RIGHT) { 1306 // If there is nothing to the right, or this is causing us to 1307 // jump to the left, then what we really want to do is page right. 1308 if (currentFocused != null && nextFocused.getLeft() <= currentFocused.getLeft()) { 1309 handled = pageRight(); 1310 } else { 1311 handled = nextFocused.requestFocus(); 1312 } 1313 } 1314 } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) { 1315 // Trying to move left and nothing there; try to page. 1316 handled = pageLeft(); 1317 } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) { 1318 // Trying to move right and nothing there; try to page. 1319 handled = pageRight(); 1320 } 1321 if (handled) { 1322 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); 1323 } 1324 return handled; 1325 } 1326 1327 boolean pageLeft() { 1328 if (mCurItem > 0) { 1329 setCurrentItem(mCurItem-1, true); 1330 return true; 1331 } 1332 return false; 1333 } 1334 1335 boolean pageRight() { 1336 if (mCurItem < (mAdapter.getCount()-1)) { 1337 setCurrentItem(mCurItem+1, true); 1338 return true; 1339 } 1340 return false; 1341 } 1342 1343 /** 1344 * We only want the current page that is being shown to be focusable. 1345 */ 1346 @Override 1347 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 1348 final int focusableCount = views.size(); 1349 1350 final int descendantFocusability = getDescendantFocusability(); 1351 1352 if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { 1353 for (int i = 0; i < getChildCount(); i++) { 1354 final View child = getChildAt(i); 1355 if (child.getVisibility() == VISIBLE) { 1356 ItemInfo ii = infoForChild(child); 1357 if (ii != null && ii.position == mCurItem) { 1358 child.addFocusables(views, direction, focusableMode); 1359 } 1360 } 1361 } 1362 } 1363 1364 // we add ourselves (if focusable) in all cases except for when we are 1365 // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is 1366 // to avoid the focus search finding layouts when a more precise search 1367 // among the focusable children would be more interesting. 1368 if ( 1369 descendantFocusability != FOCUS_AFTER_DESCENDANTS || 1370 // No focusable descendants 1371 (focusableCount == views.size())) { 1372 // Note that we can't call the superclass here, because it will 1373 // add all views in. So we need to do the same thing View does. 1374 if (!isFocusable()) { 1375 return; 1376 } 1377 if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE && 1378 isInTouchMode() && !isFocusableInTouchMode()) { 1379 return; 1380 } 1381 if (views != null) { 1382 views.add(this); 1383 } 1384 } 1385 } 1386 1387 /** 1388 * We only want the current page that is being shown to be touchable. 1389 */ 1390 @Override 1391 public void addTouchables(ArrayList<View> views) { 1392 // Note that we don't call super.addTouchables(), which means that 1393 // we don't call View.addTouchables(). This is okay because a ViewPager 1394 // is itself not touchable. 1395 for (int i = 0; i < getChildCount(); i++) { 1396 final View child = getChildAt(i); 1397 if (child.getVisibility() == VISIBLE) { 1398 ItemInfo ii = infoForChild(child); 1399 if (ii != null && ii.position == mCurItem) { 1400 child.addTouchables(views); 1401 } 1402 } 1403 } 1404 } 1405 1406 /** 1407 * We only want the current page that is being shown to be focusable. 1408 */ 1409 @Override 1410 protected boolean onRequestFocusInDescendants(int direction, 1411 Rect previouslyFocusedRect) { 1412 int index; 1413 int increment; 1414 int end; 1415 int count = getChildCount(); 1416 if ((direction & FOCUS_FORWARD) != 0) { 1417 index = 0; 1418 increment = 1; 1419 end = count; 1420 } else { 1421 index = count - 1; 1422 increment = -1; 1423 end = -1; 1424 } 1425 for (int i = index; i != end; i += increment) { 1426 View child = getChildAt(i); 1427 if (child.getVisibility() == VISIBLE) { 1428 ItemInfo ii = infoForChild(child); 1429 if (ii != null && ii.position == mCurItem) { 1430 if (child.requestFocus(direction, previouslyFocusedRect)) { 1431 return true; 1432 } 1433 } 1434 } 1435 } 1436 return false; 1437 } 1438 1439 @Override 1440 public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { 1441 // Scroll to the page that contains the child. 1442 final ItemInfo ii = infoForChild(child); 1443 if (ii != null) { 1444 setCurrentItem(ii.position, !immediate); 1445 return true; 1446 } 1447 return false; 1448 } 1449 1450 @Override 1451 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 1452 // ViewPagers should only report accessibility info for the current page, 1453 // otherwise things get very confusing. 1454 1455 // TODO: Should this note something about the paging container? 1456 1457 final int childCount = getChildCount(); 1458 for (int i = 0; i < childCount; i++) { 1459 final View child = getChildAt(i); 1460 if (child.getVisibility() == VISIBLE) { 1461 final ItemInfo ii = infoForChild(child); 1462 if (ii != null && ii.position == mCurItem && 1463 child.dispatchPopulateAccessibilityEvent(event)) { 1464 return true; 1465 } 1466 } 1467 } 1468 1469 return false; 1470 } 1471 1472 private class DataSetObserver implements PagerAdapter.DataSetObserver { 1473 @Override 1474 public void onDataSetChanged() { 1475 dataSetChanged(); 1476 } 1477 } 1478} 1479