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