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