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