ViewPager.java revision f98be2efe67cf4ce160d7ea0b1fba5b46c92a01b
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 } 666 667 @Override 668 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 669 // For simple implementation, or internal size is always 0. 670 // We depend on the container to specify the layout size of 671 // our view. We can't really know what it is since we will be 672 // adding and removing different arbitrary views and do not 673 // want the layout to change as this happens. 674 setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), 675 getDefaultSize(0, heightMeasureSpec)); 676 677 // Children are just made to fill our space. 678 mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - 679 getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY); 680 mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - 681 getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY); 682 683 // Make sure we have created all fragments that we need to have shown. 684 mInLayout = true; 685 populate(); 686 mInLayout = false; 687 688 // Make sure all children have been properly measured. 689 final int size = getChildCount(); 690 for (int i = 0; i < size; ++i) { 691 final View child = getChildAt(i); 692 if (child.getVisibility() != GONE) { 693 if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child 694 + ": " + mChildWidthMeasureSpec); 695 child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec); 696 } 697 } 698 } 699 700 @Override 701 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 702 super.onSizeChanged(w, h, oldw, oldh); 703 704 // Make sure scroll position is set correctly. 705 if (w != oldw) { 706 if (oldw > 0) { 707 final int oldScrollPos = getScrollX(); 708 final int oldScrollItem = oldScrollPos / oldw; 709 final float scrollOffset = (float) (oldScrollPos % oldw) / oldw; 710 final int scrollPos = (int) ((oldScrollItem + scrollOffset) * w); 711 scrollTo(scrollPos, getScrollY()); 712 if (!mScroller.isFinished()) { 713 // We now return to your regularly scheduled scroll, already in progress. 714 final int newDuration = mScroller.getDuration() - mScroller.timePassed(); 715 mScroller.startScroll(scrollPos, 0, mCurItem * w, 0, newDuration); 716 } 717 } else { 718 int scrollPos = mCurItem * w; 719 if (scrollPos != getScrollX()) { 720 completeScroll(); 721 scrollTo(scrollPos, getScrollY()); 722 } 723 } 724 } 725 } 726 727 @Override 728 protected void onLayout(boolean changed, int l, int t, int r, int b) { 729 mInLayout = true; 730 populate(); 731 mInLayout = false; 732 733 final int count = getChildCount(); 734 final int width = r-l; 735 736 for (int i = 0; i < count; i++) { 737 View child = getChildAt(i); 738 ItemInfo ii; 739 if (child.getVisibility() != GONE && (ii=infoForChild(child)) != null) { 740 int loff = width*ii.position; 741 int childLeft = getPaddingLeft() + loff; 742 int childTop = getPaddingTop(); 743 if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object 744 + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth() 745 + "x" + child.getMeasuredHeight()); 746 child.layout(childLeft, childTop, 747 childLeft + child.getMeasuredWidth(), 748 childTop + child.getMeasuredHeight()); 749 } 750 } 751 mFirstLayout = false; 752 } 753 754 @Override 755 public void computeScroll() { 756 if (DEBUG) Log.i(TAG, "computeScroll: finished=" + mScroller.isFinished()); 757 if (!mScroller.isFinished()) { 758 if (mScroller.computeScrollOffset()) { 759 if (DEBUG) Log.i(TAG, "computeScroll: still scrolling"); 760 int oldX = getScrollX(); 761 int oldY = getScrollY(); 762 int x = mScroller.getCurrX(); 763 int y = mScroller.getCurrY(); 764 765 if (oldX != x || oldY != y) { 766 scrollTo(x, y); 767 } 768 769 if (mOnPageChangeListener != null) { 770 final int width = getWidth(); 771 final int position = x / width; 772 final int offsetPixels = x % width; 773 final float offset = (float) offsetPixels / width; 774 mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels); 775 } 776 777 // Keep on drawing until the animation has finished. 778 invalidate(); 779 return; 780 } 781 } 782 783 // Done with scroll, clean up state. 784 completeScroll(); 785 } 786 787 private void completeScroll() { 788 boolean needPopulate = mScrolling; 789 if (needPopulate) { 790 // Done with scroll, no longer want to cache view drawing. 791 setScrollingCacheEnabled(false); 792 mScroller.abortAnimation(); 793 int oldX = getScrollX(); 794 int oldY = getScrollY(); 795 int x = mScroller.getCurrX(); 796 int y = mScroller.getCurrY(); 797 if (oldX != x || oldY != y) { 798 scrollTo(x, y); 799 } 800 setScrollState(SCROLL_STATE_IDLE); 801 } 802 mPopulatePending = false; 803 mScrolling = false; 804 for (int i=0; i<mItems.size(); i++) { 805 ItemInfo ii = mItems.get(i); 806 if (ii.scrolling) { 807 needPopulate = true; 808 ii.scrolling = false; 809 } 810 } 811 if (needPopulate) { 812 populate(); 813 } 814 } 815 816 @Override 817 public boolean onInterceptTouchEvent(MotionEvent ev) { 818 /* 819 * This method JUST determines whether we want to intercept the motion. 820 * If we return true, onMotionEvent will be called and we do the actual 821 * scrolling there. 822 */ 823 824 final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; 825 826 // Always take care of the touch gesture being complete. 827 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 828 // Release the drag. 829 if (DEBUG) Log.v(TAG, "Intercept done!"); 830 mIsBeingDragged = false; 831 mIsUnableToDrag = false; 832 mActivePointerId = INVALID_POINTER; 833 return false; 834 } 835 836 // Nothing more to do here if we have decided whether or not we 837 // are dragging. 838 if (action != MotionEvent.ACTION_DOWN) { 839 if (mIsBeingDragged) { 840 if (DEBUG) Log.v(TAG, "Intercept returning true!"); 841 return true; 842 } 843 if (mIsUnableToDrag) { 844 if (DEBUG) Log.v(TAG, "Intercept returning false!"); 845 return false; 846 } 847 } 848 849 switch (action) { 850 case MotionEvent.ACTION_MOVE: { 851 /* 852 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 853 * whether the user has moved far enough from his original down touch. 854 */ 855 856 /* 857 * Locally do absolute value. mLastMotionY is set to the y value 858 * of the down event. 859 */ 860 final int activePointerId = mActivePointerId; 861 if (activePointerId == INVALID_POINTER) { 862 // If we don't have a valid id, the touch down wasn't on content. 863 break; 864 } 865 866 final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId); 867 final float x = MotionEventCompat.getX(ev, pointerIndex); 868 final float dx = x - mLastMotionX; 869 final float xDiff = Math.abs(dx); 870 final float y = MotionEventCompat.getY(ev, pointerIndex); 871 final float yDiff = Math.abs(y - mLastMotionY); 872 final int scrollX = getScrollX(); 873 final boolean atEdge = (dx > 0 && scrollX == 0) || (dx < 0 && mAdapter != null && 874 scrollX >= (mAdapter.getCount() - 1) * getWidth() - 1); 875 if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); 876 877 if (canScroll(this, false, (int) dx, (int) x, (int) y)) { 878 // Nested view has scrollable area under this point. Let it be handled there. 879 mInitialMotionX = mLastMotionX = x; 880 mLastMotionY = y; 881 return false; 882 } 883 if (xDiff > mTouchSlop && xDiff > yDiff) { 884 if (DEBUG) Log.v(TAG, "Starting drag!"); 885 mIsBeingDragged = true; 886 setScrollState(SCROLL_STATE_DRAGGING); 887 mLastMotionX = x; 888 setScrollingCacheEnabled(true); 889 } else { 890 if (yDiff > mTouchSlop) { 891 // The finger has moved enough in the vertical 892 // direction to be counted as a drag... abort 893 // any attempt to drag horizontally, to work correctly 894 // with children that have scrolling containers. 895 if (DEBUG) Log.v(TAG, "Starting unable to drag!"); 896 mIsUnableToDrag = true; 897 } 898 } 899 break; 900 } 901 902 case MotionEvent.ACTION_DOWN: { 903 /* 904 * Remember location of down touch. 905 * ACTION_DOWN always refers to pointer index 0. 906 */ 907 mLastMotionX = mInitialMotionX = ev.getX(); 908 mLastMotionY = ev.getY(); 909 mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 910 911 if (mScrollState == SCROLL_STATE_SETTLING) { 912 // Let the user 'catch' the pager as it animates. 913 mIsBeingDragged = true; 914 mIsUnableToDrag = false; 915 setScrollState(SCROLL_STATE_DRAGGING); 916 } else { 917 completeScroll(); 918 mIsBeingDragged = false; 919 mIsUnableToDrag = false; 920 } 921 922 if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY 923 + " mIsBeingDragged=" + mIsBeingDragged 924 + "mIsUnableToDrag=" + mIsUnableToDrag); 925 break; 926 } 927 928 case MotionEventCompat.ACTION_POINTER_UP: 929 onSecondaryPointerUp(ev); 930 break; 931 } 932 933 /* 934 * The only time we want to intercept motion events is if we are in the 935 * drag mode. 936 */ 937 return mIsBeingDragged; 938 } 939 940 @Override 941 public boolean onTouchEvent(MotionEvent ev) { 942 if (mFakeDragging) { 943 // A fake drag is in progress already, ignore this real one 944 // but still eat the touch events. 945 // (It is likely that the user is multi-touching the screen.) 946 return true; 947 } 948 949 if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) { 950 // Don't handle edge touches immediately -- they may actually belong to one of our 951 // descendants. 952 return false; 953 } 954 955 if (mAdapter == null || mAdapter.getCount() == 0) { 956 // Nothing to present or scroll; nothing to touch. 957 return false; 958 } 959 960 if (mVelocityTracker == null) { 961 mVelocityTracker = VelocityTracker.obtain(); 962 } 963 mVelocityTracker.addMovement(ev); 964 965 final int action = ev.getAction(); 966 boolean needsInvalidate = false; 967 968 switch (action & MotionEventCompat.ACTION_MASK) { 969 case MotionEvent.ACTION_DOWN: { 970 /* 971 * If being flinged and user touches, stop the fling. isFinished 972 * will be false if being flinged. 973 */ 974 completeScroll(); 975 976 // Remember where the motion event started 977 mLastMotionX = mInitialMotionX = ev.getX(); 978 mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 979 break; 980 } 981 case MotionEvent.ACTION_MOVE: 982 if (!mIsBeingDragged) { 983 final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 984 final float x = MotionEventCompat.getX(ev, pointerIndex); 985 final float xDiff = Math.abs(x - mLastMotionX); 986 final float y = MotionEventCompat.getY(ev, pointerIndex); 987 final float yDiff = Math.abs(y - mLastMotionY); 988 if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); 989 if (xDiff > mTouchSlop && xDiff > yDiff) { 990 if (DEBUG) Log.v(TAG, "Starting drag!"); 991 mIsBeingDragged = true; 992 mLastMotionX = x; 993 setScrollState(SCROLL_STATE_DRAGGING); 994 setScrollingCacheEnabled(true); 995 } 996 } 997 if (mIsBeingDragged) { 998 // Scroll to follow the motion event 999 final int activePointerIndex = MotionEventCompat.findPointerIndex( 1000 ev, mActivePointerId); 1001 final float x = MotionEventCompat.getX(ev, activePointerIndex); 1002 final float deltaX = mLastMotionX - x; 1003 mLastMotionX = x; 1004 float oldScrollX = getScrollX(); 1005 float scrollX = oldScrollX + deltaX; 1006 final int width = getWidth(); 1007 1008 final int lastItemIndex = mAdapter.getCount() - 1; 1009 final float leftBound = Math.max(0, (mCurItem - 1) * width); 1010 final float rightBound = Math.min(mCurItem + 1, lastItemIndex) * width; 1011 if (scrollX < leftBound) { 1012 if (leftBound == 0) { 1013 float over = -scrollX; 1014 needsInvalidate = mLeftEdge.onPull(over / width); 1015 } 1016 scrollX = leftBound; 1017 } else if (scrollX > rightBound) { 1018 if (rightBound == lastItemIndex * width) { 1019 float over = scrollX - rightBound; 1020 needsInvalidate = mRightEdge.onPull(over / width); 1021 } 1022 scrollX = rightBound; 1023 } 1024 // Don't lose the rounded component 1025 mLastMotionX += scrollX - (int) scrollX; 1026 scrollTo((int) scrollX, getScrollY()); 1027 if (mOnPageChangeListener != null) { 1028 final int position = (int) scrollX / width; 1029 final int positionOffsetPixels = (int) scrollX % width; 1030 final float positionOffset = (float) positionOffsetPixels / width; 1031 mOnPageChangeListener.onPageScrolled(position, positionOffset, 1032 positionOffsetPixels); 1033 } 1034 } 1035 break; 1036 case MotionEvent.ACTION_UP: 1037 if (mIsBeingDragged) { 1038 final VelocityTracker velocityTracker = mVelocityTracker; 1039 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1040 int initialVelocity = (int)VelocityTrackerCompat.getYVelocity( 1041 velocityTracker, mActivePointerId); 1042 mPopulatePending = true; 1043 if ((Math.abs(initialVelocity) > mMinimumVelocity) 1044 || Math.abs(mInitialMotionX-mLastMotionX) >= (getWidth()/3)) { 1045 if (mLastMotionX > mInitialMotionX) { 1046 setCurrentItemInternal(mCurItem-1, true, true); 1047 } else { 1048 setCurrentItemInternal(mCurItem+1, true, true); 1049 } 1050 } else { 1051 setCurrentItemInternal(mCurItem, true, true); 1052 } 1053 1054 mActivePointerId = INVALID_POINTER; 1055 endDrag(); 1056 needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease(); 1057 } 1058 break; 1059 case MotionEvent.ACTION_CANCEL: 1060 if (mIsBeingDragged) { 1061 setCurrentItemInternal(mCurItem, true, true); 1062 mActivePointerId = INVALID_POINTER; 1063 endDrag(); 1064 needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease(); 1065 } 1066 break; 1067 case MotionEventCompat.ACTION_POINTER_DOWN: { 1068 final int index = MotionEventCompat.getActionIndex(ev); 1069 final float x = MotionEventCompat.getX(ev, index); 1070 mLastMotionX = x; 1071 mActivePointerId = MotionEventCompat.getPointerId(ev, index); 1072 break; 1073 } 1074 case MotionEventCompat.ACTION_POINTER_UP: 1075 onSecondaryPointerUp(ev); 1076 mLastMotionX = MotionEventCompat.getX(ev, 1077 MotionEventCompat.findPointerIndex(ev, mActivePointerId)); 1078 break; 1079 } 1080 if (needsInvalidate) { 1081 invalidate(); 1082 } 1083 return true; 1084 } 1085 1086 @Override 1087 public void draw(Canvas canvas) { 1088 super.draw(canvas); 1089 boolean needsInvalidate = false; 1090 1091 final int overScrollMode = ViewCompat.getOverScrollMode(this); 1092 if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS || 1093 (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && 1094 mAdapter != null && mAdapter.getCount() > 1)) { 1095 if (!mLeftEdge.isFinished()) { 1096 final int restoreCount = canvas.save(); 1097 final int height = getHeight() - getPaddingTop() - getPaddingBottom(); 1098 1099 canvas.rotate(270); 1100 canvas.translate(-height + getPaddingTop(), 0); 1101 mLeftEdge.setSize(height, getWidth()); 1102 needsInvalidate |= mLeftEdge.draw(canvas); 1103 canvas.restoreToCount(restoreCount); 1104 } 1105 if (!mRightEdge.isFinished()) { 1106 final int restoreCount = canvas.save(); 1107 final int width = getWidth(); 1108 final int height = getHeight() - getPaddingTop() - getPaddingBottom(); 1109 final int itemCount = mAdapter != null ? mAdapter.getCount() : 1; 1110 1111 canvas.rotate(90); 1112 canvas.translate(-getPaddingTop(), -itemCount * width); 1113 mRightEdge.setSize(height, width); 1114 needsInvalidate |= mRightEdge.draw(canvas); 1115 canvas.restoreToCount(restoreCount); 1116 } 1117 } else { 1118 mLeftEdge.finish(); 1119 mRightEdge.finish(); 1120 } 1121 1122 if (needsInvalidate) { 1123 // Keep animating 1124 invalidate(); 1125 } 1126 } 1127 1128 /** 1129 * Start a fake drag of the pager. 1130 * 1131 * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager 1132 * with the touch scrolling of another view, while still letting the ViewPager 1133 * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.) 1134 * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call 1135 * {@link #endFakeDrag()} to complete the fake drag and fling as necessary. 1136 * 1137 * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag 1138 * is already in progress, this method will return false. 1139 * 1140 * @return true if the fake drag began successfully, false if it could not be started. 1141 * 1142 * @see #fakeDragBy(float) 1143 * @see #endFakeDrag() 1144 */ 1145 public boolean beginFakeDrag() { 1146 if (mIsBeingDragged) { 1147 return false; 1148 } 1149 mFakeDragging = true; 1150 setScrollState(SCROLL_STATE_DRAGGING); 1151 mInitialMotionX = mLastMotionX = 0; 1152 if (mVelocityTracker == null) { 1153 mVelocityTracker = VelocityTracker.obtain(); 1154 } else { 1155 mVelocityTracker.clear(); 1156 } 1157 final long time = SystemClock.uptimeMillis(); 1158 final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0); 1159 mVelocityTracker.addMovement(ev); 1160 ev.recycle(); 1161 mFakeDragBeginTime = time; 1162 return true; 1163 } 1164 1165 /** 1166 * End a fake drag of the pager. 1167 * 1168 * @see #beginFakeDrag() 1169 * @see #fakeDragBy(float) 1170 */ 1171 public void endFakeDrag() { 1172 if (!mFakeDragging) { 1173 throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); 1174 } 1175 1176 final VelocityTracker velocityTracker = mVelocityTracker; 1177 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1178 int initialVelocity = (int)VelocityTrackerCompat.getYVelocity( 1179 velocityTracker, mActivePointerId); 1180 mPopulatePending = true; 1181 if ((Math.abs(initialVelocity) > mMinimumVelocity) 1182 || Math.abs(mInitialMotionX-mLastMotionX) >= (getWidth()/3)) { 1183 if (mLastMotionX > mInitialMotionX) { 1184 setCurrentItemInternal(mCurItem-1, true, true); 1185 } else { 1186 setCurrentItemInternal(mCurItem+1, true, true); 1187 } 1188 } else { 1189 setCurrentItemInternal(mCurItem, true, true); 1190 } 1191 endDrag(); 1192 1193 mFakeDragging = false; 1194 } 1195 1196 /** 1197 * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first. 1198 * 1199 * @param xOffset Offset in pixels to drag by. 1200 * @see #beginFakeDrag() 1201 * @see #endFakeDrag() 1202 */ 1203 public void fakeDragBy(float xOffset) { 1204 if (!mFakeDragging) { 1205 throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); 1206 } 1207 1208 mLastMotionX += xOffset; 1209 float scrollX = getScrollX() - xOffset; 1210 final int width = getWidth(); 1211 1212 final float leftBound = Math.max(0, (mCurItem - 1) * width); 1213 final float rightBound = 1214 Math.min(mCurItem + 1, mAdapter.getCount() - 1) * width; 1215 if (scrollX < leftBound) { 1216 scrollX = leftBound; 1217 } else if (scrollX > rightBound) { 1218 scrollX = rightBound; 1219 } 1220 // Don't lose the rounded component 1221 mLastMotionX += scrollX - (int) scrollX; 1222 scrollTo((int) scrollX, getScrollY()); 1223 if (mOnPageChangeListener != null) { 1224 final int position = (int) scrollX / width; 1225 final int positionOffsetPixels = (int) scrollX % width; 1226 final float positionOffset = (float) positionOffsetPixels / width; 1227 mOnPageChangeListener.onPageScrolled(position, positionOffset, 1228 positionOffsetPixels); 1229 } 1230 1231 // Synthesize an event for the VelocityTracker. 1232 final long time = SystemClock.uptimeMillis(); 1233 final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE, 1234 mLastMotionX, 0, 0); 1235 mVelocityTracker.addMovement(ev); 1236 ev.recycle(); 1237 } 1238 1239 /** 1240 * Returns true if a fake drag is in progress. 1241 * 1242 * @return true if currently in a fake drag, false otherwise. 1243 * 1244 * @see #beginFakeDrag() 1245 * @see #fakeDragBy(float) 1246 * @see #endFakeDrag() 1247 */ 1248 public boolean isFakeDragging() { 1249 return mFakeDragging; 1250 } 1251 1252 private void onSecondaryPointerUp(MotionEvent ev) { 1253 final int pointerIndex = MotionEventCompat.getActionIndex(ev); 1254 final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 1255 if (pointerId == mActivePointerId) { 1256 // This was our active pointer going up. Choose a new 1257 // active pointer and adjust accordingly. 1258 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 1259 mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex); 1260 mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); 1261 if (mVelocityTracker != null) { 1262 mVelocityTracker.clear(); 1263 } 1264 } 1265 } 1266 1267 private void endDrag() { 1268 mIsBeingDragged = false; 1269 mIsUnableToDrag = false; 1270 1271 if (mVelocityTracker != null) { 1272 mVelocityTracker.recycle(); 1273 mVelocityTracker = null; 1274 } 1275 } 1276 1277 private void setScrollingCacheEnabled(boolean enabled) { 1278 if (mScrollingCacheEnabled != enabled) { 1279 mScrollingCacheEnabled = enabled; 1280 if (USE_CACHE) { 1281 final int size = getChildCount(); 1282 for (int i = 0; i < size; ++i) { 1283 final View child = getChildAt(i); 1284 if (child.getVisibility() != GONE) { 1285 child.setDrawingCacheEnabled(enabled); 1286 } 1287 } 1288 } 1289 } 1290 } 1291 1292 /** 1293 * Tests scrollability within child views of v given a delta of dx. 1294 * 1295 * @param v View to test for horizontal scrollability 1296 * @param checkV Whether the view v passed should itself be checked for scrollability (true), 1297 * or just its children (false). 1298 * @param dx Delta scrolled in pixels 1299 * @param x X coordinate of the active touch point 1300 * @param y Y coordinate of the active touch point 1301 * @return true if child views of v can be scrolled by delta of dx. 1302 */ 1303 protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { 1304 if (v instanceof ViewGroup) { 1305 final ViewGroup group = (ViewGroup) v; 1306 final int scrollX = v.getScrollX(); 1307 final int scrollY = v.getScrollY(); 1308 final int count = group.getChildCount(); 1309 // Count backwards - let topmost views consume scroll distance first. 1310 for (int i = count - 1; i >= 0; i--) { 1311 // TODO: Add versioned support here for transformed views. 1312 // This will not work for transformed views in Honeycomb+ 1313 final View child = group.getChildAt(i); 1314 if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && 1315 y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && 1316 canScroll(child, true, dx, x + scrollX - child.getLeft(), 1317 y + scrollY - child.getTop())) { 1318 return true; 1319 } 1320 } 1321 } 1322 1323 return checkV && ViewCompat.canScrollHorizontally(v, -dx); 1324 } 1325 1326 @Override 1327 public boolean dispatchKeyEvent(KeyEvent event) { 1328 // Let the focused view and/or our descendants get the key first 1329 return super.dispatchKeyEvent(event) || executeKeyEvent(event); 1330 } 1331 1332 /** 1333 * You can call this function yourself to have the scroll view perform 1334 * scrolling from a key event, just as if the event had been dispatched to 1335 * it by the view hierarchy. 1336 * 1337 * @param event The key event to execute. 1338 * @return Return true if the event was handled, else false. 1339 */ 1340 public boolean executeKeyEvent(KeyEvent event) { 1341 boolean handled = false; 1342 if (event.getAction() == KeyEvent.ACTION_DOWN) { 1343 switch (event.getKeyCode()) { 1344 case KeyEvent.KEYCODE_DPAD_LEFT: 1345 handled = arrowScroll(FOCUS_LEFT); 1346 break; 1347 case KeyEvent.KEYCODE_DPAD_RIGHT: 1348 handled = arrowScroll(FOCUS_RIGHT); 1349 break; 1350 case KeyEvent.KEYCODE_TAB: 1351 if (KeyEventCompat.hasNoModifiers(event)) { 1352 handled = arrowScroll(FOCUS_FORWARD); 1353 } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) { 1354 handled = arrowScroll(FOCUS_BACKWARD); 1355 } 1356 break; 1357 } 1358 } 1359 return handled; 1360 } 1361 1362 public boolean arrowScroll(int direction) { 1363 View currentFocused = findFocus(); 1364 if (currentFocused == this) currentFocused = null; 1365 1366 boolean handled = false; 1367 1368 View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, 1369 direction); 1370 if (nextFocused != null && nextFocused != currentFocused) { 1371 if (direction == View.FOCUS_LEFT) { 1372 // If there is nothing to the left, or this is causing us to 1373 // jump to the right, then what we really want to do is page left. 1374 if (currentFocused != null && nextFocused.getLeft() >= currentFocused.getLeft()) { 1375 handled = pageLeft(); 1376 } else { 1377 handled = nextFocused.requestFocus(); 1378 } 1379 } else if (direction == View.FOCUS_RIGHT) { 1380 // If there is nothing to the right, or this is causing us to 1381 // jump to the left, then what we really want to do is page right. 1382 if (currentFocused != null && nextFocused.getLeft() <= currentFocused.getLeft()) { 1383 handled = pageRight(); 1384 } else { 1385 handled = nextFocused.requestFocus(); 1386 } 1387 } 1388 } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) { 1389 // Trying to move left and nothing there; try to page. 1390 handled = pageLeft(); 1391 } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) { 1392 // Trying to move right and nothing there; try to page. 1393 handled = pageRight(); 1394 } 1395 if (handled) { 1396 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); 1397 } 1398 return handled; 1399 } 1400 1401 boolean pageLeft() { 1402 if (mCurItem > 0) { 1403 setCurrentItem(mCurItem-1, true); 1404 return true; 1405 } 1406 return false; 1407 } 1408 1409 boolean pageRight() { 1410 if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) { 1411 setCurrentItem(mCurItem+1, true); 1412 return true; 1413 } 1414 return false; 1415 } 1416 1417 /** 1418 * We only want the current page that is being shown to be focusable. 1419 */ 1420 @Override 1421 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 1422 final int focusableCount = views.size(); 1423 1424 final int descendantFocusability = getDescendantFocusability(); 1425 1426 if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { 1427 for (int i = 0; i < getChildCount(); i++) { 1428 final View child = getChildAt(i); 1429 if (child.getVisibility() == VISIBLE) { 1430 ItemInfo ii = infoForChild(child); 1431 if (ii != null && ii.position == mCurItem) { 1432 child.addFocusables(views, direction, focusableMode); 1433 } 1434 } 1435 } 1436 } 1437 1438 // we add ourselves (if focusable) in all cases except for when we are 1439 // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is 1440 // to avoid the focus search finding layouts when a more precise search 1441 // among the focusable children would be more interesting. 1442 if ( 1443 descendantFocusability != FOCUS_AFTER_DESCENDANTS || 1444 // No focusable descendants 1445 (focusableCount == views.size())) { 1446 // Note that we can't call the superclass here, because it will 1447 // add all views in. So we need to do the same thing View does. 1448 if (!isFocusable()) { 1449 return; 1450 } 1451 if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE && 1452 isInTouchMode() && !isFocusableInTouchMode()) { 1453 return; 1454 } 1455 if (views != null) { 1456 views.add(this); 1457 } 1458 } 1459 } 1460 1461 /** 1462 * We only want the current page that is being shown to be touchable. 1463 */ 1464 @Override 1465 public void addTouchables(ArrayList<View> views) { 1466 // Note that we don't call super.addTouchables(), which means that 1467 // we don't call View.addTouchables(). This is okay because a ViewPager 1468 // is itself not touchable. 1469 for (int i = 0; i < getChildCount(); i++) { 1470 final View child = getChildAt(i); 1471 if (child.getVisibility() == VISIBLE) { 1472 ItemInfo ii = infoForChild(child); 1473 if (ii != null && ii.position == mCurItem) { 1474 child.addTouchables(views); 1475 } 1476 } 1477 } 1478 } 1479 1480 /** 1481 * We only want the current page that is being shown to be focusable. 1482 */ 1483 @Override 1484 protected boolean onRequestFocusInDescendants(int direction, 1485 Rect previouslyFocusedRect) { 1486 int index; 1487 int increment; 1488 int end; 1489 int count = getChildCount(); 1490 if ((direction & FOCUS_FORWARD) != 0) { 1491 index = 0; 1492 increment = 1; 1493 end = count; 1494 } else { 1495 index = count - 1; 1496 increment = -1; 1497 end = -1; 1498 } 1499 for (int i = index; i != end; i += increment) { 1500 View child = getChildAt(i); 1501 if (child.getVisibility() == VISIBLE) { 1502 ItemInfo ii = infoForChild(child); 1503 if (ii != null && ii.position == mCurItem) { 1504 if (child.requestFocus(direction, previouslyFocusedRect)) { 1505 return true; 1506 } 1507 } 1508 } 1509 } 1510 return false; 1511 } 1512 1513 @Override 1514 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 1515 // ViewPagers should only report accessibility info for the current page, 1516 // otherwise things get very confusing. 1517 1518 // TODO: Should this note something about the paging container? 1519 1520 final int childCount = getChildCount(); 1521 for (int i = 0; i < childCount; i++) { 1522 final View child = getChildAt(i); 1523 if (child.getVisibility() == VISIBLE) { 1524 final ItemInfo ii = infoForChild(child); 1525 if (ii != null && ii.position == mCurItem && 1526 child.dispatchPopulateAccessibilityEvent(event)) { 1527 return true; 1528 } 1529 } 1530 } 1531 1532 return false; 1533 } 1534 1535 private class DataSetObserver implements PagerAdapter.DataSetObserver { 1536 @Override 1537 public void onDataSetChanged() { 1538 dataSetChanged(); 1539 } 1540 } 1541} 1542