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