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