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