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