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