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