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