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