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