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