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