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