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