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