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