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