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