ViewPager.java revision 0f034dc2c69044597a91649b8c073ae5e98a2f25
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.content.res.TypedArray; 21import android.database.DataSetObserver; 22import android.graphics.Canvas; 23import android.graphics.Rect; 24import android.graphics.drawable.Drawable; 25import android.os.Build; 26import android.os.Parcel; 27import android.os.Parcelable; 28import android.os.SystemClock; 29import android.support.v4.os.ParcelableCompat; 30import android.support.v4.os.ParcelableCompatCreatorCallbacks; 31import android.support.v4.widget.EdgeEffectCompat; 32import android.util.AttributeSet; 33import android.util.Log; 34import android.view.FocusFinder; 35import android.view.Gravity; 36import android.view.KeyEvent; 37import android.view.MotionEvent; 38import android.view.SoundEffectConstants; 39import android.view.VelocityTracker; 40import android.view.View; 41import android.view.ViewConfiguration; 42import android.view.ViewGroup; 43import android.view.ViewParent; 44import android.view.accessibility.AccessibilityEvent; 45import android.view.animation.Interpolator; 46import android.widget.Scroller; 47 48import java.util.ArrayList; 49import java.util.Collections; 50import java.util.Comparator; 51 52/** 53 * Layout manager that allows the user to flip left and right 54 * through pages of data. You supply an implementation of a 55 * {@link PagerAdapter} to generate the pages that the view shows. 56 * 57 * <p>Note this class is currently under early design and 58 * development. The API will likely change in later updates of 59 * the compatibility library, requiring changes to the source code 60 * of apps when they are compiled against the newer version.</p> 61 * 62 * <p>ViewPager is most often used in conjunction with {@link android.app.Fragment}, 63 * which is a convenient way to supply and manage the lifecycle of each page. 64 * There are standard adapters implemented for using fragments with the ViewPager, 65 * which cover the most common use cases. These are 66 * {@link android.support.v4.app.FragmentPagerAdapter}, 67 * {@link android.support.v4.app.FragmentStatePagerAdapter}, 68 * {@link android.support.v13.app.FragmentPagerAdapter}, and 69 * {@link android.support.v13.app.FragmentStatePagerAdapter}; each of these 70 * classes have simple code showing how to build a full user interface 71 * with them. 72 * 73 * <p>Here is a more complicated example of ViewPager, using it in conjuction 74 * with {@link android.app.ActionBar} tabs. You can find other examples of using 75 * ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code. 76 * 77 * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java 78 * complete} 79 */ 80public class ViewPager extends ViewGroup { 81 private static final String TAG = "ViewPager"; 82 private static final boolean DEBUG = false; 83 84 private static final boolean USE_CACHE = false; 85 86 private static final int DEFAULT_OFFSCREEN_PAGES = 1; 87 private static final int MAX_SETTLE_DURATION = 600; // ms 88 private static final int MIN_DISTANCE_FOR_FLING = 25; // dips 89 90 private static final int[] LAYOUT_ATTRS = new int[] { 91 android.R.attr.layout_gravity 92 }; 93 94 static class ItemInfo { 95 Object object; 96 int position; 97 boolean scrolling; 98 float widthFactor; 99 float offset; 100 } 101 102 private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>(){ 103 @Override 104 public int compare(ItemInfo lhs, ItemInfo rhs) { 105 return lhs.position - rhs.position; 106 } 107 }; 108 109 private static final Interpolator sInterpolator = new Interpolator() { 110 public float getInterpolation(float t) { 111 t -= 1.0f; 112 return t * t * t * t * t + 1.0f; 113 } 114 }; 115 116 private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>(); 117 private final ItemInfo mTempItem = new ItemInfo(); 118 119 private PagerAdapter mAdapter; 120 private int mCurItem; // Index of currently displayed page. 121 private int mRestoredCurItem = -1; 122 private Parcelable mRestoredAdapterState = null; 123 private ClassLoader mRestoredClassLoader = null; 124 private Scroller mScroller; 125 private PagerObserver mObserver; 126 127 private int mPageMargin; 128 private Drawable mMarginDrawable; 129 private int mTopPageBounds; 130 private int mBottomPageBounds; 131 132 // Offsets of the first and last items, if known. 133 // Set during population, used to determine if we are at the beginning 134 // or end of the pager data set during touch scrolling. 135 private float mFirstOffset = -Float.MAX_VALUE; 136 private float mLastOffset = Float.MAX_VALUE; 137 138 private int mChildWidthMeasureSpec; 139 private int mChildHeightMeasureSpec; 140 private boolean mInLayout; 141 142 private boolean mScrollingCacheEnabled; 143 144 private boolean mPopulatePending; 145 private boolean mScrolling; 146 private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES; 147 148 private boolean mIsBeingDragged; 149 private boolean mIsUnableToDrag; 150 private int mTouchSlop; 151 private float mInitialMotionX; 152 /** 153 * Position of the last motion event. 154 */ 155 private float mLastMotionX; 156 private float mLastMotionY; 157 /** 158 * ID of the active pointer. This is used to retain consistency during 159 * drags/flings if multiple pointers are used. 160 */ 161 private int mActivePointerId = INVALID_POINTER; 162 /** 163 * Sentinel value for no current active pointer. 164 * Used by {@link #mActivePointerId}. 165 */ 166 private static final int INVALID_POINTER = -1; 167 168 /** 169 * Determines speed during touch scrolling 170 */ 171 private VelocityTracker mVelocityTracker; 172 private int mMinimumVelocity; 173 private int mMaximumVelocity; 174 private int mFlingDistance; 175 176 private boolean mFakeDragging; 177 private long mFakeDragBeginTime; 178 179 private EdgeEffectCompat mLeftEdge; 180 private EdgeEffectCompat mRightEdge; 181 182 private boolean mFirstLayout = true; 183 private boolean mNeedCalculatePageOffsets = false; 184 private boolean mCalledSuper; 185 private int mDecorChildCount; 186 187 private OnPageChangeListener mOnPageChangeListener; 188 private OnPageChangeListener mInternalPageChangeListener; 189 private OnAdapterChangeListener mAdapterChangeListener; 190 191 /** 192 * Indicates that the pager is in an idle, settled state. The current page 193 * is fully in view and no animation is in progress. 194 */ 195 public static final int SCROLL_STATE_IDLE = 0; 196 197 /** 198 * Indicates that the pager is currently being dragged by the user. 199 */ 200 public static final int SCROLL_STATE_DRAGGING = 1; 201 202 /** 203 * Indicates that the pager is in the process of settling to a final position. 204 */ 205 public static final int SCROLL_STATE_SETTLING = 2; 206 207 private int mScrollState = SCROLL_STATE_IDLE; 208 209 /** 210 * Callback interface for responding to changing state of the selected page. 211 */ 212 public interface OnPageChangeListener { 213 214 /** 215 * This method will be invoked when the current page is scrolled, either as part 216 * of a programmatically initiated smooth scroll or a user initiated touch scroll. 217 * 218 * @param position Position index of the first page currently being displayed. 219 * Page position+1 will be visible if positionOffset is nonzero. 220 * @param positionOffset Value from [0, 1) indicating the offset from the page at position. 221 * @param positionOffsetPixels Value in pixels indicating the offset from position. 222 */ 223 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels); 224 225 /** 226 * This method will be invoked when a new page becomes selected. Animation is not 227 * necessarily complete. 228 * 229 * @param position Position index of the new selected page. 230 */ 231 public void onPageSelected(int position); 232 233 /** 234 * Called when the scroll state changes. Useful for discovering when the user 235 * begins dragging, when the pager is automatically settling to the current page, 236 * or when it is fully stopped/idle. 237 * 238 * @param state The new scroll state. 239 * @see ViewPager#SCROLL_STATE_IDLE 240 * @see ViewPager#SCROLL_STATE_DRAGGING 241 * @see ViewPager#SCROLL_STATE_SETTLING 242 */ 243 public void onPageScrollStateChanged(int state); 244 } 245 246 /** 247 * Simple implementation of the {@link OnPageChangeListener} interface with stub 248 * implementations of each method. Extend this if you do not intend to override 249 * every method of {@link OnPageChangeListener}. 250 */ 251 public static class SimpleOnPageChangeListener implements OnPageChangeListener { 252 @Override 253 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 254 // This space for rent 255 } 256 257 @Override 258 public void onPageSelected(int position) { 259 // This space for rent 260 } 261 262 @Override 263 public void onPageScrollStateChanged(int state) { 264 // This space for rent 265 } 266 } 267 268 /** 269 * Used internally to monitor when adapters are switched. 270 */ 271 interface OnAdapterChangeListener { 272 public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter); 273 } 274 275 /** 276 * Used internally to tag special types of child views that should be added as 277 * pager decorations by default. 278 */ 279 interface Decor {} 280 281 public ViewPager(Context context) { 282 super(context); 283 initViewPager(); 284 } 285 286 public ViewPager(Context context, AttributeSet attrs) { 287 super(context, attrs); 288 initViewPager(); 289 } 290 291 void initViewPager() { 292 setWillNotDraw(false); 293 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 294 setFocusable(true); 295 final Context context = getContext(); 296 mScroller = new Scroller(context, sInterpolator); 297 final ViewConfiguration configuration = ViewConfiguration.get(context); 298 mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); 299 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 300 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 301 mLeftEdge = new EdgeEffectCompat(context); 302 mRightEdge = new EdgeEffectCompat(context); 303 304 final float density = context.getResources().getDisplayMetrics().density; 305 mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density); 306 } 307 308 private void setScrollState(int newState) { 309 if (mScrollState == newState) { 310 return; 311 } 312 313 mScrollState = newState; 314 if (mOnPageChangeListener != null) { 315 mOnPageChangeListener.onPageScrollStateChanged(newState); 316 } 317 } 318 319 /** 320 * Set a PagerAdapter that will supply views for this pager as needed. 321 * 322 * @param adapter Adapter to use 323 */ 324 public void setAdapter(PagerAdapter adapter) { 325 if (mAdapter != null) { 326 mAdapter.unregisterDataSetObserver(mObserver); 327 mAdapter.startUpdate(this); 328 for (int i = 0; i < mItems.size(); i++) { 329 final ItemInfo ii = mItems.get(i); 330 mAdapter.destroyItem(this, ii.position, ii.object); 331 } 332 mAdapter.finishUpdate(this); 333 mItems.clear(); 334 removeNonDecorViews(); 335 mCurItem = 0; 336 scrollTo(0, 0); 337 } 338 339 final PagerAdapter oldAdapter = mAdapter; 340 mAdapter = adapter; 341 342 if (mAdapter != null) { 343 if (mObserver == null) { 344 mObserver = new PagerObserver(); 345 } 346 mAdapter.registerDataSetObserver(mObserver); 347 mPopulatePending = false; 348 mFirstLayout = true; 349 if (mRestoredCurItem >= 0) { 350 mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader); 351 setCurrentItemInternal(mRestoredCurItem, false, true); 352 mRestoredCurItem = -1; 353 mRestoredAdapterState = null; 354 mRestoredClassLoader = null; 355 } else { 356 populate(); 357 } 358 } 359 360 if (mAdapterChangeListener != null && oldAdapter != adapter) { 361 mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter); 362 } 363 } 364 365 private void removeNonDecorViews() { 366 for (int i = 0; i < getChildCount(); i++) { 367 final View child = getChildAt(i); 368 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 369 if (!lp.isDecor) { 370 removeViewAt(i); 371 i--; 372 } 373 } 374 } 375 376 /** 377 * Retrieve the current adapter supplying pages. 378 * 379 * @return The currently registered PagerAdapter 380 */ 381 public PagerAdapter getAdapter() { 382 return mAdapter; 383 } 384 385 void setOnAdapterChangeListener(OnAdapterChangeListener listener) { 386 mAdapterChangeListener = listener; 387 } 388 389 /** 390 * Set the currently selected page. If the ViewPager has already been through its first 391 * layout with its current adapter there will be a smooth animated transition between 392 * the current item and the specified item. 393 * 394 * @param item Item index to select 395 */ 396 public void setCurrentItem(int item) { 397 mPopulatePending = false; 398 setCurrentItemInternal(item, !mFirstLayout, false); 399 } 400 401 /** 402 * Set the currently selected page. 403 * 404 * @param item Item index to select 405 * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately 406 */ 407 public void setCurrentItem(int item, boolean smoothScroll) { 408 mPopulatePending = false; 409 setCurrentItemInternal(item, smoothScroll, false); 410 } 411 412 public int getCurrentItem() { 413 return mCurItem; 414 } 415 416 void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) { 417 setCurrentItemInternal(item, smoothScroll, always, 0); 418 } 419 420 void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) { 421 if (mAdapter == null || mAdapter.getCount() <= 0) { 422 setScrollingCacheEnabled(false); 423 return; 424 } 425 if (!always && mCurItem == item && mItems.size() != 0) { 426 setScrollingCacheEnabled(false); 427 return; 428 } 429 if (item < 0) { 430 item = 0; 431 } else if (item >= mAdapter.getCount()) { 432 item = mAdapter.getCount() - 1; 433 } 434 final int pageLimit = mOffscreenPageLimit; 435 if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) { 436 // We are doing a jump by more than one page. To avoid 437 // glitches, we want to keep all current pages in the view 438 // until the scroll ends. 439 for (int i=0; i<mItems.size(); i++) { 440 mItems.get(i).scrolling = true; 441 } 442 } 443 final boolean dispatchSelected = mCurItem != item; 444 populate(item); 445 final ItemInfo curInfo = infoForPosition(item); 446 final int destX = curInfo != null ? (int) (getWidth() * curInfo.offset) : 0; 447 if (smoothScroll) { 448 smoothScrollTo(destX, 0, velocity); 449 if (dispatchSelected && mOnPageChangeListener != null) { 450 mOnPageChangeListener.onPageSelected(item); 451 } 452 if (dispatchSelected && mInternalPageChangeListener != null) { 453 mInternalPageChangeListener.onPageSelected(item); 454 } 455 } else { 456 if (dispatchSelected && mOnPageChangeListener != null) { 457 mOnPageChangeListener.onPageSelected(item); 458 } 459 if (dispatchSelected && mInternalPageChangeListener != null) { 460 mInternalPageChangeListener.onPageSelected(item); 461 } 462 completeScroll(); 463 scrollTo(destX, 0); 464 } 465 } 466 467 /** 468 * Set a listener that will be invoked whenever the page changes or is incrementally 469 * scrolled. See {@link OnPageChangeListener}. 470 * 471 * @param listener Listener to set 472 */ 473 public void setOnPageChangeListener(OnPageChangeListener listener) { 474 mOnPageChangeListener = listener; 475 } 476 477 /** 478 * Set a separate OnPageChangeListener for internal use by the support library. 479 * 480 * @param listener Listener to set 481 * @return The old listener that was set, if any. 482 */ 483 OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) { 484 OnPageChangeListener oldListener = mInternalPageChangeListener; 485 mInternalPageChangeListener = listener; 486 return oldListener; 487 } 488 489 /** 490 * Returns the number of pages that will be retained to either side of the 491 * current page in the view hierarchy in an idle state. Defaults to 1. 492 * 493 * @return How many pages will be kept offscreen on either side 494 * @see #setOffscreenPageLimit(int) 495 */ 496 public int getOffscreenPageLimit() { 497 return mOffscreenPageLimit; 498 } 499 500 /** 501 * Set the number of pages that should be retained to either side of the 502 * current page in the view hierarchy in an idle state. Pages beyond this 503 * limit will be recreated from the adapter when needed. 504 * 505 * <p>This is offered as an optimization. If you know in advance the number 506 * of pages you will need to support or have lazy-loading mechanisms in place 507 * on your pages, tweaking this setting can have benefits in perceived smoothness 508 * of paging animations and interaction. If you have a small number of pages (3-4) 509 * that you can keep active all at once, less time will be spent in layout for 510 * newly created view subtrees as the user pages back and forth.</p> 511 * 512 * <p>You should keep this limit low, especially if your pages have complex layouts. 513 * This setting defaults to 1.</p> 514 * 515 * @param limit How many pages will be kept offscreen in an idle state. 516 */ 517 public void setOffscreenPageLimit(int limit) { 518 if (limit < DEFAULT_OFFSCREEN_PAGES) { 519 Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " + 520 DEFAULT_OFFSCREEN_PAGES); 521 limit = DEFAULT_OFFSCREEN_PAGES; 522 } 523 if (limit != mOffscreenPageLimit) { 524 mOffscreenPageLimit = limit; 525 populate(); 526 } 527 } 528 529 /** 530 * Set the margin between pages. 531 * 532 * @param marginPixels Distance between adjacent pages in pixels 533 * @see #getPageMargin() 534 * @see #setPageMarginDrawable(Drawable) 535 * @see #setPageMarginDrawable(int) 536 */ 537 public void setPageMargin(int marginPixels) { 538 final int oldMargin = mPageMargin; 539 mPageMargin = marginPixels; 540 541 final int width = getWidth(); 542 recomputeScrollPosition(width, width, marginPixels, oldMargin); 543 544 requestLayout(); 545 } 546 547 /** 548 * Return the margin between pages. 549 * 550 * @return The size of the margin in pixels 551 */ 552 public int getPageMargin() { 553 return mPageMargin; 554 } 555 556 /** 557 * Set a drawable that will be used to fill the margin between pages. 558 * 559 * @param d Drawable to display between pages 560 */ 561 public void setPageMarginDrawable(Drawable d) { 562 mMarginDrawable = d; 563 if (d != null) refreshDrawableState(); 564 setWillNotDraw(d == null); 565 invalidate(); 566 } 567 568 /** 569 * Set a drawable that will be used to fill the margin between pages. 570 * 571 * @param resId Resource ID of a drawable to display between pages 572 */ 573 public void setPageMarginDrawable(int resId) { 574 setPageMarginDrawable(getContext().getResources().getDrawable(resId)); 575 } 576 577 @Override 578 protected boolean verifyDrawable(Drawable who) { 579 return super.verifyDrawable(who) || who == mMarginDrawable; 580 } 581 582 @Override 583 protected void drawableStateChanged() { 584 super.drawableStateChanged(); 585 final Drawable d = mMarginDrawable; 586 if (d != null && d.isStateful()) { 587 d.setState(getDrawableState()); 588 } 589 } 590 591 // We want the duration of the page snap animation to be influenced by the distance that 592 // the screen has to travel, however, we don't want this duration to be effected in a 593 // purely linear fashion. Instead, we use this method to moderate the effect that the distance 594 // of travel has on the overall snap duration. 595 float distanceInfluenceForSnapDuration(float f) { 596 f -= 0.5f; // center the values about 0. 597 f *= 0.3f * Math.PI / 2.0f; 598 return (float) Math.sin(f); 599 } 600 601 /** 602 * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. 603 * 604 * @param x the number of pixels to scroll by on the X axis 605 * @param y the number of pixels to scroll by on the Y axis 606 */ 607 void smoothScrollTo(int x, int y) { 608 smoothScrollTo(x, y, 0); 609 } 610 611 /** 612 * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. 613 * 614 * @param x the number of pixels to scroll by on the X axis 615 * @param y the number of pixels to scroll by on the Y axis 616 * @param velocity the velocity associated with a fling, if applicable. (0 otherwise) 617 */ 618 void smoothScrollTo(int x, int y, int velocity) { 619 if (getChildCount() == 0) { 620 // Nothing to do. 621 setScrollingCacheEnabled(false); 622 return; 623 } 624 int sx = getScrollX(); 625 int sy = getScrollY(); 626 int dx = x - sx; 627 int dy = y - sy; 628 if (dx == 0 && dy == 0) { 629 completeScroll(); 630 setScrollState(SCROLL_STATE_IDLE); 631 return; 632 } 633 634 setScrollingCacheEnabled(true); 635 mScrolling = true; 636 setScrollState(SCROLL_STATE_SETTLING); 637 638 final int width = getWidth(); 639 final int halfWidth = width / 2; 640 final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width); 641 final float distance = halfWidth + halfWidth * 642 distanceInfluenceForSnapDuration(distanceRatio); 643 644 int duration = 0; 645 velocity = Math.abs(velocity); 646 if (velocity > 0) { 647 duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); 648 } else { 649 final float pageWidth = width * mAdapter.getPageWidth(mCurItem); 650 final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin); 651 duration = (int) ((pageDelta + 1) * 100); 652 } 653 duration = Math.min(duration, MAX_SETTLE_DURATION); 654 655 mScroller.startScroll(sx, sy, dx, dy, duration); 656 invalidate(); 657 } 658 659 ItemInfo addNewItem(int position, int index) { 660 ItemInfo ii = new ItemInfo(); 661 ii.position = position; 662 ii.object = mAdapter.instantiateItem(this, position); 663 ii.widthFactor = mAdapter.getPageWidth(position); 664 if (index < 0 || index >= mItems.size()) { 665 mItems.add(ii); 666 } else { 667 mItems.add(index, ii); 668 } 669 return ii; 670 } 671 672 void dataSetChanged() { 673 // This method only gets called if our observer is attached, so mAdapter is non-null. 674 675 boolean needPopulate = mItems.size() < 3 && mItems.size() < mAdapter.getCount(); 676 int newCurrItem = -1; 677 678 boolean isUpdating = false; 679 for (int i = 0; i < mItems.size(); i++) { 680 final ItemInfo ii = mItems.get(i); 681 final int newPos = mAdapter.getItemPosition(ii.object); 682 683 if (newPos == PagerAdapter.POSITION_UNCHANGED) { 684 continue; 685 } 686 687 if (newPos == PagerAdapter.POSITION_NONE) { 688 mItems.remove(i); 689 i--; 690 691 if (!isUpdating) { 692 mAdapter.startUpdate(this); 693 isUpdating = true; 694 } 695 696 mAdapter.destroyItem(this, ii.position, ii.object); 697 needPopulate = true; 698 699 if (mCurItem == ii.position) { 700 // Keep the current item in the valid range 701 newCurrItem = Math.max(0, Math.min(mCurItem, mAdapter.getCount() - 1)); 702 } 703 continue; 704 } 705 706 if (ii.position != newPos) { 707 if (ii.position == mCurItem) { 708 // Our current item changed position. Follow it. 709 newCurrItem = newPos; 710 } 711 712 ii.position = newPos; 713 needPopulate = true; 714 } 715 } 716 717 if (isUpdating) { 718 mAdapter.finishUpdate(this); 719 } 720 721 Collections.sort(mItems, COMPARATOR); 722 723 if (needPopulate) { 724 // Reset our known page widths; populate will recompute them. 725 final int childCount = getChildCount(); 726 for (int i = 0; i < childCount; i++) { 727 final View child = getChildAt(i); 728 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 729 if (!lp.isDecor) { 730 lp.widthFactor = 0.f; 731 } 732 } 733 } 734 735 if (newCurrItem >= 0) { 736 // TODO This currently causes a jump. 737 setCurrentItemInternal(newCurrItem, false, true); 738 needPopulate = true; 739 } 740 if (needPopulate) { 741 populate(); 742 requestLayout(); 743 } 744 } 745 746 void populate() { 747 populate(mCurItem); 748 } 749 750 void populate(int newCurrentItem) { 751 ItemInfo oldCurInfo = null; 752 if (mCurItem != newCurrentItem) { 753 oldCurInfo = infoForPosition(mCurItem); 754 mCurItem = newCurrentItem; 755 } 756 757 if (mAdapter == null) { 758 return; 759 } 760 761 // Bail now if we are waiting to populate. This is to hold off 762 // on creating views from the time the user releases their finger to 763 // fling to a new position until we have finished the scroll to 764 // that position, avoiding glitches from happening at that point. 765 if (mPopulatePending) { 766 if (DEBUG) Log.i(TAG, "populate is pending, skipping for now..."); 767 return; 768 } 769 770 // Also, don't populate until we are attached to a window. This is to 771 // avoid trying to populate before we have restored our view hierarchy 772 // state and conflicting with what is restored. 773 if (getWindowToken() == null) { 774 return; 775 } 776 777 mAdapter.startUpdate(this); 778 779 final int pageLimit = mOffscreenPageLimit; 780 final int startPos = Math.max(0, mCurItem - pageLimit); 781 final int N = mAdapter.getCount(); 782 final int endPos = Math.min(N-1, mCurItem + pageLimit); 783 784 // Locate the currently focused item or add it if needed. 785 int curIndex = -1; 786 ItemInfo curItem = null; 787 for (curIndex = 0; curIndex < mItems.size(); curIndex++) { 788 final ItemInfo ii = mItems.get(curIndex); 789 if (ii.position >= mCurItem) { 790 if (ii.position == mCurItem) curItem = ii; 791 break; 792 } 793 } 794 795 if (curItem == null && N > 0) { 796 curItem = addNewItem(mCurItem, curIndex); 797 } 798 799 // Fill 3x the available width or up to the number of offscreen 800 // pages requested to either side, whichever is larger. 801 // If we have no current item we have no work to do. 802 if (curItem != null) { 803 float extraWidthLeft = 0.f; 804 int itemIndex = curIndex - 1; 805 ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; 806 for (int pos = mCurItem - 1; pos >= 0; pos--) { 807 if (extraWidthLeft >= 1.f && pos < startPos) { 808 if (ii == null) { 809 break; 810 } 811 if (pos == ii.position && !ii.scrolling) { 812 mItems.remove(itemIndex); 813 mAdapter.destroyItem(this, pos, ii.object); 814 itemIndex--; 815 curIndex--; 816 ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; 817 } 818 } else if (ii != null && pos == ii.position) { 819 extraWidthLeft += ii.widthFactor; 820 itemIndex--; 821 ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; 822 } else { 823 ii = addNewItem(pos, itemIndex + 1); 824 extraWidthLeft += ii.widthFactor; 825 curIndex++; 826 ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; 827 } 828 } 829 830 float extraWidthRight = curItem.widthFactor; 831 itemIndex = curIndex + 1; 832 if (extraWidthRight < 2.f) { 833 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; 834 for (int pos = mCurItem + 1; pos < N; pos++) { 835 if (extraWidthRight >= 2.f && pos > endPos) { 836 if (ii == null) { 837 break; 838 } 839 if (pos == ii.position && !ii.scrolling) { 840 mItems.remove(itemIndex); 841 mAdapter.destroyItem(this, pos, ii.object); 842 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; 843 } 844 } else if (ii != null && pos == ii.position) { 845 extraWidthRight += ii.widthFactor; 846 itemIndex++; 847 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; 848 } else { 849 ii = addNewItem(pos, itemIndex); 850 itemIndex++; 851 extraWidthRight += ii.widthFactor; 852 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; 853 } 854 } 855 } 856 857 calculatePageOffsets(curItem, curIndex, oldCurInfo); 858 } 859 860 if (DEBUG) { 861 Log.i(TAG, "Current page list:"); 862 for (int i=0; i<mItems.size(); i++) { 863 Log.i(TAG, "#" + i + ": page " + mItems.get(i).position); 864 } 865 } 866 867 mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null); 868 869 mAdapter.finishUpdate(this); 870 871 // Check width measurement of current pages. Update LayoutParams as needed. 872 final int childCount = getChildCount(); 873 for (int i = 0; i < childCount; i++) { 874 final View child = getChildAt(i); 875 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 876 if (!lp.isDecor && lp.widthFactor == 0.f) { 877 // 0 means requery the adapter for this, it doesn't have a valid width. 878 final ItemInfo ii = infoForChild(child); 879 if (ii != null) { 880 lp.widthFactor = ii.widthFactor; 881 } 882 } 883 } 884 885 if (hasFocus()) { 886 View currentFocused = findFocus(); 887 ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null; 888 if (ii == null || ii.position != mCurItem) { 889 for (int i=0; i<getChildCount(); i++) { 890 View child = getChildAt(i); 891 ii = infoForChild(child); 892 if (ii != null && ii.position == mCurItem) { 893 if (child.requestFocus(FOCUS_FORWARD)) { 894 break; 895 } 896 } 897 } 898 } 899 } 900 } 901 902 private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) { 903 final int N = mAdapter.getCount(); 904 final int width = getWidth(); 905 final float marginOffset = width > 0 ? (float) mPageMargin / width : 0; 906 // Fix up offsets for later layout. 907 if (oldCurInfo != null) { 908 final int oldCurPosition = oldCurInfo.position; 909 // Base offsets off of oldCurInfo. 910 if (oldCurPosition < curItem.position) { 911 int itemIndex = 0; 912 ItemInfo ii = null; 913 float offset = oldCurInfo.offset + oldCurInfo.widthFactor; 914 for (int pos = oldCurPosition + 1; 915 pos <= curItem.position && itemIndex < mItems.size(); pos++) { 916 ii = mItems.get(itemIndex); 917 while (pos > ii.position && itemIndex < mItems.size() - 1) { 918 itemIndex++; 919 ii = mItems.get(itemIndex); 920 } 921 while (pos < ii.position) { 922 // We don't have an item populated for this, 923 // ask the adapter for an offset. 924 offset += mAdapter.getPageWidth(pos) + marginOffset; 925 pos++; 926 } 927 ii.offset = offset; 928 offset += ii.widthFactor + marginOffset; 929 } 930 } else if (oldCurPosition > curItem.position) { 931 int itemIndex = mItems.size() - 1; 932 ItemInfo ii = null; 933 float offset = oldCurInfo.offset; 934 for (int pos = oldCurPosition - 1; 935 pos >= curItem.position && itemIndex >= 0; pos--) { 936 ii = mItems.get(itemIndex); 937 while (pos < ii.position && itemIndex > 0) { 938 itemIndex--; 939 ii = mItems.get(itemIndex); 940 } 941 while (pos > ii.position) { 942 // We don't have an item populated for this, 943 // ask the adapter for an offset. 944 offset -= mAdapter.getPageWidth(pos) + marginOffset; 945 pos--; 946 } 947 offset -= ii.widthFactor + marginOffset; 948 ii.offset = offset; 949 } 950 } 951 } 952 953 // Base all offsets off of curItem. 954 final int itemCount = mItems.size(); 955 float offset = curItem.offset; 956 int pos = curItem.position - 1; 957 mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE; 958 mLastOffset = curItem.position == N - 1 ? curItem.offset : Float.MAX_VALUE; 959 // Previous pages 960 for (int i = curIndex - 1; i >= 0; i--, pos--) { 961 final ItemInfo ii = mItems.get(i); 962 while (pos > ii.position) { 963 offset -= mAdapter.getPageWidth(pos--) + marginOffset; 964 } 965 offset -= ii.widthFactor + marginOffset; 966 ii.offset = offset; 967 if (ii.position == 0) mFirstOffset = offset; 968 } 969 offset = curItem.offset + curItem.widthFactor + marginOffset; 970 pos = curItem.position + 1; 971 // Next pages 972 for (int i = curIndex + 1; i < itemCount; i++, pos++) { 973 final ItemInfo ii = mItems.get(i); 974 while (pos < ii.position) { 975 offset += mAdapter.getPageWidth(pos++) + marginOffset; 976 } 977 if (ii.position == N - 1) mLastOffset = offset; 978 ii.offset = offset; 979 offset += ii.widthFactor + marginOffset; 980 } 981 982 mNeedCalculatePageOffsets = false; 983 } 984 985 /** 986 * This is the persistent state that is saved by ViewPager. Only needed 987 * if you are creating a sublass of ViewPager that must save its own 988 * state, in which case it should implement a subclass of this which 989 * contains that state. 990 */ 991 public static class SavedState extends BaseSavedState { 992 int position; 993 Parcelable adapterState; 994 ClassLoader loader; 995 996 public SavedState(Parcelable superState) { 997 super(superState); 998 } 999 1000 @Override 1001 public void writeToParcel(Parcel out, int flags) { 1002 super.writeToParcel(out, flags); 1003 out.writeInt(position); 1004 out.writeParcelable(adapterState, flags); 1005 } 1006 1007 @Override 1008 public String toString() { 1009 return "FragmentPager.SavedState{" 1010 + Integer.toHexString(System.identityHashCode(this)) 1011 + " position=" + position + "}"; 1012 } 1013 1014 public static final Parcelable.Creator<SavedState> CREATOR 1015 = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() { 1016 @Override 1017 public SavedState createFromParcel(Parcel in, ClassLoader loader) { 1018 return new SavedState(in, loader); 1019 } 1020 @Override 1021 public SavedState[] newArray(int size) { 1022 return new SavedState[size]; 1023 } 1024 }); 1025 1026 SavedState(Parcel in, ClassLoader loader) { 1027 super(in); 1028 if (loader == null) { 1029 loader = getClass().getClassLoader(); 1030 } 1031 position = in.readInt(); 1032 adapterState = in.readParcelable(loader); 1033 this.loader = loader; 1034 } 1035 } 1036 1037 @Override 1038 public Parcelable onSaveInstanceState() { 1039 Parcelable superState = super.onSaveInstanceState(); 1040 SavedState ss = new SavedState(superState); 1041 ss.position = mCurItem; 1042 if (mAdapter != null) { 1043 ss.adapterState = mAdapter.saveState(); 1044 } 1045 return ss; 1046 } 1047 1048 @Override 1049 public void onRestoreInstanceState(Parcelable state) { 1050 if (!(state instanceof SavedState)) { 1051 super.onRestoreInstanceState(state); 1052 return; 1053 } 1054 1055 SavedState ss = (SavedState)state; 1056 super.onRestoreInstanceState(ss.getSuperState()); 1057 1058 if (mAdapter != null) { 1059 mAdapter.restoreState(ss.adapterState, ss.loader); 1060 setCurrentItemInternal(ss.position, false, true); 1061 } else { 1062 mRestoredCurItem = ss.position; 1063 mRestoredAdapterState = ss.adapterState; 1064 mRestoredClassLoader = ss.loader; 1065 } 1066 } 1067 1068 @Override 1069 public void addView(View child, int index, ViewGroup.LayoutParams params) { 1070 if (!checkLayoutParams(params)) { 1071 params = generateLayoutParams(params); 1072 } 1073 final LayoutParams lp = (LayoutParams) params; 1074 lp.isDecor |= child instanceof Decor; 1075 if (mInLayout) { 1076 if (lp != null && lp.isDecor) { 1077 throw new IllegalStateException("Cannot add pager decor view during layout"); 1078 } 1079 lp.needsMeasure = true; 1080 addViewInLayout(child, index, params); 1081 } else { 1082 super.addView(child, index, params); 1083 } 1084 1085 if (USE_CACHE) { 1086 if (child.getVisibility() != GONE) { 1087 child.setDrawingCacheEnabled(mScrollingCacheEnabled); 1088 } else { 1089 child.setDrawingCacheEnabled(false); 1090 } 1091 } 1092 } 1093 1094 ItemInfo infoForChild(View child) { 1095 for (int i=0; i<mItems.size(); i++) { 1096 ItemInfo ii = mItems.get(i); 1097 if (mAdapter.isViewFromObject(child, ii.object)) { 1098 return ii; 1099 } 1100 } 1101 return null; 1102 } 1103 1104 ItemInfo infoForAnyChild(View child) { 1105 ViewParent parent; 1106 while ((parent=child.getParent()) != this) { 1107 if (parent == null || !(parent instanceof View)) { 1108 return null; 1109 } 1110 child = (View)parent; 1111 } 1112 return infoForChild(child); 1113 } 1114 1115 ItemInfo infoForPosition(int position) { 1116 for (int i = 0; i < mItems.size(); i++) { 1117 ItemInfo ii = mItems.get(i); 1118 if (ii.position == position) { 1119 return ii; 1120 } 1121 } 1122 return null; 1123 } 1124 1125 @Override 1126 protected void onAttachedToWindow() { 1127 super.onAttachedToWindow(); 1128 mFirstLayout = true; 1129 } 1130 1131 @Override 1132 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1133 // For simple implementation, or internal size is always 0. 1134 // We depend on the container to specify the layout size of 1135 // our view. We can't really know what it is since we will be 1136 // adding and removing different arbitrary views and do not 1137 // want the layout to change as this happens. 1138 setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), 1139 getDefaultSize(0, heightMeasureSpec)); 1140 1141 // Children are just made to fill our space. 1142 int childWidthSize = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); 1143 int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); 1144 1145 /* 1146 * Make sure all children have been properly measured. Decor views first. 1147 * Right now we cheat and make this less complicated by assuming decor 1148 * views won't intersect. We will pin to edges based on gravity. 1149 */ 1150 int size = getChildCount(); 1151 for (int i = 0; i < size; ++i) { 1152 final View child = getChildAt(i); 1153 if (child.getVisibility() != GONE) { 1154 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1155 if (lp != null && lp.isDecor) { 1156 final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; 1157 final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; 1158 int widthMode = MeasureSpec.AT_MOST; 1159 int heightMode = MeasureSpec.AT_MOST; 1160 boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM; 1161 boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT; 1162 1163 if (consumeVertical) { 1164 widthMode = MeasureSpec.EXACTLY; 1165 } else if (consumeHorizontal) { 1166 heightMode = MeasureSpec.EXACTLY; 1167 } 1168 1169 int widthSize = childWidthSize; 1170 int heightSize = childHeightSize; 1171 if (lp.width != LayoutParams.WRAP_CONTENT) { 1172 widthMode = MeasureSpec.EXACTLY; 1173 if (lp.width != LayoutParams.FILL_PARENT) { 1174 widthSize = lp.width; 1175 } 1176 } 1177 if (lp.height != LayoutParams.WRAP_CONTENT) { 1178 heightMode = MeasureSpec.EXACTLY; 1179 if (lp.height != LayoutParams.FILL_PARENT) { 1180 heightSize = lp.height; 1181 } 1182 } 1183 final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode); 1184 final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode); 1185 child.measure(widthSpec, heightSpec); 1186 1187 if (consumeVertical) { 1188 childHeightSize -= child.getMeasuredHeight(); 1189 } else if (consumeHorizontal) { 1190 childWidthSize -= child.getMeasuredWidth(); 1191 } 1192 } 1193 } 1194 } 1195 1196 mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY); 1197 mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY); 1198 1199 // Make sure we have created all fragments that we need to have shown. 1200 mInLayout = true; 1201 populate(); 1202 mInLayout = false; 1203 1204 // Page views next. 1205 size = getChildCount(); 1206 for (int i = 0; i < size; ++i) { 1207 final View child = getChildAt(i); 1208 if (child.getVisibility() != GONE) { 1209 if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child 1210 + ": " + mChildWidthMeasureSpec); 1211 1212 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1213 if (lp == null || !lp.isDecor) { 1214 final int widthSpec = MeasureSpec.makeMeasureSpec( 1215 (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY); 1216 child.measure(widthSpec, mChildHeightMeasureSpec); 1217 } 1218 } 1219 } 1220 } 1221 1222 @Override 1223 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 1224 super.onSizeChanged(w, h, oldw, oldh); 1225 1226 // Make sure scroll position is set correctly. 1227 if (w != oldw) { 1228 recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin); 1229 } 1230 } 1231 1232 private void recomputeScrollPosition(int width, int oldWidth, int margin, int oldMargin) { 1233 if (oldWidth > 0 && !mItems.isEmpty()) { 1234 final int xpos = getScrollX(); 1235 final ItemInfo ii = infoForCurrentScrollPosition(); 1236 final float pageOffset = (((float) xpos / width) - ii.offset) / ii.widthFactor; 1237 final int offsetPixels = (int) (pageOffset * width); 1238 1239 scrollTo(offsetPixels, getScrollY()); 1240 if (!mScroller.isFinished()) { 1241 // We now return to your regularly scheduled scroll, already in progress. 1242 final int newDuration = mScroller.getDuration() - mScroller.timePassed(); 1243 ItemInfo targetInfo = infoForPosition(mCurItem); 1244 mScroller.startScroll(offsetPixels, 0, (int) (targetInfo.offset * width), 0, 1245 newDuration); 1246 } 1247 } else { 1248 final ItemInfo ii = infoForPosition(mCurItem); 1249 final int scrollPos = (int) ((ii != null ? ii.offset : 0) * width); 1250 if (scrollPos != getScrollX()) { 1251 completeScroll(); 1252 scrollTo(scrollPos, getScrollY()); 1253 } 1254 } 1255 } 1256 1257 @Override 1258 protected void onLayout(boolean changed, int l, int t, int r, int b) { 1259 mInLayout = true; 1260 populate(); 1261 mInLayout = false; 1262 1263 final int count = getChildCount(); 1264 int width = r - l; 1265 int height = b - t; 1266 int paddingLeft = getPaddingLeft(); 1267 int paddingTop = getPaddingTop(); 1268 int paddingRight = getPaddingRight(); 1269 int paddingBottom = getPaddingBottom(); 1270 final int scrollX = getScrollX(); 1271 1272 int decorCount = 0; 1273 1274 // First pass - decor views. We need to do this in two passes so that 1275 // we have the proper offsets for non-decor views later. 1276 for (int i = 0; i < count; i++) { 1277 final View child = getChildAt(i); 1278 if (child.getVisibility() != GONE) { 1279 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1280 int childLeft = 0; 1281 int childTop = 0; 1282 if (lp.isDecor) { 1283 final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; 1284 final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; 1285 switch (hgrav) { 1286 default: 1287 childLeft = paddingLeft; 1288 break; 1289 case Gravity.LEFT: 1290 childLeft = paddingLeft; 1291 paddingLeft += child.getMeasuredWidth(); 1292 break; 1293 case Gravity.CENTER_HORIZONTAL: 1294 childLeft = Math.max((width - child.getMeasuredWidth()) / 2, 1295 paddingLeft); 1296 break; 1297 case Gravity.RIGHT: 1298 childLeft = width - paddingRight - child.getMeasuredWidth(); 1299 paddingRight += child.getMeasuredWidth(); 1300 break; 1301 } 1302 switch (vgrav) { 1303 default: 1304 childTop = paddingTop; 1305 break; 1306 case Gravity.TOP: 1307 childTop = paddingTop; 1308 paddingTop += child.getMeasuredHeight(); 1309 break; 1310 case Gravity.CENTER_VERTICAL: 1311 childTop = Math.max((height - child.getMeasuredHeight()) / 2, 1312 paddingTop); 1313 break; 1314 case Gravity.BOTTOM: 1315 childTop = height - paddingBottom - child.getMeasuredHeight(); 1316 paddingBottom += child.getMeasuredHeight(); 1317 break; 1318 } 1319 childLeft += scrollX; 1320 child.layout(childLeft, childTop, 1321 childLeft + child.getMeasuredWidth(), 1322 childTop + child.getMeasuredHeight()); 1323 decorCount++; 1324 } 1325 } 1326 } 1327 1328 // Page views. Do this once we have the right padding offsets from above. 1329 for (int i = 0; i < count; i++) { 1330 final View child = getChildAt(i); 1331 if (child.getVisibility() != GONE) { 1332 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1333 ItemInfo ii; 1334 if (!lp.isDecor && (ii = infoForChild(child)) != null) { 1335 int loff = (int) (width * ii.offset); 1336 int childLeft = paddingLeft + loff; 1337 int childTop = paddingTop; 1338 if (lp.needsMeasure) { 1339 // This was added during layout and needs measurement. 1340 // Do it now that we know what we're working with. 1341 lp.needsMeasure = false; 1342 final int widthSpec = MeasureSpec.makeMeasureSpec( 1343 (int) ((width - paddingLeft - paddingRight) * lp.widthFactor), 1344 MeasureSpec.EXACTLY); 1345 final int heightSpec = MeasureSpec.makeMeasureSpec( 1346 (int) (height - paddingTop - paddingBottom), 1347 MeasureSpec.EXACTLY); 1348 child.measure(widthSpec, heightSpec); 1349 } 1350 if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object 1351 + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth() 1352 + "x" + child.getMeasuredHeight()); 1353 child.layout(childLeft, childTop, 1354 childLeft + child.getMeasuredWidth(), 1355 childTop + child.getMeasuredHeight()); 1356 } 1357 } 1358 } 1359 mTopPageBounds = paddingTop; 1360 mBottomPageBounds = height - paddingBottom; 1361 mDecorChildCount = decorCount; 1362 mFirstLayout = false; 1363 } 1364 1365 @Override 1366 public void computeScroll() { 1367 if (DEBUG) Log.i(TAG, "computeScroll: finished=" + mScroller.isFinished()); 1368 if (!mScroller.isFinished() && mScroller.computeScrollOffset()) { 1369 if (DEBUG) Log.i(TAG, "computeScroll: still scrolling"); 1370 int oldX = getScrollX(); 1371 int oldY = getScrollY(); 1372 int x = mScroller.getCurrX(); 1373 int y = mScroller.getCurrY(); 1374 1375 if (oldX != x || oldY != y) { 1376 scrollTo(x, y); 1377 pageScrolled(x); 1378 } 1379 1380 // Keep on drawing until the animation has finished. 1381 invalidate(); 1382 return; 1383 } 1384 1385 // Done with scroll, clean up state. 1386 completeScroll(); 1387 } 1388 1389 private void pageScrolled(int xpos) { 1390 final ItemInfo ii = infoForCurrentScrollPosition(); 1391 final int width = getWidth(); 1392 final int widthWithMargin = width + mPageMargin; 1393 final float marginOffset = (float) mPageMargin / width; 1394 final int currentPage = ii.position; 1395 final float pageOffset = (((float) xpos / width) - ii.offset) / 1396 (ii.widthFactor + marginOffset); 1397 final int offsetPixels = (int) (pageOffset * widthWithMargin); 1398 1399 mCalledSuper = false; 1400 onPageScrolled(currentPage, pageOffset, offsetPixels); 1401 if (!mCalledSuper) { 1402 throw new IllegalStateException( 1403 "onPageScrolled did not call superclass implementation"); 1404 } 1405 } 1406 1407 /** 1408 * This method will be invoked when the current page is scrolled, either as part 1409 * of a programmatically initiated smooth scroll or a user initiated touch scroll. 1410 * If you override this method you must call through to the superclass implementation 1411 * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled 1412 * returns. 1413 * 1414 * @param position Position index of the first page currently being displayed. 1415 * Page position+1 will be visible if positionOffset is nonzero. 1416 * @param offset Value from [0, 1) indicating the offset from the page at position. 1417 * @param offsetPixels Value in pixels indicating the offset from position. 1418 */ 1419 protected void onPageScrolled(int position, float offset, int offsetPixels) { 1420 // Offset any decor views if needed - keep them on-screen at all times. 1421 if (mDecorChildCount > 0) { 1422 final int scrollX = getScrollX(); 1423 int paddingLeft = getPaddingLeft(); 1424 int paddingRight = getPaddingRight(); 1425 final int width = getWidth(); 1426 final int childCount = getChildCount(); 1427 for (int i = 0; i < childCount; i++) { 1428 final View child = getChildAt(i); 1429 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1430 if (!lp.isDecor) continue; 1431 1432 final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; 1433 int childLeft = 0; 1434 switch (hgrav) { 1435 default: 1436 childLeft = paddingLeft; 1437 break; 1438 case Gravity.LEFT: 1439 childLeft = paddingLeft; 1440 paddingLeft += child.getWidth(); 1441 break; 1442 case Gravity.CENTER_HORIZONTAL: 1443 childLeft = Math.max((width - child.getMeasuredWidth()) / 2, 1444 paddingLeft); 1445 break; 1446 case Gravity.RIGHT: 1447 childLeft = width - paddingRight - child.getMeasuredWidth(); 1448 paddingRight += child.getMeasuredWidth(); 1449 break; 1450 } 1451 childLeft += scrollX; 1452 1453 final int childOffset = childLeft - child.getLeft(); 1454 if (childOffset != 0) { 1455 child.offsetLeftAndRight(childOffset); 1456 } 1457 } 1458 } 1459 1460 if (mOnPageChangeListener != null) { 1461 mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels); 1462 } 1463 if (mInternalPageChangeListener != null) { 1464 mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels); 1465 } 1466 mCalledSuper = true; 1467 } 1468 1469 private void completeScroll() { 1470 boolean needPopulate = mScrolling; 1471 if (needPopulate) { 1472 // Done with scroll, no longer want to cache view drawing. 1473 setScrollingCacheEnabled(false); 1474 mScroller.abortAnimation(); 1475 int oldX = getScrollX(); 1476 int oldY = getScrollY(); 1477 int x = mScroller.getCurrX(); 1478 int y = mScroller.getCurrY(); 1479 if (oldX != x || oldY != y) { 1480 scrollTo(x, y); 1481 } 1482 setScrollState(SCROLL_STATE_IDLE); 1483 } 1484 mPopulatePending = false; 1485 mScrolling = false; 1486 for (int i=0; i<mItems.size(); i++) { 1487 ItemInfo ii = mItems.get(i); 1488 if (ii.scrolling) { 1489 needPopulate = true; 1490 ii.scrolling = false; 1491 } 1492 } 1493 if (needPopulate) { 1494 populate(); 1495 } 1496 } 1497 1498 @Override 1499 public boolean onInterceptTouchEvent(MotionEvent ev) { 1500 /* 1501 * This method JUST determines whether we want to intercept the motion. 1502 * If we return true, onMotionEvent will be called and we do the actual 1503 * scrolling there. 1504 */ 1505 1506 final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; 1507 1508 // Always take care of the touch gesture being complete. 1509 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 1510 // Release the drag. 1511 if (DEBUG) Log.v(TAG, "Intercept done!"); 1512 mIsBeingDragged = false; 1513 mIsUnableToDrag = false; 1514 mActivePointerId = INVALID_POINTER; 1515 if (mVelocityTracker != null) { 1516 mVelocityTracker.recycle(); 1517 mVelocityTracker = null; 1518 } 1519 return false; 1520 } 1521 1522 // Nothing more to do here if we have decided whether or not we 1523 // are dragging. 1524 if (action != MotionEvent.ACTION_DOWN) { 1525 if (mIsBeingDragged) { 1526 if (DEBUG) Log.v(TAG, "Intercept returning true!"); 1527 return true; 1528 } 1529 if (mIsUnableToDrag) { 1530 if (DEBUG) Log.v(TAG, "Intercept returning false!"); 1531 return false; 1532 } 1533 } 1534 1535 switch (action) { 1536 case MotionEvent.ACTION_MOVE: { 1537 /* 1538 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 1539 * whether the user has moved far enough from his original down touch. 1540 */ 1541 1542 /* 1543 * Locally do absolute value. mLastMotionY is set to the y value 1544 * of the down event. 1545 */ 1546 final int activePointerId = mActivePointerId; 1547 if (activePointerId == INVALID_POINTER) { 1548 // If we don't have a valid id, the touch down wasn't on content. 1549 break; 1550 } 1551 1552 final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId); 1553 final float x = MotionEventCompat.getX(ev, pointerIndex); 1554 final float dx = x - mLastMotionX; 1555 final float xDiff = Math.abs(dx); 1556 final float y = MotionEventCompat.getY(ev, pointerIndex); 1557 final float yDiff = Math.abs(y - mLastMotionY); 1558 if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); 1559 1560 if (canScroll(this, false, (int) dx, (int) x, (int) y)) { 1561 // Nested view has scrollable area under this point. Let it be handled there. 1562 mInitialMotionX = mLastMotionX = x; 1563 mLastMotionY = y; 1564 return false; 1565 } 1566 if (xDiff > mTouchSlop && xDiff > yDiff) { 1567 if (DEBUG) Log.v(TAG, "Starting drag!"); 1568 mIsBeingDragged = true; 1569 setScrollState(SCROLL_STATE_DRAGGING); 1570 mLastMotionX = x; 1571 setScrollingCacheEnabled(true); 1572 } else { 1573 if (yDiff > mTouchSlop) { 1574 // The finger has moved enough in the vertical 1575 // direction to be counted as a drag... abort 1576 // any attempt to drag horizontally, to work correctly 1577 // with children that have scrolling containers. 1578 if (DEBUG) Log.v(TAG, "Starting unable to drag!"); 1579 mIsUnableToDrag = true; 1580 } 1581 } 1582 break; 1583 } 1584 1585 case MotionEvent.ACTION_DOWN: { 1586 /* 1587 * Remember location of down touch. 1588 * ACTION_DOWN always refers to pointer index 0. 1589 */ 1590 mLastMotionX = mInitialMotionX = ev.getX(); 1591 mLastMotionY = ev.getY(); 1592 mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 1593 1594 if (mScrollState == SCROLL_STATE_SETTLING) { 1595 // Let the user 'catch' the pager as it animates. 1596 mIsBeingDragged = true; 1597 mIsUnableToDrag = false; 1598 setScrollState(SCROLL_STATE_DRAGGING); 1599 } else { 1600 completeScroll(); 1601 mIsBeingDragged = false; 1602 mIsUnableToDrag = false; 1603 } 1604 1605 if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY 1606 + " mIsBeingDragged=" + mIsBeingDragged 1607 + "mIsUnableToDrag=" + mIsUnableToDrag); 1608 break; 1609 } 1610 1611 case MotionEventCompat.ACTION_POINTER_UP: 1612 onSecondaryPointerUp(ev); 1613 break; 1614 } 1615 1616 if (!mIsBeingDragged) { 1617 // Track the velocity as long as we aren't dragging. 1618 // Once we start a real drag we will track in onTouchEvent. 1619 if (mVelocityTracker == null) { 1620 mVelocityTracker = VelocityTracker.obtain(); 1621 } 1622 mVelocityTracker.addMovement(ev); 1623 } 1624 1625 /* 1626 * The only time we want to intercept motion events is if we are in the 1627 * drag mode. 1628 */ 1629 return mIsBeingDragged; 1630 } 1631 1632 @Override 1633 public boolean onTouchEvent(MotionEvent ev) { 1634 if (mFakeDragging) { 1635 // A fake drag is in progress already, ignore this real one 1636 // but still eat the touch events. 1637 // (It is likely that the user is multi-touching the screen.) 1638 return true; 1639 } 1640 1641 if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) { 1642 // Don't handle edge touches immediately -- they may actually belong to one of our 1643 // descendants. 1644 return false; 1645 } 1646 1647 if (mAdapter == null || mAdapter.getCount() == 0) { 1648 // Nothing to present or scroll; nothing to touch. 1649 return false; 1650 } 1651 1652 if (mVelocityTracker == null) { 1653 mVelocityTracker = VelocityTracker.obtain(); 1654 } 1655 mVelocityTracker.addMovement(ev); 1656 1657 final int action = ev.getAction(); 1658 boolean needsInvalidate = false; 1659 1660 switch (action & MotionEventCompat.ACTION_MASK) { 1661 case MotionEvent.ACTION_DOWN: { 1662 /* 1663 * If being flinged and user touches, stop the fling. isFinished 1664 * will be false if being flinged. 1665 */ 1666 completeScroll(); 1667 1668 // Remember where the motion event started 1669 mLastMotionX = mInitialMotionX = ev.getX(); 1670 mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 1671 break; 1672 } 1673 case MotionEvent.ACTION_MOVE: 1674 if (!mIsBeingDragged) { 1675 final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 1676 final float x = MotionEventCompat.getX(ev, pointerIndex); 1677 final float xDiff = Math.abs(x - mLastMotionX); 1678 final float y = MotionEventCompat.getY(ev, pointerIndex); 1679 final float yDiff = Math.abs(y - mLastMotionY); 1680 if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); 1681 if (xDiff > mTouchSlop && xDiff > yDiff) { 1682 if (DEBUG) Log.v(TAG, "Starting drag!"); 1683 mIsBeingDragged = true; 1684 mLastMotionX = x; 1685 setScrollState(SCROLL_STATE_DRAGGING); 1686 setScrollingCacheEnabled(true); 1687 } 1688 } 1689 if (mIsBeingDragged) { 1690 // Scroll to follow the motion event 1691 final int activePointerIndex = MotionEventCompat.findPointerIndex( 1692 ev, mActivePointerId); 1693 final float x = MotionEventCompat.getX(ev, activePointerIndex); 1694 final float deltaX = mLastMotionX - x; 1695 mLastMotionX = x; 1696 float oldScrollX = getScrollX(); 1697 float scrollX = oldScrollX + deltaX; 1698 final int width = getWidth(); 1699 1700 float leftBound = width * mFirstOffset; 1701 float rightBound = width * mLastOffset; 1702 boolean leftAbsolute = true; 1703 boolean rightAbsolute = true; 1704 1705 final ItemInfo firstItem = mItems.get(0); 1706 final ItemInfo lastItem = mItems.get(mItems.size() - 1); 1707 if (firstItem.position != 0) { 1708 leftAbsolute = false; 1709 leftBound = firstItem.offset * width; 1710 } 1711 if (lastItem.position != mAdapter.getCount() - 1) { 1712 rightAbsolute = false; 1713 rightBound = lastItem.offset * width; 1714 } 1715 1716 if (scrollX < leftBound) { 1717 if (leftAbsolute) { 1718 float over = leftBound - scrollX; 1719 needsInvalidate = mLeftEdge.onPull(Math.abs(over) / width); 1720 } 1721 scrollX = leftBound; 1722 } else if (scrollX > rightBound) { 1723 if (rightAbsolute) { 1724 float over = scrollX - rightBound; 1725 needsInvalidate = mRightEdge.onPull(Math.abs(over) / width); 1726 } 1727 scrollX = rightBound; 1728 } 1729 // Don't lose the rounded component 1730 mLastMotionX += scrollX - (int) scrollX; 1731 scrollTo((int) scrollX, getScrollY()); 1732 pageScrolled((int) scrollX); 1733 } 1734 break; 1735 case MotionEvent.ACTION_UP: 1736 if (mIsBeingDragged) { 1737 final VelocityTracker velocityTracker = mVelocityTracker; 1738 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1739 int initialVelocity = (int) VelocityTrackerCompat.getXVelocity( 1740 velocityTracker, mActivePointerId); 1741 mPopulatePending = true; 1742 final int width = getWidth(); 1743 final int scrollX = getScrollX(); 1744 final ItemInfo ii = infoForCurrentScrollPosition(); 1745 final int currentPage = ii.position; 1746 final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor; 1747 final int activePointerIndex = 1748 MotionEventCompat.findPointerIndex(ev, mActivePointerId); 1749 final float x = MotionEventCompat.getX(ev, activePointerIndex); 1750 final int totalDelta = (int) (x - mInitialMotionX); 1751 int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, 1752 totalDelta); 1753 setCurrentItemInternal(nextPage, true, true, initialVelocity); 1754 1755 mActivePointerId = INVALID_POINTER; 1756 endDrag(); 1757 needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease(); 1758 } 1759 break; 1760 case MotionEvent.ACTION_CANCEL: 1761 if (mIsBeingDragged) { 1762 setCurrentItemInternal(mCurItem, true, true); 1763 mActivePointerId = INVALID_POINTER; 1764 endDrag(); 1765 needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease(); 1766 } 1767 break; 1768 case MotionEventCompat.ACTION_POINTER_DOWN: { 1769 final int index = MotionEventCompat.getActionIndex(ev); 1770 final float x = MotionEventCompat.getX(ev, index); 1771 mLastMotionX = x; 1772 mActivePointerId = MotionEventCompat.getPointerId(ev, index); 1773 break; 1774 } 1775 case MotionEventCompat.ACTION_POINTER_UP: 1776 onSecondaryPointerUp(ev); 1777 mLastMotionX = MotionEventCompat.getX(ev, 1778 MotionEventCompat.findPointerIndex(ev, mActivePointerId)); 1779 break; 1780 } 1781 if (needsInvalidate) { 1782 invalidate(); 1783 } 1784 return true; 1785 } 1786 1787 /** 1788 * @return Info about the page at the current scroll position. 1789 * This can be synthetic for a missing middle page; the 'object' field can be null. 1790 */ 1791 private ItemInfo infoForCurrentScrollPosition() { 1792 final int width = getWidth(); 1793 final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0; 1794 final float marginOffset = width > 0 ? (float) mPageMargin / width : 0; 1795 int lastPos = -1; 1796 float lastOffset = 0.f; 1797 float lastWidth = 0.f; 1798 boolean first = true; 1799 1800 for (int i = 0; i < mItems.size(); i++) { 1801 ItemInfo ii = mItems.get(i); 1802 float offset; 1803 if (!first && ii.position != lastPos + 1) { 1804 // Create a synthetic item for a missing page. 1805 ii = mTempItem; 1806 ii.offset = lastOffset + lastWidth + marginOffset; 1807 ii.position = lastPos + 1; 1808 ii.widthFactor = mAdapter.getPageWidth(ii.position); 1809 i--; 1810 } 1811 offset = ii.offset; 1812 1813 // TODO: These epsilon checks are lame. A better implementation wouldn't 1814 // accumulate floating point values and instead track actual pixel offsets. 1815 final float leftBound = offset - 0.0001f; 1816 final float rightBound = offset + ii.widthFactor + marginOffset + 0.0001f; 1817 if ((first || scrollOffset >= leftBound) && 1818 (scrollOffset < rightBound || i == mItems.size() - 1)) { 1819 return ii; 1820 } 1821 first = false; 1822 lastPos = ii.position; 1823 lastOffset = offset; 1824 lastWidth = ii.widthFactor; 1825 } 1826 1827 return null; 1828 } 1829 1830 private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) { 1831 int targetPage; 1832 if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) { 1833 targetPage = velocity > 0 ? currentPage : currentPage + 1; 1834 } else { 1835 targetPage = (int) (currentPage + pageOffset + 0.5f); 1836 } 1837 1838 if (mItems.size() > 0) { 1839 final ItemInfo firstItem = mItems.get(0); 1840 final ItemInfo lastItem = mItems.get(mItems.size() - 1); 1841 1842 // Only let the user target pages we have items for 1843 targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position)); 1844 } 1845 1846 return targetPage; 1847 } 1848 1849 @Override 1850 public void draw(Canvas canvas) { 1851 super.draw(canvas); 1852 boolean needsInvalidate = false; 1853 1854 final int overScrollMode = ViewCompat.getOverScrollMode(this); 1855 if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS || 1856 (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && 1857 mAdapter != null && mAdapter.getCount() > 1)) { 1858 if (!mLeftEdge.isFinished()) { 1859 final int restoreCount = canvas.save(); 1860 final int height = getHeight() - getPaddingTop() - getPaddingBottom(); 1861 final int width = getWidth(); 1862 1863 canvas.rotate(270); 1864 canvas.translate(-height + getPaddingTop(), mFirstOffset * width); 1865 mLeftEdge.setSize(height, width); 1866 needsInvalidate |= mLeftEdge.draw(canvas); 1867 canvas.restoreToCount(restoreCount); 1868 } 1869 if (!mRightEdge.isFinished()) { 1870 final int restoreCount = canvas.save(); 1871 final int width = getWidth(); 1872 final int height = getHeight() - getPaddingTop() - getPaddingBottom(); 1873 1874 canvas.rotate(90); 1875 canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width); 1876 mRightEdge.setSize(height, width); 1877 needsInvalidate |= mRightEdge.draw(canvas); 1878 canvas.restoreToCount(restoreCount); 1879 } 1880 } else { 1881 mLeftEdge.finish(); 1882 mRightEdge.finish(); 1883 } 1884 1885 if (needsInvalidate) { 1886 // Keep animating 1887 invalidate(); 1888 } 1889 } 1890 1891 @Override 1892 protected void onDraw(Canvas canvas) { 1893 super.onDraw(canvas); 1894 1895 // Draw the margin drawable between pages if needed. 1896 if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) { 1897 final int scrollX = getScrollX(); 1898 final int width = getWidth(); 1899 1900 final float marginOffset = (float) mPageMargin / width; 1901 int itemIndex = 0; 1902 ItemInfo ii = mItems.get(0); 1903 float offset = ii.offset; 1904 final int itemCount = mItems.size(); 1905 final int firstPos = ii.position; 1906 final int lastPos = mItems.get(itemCount - 1).position; 1907 for (int pos = firstPos; pos < lastPos; pos++) { 1908 while (pos > ii.position && itemIndex < itemCount) { 1909 ii = mItems.get(++itemIndex); 1910 } 1911 1912 float drawAt; 1913 if (pos == ii.position) { 1914 drawAt = (ii.offset + ii.widthFactor) * width; 1915 offset = ii.offset + ii.widthFactor + marginOffset; 1916 } else { 1917 float widthFactor = mAdapter.getPageWidth(pos); 1918 drawAt = (offset + widthFactor) * width; 1919 offset += widthFactor + marginOffset; 1920 } 1921 1922 if (drawAt + mPageMargin > scrollX) { 1923 mMarginDrawable.setBounds((int) (drawAt - 0.5f), mTopPageBounds, 1924 (int) (drawAt + mPageMargin + 0.5f), mBottomPageBounds); 1925 mMarginDrawable.draw(canvas); 1926 } 1927 1928 if (drawAt > scrollX + width) { 1929 break; // No more visible, no sense in continuing 1930 } 1931 } 1932 } 1933 } 1934 1935 /** 1936 * Start a fake drag of the pager. 1937 * 1938 * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager 1939 * with the touch scrolling of another view, while still letting the ViewPager 1940 * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.) 1941 * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call 1942 * {@link #endFakeDrag()} to complete the fake drag and fling as necessary. 1943 * 1944 * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag 1945 * is already in progress, this method will return false. 1946 * 1947 * @return true if the fake drag began successfully, false if it could not be started. 1948 * 1949 * @see #fakeDragBy(float) 1950 * @see #endFakeDrag() 1951 */ 1952 public boolean beginFakeDrag() { 1953 if (mIsBeingDragged) { 1954 return false; 1955 } 1956 mFakeDragging = true; 1957 setScrollState(SCROLL_STATE_DRAGGING); 1958 mInitialMotionX = mLastMotionX = 0; 1959 if (mVelocityTracker == null) { 1960 mVelocityTracker = VelocityTracker.obtain(); 1961 } else { 1962 mVelocityTracker.clear(); 1963 } 1964 final long time = SystemClock.uptimeMillis(); 1965 final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0); 1966 mVelocityTracker.addMovement(ev); 1967 ev.recycle(); 1968 mFakeDragBeginTime = time; 1969 return true; 1970 } 1971 1972 /** 1973 * End a fake drag of the pager. 1974 * 1975 * @see #beginFakeDrag() 1976 * @see #fakeDragBy(float) 1977 */ 1978 public void endFakeDrag() { 1979 if (!mFakeDragging) { 1980 throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); 1981 } 1982 1983 final VelocityTracker velocityTracker = mVelocityTracker; 1984 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1985 int initialVelocity = (int) VelocityTrackerCompat.getXVelocity( 1986 velocityTracker, mActivePointerId); 1987 mPopulatePending = true; 1988 final int width = getWidth(); 1989 final int scrollX = getScrollX(); 1990 final ItemInfo ii = infoForCurrentScrollPosition(); 1991 final int currentPage = ii.position; 1992 final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor; 1993 final int totalDelta = (int) (mLastMotionX - mInitialMotionX); 1994 int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, 1995 totalDelta); 1996 setCurrentItemInternal(nextPage, true, true, initialVelocity); 1997 endDrag(); 1998 1999 mFakeDragging = false; 2000 } 2001 2002 /** 2003 * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first. 2004 * 2005 * @param xOffset Offset in pixels to drag by. 2006 * @see #beginFakeDrag() 2007 * @see #endFakeDrag() 2008 */ 2009 public void fakeDragBy(float xOffset) { 2010 if (!mFakeDragging) { 2011 throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); 2012 } 2013 2014 mLastMotionX += xOffset; 2015 2016 float oldScrollX = getScrollX(); 2017 float scrollX = oldScrollX - xOffset; 2018 final int width = getWidth(); 2019 2020 float leftBound = width * mFirstOffset; 2021 float rightBound = width * mLastOffset; 2022 2023 final ItemInfo firstItem = mItems.get(0); 2024 final ItemInfo lastItem = mItems.get(mItems.size() - 1); 2025 if (firstItem.position != 0) { 2026 leftBound = firstItem.offset * width; 2027 } 2028 if (lastItem.position != mAdapter.getCount() - 1) { 2029 rightBound = lastItem.offset * width; 2030 } 2031 2032 if (scrollX < leftBound) { 2033 scrollX = leftBound; 2034 } else if (scrollX > rightBound) { 2035 scrollX = rightBound; 2036 } 2037 // Don't lose the rounded component 2038 mLastMotionX += scrollX - (int) scrollX; 2039 scrollTo((int) scrollX, getScrollY()); 2040 pageScrolled((int) scrollX); 2041 2042 // Synthesize an event for the VelocityTracker. 2043 final long time = SystemClock.uptimeMillis(); 2044 final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE, 2045 mLastMotionX, 0, 0); 2046 mVelocityTracker.addMovement(ev); 2047 ev.recycle(); 2048 } 2049 2050 /** 2051 * Returns true if a fake drag is in progress. 2052 * 2053 * @return true if currently in a fake drag, false otherwise. 2054 * 2055 * @see #beginFakeDrag() 2056 * @see #fakeDragBy(float) 2057 * @see #endFakeDrag() 2058 */ 2059 public boolean isFakeDragging() { 2060 return mFakeDragging; 2061 } 2062 2063 private void onSecondaryPointerUp(MotionEvent ev) { 2064 final int pointerIndex = MotionEventCompat.getActionIndex(ev); 2065 final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 2066 if (pointerId == mActivePointerId) { 2067 // This was our active pointer going up. Choose a new 2068 // active pointer and adjust accordingly. 2069 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 2070 mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex); 2071 mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); 2072 if (mVelocityTracker != null) { 2073 mVelocityTracker.clear(); 2074 } 2075 } 2076 } 2077 2078 private void endDrag() { 2079 mIsBeingDragged = false; 2080 mIsUnableToDrag = false; 2081 2082 if (mVelocityTracker != null) { 2083 mVelocityTracker.recycle(); 2084 mVelocityTracker = null; 2085 } 2086 } 2087 2088 private void setScrollingCacheEnabled(boolean enabled) { 2089 if (mScrollingCacheEnabled != enabled) { 2090 mScrollingCacheEnabled = enabled; 2091 if (USE_CACHE) { 2092 final int size = getChildCount(); 2093 for (int i = 0; i < size; ++i) { 2094 final View child = getChildAt(i); 2095 if (child.getVisibility() != GONE) { 2096 child.setDrawingCacheEnabled(enabled); 2097 } 2098 } 2099 } 2100 } 2101 } 2102 2103 /** 2104 * Tests scrollability within child views of v given a delta of dx. 2105 * 2106 * @param v View to test for horizontal scrollability 2107 * @param checkV Whether the view v passed should itself be checked for scrollability (true), 2108 * or just its children (false). 2109 * @param dx Delta scrolled in pixels 2110 * @param x X coordinate of the active touch point 2111 * @param y Y coordinate of the active touch point 2112 * @return true if child views of v can be scrolled by delta of dx. 2113 */ 2114 protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { 2115 if (v instanceof ViewGroup) { 2116 final ViewGroup group = (ViewGroup) v; 2117 final int scrollX = v.getScrollX(); 2118 final int scrollY = v.getScrollY(); 2119 final int count = group.getChildCount(); 2120 // Count backwards - let topmost views consume scroll distance first. 2121 for (int i = count - 1; i >= 0; i--) { 2122 // TODO: Add versioned support here for transformed views. 2123 // This will not work for transformed views in Honeycomb+ 2124 final View child = group.getChildAt(i); 2125 if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && 2126 y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && 2127 canScroll(child, true, dx, x + scrollX - child.getLeft(), 2128 y + scrollY - child.getTop())) { 2129 return true; 2130 } 2131 } 2132 } 2133 2134 return checkV && ViewCompat.canScrollHorizontally(v, -dx); 2135 } 2136 2137 @Override 2138 public boolean dispatchKeyEvent(KeyEvent event) { 2139 // Let the focused view and/or our descendants get the key first 2140 return super.dispatchKeyEvent(event) || executeKeyEvent(event); 2141 } 2142 2143 /** 2144 * You can call this function yourself to have the scroll view perform 2145 * scrolling from a key event, just as if the event had been dispatched to 2146 * it by the view hierarchy. 2147 * 2148 * @param event The key event to execute. 2149 * @return Return true if the event was handled, else false. 2150 */ 2151 public boolean executeKeyEvent(KeyEvent event) { 2152 boolean handled = false; 2153 if (event.getAction() == KeyEvent.ACTION_DOWN) { 2154 switch (event.getKeyCode()) { 2155 case KeyEvent.KEYCODE_DPAD_LEFT: 2156 handled = arrowScroll(FOCUS_LEFT); 2157 break; 2158 case KeyEvent.KEYCODE_DPAD_RIGHT: 2159 handled = arrowScroll(FOCUS_RIGHT); 2160 break; 2161 case KeyEvent.KEYCODE_TAB: 2162 if (Build.VERSION.SDK_INT >= 11) { 2163 // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD 2164 // before Android 3.0. Ignore the tab key on those devices. 2165 if (KeyEventCompat.hasNoModifiers(event)) { 2166 handled = arrowScroll(FOCUS_FORWARD); 2167 } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) { 2168 handled = arrowScroll(FOCUS_BACKWARD); 2169 } 2170 } 2171 break; 2172 } 2173 } 2174 return handled; 2175 } 2176 2177 public boolean arrowScroll(int direction) { 2178 View currentFocused = findFocus(); 2179 if (currentFocused == this) currentFocused = null; 2180 2181 boolean handled = false; 2182 2183 View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, 2184 direction); 2185 if (nextFocused != null && nextFocused != currentFocused) { 2186 if (direction == View.FOCUS_LEFT) { 2187 // If there is nothing to the left, or this is causing us to 2188 // jump to the right, then what we really want to do is page left. 2189 if (currentFocused != null && nextFocused.getLeft() >= currentFocused.getLeft()) { 2190 handled = pageLeft(); 2191 } else { 2192 handled = nextFocused.requestFocus(); 2193 } 2194 } else if (direction == View.FOCUS_RIGHT) { 2195 // If there is nothing to the right, or this is causing us to 2196 // jump to the left, then what we really want to do is page right. 2197 if (currentFocused != null && nextFocused.getLeft() <= currentFocused.getLeft()) { 2198 handled = pageRight(); 2199 } else { 2200 handled = nextFocused.requestFocus(); 2201 } 2202 } 2203 } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) { 2204 // Trying to move left and nothing there; try to page. 2205 handled = pageLeft(); 2206 } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) { 2207 // Trying to move right and nothing there; try to page. 2208 handled = pageRight(); 2209 } 2210 if (handled) { 2211 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); 2212 } 2213 return handled; 2214 } 2215 2216 boolean pageLeft() { 2217 if (mCurItem > 0) { 2218 setCurrentItem(mCurItem-1, true); 2219 return true; 2220 } 2221 return false; 2222 } 2223 2224 boolean pageRight() { 2225 if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) { 2226 setCurrentItem(mCurItem+1, true); 2227 return true; 2228 } 2229 return false; 2230 } 2231 2232 /** 2233 * We only want the current page that is being shown to be focusable. 2234 */ 2235 @Override 2236 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 2237 final int focusableCount = views.size(); 2238 2239 final int descendantFocusability = getDescendantFocusability(); 2240 2241 if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { 2242 for (int i = 0; i < getChildCount(); i++) { 2243 final View child = getChildAt(i); 2244 if (child.getVisibility() == VISIBLE) { 2245 ItemInfo ii = infoForChild(child); 2246 if (ii != null && ii.position == mCurItem) { 2247 child.addFocusables(views, direction, focusableMode); 2248 } 2249 } 2250 } 2251 } 2252 2253 // we add ourselves (if focusable) in all cases except for when we are 2254 // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is 2255 // to avoid the focus search finding layouts when a more precise search 2256 // among the focusable children would be more interesting. 2257 if ( 2258 descendantFocusability != FOCUS_AFTER_DESCENDANTS || 2259 // No focusable descendants 2260 (focusableCount == views.size())) { 2261 // Note that we can't call the superclass here, because it will 2262 // add all views in. So we need to do the same thing View does. 2263 if (!isFocusable()) { 2264 return; 2265 } 2266 if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE && 2267 isInTouchMode() && !isFocusableInTouchMode()) { 2268 return; 2269 } 2270 if (views != null) { 2271 views.add(this); 2272 } 2273 } 2274 } 2275 2276 /** 2277 * We only want the current page that is being shown to be touchable. 2278 */ 2279 @Override 2280 public void addTouchables(ArrayList<View> views) { 2281 // Note that we don't call super.addTouchables(), which means that 2282 // we don't call View.addTouchables(). This is okay because a ViewPager 2283 // is itself not touchable. 2284 for (int i = 0; i < getChildCount(); i++) { 2285 final View child = getChildAt(i); 2286 if (child.getVisibility() == VISIBLE) { 2287 ItemInfo ii = infoForChild(child); 2288 if (ii != null && ii.position == mCurItem) { 2289 child.addTouchables(views); 2290 } 2291 } 2292 } 2293 } 2294 2295 /** 2296 * We only want the current page that is being shown to be focusable. 2297 */ 2298 @Override 2299 protected boolean onRequestFocusInDescendants(int direction, 2300 Rect previouslyFocusedRect) { 2301 int index; 2302 int increment; 2303 int end; 2304 int count = getChildCount(); 2305 if ((direction & FOCUS_FORWARD) != 0) { 2306 index = 0; 2307 increment = 1; 2308 end = count; 2309 } else { 2310 index = count - 1; 2311 increment = -1; 2312 end = -1; 2313 } 2314 for (int i = index; i != end; i += increment) { 2315 View child = getChildAt(i); 2316 if (child.getVisibility() == VISIBLE) { 2317 ItemInfo ii = infoForChild(child); 2318 if (ii != null && ii.position == mCurItem) { 2319 if (child.requestFocus(direction, previouslyFocusedRect)) { 2320 return true; 2321 } 2322 } 2323 } 2324 } 2325 return false; 2326 } 2327 2328 @Override 2329 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 2330 // ViewPagers should only report accessibility info for the current page, 2331 // otherwise things get very confusing. 2332 2333 // TODO: Should this note something about the paging container? 2334 2335 final int childCount = getChildCount(); 2336 for (int i = 0; i < childCount; i++) { 2337 final View child = getChildAt(i); 2338 if (child.getVisibility() == VISIBLE) { 2339 final ItemInfo ii = infoForChild(child); 2340 if (ii != null && ii.position == mCurItem && 2341 child.dispatchPopulateAccessibilityEvent(event)) { 2342 return true; 2343 } 2344 } 2345 } 2346 2347 return false; 2348 } 2349 2350 @Override 2351 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 2352 return new LayoutParams(); 2353 } 2354 2355 @Override 2356 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 2357 return generateDefaultLayoutParams(); 2358 } 2359 2360 @Override 2361 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 2362 return p instanceof LayoutParams && super.checkLayoutParams(p); 2363 } 2364 2365 @Override 2366 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 2367 return new LayoutParams(getContext(), attrs); 2368 } 2369 2370 private class PagerObserver extends DataSetObserver { 2371 @Override 2372 public void onChanged() { 2373 dataSetChanged(); 2374 } 2375 @Override 2376 public void onInvalidated() { 2377 dataSetChanged(); 2378 } 2379 } 2380 2381 /** 2382 * Layout parameters that should be supplied for views added to a 2383 * ViewPager. 2384 */ 2385 public static class LayoutParams extends ViewGroup.LayoutParams { 2386 /** 2387 * true if this view is a decoration on the pager itself and not 2388 * a view supplied by the adapter. 2389 */ 2390 public boolean isDecor; 2391 2392 /** 2393 * Gravity setting for use on decor views only: 2394 * Where to position the view page within the overall ViewPager 2395 * container; constants are defined in {@link android.view.Gravity}. 2396 */ 2397 public int gravity; 2398 2399 /** 2400 * Width as a 0-1 multiplier of the measured pager width 2401 */ 2402 public float widthFactor = 0.f; 2403 2404 /** 2405 * true if this view was added during layout and needs to be measured 2406 * before being positioned. 2407 */ 2408 public boolean needsMeasure; 2409 2410 public LayoutParams() { 2411 super(FILL_PARENT, FILL_PARENT); 2412 } 2413 2414 public LayoutParams(Context context, AttributeSet attrs) { 2415 super(context, attrs); 2416 2417 final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); 2418 gravity = a.getInteger(0, Gravity.TOP); 2419 a.recycle(); 2420 } 2421 } 2422} 2423