1 package com.folioreader.view;
3 import android.annotation.SuppressLint;
4 import android.content.Context;
5 import android.content.res.Resources;
6 import android.content.res.TypedArray;
7 import android.database.DataSetObserver;
8 import android.graphics.Canvas;
9 import android.graphics.Rect;
10 import android.graphics.drawable.Drawable;
11 import android.os.Build;
12 import android.os.Bundle;
13 import android.os.Parcel;
14 import android.os.Parcelable;
15 import android.os.SystemClock;
16 import android.support.v4.os.ParcelableCompat;
17 import android.support.v4.os.ParcelableCompatCreatorCallbacks;
18 import android.support.v4.view.AccessibilityDelegateCompat;
19 import android.support.v4.view.MotionEventCompat;
20 import android.support.v4.view.PagerAdapter;
21 import android.support.v4.view.VelocityTrackerCompat;
22 import android.support.v4.view.ViewCompat;
23 import android.support.v4.view.ViewConfigurationCompat;
24 import android.support.v4.view.ViewPager;
25 import android.support.v4.view.ViewPager.OnPageChangeListener;
26 import android.support.v4.view.ViewPager.PageTransformer;
27 import android.support.v4.view.accessibility.AccessibilityEventCompat;
28 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
29 import android.support.v4.view.accessibility.AccessibilityRecordCompat;
30 import android.support.v4.widget.EdgeEffectCompat;
31 import android.util.AttributeSet;
32 import android.util.Log;
33 import android.view.FocusFinder;
34 import android.view.Gravity;
35 import android.view.KeyEvent;
36 import android.view.MotionEvent;
37 import android.view.SoundEffectConstants;
38 import android.view.VelocityTracker;
39 import android.view.View;
40 import android.view.ViewConfiguration;
41 import android.view.ViewGroup;
42 import android.view.ViewParent;
43 import android.view.accessibility.AccessibilityEvent;
44 import android.view.animation.Interpolator;
45 import android.widget.Scroller;
47 import java.lang.reflect.Method;
48 import java.util.ArrayList;
49 import java.util.Collections;
50 import java.util.Comparator;
53 * Created by castorflex on 12/29/13.
54 * Just a copy of the original ViewPager modified to support vertical Scrolling
56 public class VerticalViewPager extends ViewGroup {
57 private static final String TAG = "ViewPager";
58 private static final boolean DEBUG = false;
60 private static final boolean USE_CACHE = false;
62 private static final int DEFAULT_OFFSCREEN_PAGES = 1;
63 private static final int MAX_SETTLE_DURATION = 600; // ms
64 private static final int MIN_DISTANCE_FOR_FLING = 25; // dips
66 private static final int DEFAULT_GUTTER_SIZE = 16; // dips
68 private static final int MIN_FLING_VELOCITY = 400; // dips
70 private static final int[] LAYOUT_ATTRS = new int[]{
71 android.R.attr.layout_gravity
77 * Used to track what the expected number of items in the adapter should be.
78 * If the app changes this when we don't expect it, we'll throw a big obnoxious exception.
80 private int mExpectedAdapterCount;
82 static class ItemInfo {
91 private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>() {
93 public int compare(ItemInfo lhs, ItemInfo rhs) {
94 return lhs.position - rhs.position;
98 private static final Interpolator sInterpolator = new Interpolator() {
99 public float getInterpolation(float t) {
101 return t * t * t * t * t + 1.0f;
105 private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
106 private final ItemInfo mTempItem = new ItemInfo();
108 private final Rect mTempRect = new Rect();
110 private PagerAdapter mAdapter;
111 private int mCurItem; // Index of currently displayed page.
112 private int mRestoredCurItem = -1;
113 private Parcelable mRestoredAdapterState = null;
114 private ClassLoader mRestoredClassLoader = null;
115 private Scroller mScroller;
116 private PagerObserver mObserver;
118 private int mPageMargin;
119 private Drawable mMarginDrawable;
120 private int mLeftPageBounds;
121 private int mRightPageBounds;
123 // Offsets of the first and last items, if known.
124 // Set during population, used to determine if we are at the beginning
125 // or end of the pager data set during touch scrolling.
126 private float mFirstOffset = -Float.MAX_VALUE;
127 private float mLastOffset = Float.MAX_VALUE;
129 private int mChildWidthMeasureSpec;
130 private int mChildHeightMeasureSpec;
131 private boolean mInLayout;
133 private boolean mScrollingCacheEnabled;
135 private boolean mPopulatePending;
136 private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
138 private boolean mIsBeingDragged;
139 private boolean mIsUnableToDrag;
140 private boolean mIgnoreGutter;
141 private int mDefaultGutterSize;
142 private int mGutterSize;
143 private int mTouchSlop;
145 * Position of the last motion event.
147 private float mLastMotionX;
148 private float mLastMotionY;
149 private float mInitialMotionX;
150 private float mInitialMotionY;
152 * ID of the active pointer. This is used to retain consistency during
153 * drags/flings if multiple pointers are used.
155 private int mActivePointerId = INVALID_POINTER;
157 * Sentinel value for no current active pointer.
158 * Used by {@link #mActivePointerId}.
160 private static final int INVALID_POINTER = -1;
163 * Determines speed during touch scrolling
165 private VelocityTracker mVelocityTracker;
166 private int mMinimumVelocity;
167 private int mMaximumVelocity;
168 private int mFlingDistance;
169 private int mCloseEnough;
171 // If the pager is at least this close to its final position, complete the scroll
172 // on touch down and let the user interact with the content inside instead of
173 // "catching" the flinging pager.
174 private static final int CLOSE_ENOUGH = 2; // dp
176 private boolean mFakeDragging;
177 private long mFakeDragBeginTime;
179 private EdgeEffectCompat mTopEdge;
180 private EdgeEffectCompat mBottomEdge;
181 private boolean mFirstLayout = true;
182 private boolean mNeedCalculatePageOffsets = false;
183 private boolean mCalledSuper;
184 private int mDecorChildCount;
186 private ViewPager.OnPageChangeListener mOnPageChangeListener;
187 private ViewPager.OnPageChangeListener mInternalPageChangeListener;
188 private OnAdapterChangeListener mAdapterChangeListener;
189 private ViewPager.PageTransformer mPageTransformer;
190 private Method mSetChildrenDrawingOrderEnabled;
192 private static final int DRAW_ORDER_DEFAULT = 0;
193 private static final int DRAW_ORDER_FORWARD = 1;
194 private static final int DRAW_ORDER_REVERSE = 2;
195 private int mDrawingOrder;
196 private ArrayList<View> mDrawingOrderedChildren;
197 private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator();
200 * Indicates that the pager is in an idle, settled state. The current page
201 * is fully in view and no animation is in progress.
203 public static final int SCROLL_STATE_IDLE = 0;
206 * Indicates that the pager is currently being dragged by the user.
208 public static final int SCROLL_STATE_DRAGGING = 1;
211 * Indicates that the pager is in the process of settling to a final position.
213 public static final int SCROLL_STATE_SETTLING = 2;
215 private final Runnable mEndScrollRunnable = new Runnable() {
217 setScrollState(SCROLL_STATE_IDLE);
222 private int mScrollState = SCROLL_STATE_IDLE;
224 // private ScrollerCustomDuration mScrollerCustomDuration = null;
227 * Used internally to monitor when adapters are switched.
229 interface OnAdapterChangeListener {
230 public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);
234 * Used internally to tag special types of child views that should be added as
235 * pager decorations by default.
240 public VerticalViewPager(Context context) {
243 //postInitViewPager();
246 public VerticalViewPager(Context context, AttributeSet attrs) {
247 super(context, attrs);
249 // postInitViewPager();
252 void initViewPager() {
253 setWillNotDraw(false);
254 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
256 final Context context = getContext();
257 mScroller = new Scroller(context, sInterpolator);
258 final ViewConfiguration configuration = ViewConfiguration.get(context);
259 final float density = context.getResources().getDisplayMetrics().density;
261 mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
262 mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);
263 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
264 mTopEdge = new EdgeEffectCompat(context);
265 mBottomEdge = new EdgeEffectCompat(context);
267 mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
268 mCloseEnough = (int) (CLOSE_ENOUGH * density);
269 mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);
271 ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate());
273 if (ViewCompat.getImportantForAccessibility(this)
274 == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
275 ViewCompat.setImportantForAccessibility(this,
276 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
278 //postInitViewPager();
282 /* private void postInitViewPager() {
284 Field scroller = ViewPager.class.getDeclaredField("mScroller");
285 scroller.setAccessible(true);
286 Field interpolator = ViewPager.class.getDeclaredField("sInterpolator");
287 interpolator.setAccessible(true);
289 mScrollerCustomDuration = new ScrollerCustomDuration(getContext(),
290 (Interpolator) interpolator.get(null));
291 scroller.set(this, mScrollerCustomDuration);
292 } catch (Exception e) {
296 public void setScrollDurationFactor(double scrollFactor) {
297 mScrollerCustomDuration.setScrollDurationFactor(scrollFactor);
301 protected void onDetachedFromWindow() {
302 removeCallbacks(mEndScrollRunnable);
303 super.onDetachedFromWindow();
306 private void setScrollState(int newState) {
307 if (mScrollState == newState) {
311 mScrollState = newState;
312 if (mPageTransformer != null) {
313 // PageTransformers can do complex things that benefit from hardware layers.
314 enableLayers(newState != SCROLL_STATE_IDLE);
316 if (mOnPageChangeListener != null) {
317 mOnPageChangeListener.onPageScrollStateChanged(newState);
322 * Set a PagerAdapter that will supply views for this pager as needed.
324 * @param adapter Adapter to use
326 public void setAdapter(PagerAdapter adapter) {
327 if (mAdapter != null) {
328 mAdapter.unregisterDataSetObserver(mObserver);
329 mAdapter.startUpdate(this);
330 for (int i = 0; i < mItems.size(); i++) {
331 final ItemInfo ii = mItems.get(i);
332 mAdapter.destroyItem(this, ii.position, ii.object);
334 mAdapter.finishUpdate(this);
336 removeNonDecorViews();
341 final PagerAdapter oldAdapter = mAdapter;
343 mExpectedAdapterCount = 0;
345 if (mAdapter != null) {
346 if (mObserver == null) {
347 mObserver = new PagerObserver();
349 mAdapter.registerDataSetObserver(mObserver);
350 mPopulatePending = false;
351 final boolean wasFirstLayout = mFirstLayout;
353 mExpectedAdapterCount = mAdapter.getCount();
354 if (mRestoredCurItem >= 0) {
355 mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
356 setCurrentItemInternal(mRestoredCurItem, false, true);
357 mRestoredCurItem = -1;
358 mRestoredAdapterState = null;
359 mRestoredClassLoader = null;
360 } else if (!wasFirstLayout) {
367 if (mAdapterChangeListener != null && oldAdapter != adapter) {
368 mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
372 private void removeNonDecorViews() {
373 for (int i = 0; i < getChildCount(); i++) {
374 final View child = getChildAt(i);
375 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
384 * Retrieve the current adapter supplying pages.
386 * @return The currently registered PagerAdapter
388 public PagerAdapter getAdapter() {
392 void setOnAdapterChangeListener(OnAdapterChangeListener listener) {
393 mAdapterChangeListener = listener;
396 // private int getClientWidth() {
397 // return getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
400 private int getClientHeight() {
401 return getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
406 * Set the currently selected page. If the ViewPager has already been through its first
407 * layout with its current adapter there will be a smooth animated transition between
408 * the current item and the specified item.
410 * @param item Item index to select
412 public void setCurrentItem(int item) {
413 mPopulatePending = false;
414 setCurrentItemInternal(item, !mFirstLayout, false);
418 * Set the currently selected page.
420 * @param item Item index to select
421 * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
423 public void setCurrentItem(int item, boolean smoothScroll) {
424 mPopulatePending = false;
425 setCurrentItemInternal(item, smoothScroll, false);
428 public int getCurrentItem() {
432 void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
433 setCurrentItemInternal(item, smoothScroll, always, 0);
436 void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
437 if (mAdapter == null || mAdapter.getCount() <= 0) {
438 setScrollingCacheEnabled(false);
441 if (!always && mCurItem == item && mItems.size() != 0) {
442 setScrollingCacheEnabled(false);
448 } else if (item >= mAdapter.getCount()) {
449 item = mAdapter.getCount() - 1;
451 final int pageLimit = mOffscreenPageLimit;
452 if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
453 // We are doing a jump by more than one page. To avoid
454 // glitches, we want to keep all current pages in the view
455 // until the scroll ends.
456 for (int i = 0; i < mItems.size(); i++) {
457 mItems.get(i).scrolling = true;
460 final boolean dispatchSelected = mCurItem != item;
463 // We don't have any idea how big we are yet and shouldn't have any pages either.
464 // Just set things up and let the pending layout handle things.
466 if (dispatchSelected && mOnPageChangeListener != null) {
467 mOnPageChangeListener.onPageSelected(item);
469 if (dispatchSelected && mInternalPageChangeListener != null) {
470 mInternalPageChangeListener.onPageSelected(item);
475 scrollToItem(item, smoothScroll, velocity, dispatchSelected);
479 private void scrollToItem(int item, boolean smoothScroll, int velocity,
480 boolean dispatchSelected) {
481 final ItemInfo curInfo = infoForPosition(item);
483 if (curInfo != null) {
484 final int height = getClientHeight();
485 destY = (int) (height * Math.max(mFirstOffset,
486 Math.min(curInfo.offset, mLastOffset)));
489 smoothScrollTo(0, destY, velocity);
490 if (dispatchSelected && mOnPageChangeListener != null) {
491 mOnPageChangeListener.onPageSelected(item);
493 if (dispatchSelected && mInternalPageChangeListener != null) {
494 mInternalPageChangeListener.onPageSelected(item);
497 if (dispatchSelected && mOnPageChangeListener != null) {
498 mOnPageChangeListener.onPageSelected(item);
500 if (dispatchSelected && mInternalPageChangeListener != null) {
501 mInternalPageChangeListener.onPageSelected(item);
503 completeScroll(false);
510 * Set a listener that will be invoked whenever the page changes or is incrementally
511 * scrolled. See {@link ViewPager.OnPageChangeListener}.
513 * @param listener Listener to set
515 public void setOnPageChangeListener(OnPageChangeListener listener) {
516 mOnPageChangeListener = listener;
520 * Set a {@link ViewPager.PageTransformer} that will be called for each attached page whenever
521 * the scroll position is changed. This allows the application to apply custom property
522 * transformations to each page, overriding the default sliding look and feel.
524 * <p><em>Note:</em> Prior to Android 3.0 the property animation APIs did not exist.
525 * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no
528 * @param reverseDrawingOrder true if the supplied PageTransformer requires page views
529 * to be drawn from last to first instead of first to last.
530 * @param transformer PageTransformer that will modify each page's animation properties
532 public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) {
533 if (Build.VERSION.SDK_INT >= 11) {
534 final boolean hasTransformer = transformer != null;
535 final boolean needsPopulate = hasTransformer != (mPageTransformer != null);
536 mPageTransformer = transformer;
537 setChildrenDrawingOrderEnabledCompat(hasTransformer);
538 if (hasTransformer) {
539 mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD;
541 mDrawingOrder = DRAW_ORDER_DEFAULT;
543 if (needsPopulate) populate();
547 void setChildrenDrawingOrderEnabledCompat(boolean enable) {
548 if (Build.VERSION.SDK_INT >= 7) {
549 if (mSetChildrenDrawingOrderEnabled == null) {
551 mSetChildrenDrawingOrderEnabled = ViewGroup.class.getDeclaredMethod(
552 "setChildrenDrawingOrderEnabled", new Class[]{Boolean.TYPE});
553 } catch (NoSuchMethodException e) {
554 Log.e(TAG, "Can't find setChildrenDrawingOrderEnabled", e);
558 mSetChildrenDrawingOrderEnabled.invoke(this, enable);
559 } catch (Exception e) {
560 Log.e(TAG, "Error changing children drawing order", e);
566 protected int getChildDrawingOrder(int childCount, int i) {
568 mDrawingOrder == DRAW_ORDER_REVERSE ? childCount - 1 - i : i;
570 ((LayoutParams) mDrawingOrderedChildren.get(index).getLayoutParams()).childIndex;
575 * Set a separate OnPageChangeListener for internal
576 * use by the support library.
578 * @param listener Listener to set
579 * @return The old listener that was set,
582 OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) {
583 OnPageChangeListener oldListener = mInternalPageChangeListener;
584 mInternalPageChangeListener = listener;
589 * Returns the number of pages that will be retained to either side of the
590 * current page in the view hierarchy in an idle state. Defaults to 1.
592 * @return How many pages will be kept offscreen on either side
593 * @see #setOffscreenPageLimit(int)
595 public int getOffscreenPageLimit() {
596 return mOffscreenPageLimit;
600 * Set the number of pages that should be retained to either side of the
601 * current page in the view hierarchy in an idle state. Pages beyond this
602 * limit will be recreated from the adapter when needed.
604 * <p>This is offered as an optimization. If you know in advance the number
605 * of pages you will need to support or have lazy-loading mechanisms in place
606 * on your pages, tweaking this setting can have benefits in perceived smoothness
607 * of paging animations and interaction. If you have a small number of pages (3-4)
608 * that you can keep active all at once, less time will be spent in layout for
609 * newly created view subtrees as the user pages back and forth.</p>
611 * <p>You should keep this limit low, especially if your pages have complex layouts.
612 * This setting defaults to 1.</p>
614 * @param limit How many pages will be kept offscreen in an idle state.
616 public void setOffscreenPageLimit(int limit) {
617 if (limit < DEFAULT_OFFSCREEN_PAGES) {
618 Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
619 DEFAULT_OFFSCREEN_PAGES);
620 limit = DEFAULT_OFFSCREEN_PAGES;
622 if (limit != mOffscreenPageLimit) {
623 mOffscreenPageLimit = limit;
629 * Set the margin between pages.
631 * @param marginPixels Distance between adjacent pages in pixels
632 * @see #getPageMargin()
633 * @see #setPageMarginDrawable(Drawable)
634 * @see #setPageMarginDrawable(int)
636 public void setPageMargin(int marginPixels) {
637 final int oldMargin = mPageMargin;
638 mPageMargin = marginPixels;
640 final int height = getHeight();
641 recomputeScrollPosition(height, height, marginPixels, oldMargin);
647 * Return the margin between pages.
649 * @return The size of the margin in pixels
651 public int getPageMargin() {
656 * Set a drawable that will be used to fill the margin between pages.
658 * @param d Drawable to display between pages
660 public void setPageMarginDrawable(Drawable d) {
662 if (d != null) refreshDrawableState();
663 setWillNotDraw(d == null);
668 * Set a drawable that will be used to fill the margin between pages.
670 * @param resId Resource ID of a drawable to display between pages
672 public void setPageMarginDrawable(int resId) {
673 setPageMarginDrawable(getContext().getResources().getDrawable(resId));
677 protected boolean verifyDrawable(Drawable who) {
678 return super.verifyDrawable(who) || who == mMarginDrawable;
682 protected void drawableStateChanged() {
683 super.drawableStateChanged();
684 final Drawable d = mMarginDrawable;
685 if (d != null && d.isStateful()) {
686 d.setState(getDrawableState());
690 // We want the duration of the page snap animation to be influenced by the distance that
691 // the screen has to travel, however, we don't want this duration to be effected in a
692 // purely linear fashion. Instead, we use this method to moderate the effect that the distance
693 // of travel has on the overall snap duration.
694 float distanceInfluenceForSnapDuration(float f) {
695 f -= 0.5f; // center the values about 0.
696 f *= 0.3f * Math.PI / 2.0f;
697 return (float) Math.sin(f);
701 * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
703 * @param x the number of pixels to scroll by on the X axis
704 * @param y the number of pixels to scroll by on the Y axis
706 void smoothScrollTo(int x, int y) {
707 smoothScrollTo(x, y, 0);
711 * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
713 * @param x the number of pixels to scroll by on the X axis
714 * @param y the number of pixels to scroll by on the Y axis
715 * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
717 void smoothScrollTo(int x, int y, int velocity) {
718 if (getChildCount() == 0) {
720 setScrollingCacheEnabled(false);
723 int sx = getScrollX();
724 int sy = getScrollY();
727 if (dx == 0 && dy == 0) {
728 completeScroll(false);
730 setScrollState(SCROLL_STATE_IDLE);
734 setScrollingCacheEnabled(true);
735 setScrollState(SCROLL_STATE_SETTLING);
737 final int height = getClientHeight();
738 final int halfHeight = height / 2;
739 final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / height);
740 final float distance = halfHeight + halfHeight *
741 distanceInfluenceForSnapDuration(distanceRatio);
744 velocity = Math.abs(velocity);
746 duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
748 final float pageHeight = height * mAdapter.getPageWidth(mCurItem);
749 final float pageDelta = (float) Math.abs(dx) / (pageHeight + mPageMargin);
750 duration = (int) ((pageDelta + 1) * 100);
752 duration = Math.min(duration, MAX_SETTLE_DURATION);
754 mScroller.startScroll(sx, sy, dx, dy, duration);
755 ViewCompat.postInvalidateOnAnimation(this);
758 ItemInfo addNewItem(int position, int index) {
759 ItemInfo ii = new ItemInfo();
760 ii.position = position;
761 ii.object = mAdapter.instantiateItem(this, position);
762 ii.heightFactor = mAdapter.getPageWidth(position);
763 if (index < 0 || index >= mItems.size()) {
766 mItems.add(index, ii);
771 void dataSetChanged() {
772 // This method only gets called if our observer is attached, so mAdapter is non-null.
774 final int adapterCount = mAdapter.getCount();
775 mExpectedAdapterCount = adapterCount;
776 boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
777 mItems.size() < adapterCount;
778 int newCurrItem = mCurItem;
780 boolean isUpdating = false;
781 for (int i = 0; i < mItems.size(); i++) {
782 final ItemInfo ii = mItems.get(i);
783 final int newPos = mAdapter.getItemPosition(ii.object);
785 if (newPos == PagerAdapter.POSITION_UNCHANGED) {
789 if (newPos == PagerAdapter.POSITION_NONE) {
794 mAdapter.startUpdate(this);
798 mAdapter.destroyItem(this, ii.position, ii.object);
801 if (mCurItem == ii.position) {
802 // Keep the current item in the valid range
803 newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
809 if (ii.position != newPos) {
810 if (ii.position == mCurItem) {
811 // Our current item changed position. Follow it.
812 newCurrItem = newPos;
815 ii.position = newPos;
821 mAdapter.finishUpdate(this);
824 Collections.sort(mItems, COMPARATOR);
827 // Reset our known page widths; populate will recompute them.
828 final int childCount = getChildCount();
829 for (int i = 0; i < childCount; i++) {
830 final View child = getChildAt(i);
831 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
833 lp.heightFactor = 0.f;
837 setCurrentItemInternal(newCurrItem, false, true);
846 void populate(int newCurrentItem) {
847 ItemInfo oldCurInfo = null;
848 int focusDirection = View.FOCUS_FORWARD;
849 if (mCurItem != newCurrentItem) {
850 focusDirection = mCurItem < newCurrentItem ? View.FOCUS_DOWN : View.FOCUS_UP;
851 oldCurInfo = infoForPosition(mCurItem);
852 mCurItem = newCurrentItem;
855 if (mAdapter == null) {
856 sortChildDrawingOrder();
860 // Bail now if we are waiting to populate. This is to hold off
861 // on creating views from the time the user releases their finger to
862 // fling to a new position until we have finished the scroll to
863 // that position, avoiding glitches from happening at that point.
864 if (mPopulatePending) {
865 if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
866 sortChildDrawingOrder();
870 // Also, don't populate until we are attached to a window. This is to
871 // avoid trying to populate before we have restored our view hierarchy
872 // state and conflicting with what is restored.
873 if (getWindowToken() == null) {
877 mAdapter.startUpdate(this);
879 final int pageLimit = mOffscreenPageLimit;
880 final int startPos = Math.max(0, mCurItem - pageLimit);
881 final int N = mAdapter.getCount();
882 final int endPos = Math.min(N - 1, mCurItem + pageLimit);
884 if (N != mExpectedAdapterCount) {
887 resName = getResources().getResourceName(getId());
888 } catch (Resources.NotFoundException e) {
889 resName = Integer.toHexString(getId());
891 throw new IllegalStateException("The application's PagerAdapter changed the adapter's" +
892 " contents without calling PagerAdapter#notifyDataSetChanged!" +
893 " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N +
894 " Pager id: " + resName +
895 " Pager class: " + getClass() +
896 " Problematic adapter: " + mAdapter.getClass());
899 // Locate the currently focused item or add it if needed.
901 ItemInfo curItem = null;
902 for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
903 final ItemInfo ii = mItems.get(curIndex);
904 if (ii.position >= mCurItem) {
905 if (ii.position == mCurItem) curItem = ii;
910 if (curItem == null && N > 0) {
911 curItem = addNewItem(mCurItem, curIndex);
914 // Fill 3x the available width or up to the number of offscreen
915 // pages requested to either side, whichever is larger.
916 // If we have no current item we have no work to do.
917 if (curItem != null) {
918 float extraHeightTop = 0.f;
919 int itemIndex = curIndex - 1;
920 ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
921 final int clientHeight = getClientHeight();
922 final float topHeightNeeded = clientHeight <= 0 ? 0 :
923 2.f - curItem.heightFactor + (float) getPaddingLeft() / (float) clientHeight;
924 for (int pos = mCurItem - 1; pos >= 0; pos--) {
925 if (extraHeightTop >= topHeightNeeded && pos < startPos) {
929 if (pos == ii.position && !ii.scrolling) {
930 mItems.remove(itemIndex);
931 mAdapter.destroyItem(this, pos, ii.object);
933 Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
934 " view: " + ((View) ii.object));
938 ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
940 } else if (ii != null && pos == ii.position) {
941 extraHeightTop += ii.heightFactor;
943 ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
945 ii = addNewItem(pos, itemIndex + 1);
946 extraHeightTop += ii.heightFactor;
948 ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
952 float extraHeightBottom = curItem.heightFactor;
953 itemIndex = curIndex + 1;
954 if (extraHeightBottom < 2.f) {
955 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
956 final float bottomHeightNeeded = clientHeight <= 0 ? 0 :
957 (float) getPaddingRight() / (float) clientHeight + 2.f;
958 for (int pos = mCurItem + 1; pos < N; pos++) {
959 if (extraHeightBottom >= bottomHeightNeeded && pos > endPos) {
963 if (pos == ii.position && !ii.scrolling) {
964 mItems.remove(itemIndex);
965 mAdapter.destroyItem(this, pos, ii.object);
967 Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
968 " view: " + ((View) ii.object));
970 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
972 } else if (ii != null && pos == ii.position) {
973 extraHeightBottom += ii.heightFactor;
975 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
977 ii = addNewItem(pos, itemIndex);
979 extraHeightBottom += ii.heightFactor;
980 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
985 calculatePageOffsets(curItem, curIndex, oldCurInfo);
989 Log.i(TAG, "Current page list:");
990 for (int i = 0; i < mItems.size(); i++) {
991 Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
995 mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
997 mAdapter.finishUpdate(this);
999 // Check width measurement of current pages and drawing sort order.
1000 // Update LayoutParams as needed.
1001 final int childCount = getChildCount();
1003 for (int i = 0; i < childCount; i++) {
1004 final View child = getChildAt(i);
1005 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1007 if (!lp.isDecor && lp.heightFactor == 0.f) {
1008 // 0 means requery the adapter for this, it doesn't have a valid width.
1009 final ItemInfo ii = infoForChild(child);
1011 lp.heightFactor = ii.heightFactor;
1012 lp.position = ii.position;
1016 sortChildDrawingOrder();
1019 View currentFocused = findFocus();
1020 ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
1021 if (ii == null || ii.position != mCurItem) {
1022 for (int i = 0; i < getChildCount(); i++) {
1023 View child = getChildAt(i);
1024 ii = infoForChild(child);
1025 if (ii != null && ii.position == mCurItem
1026 && child.requestFocus(focusDirection)) {
1027 // if (child.requestFocus(focusDirection)) {
1036 private void sortChildDrawingOrder() {
1037 if (mDrawingOrder != DRAW_ORDER_DEFAULT) {
1038 if (mDrawingOrderedChildren == null) {
1039 mDrawingOrderedChildren = new ArrayList<View>();
1041 mDrawingOrderedChildren.clear();
1043 final int childCount = getChildCount();
1044 for (int i = 0; i < childCount; i++) {
1045 final View child = getChildAt(i);
1046 mDrawingOrderedChildren.add(child);
1048 Collections.sort(mDrawingOrderedChildren, sPositionComparator);
1052 private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
1053 final int N = mAdapter.getCount();
1054 final int height = getClientHeight();
1055 final float marginOffset = height > 0 ? (float) mPageMargin / height : 0;
1056 // Fix up offsets for later layout.
1057 if (oldCurInfo != null) {
1058 final int oldCurPosition = oldCurInfo.position;
1059 // Base offsets off of oldCurInfo.
1060 if (oldCurPosition < curItem.position) {
1063 float offset = oldCurInfo.offset + oldCurInfo.heightFactor + marginOffset;
1064 for (int pos = oldCurPosition + 1;
1065 pos <= curItem.position && itemIndex < mItems.size(); pos++) {
1066 ii = mItems.get(itemIndex);
1067 while (pos > ii.position && itemIndex < mItems.size() - 1) {
1069 ii = mItems.get(itemIndex);
1071 while (pos < ii.position) {
1072 // We don't have an item populated for this,
1073 // ask the adapter for an offset.
1074 offset += mAdapter.getPageWidth(pos) + marginOffset;
1078 offset += ii.heightFactor + marginOffset;
1080 } else if (oldCurPosition > curItem.position) {
1081 int itemIndex = mItems.size() - 1;
1083 float offset = oldCurInfo.offset;
1084 for (int pos = oldCurPosition - 1;
1085 pos >= curItem.position && itemIndex >= 0;
1087 ii = mItems.get(itemIndex);
1088 while (pos < ii.position && itemIndex > 0) {
1090 ii = mItems.get(itemIndex);
1092 while (pos > ii.position) {
1093 // We don't have an item populated for this,
1094 // ask the adapter for an offset.
1095 offset -= mAdapter.getPageWidth(pos) + marginOffset;
1098 offset -= ii.heightFactor + marginOffset;
1104 // Base all offsets off of curItem.
1105 final int itemCount = mItems.size();
1106 float offset = curItem.offset;
1107 int pos = curItem.position - 1;
1108 mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
1109 mLastOffset = curItem.position == N - 1 ?
1110 curItem.offset + curItem.heightFactor - 1 : Float.MAX_VALUE;
1112 for (int i = curIndex - 1; i >= 0; i--, pos--) {
1113 final ItemInfo ii = mItems.get(i);
1114 while (pos > ii.position) {
1115 offset -= mAdapter.getPageWidth(pos--) + marginOffset;
1117 offset -= ii.heightFactor + marginOffset;
1119 if (ii.position == 0) mFirstOffset = offset;
1121 offset = curItem.offset + curItem.heightFactor + marginOffset;
1122 pos = curItem.position + 1;
1124 for (int i = curIndex + 1; i < itemCount; i++, pos++) {
1125 final ItemInfo ii = mItems.get(i);
1126 while (pos < ii.position) {
1127 offset += mAdapter.getPageWidth(pos++) + marginOffset;
1129 if (ii.position == N - 1) {
1130 mLastOffset = offset + ii.heightFactor - 1;
1133 offset += ii.heightFactor + marginOffset;
1136 mNeedCalculatePageOffsets = false;
1140 * This is the persistent state that is saved by ViewPager. Only needed
1141 * if you are creating a sublass of ViewPager that must save its own
1142 * state, in which case it should implement a subclass of this which
1143 * contains that state.
1145 public static class SavedState extends BaseSavedState {
1147 Parcelable adapterState;
1150 public SavedState(Parcelable superState) {
1155 public void writeToParcel(Parcel out, int flags) {
1156 super.writeToParcel(out, flags);
1157 out.writeInt(position);
1158 out.writeParcelable(adapterState, flags);
1162 public String toString() {
1163 return "FragmentPager.SavedState{"
1164 + Integer.toHexString(System.identityHashCode(this))
1165 + " position=" + position + "}";
1168 public static final Creator<SavedState> CREATOR =
1169 ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
1171 public SavedState createFromParcel(Parcel in, ClassLoader loader) {
1172 return new SavedState(in, loader);
1176 public SavedState[] newArray(int size) {
1177 return new SavedState[size];
1181 SavedState(Parcel in, ClassLoader loader) {
1183 if (loader == null) {
1184 loader = getClass().getClassLoader();
1186 position = in.readInt();
1187 adapterState = in.readParcelable(loader);
1188 this.loader = loader;
1193 public Parcelable onSaveInstanceState() {
1194 Parcelable superState = super.onSaveInstanceState();
1195 SavedState ss = new SavedState(superState);
1196 ss.position = mCurItem;
1197 if (mAdapter != null) {
1198 ss.adapterState = mAdapter.saveState();
1204 public void onRestoreInstanceState(Parcelable state) {
1205 if (!(state instanceof SavedState)) {
1206 super.onRestoreInstanceState(state);
1210 SavedState ss = (SavedState) state;
1211 super.onRestoreInstanceState(ss.getSuperState());
1213 if (mAdapter != null) {
1214 mAdapter.restoreState(ss.adapterState, ss.loader);
1215 setCurrentItemInternal(ss.position, false, true);
1217 mRestoredCurItem = ss.position;
1218 mRestoredAdapterState = ss.adapterState;
1219 mRestoredClassLoader = ss.loader;
1224 public void addView(View child, int index, ViewGroup.LayoutParams params) {
1225 if (!checkLayoutParams(params)) {
1226 params = generateLayoutParams(params);
1228 final LayoutParams lp = (LayoutParams) params;
1229 lp.isDecor |= child instanceof Decor;
1231 if (lp != null && lp.isDecor) {
1232 throw new IllegalStateException("Cannot add pager decor view during layout");
1234 lp.needsMeasure = true;
1235 addViewInLayout(child, index, params);
1237 super.addView(child, index, params);
1241 if (child.getVisibility() != GONE) {
1242 child.setDrawingCacheEnabled(mScrollingCacheEnabled);
1244 child.setDrawingCacheEnabled(false);
1250 public void removeView(View view) {
1252 removeViewInLayout(view);
1254 super.removeView(view);
1258 ItemInfo infoForChild(View child) {
1259 for (int i = 0; i < mItems.size(); i++) {
1260 ItemInfo ii = mItems.get(i);
1261 if (mAdapter.isViewFromObject(child, ii.object)) {
1268 ItemInfo infoForAnyChild(View child) {
1270 while ((parent = child.getParent()) != this) {
1271 if (parent == null || !(parent instanceof View)) {
1274 child = (View) parent;
1276 return infoForChild(child);
1279 ItemInfo infoForPosition(int position) {
1280 for (int i = 0; i < mItems.size(); i++) {
1281 ItemInfo ii = mItems.get(i);
1282 if (ii.position == position) {
1290 protected void onAttachedToWindow() {
1291 super.onAttachedToWindow();
1292 mFirstLayout = true;
1296 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1297 // For simple implementation, our internal size is always 0.
1298 // We depend on the container to specify the layout size of
1299 // our view. We can't really know what it is since we will be
1300 // adding and removing different arbitrary views and do not
1301 // want the layout to change as this happens.
1302 setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
1303 getDefaultSize(0, heightMeasureSpec));
1305 final int measuredHeight = getMeasuredHeight();
1306 final int maxGutterSize = measuredHeight / 10;
1307 mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
1309 // Children are just made to fill our space.
1310 int childWidthSize = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
1311 int childHeightSize = measuredHeight - getPaddingTop() - getPaddingBottom();
1314 * Make sure all children have been properly measured. Decor views first.
1315 * Right now we cheat and make this less complicated by assuming decor
1316 * views won't intersect. We will pin to edges based on gravity.
1318 int size = getChildCount();
1319 for (int i = 0; i < size; ++i) {
1320 final View child = getChildAt(i);
1321 if (child.getVisibility() != GONE) {
1322 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1323 if (lp != null && lp.isDecor) {
1324 final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
1325 final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
1326 int widthMode = MeasureSpec.AT_MOST;
1327 int heightMode = MeasureSpec.AT_MOST;
1328 boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
1329 boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;
1331 if (consumeVertical) {
1332 widthMode = MeasureSpec.EXACTLY;
1333 } else if (consumeHorizontal) {
1334 heightMode = MeasureSpec.EXACTLY;
1337 int widthSize = childWidthSize;
1338 int heightSize = childHeightSize;
1339 if (lp.width != LayoutParams.WRAP_CONTENT) {
1340 widthMode = MeasureSpec.EXACTLY;
1341 if (lp.width != LayoutParams.FILL_PARENT) {
1342 widthSize = lp.width;
1345 if (lp.height != LayoutParams.WRAP_CONTENT) {
1346 heightMode = MeasureSpec.EXACTLY;
1347 if (lp.height != LayoutParams.FILL_PARENT) {
1348 heightSize = lp.height;
1351 final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
1352 final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
1353 child.measure(widthSpec, heightSpec);
1355 if (consumeVertical) {
1356 childHeightSize -= child.getMeasuredHeight();
1357 } else if (consumeHorizontal) {
1358 childWidthSize -= child.getMeasuredWidth();
1364 mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
1365 mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
1367 // Make sure we have created all fragments that we need to have shown.
1373 size = getChildCount();
1374 for (int i = 0; i < size; ++i) {
1375 final View child = getChildAt(i);
1376 if (child.getVisibility() != GONE) {
1377 if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
1378 + ": " + mChildWidthMeasureSpec);
1380 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1381 if (lp == null || !lp.isDecor) {
1382 final int heightSpec = MeasureSpec.makeMeasureSpec(
1383 (int) (childHeightSize * lp.heightFactor), MeasureSpec.EXACTLY);
1384 child.measure(mChildWidthMeasureSpec, heightSpec);
1391 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1392 super.onSizeChanged(w, h, oldw, oldh);
1394 // Make sure scroll position is set correctly.
1396 recomputeScrollPosition(h, oldh, mPageMargin, mPageMargin);
1400 private void recomputeScrollPosition(int height, int oldHeight, int margin, int oldMargin) {
1401 if (oldHeight > 0 && !mItems.isEmpty()) {
1402 final int heightWithMargin = height - getPaddingTop() - getPaddingBottom() + margin;
1403 final int oldHeightWithMargin = oldHeight - getPaddingTop() - getPaddingBottom()
1405 final int ypos = getScrollY();
1406 final float pageOffset = (float) ypos / oldHeightWithMargin;
1407 final int newOffsetPixels = (int) (pageOffset * heightWithMargin);
1409 scrollTo(getScrollX(), newOffsetPixels);
1410 if (!mScroller.isFinished()) {
1411 // We now return to your regularly scheduled scroll, already in progress.
1412 final int newDuration = mScroller.getDuration() - mScroller.timePassed();
1413 ItemInfo targetInfo = infoForPosition(mCurItem);
1414 mScroller.startScroll(0, newOffsetPixels,
1415 0, (int) (targetInfo.offset * height), newDuration);
1418 final ItemInfo ii = infoForPosition(mCurItem);
1419 final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;
1420 final int scrollPos = (int) (scrollOffset *
1421 (height - getPaddingTop() - getPaddingBottom()));
1422 if (scrollPos != getScrollY()) {
1423 completeScroll(false);
1424 scrollTo(getScrollX(), scrollPos);
1430 protected void onLayout(boolean changed, int l, int t, int r, int b) {
1431 final int count = getChildCount();
1434 int paddingLeft = getPaddingLeft();
1435 int paddingTop = getPaddingTop();
1436 int paddingRight = getPaddingRight();
1437 int paddingBottom = getPaddingBottom();
1438 final int scrollY = getScrollY();
1442 // First pass - decor views. We need to do this in two passes so that
1443 // we have the proper offsets for non-decor views later.
1444 for (int i = 0; i < count; i++) {
1445 final View child = getChildAt(i);
1446 if (child.getVisibility() != GONE) {
1447 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1451 final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
1452 final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
1455 childLeft = paddingLeft;
1458 childLeft = paddingLeft;
1459 paddingLeft += child.getMeasuredWidth();
1461 case Gravity.CENTER_HORIZONTAL:
1462 childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
1466 childLeft = width - paddingRight - child.getMeasuredWidth();
1467 paddingRight += child.getMeasuredWidth();
1472 childTop = paddingTop;
1475 childTop = paddingTop;
1476 paddingTop += child.getMeasuredHeight();
1478 case Gravity.CENTER_VERTICAL:
1479 childTop = Math.max((height - child.getMeasuredHeight()) / 2,
1482 case Gravity.BOTTOM:
1483 childTop = height - paddingBottom - child.getMeasuredHeight();
1484 paddingBottom += child.getMeasuredHeight();
1487 childTop += scrollY;
1488 child.layout(childLeft, childTop,
1489 childLeft + child.getMeasuredWidth(),
1490 childTop + child.getMeasuredHeight());
1496 final int childHeight = height - paddingTop - paddingBottom;
1497 // Page views. Do this once we have the right padding offsets from above.
1498 for (int i = 0; i < count; i++) {
1499 final View child = getChildAt(i);
1500 if (child.getVisibility() != GONE) {
1501 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1503 if (!lp.isDecor && (ii = infoForChild(child)) != null) {
1504 int toff = (int) (childHeight * ii.offset);
1505 int childLeft = paddingLeft;
1506 int childTop = paddingTop + toff;
1507 if (lp.needsMeasure) {
1508 // This was added during layout and needs measurement.
1509 // Do it now that we know what we're working with.
1510 lp.needsMeasure = false;
1511 final int widthSpec = MeasureSpec.makeMeasureSpec(
1512 (int) (width - paddingLeft - paddingRight),
1513 MeasureSpec.EXACTLY);
1514 final int heightSpec = MeasureSpec.makeMeasureSpec(
1515 (int) (childHeight * lp.heightFactor),
1516 MeasureSpec.EXACTLY);
1517 child.measure(widthSpec, heightSpec);
1519 if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
1520 + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
1521 + "x" + child.getMeasuredHeight());
1522 child.layout(childLeft, childTop,
1523 childLeft + child.getMeasuredWidth(),
1524 childTop + child.getMeasuredHeight());
1528 mLeftPageBounds = paddingLeft;
1529 mRightPageBounds = width - paddingRight;
1530 mDecorChildCount = decorCount;
1533 scrollToItem(mCurItem, false, 0, false);
1535 mFirstLayout = false;
1539 public void computeScroll() {
1540 if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
1541 int oldX = getScrollX();
1542 int oldY = getScrollY();
1543 int x = mScroller.getCurrX();
1544 int y = mScroller.getCurrY();
1546 if (oldX != x || oldY != y) {
1548 if (!pageScrolled(y)) {
1549 mScroller.abortAnimation();
1554 // Keep on drawing until the animation has finished.
1555 ViewCompat.postInvalidateOnAnimation(this);
1559 // Done with scroll, clean up state.
1560 completeScroll(true);
1563 private boolean pageScrolled(int ypos) {
1564 if (mItems.size() == 0) {
1565 mCalledSuper = false;
1566 onPageScrolled(0, 0, 0);
1567 if (!mCalledSuper) {
1568 throw new IllegalStateException(
1569 "onPageScrolled did not call superclass implementation");
1573 final ItemInfo ii = infoForCurrentScrollPosition();
1574 final int height = getClientHeight();
1575 final int heightWithMargin = height + mPageMargin;
1576 final float marginOffset = (float) mPageMargin / height;
1577 final int currentPage = ii.position;
1578 final float pageOffset = (((float) ypos / height) - ii.offset) /
1579 (ii.heightFactor + marginOffset);
1580 final int offsetPixels = (int) (pageOffset * heightWithMargin);
1582 mCalledSuper = false;
1583 onPageScrolled(currentPage, pageOffset, offsetPixels);
1584 if (!mCalledSuper) {
1585 throw new IllegalStateException(
1586 "onPageScrolled did not call superclass implementation");
1592 * This method will be invoked when the current page is scrolled, either as part
1593 * of a programmatically initiated smooth scroll or a user initiated touch scroll.
1594 * If you override this method you must call through to the superclass implementation
1595 * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled
1598 * @param position Position index of the first page currently being displayed.
1599 * Page position+1 will be visible if positionOffset is nonzero.
1600 * @param offset Value from [0, 1) indicating the offset from the page at position.
1601 * @param offsetPixels Value in pixels indicating the offset from position.
1603 protected void onPageScrolled(int position, float offset, int offsetPixels) {
1604 // Offset any decor views if needed - keep them on-screen at all times.
1605 if (mDecorChildCount > 0) {
1606 final int scrollY = getScrollY();
1607 int paddingTop = getPaddingTop();
1608 int paddingBottom = getPaddingBottom();
1609 final int height = getHeight();
1610 final int childCount = getChildCount();
1611 for (int i = 0; i < childCount; i++) {
1612 final View child = getChildAt(i);
1613 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1614 if (!lp.isDecor) continue;
1616 final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
1620 childTop = paddingTop;
1623 childTop = paddingTop;
1624 paddingTop += child.getHeight();
1626 case Gravity.CENTER_VERTICAL:
1627 childTop = Math.max((height - child.getMeasuredHeight()) / 2,
1630 case Gravity.BOTTOM:
1631 childTop = height - paddingBottom - child.getMeasuredHeight();
1632 paddingBottom += child.getMeasuredHeight();
1635 childTop += scrollY;
1637 final int childOffset = childTop - child.getTop();
1638 if (childOffset != 0) {
1639 child.offsetTopAndBottom(childOffset);
1644 if (mOnPageChangeListener != null) {
1645 mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
1647 if (mInternalPageChangeListener != null) {
1648 mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
1651 if (mPageTransformer != null) {
1652 final int scrollY = getScrollY();
1653 final int childCount = getChildCount();
1654 for (int i = 0; i < childCount; i++) {
1655 final View child = getChildAt(i);
1656 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1658 if (lp.isDecor) continue;
1660 final float transformPos = (float) (child.getTop() - scrollY) / getClientHeight();
1661 mPageTransformer.transformPage(child, transformPos);
1665 mCalledSuper = true;
1668 private void completeScroll(boolean postEvents) {
1669 boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING;
1671 // Done with scroll, no longer want to cache view drawing.
1672 setScrollingCacheEnabled(false);
1673 mScroller.abortAnimation();
1674 int oldX = getScrollX();
1675 int oldY = getScrollY();
1676 int x = mScroller.getCurrX();
1677 int y = mScroller.getCurrY();
1678 if (oldX != x || oldY != y) {
1682 mPopulatePending = false;
1683 for (int i = 0; i < mItems.size(); i++) {
1684 ItemInfo ii = mItems.get(i);
1686 needPopulate = true;
1687 ii.scrolling = false;
1692 ViewCompat.postOnAnimation(this, mEndScrollRunnable);
1694 mEndScrollRunnable.run();
1699 private boolean isGutterDrag(float y, float dy) {
1700 return (y < mGutterSize && dy > 0) || (y > getHeight() - mGutterSize && dy < 0);
1703 private void enableLayers(boolean enable) {
1704 final int childCount = getChildCount();
1705 for (int i = 0; i < childCount; i++) {
1706 final int layerType = enable ?
1707 ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE;
1708 ViewCompat.setLayerType(getChildAt(i), layerType, null);
1713 public boolean onInterceptTouchEvent(MotionEvent ev) {
1715 * This method JUST determines whether we want to intercept the motion.
1716 * If we return true, onMotionEvent will be called and we do the actual
1720 final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
1722 // Always take care of the touch gesture being complete.
1723 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
1724 // Release the drag.
1725 if (DEBUG) Log.v(TAG, "Intercept done!");
1726 mIsBeingDragged = false;
1727 mIsUnableToDrag = false;
1728 mActivePointerId = INVALID_POINTER;
1729 if (mVelocityTracker != null) {
1730 mVelocityTracker.recycle();
1731 mVelocityTracker = null;
1736 // Nothing more to do here if we have decided whether or not we
1738 if (action != MotionEvent.ACTION_DOWN) {
1739 if (mIsBeingDragged) {
1740 if (DEBUG) Log.v(TAG, "Intercept returning true!");
1743 if (mIsUnableToDrag) {
1744 if (DEBUG) Log.v(TAG, "Intercept returning false!");
1750 case MotionEvent.ACTION_MOVE: {
1752 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1753 * whether the user has moved far enough from his original down touch.
1757 * Locally do absolute value. mLastMotionY is set to the y value
1758 * of the down event.
1760 final int activePointerId = mActivePointerId;
1761 if (activePointerId == INVALID_POINTER) {
1762 // If we don't have a valid id, the touch down wasn't on content.
1766 final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
1767 final float y = MotionEventCompat.getY(ev, pointerIndex);
1768 final float dy = y - mLastMotionY;
1769 final float yDiff = Math.abs(dy);
1770 final float x = MotionEventCompat.getX(ev, pointerIndex);
1771 final float xDiff = Math.abs(x - mInitialMotionX);
1772 if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
1774 if (dy != 0 && !isGutterDrag(mLastMotionY, dy) &&
1775 canScroll(this, false, (int) dy, (int) x, (int) y)) {
1776 // Nested view has scrollable area under this point. Let it be handled there.
1779 mIsUnableToDrag = true;
1782 if (yDiff > mTouchSlop && yDiff * 0.5f > xDiff) {
1783 if (DEBUG) Log.v(TAG, "Starting drag!");
1784 mIsBeingDragged = true;
1785 requestParentDisallowInterceptTouchEvent(true);
1786 setScrollState(SCROLL_STATE_DRAGGING);
1787 mLastMotionY = dy > 0 ? mInitialMotionY + mTouchSlop :
1788 mInitialMotionY - mTouchSlop;
1790 setScrollingCacheEnabled(true);
1791 } else if (xDiff > mTouchSlop) {
1792 // The finger has moved enough in the vertical
1793 // direction to be counted as a drag... abort
1794 // any attempt to drag horizontally, to work correctly
1795 // with children that have scrolling containers.
1796 if (DEBUG) Log.v(TAG, "Starting unable to drag!");
1797 mIsUnableToDrag = true;
1799 if (mIsBeingDragged && performDrag(y)) {
1800 // Scroll to follow the motion event
1801 ViewCompat.postInvalidateOnAnimation(this);
1806 case MotionEvent.ACTION_DOWN: {
1808 * Remember location of down touch.
1809 * ACTION_DOWN always refers to pointer index 0.
1811 mLastMotionX = mInitialMotionX = ev.getX();
1812 mLastMotionY = mInitialMotionY = ev.getY();
1813 mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
1814 mIsUnableToDrag = false;
1816 mScroller.computeScrollOffset();
1817 if (mScrollState == SCROLL_STATE_SETTLING &&
1818 Math.abs(mScroller.getFinalY() - mScroller.getCurrY()) > mCloseEnough) {
1819 // Let the user 'catch' the pager as it animates.
1820 mScroller.abortAnimation();
1821 mPopulatePending = false;
1823 mIsBeingDragged = true;
1824 requestParentDisallowInterceptTouchEvent(true);
1825 setScrollState(SCROLL_STATE_DRAGGING);
1827 completeScroll(false);
1828 mIsBeingDragged = false;
1831 if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
1832 + " mIsBeingDragged=" + mIsBeingDragged
1833 + "mIsUnableToDrag=" + mIsUnableToDrag);
1837 case MotionEventCompat.ACTION_POINTER_UP:
1838 onSecondaryPointerUp(ev);
1842 if (mVelocityTracker == null) {
1843 mVelocityTracker = VelocityTracker.obtain();
1845 mVelocityTracker.addMovement(ev);
1848 * The only time we want to intercept motion events is if we are in the
1851 return mIsBeingDragged;
1855 public boolean onTouchEvent(MotionEvent ev) {
1856 if (mFakeDragging) {
1857 // A fake drag is in progress already, ignore this real one
1858 // but still eat the touch events.
1859 // (It is likely that the user is multi-touching the screen.)
1863 if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
1864 // Don't handle edge touches immediately -- they may actually belong to one of our
1869 if (mAdapter == null || mAdapter.getCount() == 0) {
1870 // Nothing to present or scroll; nothing to touch.
1874 if (mVelocityTracker == null) {
1875 mVelocityTracker = VelocityTracker.obtain();
1877 mVelocityTracker.addMovement(ev);
1879 final int action = ev.getAction();
1880 boolean needsInvalidate = false;
1882 switch (action & MotionEventCompat.ACTION_MASK) {
1883 case MotionEvent.ACTION_DOWN: {
1884 mScroller.abortAnimation();
1885 mPopulatePending = false;
1888 // Remember where the motion event started
1889 mLastMotionX = mInitialMotionX = ev.getX();
1890 mLastMotionY = mInitialMotionY = ev.getY();
1891 mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
1894 case MotionEvent.ACTION_MOVE:
1895 if (!mIsBeingDragged) {
1896 final int pointerIndex =
1897 MotionEventCompat.findPointerIndex(ev, mActivePointerId);
1898 final float y = MotionEventCompat.getY(ev, pointerIndex);
1899 final float yDiff = Math.abs(y - mLastMotionY);
1900 final float x = MotionEventCompat.getX(ev, pointerIndex);
1901 final float xDiff = Math.abs(x - mLastMotionX);
1903 Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
1904 if (yDiff > mTouchSlop && yDiff > xDiff) {
1905 if (DEBUG) Log.v(TAG, "Starting drag!");
1906 mIsBeingDragged = true;
1907 requestParentDisallowInterceptTouchEvent(true);
1908 mLastMotionY = y - mInitialMotionY > 0 ? mInitialMotionY + mTouchSlop :
1909 mInitialMotionY - mTouchSlop;
1911 setScrollState(SCROLL_STATE_DRAGGING);
1912 setScrollingCacheEnabled(true);
1914 // Disallow Parent Intercept, just in case
1915 ViewParent parent = getParent();
1916 if (parent != null) {
1917 parent.requestDisallowInterceptTouchEvent(true);
1921 // Not else! Note that mIsBeingDragged can be set above.
1922 if (mIsBeingDragged) {
1923 // Scroll to follow the motion event
1924 final int activePointerIndex = MotionEventCompat.findPointerIndex(
1925 ev, mActivePointerId);
1926 final float y = MotionEventCompat.getY(ev, activePointerIndex);
1927 needsInvalidate |= performDrag(y);
1930 case MotionEvent.ACTION_UP:
1931 if (mIsBeingDragged) {
1932 final VelocityTracker velocityTracker = mVelocityTracker;
1933 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1934 int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(
1935 velocityTracker, mActivePointerId);
1936 mPopulatePending = true;
1937 final int height = getClientHeight();
1938 final int scrollY = getScrollY();
1939 final ItemInfo ii = infoForCurrentScrollPosition();
1940 final int currentPage = ii.position;
1941 final float pageOffset =
1942 (((float) scrollY / height) - ii.offset) / ii.heightFactor;
1943 final int activePointerIndex =
1944 MotionEventCompat.findPointerIndex(ev, mActivePointerId);
1945 final float y = MotionEventCompat.getY(ev, activePointerIndex);
1946 final int totalDelta = (int) (y - mInitialMotionY);
1947 int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
1949 setCurrentItemInternal(nextPage, true, true, initialVelocity);
1951 mActivePointerId = INVALID_POINTER;
1953 needsInvalidate = mTopEdge.onRelease() | mBottomEdge.onRelease();
1956 case MotionEvent.ACTION_CANCEL:
1957 if (mIsBeingDragged) {
1958 scrollToItem(mCurItem, true, 0, false);
1959 mActivePointerId = INVALID_POINTER;
1961 needsInvalidate = mTopEdge.onRelease() | mBottomEdge.onRelease();
1964 case MotionEventCompat.ACTION_POINTER_DOWN: {
1965 final int index = MotionEventCompat.getActionIndex(ev);
1966 final float y = MotionEventCompat.getY(ev, index);
1968 mActivePointerId = MotionEventCompat.getPointerId(ev, index);
1971 case MotionEventCompat.ACTION_POINTER_UP:
1972 onSecondaryPointerUp(ev);
1973 mLastMotionY = MotionEventCompat.getY(ev,
1974 MotionEventCompat.findPointerIndex(ev, mActivePointerId));
1977 if (needsInvalidate) {
1978 ViewCompat.postInvalidateOnAnimation(this);
1983 private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {
1984 final ViewParent parent = getParent();
1985 if (parent != null) {
1986 parent.requestDisallowInterceptTouchEvent(disallowIntercept);
1990 private boolean performDrag(float y) {
1991 boolean needsInvalidate = false;
1993 final float deltaY = mLastMotionY - y;
1996 float oldScrollY = getScrollY();
1997 float scrollY = oldScrollY + deltaY;
1998 final int height = getClientHeight();
2000 float topBound = height * mFirstOffset;
2001 float bottomBound = height * mLastOffset;
2002 boolean topAbsolute = true;
2003 boolean bottomAbsolute = true;
2005 final ItemInfo firstItem = mItems.get(0);
2006 final ItemInfo lastItem = mItems.get(mItems.size() - 1);
2007 if (firstItem.position != 0) {
2008 topAbsolute = false;
2009 topBound = firstItem.offset * height;
2011 if (lastItem.position != mAdapter.getCount() - 1) {
2012 bottomAbsolute = false;
2013 bottomBound = lastItem.offset * height;
2016 if (scrollY < topBound) {
2018 float over = topBound - scrollY;
2019 needsInvalidate = mTopEdge.onPull(Math.abs(over) / height);
2022 } else if (scrollY > bottomBound) {
2023 if (bottomAbsolute) {
2024 float over = scrollY - bottomBound;
2025 needsInvalidate = mBottomEdge.onPull(Math.abs(over) / height);
2027 scrollY = bottomBound;
2029 // Don't lose the rounded component
2030 mLastMotionX += scrollY - (int) scrollY;
2031 scrollTo(getScrollX(), (int) scrollY);
2032 pageScrolled((int) scrollY);
2034 return needsInvalidate;
2038 * @return Info about the page at the current scroll position.
2039 * This can be synthetic for a missing middle page; the 'object' field can be null.
2041 private ItemInfo infoForCurrentScrollPosition() {
2042 final int height = getClientHeight();
2043 final float scrollOffset = height > 0 ? (float) getScrollY() / height : 0;
2044 final float marginOffset = height > 0 ? (float) mPageMargin / height : 0;
2046 float lastOffset = 0.f;
2047 float lastHeight = 0.f;
2048 boolean first = true;
2050 ItemInfo lastItem = null;
2051 for (int i = 0; i < mItems.size(); i++) {
2052 ItemInfo ii = mItems.get(i);
2054 if (!first && ii.position != lastPos + 1) {
2055 // Create a synthetic item for a missing page.
2057 ii.offset = lastOffset + lastHeight + marginOffset;
2058 ii.position = lastPos + 1;
2059 ii.heightFactor = mAdapter.getPageWidth(ii.position);
2064 final float topBound = offset;
2065 final float bottomBound = offset + ii.heightFactor + marginOffset;
2066 if (first || scrollOffset >= topBound) {
2067 if (scrollOffset < bottomBound || i == mItems.size() - 1) {
2074 lastPos = ii.position;
2075 lastOffset = offset;
2076 lastHeight = ii.heightFactor;
2083 private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaY) {
2085 if (Math.abs(deltaY) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
2086 targetPage = velocity > 0 ? currentPage : currentPage + 1;
2088 final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
2089 targetPage = (int) (currentPage + pageOffset + truncator);
2092 if (mItems.size() > 0) {
2093 final ItemInfo firstItem = mItems.get(0);
2094 final ItemInfo lastItem = mItems.get(mItems.size() - 1);
2096 // Only let the user target pages we have items for
2097 targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
2104 public void draw(Canvas canvas) {
2106 boolean needsInvalidate = false;
2108 final int overScrollMode = ViewCompat.getOverScrollMode(this);
2109 if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
2110 (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&
2111 mAdapter != null && mAdapter.getCount() > 1)) {
2112 if (!mTopEdge.isFinished()) {
2113 final int restoreCount = canvas.save();
2114 final int height = getHeight();
2115 final int width = getWidth() - getPaddingLeft() - getPaddingRight();
2117 canvas.translate(getPaddingLeft(), mFirstOffset * height);
2118 mTopEdge.setSize(width, height);
2119 needsInvalidate |= mTopEdge.draw(canvas);
2120 canvas.restoreToCount(restoreCount);
2122 if (!mBottomEdge.isFinished()) {
2123 final int restoreCount = canvas.save();
2124 final int height = getHeight();
2125 final int width = getWidth() - getPaddingLeft() - getPaddingRight();
2128 canvas.translate(-width - getPaddingLeft(), -(mLastOffset + 1) * height);
2129 mBottomEdge.setSize(width, height);
2130 needsInvalidate |= mBottomEdge.draw(canvas);
2131 canvas.restoreToCount(restoreCount);
2135 mBottomEdge.finish();
2138 if (needsInvalidate) {
2140 ViewCompat.postInvalidateOnAnimation(this);
2145 protected void onDraw(Canvas canvas) {
2146 super.onDraw(canvas);
2148 // Draw the margin drawable between pages if needed.
2149 if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) {
2150 final int scrollY = getScrollY();
2151 final int height = getHeight();
2153 final float marginOffset = (float) mPageMargin / height;
2155 ItemInfo ii = mItems.get(0);
2156 float offset = ii.offset;
2157 final int itemCount = mItems.size();
2158 final int firstPos = ii.position;
2159 final int lastPos = mItems.get(itemCount - 1).position;
2160 for (int pos = firstPos; pos < lastPos; pos++) {
2161 while (pos > ii.position && itemIndex < itemCount) {
2162 ii = mItems.get(++itemIndex);
2166 if (pos == ii.position) {
2167 drawAt = (ii.offset + ii.heightFactor) * height;
2168 offset = ii.offset + ii.heightFactor + marginOffset;
2170 float heightFactor = mAdapter.getPageWidth(pos);
2171 drawAt = (offset + heightFactor) * height;
2172 offset += heightFactor + marginOffset;
2175 if (drawAt + mPageMargin > scrollY) {
2176 mMarginDrawable.setBounds(mLeftPageBounds, (int) drawAt,
2177 mRightPageBounds, (int) (drawAt + mPageMargin + 0.5f));
2178 mMarginDrawable.draw(canvas);
2181 if (drawAt > scrollY + height) {
2182 break; // No more visible, no sense in continuing
2189 * Start a fake drag of the pager.
2191 * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
2192 * with the touch scrolling of another view, while still letting the ViewPager
2193 * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
2194 * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
2195 * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
2197 * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
2198 * is already in progress, this method will return false.
2200 * @return true if the fake drag began successfully, false if it could not be started.
2201 * @see #fakeDragBy(float)
2202 * @see #endFakeDrag()
2204 public boolean beginFakeDrag() {
2205 if (mIsBeingDragged) {
2208 mFakeDragging = true;
2209 setScrollState(SCROLL_STATE_DRAGGING);
2210 mInitialMotionY = mLastMotionY = 0;
2211 if (mVelocityTracker == null) {
2212 mVelocityTracker = VelocityTracker.obtain();
2214 mVelocityTracker.clear();
2216 final long time = SystemClock.uptimeMillis();
2217 final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
2218 mVelocityTracker.addMovement(ev);
2220 mFakeDragBeginTime = time;
2225 * End a fake drag of the pager.
2227 * @see #beginFakeDrag()
2228 * @see #fakeDragBy(float)
2230 public void endFakeDrag() {
2231 if (!mFakeDragging) {
2232 throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
2235 final VelocityTracker velocityTracker = mVelocityTracker;
2236 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
2237 int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(
2238 velocityTracker, mActivePointerId);
2239 mPopulatePending = true;
2240 final int height = getClientHeight();
2241 final int scrollY = getScrollY();
2242 final ItemInfo ii = infoForCurrentScrollPosition();
2243 final int currentPage = ii.position;
2244 final float pageOffset = (((float) scrollY / height) - ii.offset) / ii.heightFactor;
2245 final int totalDelta = (int) (mLastMotionY - mInitialMotionY);
2246 int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
2248 setCurrentItemInternal(nextPage, true, true, initialVelocity);
2251 mFakeDragging = false;
2255 * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
2257 * @param yOffset Offset in pixels to drag by.
2258 * @see #beginFakeDrag()
2259 * @see #endFakeDrag()
2261 public void fakeDragBy(float yOffset) {
2262 if (!mFakeDragging) {
2263 throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
2267 mLastMotionY += yOffset;
2269 float oldScrollY = getScrollY();
2270 float scrollY = oldScrollY - yOffset;
2271 final int height = getClientHeight();
2273 float topBound = height * mFirstOffset;
2274 float bottomBound = height * mLastOffset;
2276 final ItemInfo firstItem = mItems.get(0);
2277 final ItemInfo lastItem = mItems.get(mItems.size() - 1);
2278 if (firstItem.position != 0) {
2279 topBound = firstItem.offset * height;
2281 if (lastItem.position != mAdapter.getCount() - 1) {
2282 bottomBound = lastItem.offset * height;
2285 if (scrollY < topBound) {
2287 } else if (scrollY > bottomBound) {
2288 scrollY = bottomBound;
2290 // Don't lose the rounded component
2291 mLastMotionY += scrollY - (int) scrollY;
2292 scrollTo(getScrollX(), (int) scrollY);
2293 pageScrolled((int) scrollY);
2295 // Synthesize an event for the VelocityTracker.
2296 final long time = SystemClock.uptimeMillis();
2297 final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
2298 0, mLastMotionY, 0);
2299 mVelocityTracker.addMovement(ev);
2304 * Returns true if a fake drag is in progress.
2306 * @return true if currently in a fake drag, false otherwise.
2307 * @see #beginFakeDrag()
2308 * @see #fakeDragBy(float)
2309 * @see #endFakeDrag()
2311 public boolean isFakeDragging() {
2312 return mFakeDragging;
2315 private void onSecondaryPointerUp(MotionEvent ev) {
2316 final int pointerIndex = MotionEventCompat.getActionIndex(ev);
2317 final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
2318 if (pointerId == mActivePointerId) {
2319 // This was our active pointer going up. Choose a new
2320 // active pointer and adjust accordingly.
2321 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
2322 mLastMotionY = MotionEventCompat.getY(ev, newPointerIndex);
2323 mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
2324 if (mVelocityTracker != null) {
2325 mVelocityTracker.clear();
2330 private void endDrag() {
2331 mIsBeingDragged = false;
2332 mIsUnableToDrag = false;
2334 if (mVelocityTracker != null) {
2335 mVelocityTracker.recycle();
2336 mVelocityTracker = null;
2340 private void setScrollingCacheEnabled(boolean enabled) {
2341 if (mScrollingCacheEnabled != enabled) {
2342 mScrollingCacheEnabled = enabled;
2344 final int size = getChildCount();
2345 for (int i = 0; i < size; ++i) {
2346 final View child = getChildAt(i);
2347 if (child.getVisibility() != GONE) {
2348 child.setDrawingCacheEnabled(enabled);
2355 public boolean internalCanScrollVertically(int direction) {
2356 if (mAdapter == null) {
2360 final int height = getClientHeight();
2361 final int scrollY = getScrollY();
2362 if (direction < 0) {
2363 return (scrollY > (int) (height * mFirstOffset));
2364 } else if (direction > 0) {
2365 return (scrollY < (int) (height * mLastOffset));
2372 * Tests scrollability within child views of v given a delta of dx.
2374 * @param v View to test for horizontal scrollability
2375 * @param checkV Whether the view v passed should itself be checked for scrollability (true),
2376 * or just its children (false).
2377 * @param dy Delta scrolled in pixels
2378 * @param x X coordinate of the active touch point
2379 * @param y Y coordinate of the active touch point
2380 * @return true if child views of v can be scrolled by delta of dx.
2382 protected boolean canScroll(View v, boolean checkV, int dy, int x, int y) {
2383 if (v instanceof ViewGroup) {
2384 final ViewGroup group = (ViewGroup) v;
2385 final int scrollX = v.getScrollX();
2386 final int scrollY = v.getScrollY();
2387 final int count = group.getChildCount();
2388 // Count backwards - let topmost views consume scroll distance first.
2389 for (int i = count - 1; i >= 0; i--) {
2390 // TODO: Add versioned support here for transformed views.
2391 // This will not work for transformed views in Honeycomb+
2392 final View child = group.getChildAt(i);
2393 if (y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
2394 x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
2395 canScroll(child, true, dy, x + scrollX - child.getLeft(),
2396 y + scrollY - child.getTop())) {
2403 return checkV && ViewCompat.canScrollVertically(v, -dy);
2407 public boolean dispatchKeyEvent(KeyEvent event) {
2408 // Let the focused view and/or our descendants get the key first
2409 return super.dispatchKeyEvent(event) || executeKeyEvent(event);
2413 * You can call this function yourself to have the scroll view perform
2414 * scrolling from a key event, just as if the event had been dispatched to
2415 * it by the view hierarchy.
2417 * @param event The key event to execute.
2418 * @return Return true if the event was handled, else false.
2420 public boolean executeKeyEvent(KeyEvent event) {
2421 boolean handled = false;
2422 if (event.getAction() == KeyEvent.ACTION_DOWN) {
2423 switch (event.getKeyCode()) {
2424 case KeyEvent.KEYCODE_DPAD_LEFT:
2425 handled = arrowScroll(FOCUS_LEFT);
2427 case KeyEvent.KEYCODE_DPAD_RIGHT:
2428 handled = arrowScroll(FOCUS_RIGHT);
2430 case KeyEvent.KEYCODE_TAB:
2431 if (Build.VERSION.SDK_INT >= 11) {
2432 // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD
2433 // before Android 3.0. Ignore the tab key on those devices.
2434 if (KeyEvent.metaStateHasNoModifiers(event.getMetaState())) {
2435 handled = arrowScroll(FOCUS_FORWARD);
2436 } else if (KeyEvent.metaStateHasNoModifiers(event.getMetaState())) {
2437 handled = arrowScroll(FOCUS_BACKWARD);
2446 public boolean arrowScroll(int direction) {
2447 View currentFocused = findFocus();
2448 if (currentFocused == this) {
2449 currentFocused = null;
2450 } else if (currentFocused != null) {
2451 boolean isChild = false;
2452 for (ViewParent parent = currentFocused.getParent();
2453 parent instanceof ViewGroup;
2454 parent = parent.getParent()) {
2455 if (parent == this) {
2461 // This would cause the focus search down below to fail in fun ways.
2462 final StringBuilder sb = new StringBuilder();
2463 sb.append(currentFocused.getClass().getSimpleName());
2464 for (ViewParent parent =
2465 currentFocused.getParent();
2466 parent instanceof ViewGroup;
2467 parent = parent.getParent()) {
2468 sb.append(" => ").append(parent.getClass().getSimpleName());
2470 Log.e(TAG, "arrowScroll tried to find focus based on non-child " +
2471 "current focused view " + sb.toString());
2472 currentFocused = null;
2476 boolean handled = false;
2478 View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
2480 if (nextFocused != null && nextFocused != currentFocused) {
2481 if (direction == View.FOCUS_UP) {
2482 // If there is nothing to the left, or this is causing us to
2483 // jump to the right, then what we really want to do is page left.
2484 final int nextTop = getChildRectInPagerCoordinates(mTempRect, nextFocused).top;
2485 final int currTop = getChildRectInPagerCoordinates(mTempRect, currentFocused).top;
2486 if (currentFocused != null && nextTop >= currTop) {
2489 handled = nextFocused.requestFocus();
2491 } else if (direction == View.FOCUS_DOWN) {
2492 final int nextDown =
2493 getChildRectInPagerCoordinates(mTempRect, nextFocused).bottom;
2494 final int currDown =
2495 getChildRectInPagerCoordinates(mTempRect, currentFocused).bottom;
2496 if (currentFocused != null && nextDown <= currDown) {
2497 handled = pageDown();
2499 handled = nextFocused.requestFocus();
2502 } else if (direction == FOCUS_UP || direction == FOCUS_BACKWARD) {
2503 // Trying to move left and nothing there; try to page.
2505 } else if (direction == FOCUS_DOWN || direction == FOCUS_FORWARD) {
2506 // Trying to move right and nothing there; try to page.
2507 handled = pageDown();
2510 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
2515 private Rect getChildRectInPagerCoordinates(Rect outRect, View child) {
2516 if (outRect == null) {
2517 outRect = new Rect();
2519 if (child == null) {
2520 outRect.set(0, 0, 0, 0);
2523 outRect.left = child.getLeft();
2524 outRect.right = child.getRight();
2525 outRect.top = child.getTop();
2526 outRect.bottom = child.getBottom();
2528 ViewParent parent = child.getParent();
2529 while (parent instanceof ViewGroup && parent != this) {
2530 final ViewGroup group = (ViewGroup) parent;
2531 outRect.left += group.getLeft();
2532 outRect.right += group.getRight();
2533 outRect.top += group.getTop();
2534 outRect.bottom += group.getBottom();
2536 parent = group.getParent();
2543 setCurrentItem(mCurItem - 1, true);
2549 boolean pageDown() {
2550 if (mAdapter != null && mCurItem < (mAdapter.getCount() - 1)) {
2551 setCurrentItem(mCurItem + 1, true);
2558 * We only want the current page that is being shown to be focusable.
2561 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
2562 final int focusableCount = views.size();
2564 final int descendantFocusability = getDescendantFocusability();
2566 if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
2567 for (int i = 0; i < getChildCount(); i++) {
2568 final View child = getChildAt(i);
2569 if (child.getVisibility() == VISIBLE) {
2570 ItemInfo ii = infoForChild(child);
2571 if (ii != null && ii.position == mCurItem) {
2572 child.addFocusables(views, direction, focusableMode);
2578 // we add ourselves (if focusable) in all cases except for when we are
2579 // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is
2580 // to avoid the focus search finding layouts when a more precise search
2581 // among the focusable children would be more interesting.
2583 descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
2584 // No focusable descendants
2585 (focusableCount == views.size())) {
2586 // Note that we can't call the superclass here, because it will
2587 // add all views in. So we need to do the same thing View does.
2588 if (!isFocusable()) {
2591 if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
2592 isInTouchMode() && !isFocusableInTouchMode()) {
2595 if (views != null) {
2602 * We only want the current page that is being shown to be touchable.
2605 public void addTouchables(ArrayList<View> views) {
2606 // Note that we don't call super.addTouchables(), which means that
2607 // we don't call View.addTouchables(). This is okay because a ViewPager
2608 // is itself not touchable.
2609 for (int i = 0; i < getChildCount(); i++) {
2610 final View child = getChildAt(i);
2611 if (child.getVisibility() == VISIBLE) {
2612 ItemInfo ii = infoForChild(child);
2613 if (ii != null && ii.position == mCurItem) {
2614 child.addTouchables(views);
2621 * We only want the current page that is being shown to be focusable.
2624 protected boolean onRequestFocusInDescendants(int direction,
2625 Rect previouslyFocusedRect) {
2629 int count = getChildCount();
2630 if ((direction & FOCUS_FORWARD) != 0) {
2639 for (int i = index; i != end; i += increment) {
2640 View child = getChildAt(i);
2641 if (child.getVisibility() == VISIBLE) {
2642 ItemInfo ii = infoForChild(child);
2643 if (ii != null && ii.position == mCurItem &&
2644 child.requestFocus(direction, previouslyFocusedRect)) {
2653 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
2654 // Dispatch scroll events from this ViewPager.
2655 if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED) {
2656 return super.dispatchPopulateAccessibilityEvent(event);
2659 // Dispatch all other accessibility events from the current page.
2660 final int childCount = getChildCount();
2661 for (int i = 0; i < childCount; i++) {
2662 final View child = getChildAt(i);
2663 if (child.getVisibility() == VISIBLE) {
2664 final ItemInfo ii = infoForChild(child);
2665 if (ii != null && ii.position == mCurItem &&
2666 child.dispatchPopulateAccessibilityEvent(event)) {
2676 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
2677 return new LayoutParams();
2681 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2682 return generateDefaultLayoutParams();
2686 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2687 return p instanceof LayoutParams && super.checkLayoutParams(p);
2691 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2692 return new LayoutParams(getContext(), attrs);
2695 class MyAccessibilityDelegate extends AccessibilityDelegateCompat {
2698 public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
2699 super.onInitializeAccessibilityEvent(host, event);
2700 event.setClassName(ViewPager.class.getName());
2701 final AccessibilityRecordCompat recordCompat = AccessibilityRecordCompat.obtain();
2702 recordCompat.setScrollable(canScroll());
2703 if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED
2704 && mAdapter != null) {
2705 recordCompat.setItemCount(mAdapter.getCount());
2706 recordCompat.setFromIndex(mCurItem);
2707 recordCompat.setToIndex(mCurItem);
2712 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
2713 super.onInitializeAccessibilityNodeInfo(host, info);
2714 info.setClassName(ViewPager.class.getName());
2715 info.setScrollable(canScroll());
2716 if (internalCanScrollVertically(1)) {
2717 info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
2719 if (internalCanScrollVertically(-1)) {
2720 info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
2725 public boolean performAccessibilityAction(View host, int action, Bundle args) {
2726 if (super.performAccessibilityAction(host, action, args)) {
2730 case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {
2731 if (internalCanScrollVertically(1)) {
2732 setCurrentItem(mCurItem + 1);
2737 case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {
2738 if (internalCanScrollVertically(-1)) {
2739 setCurrentItem(mCurItem - 1);
2748 private boolean canScroll() {
2749 return (mAdapter != null) && (mAdapter.getCount() > 1);
2753 private class PagerObserver extends DataSetObserver {
2755 public void onChanged() {
2760 public void onInvalidated() {
2766 * Layout parameters that should be supplied for views added to a
2769 public static class LayoutParams extends ViewGroup.LayoutParams {
2771 * true if this view is a decoration on the pager itself and not
2772 * a view supplied by the adapter.
2774 public boolean isDecor;
2777 * Gravity setting for use on decor views only:
2778 * Where to position the view page within the overall ViewPager
2779 * container; constants are defined in {@link Gravity}.
2784 * Width as a 0-1 multiplier of the measured pager width
2786 float heightFactor = 0.f;
2789 * true if this view was added during layout and needs to be measured
2790 * before being positioned.
2792 boolean needsMeasure;
2795 * Adapter position this view is for if !isDecor
2800 * Current child index within the ViewPager that this view occupies
2804 public LayoutParams() {
2805 super(FILL_PARENT, FILL_PARENT);
2808 public LayoutParams(Context context, AttributeSet attrs) {
2809 super(context, attrs);
2811 final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
2812 gravity = a.getInteger(0, Gravity.TOP);
2817 static class ViewPositionComparator implements Comparator<View> {
2819 public int compare(View lhs, View rhs) {
2820 final LayoutParams llp = (LayoutParams) lhs.getLayoutParams();
2821 final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams();
2822 if (llp.isDecor != rlp.isDecor) {
2823 return llp.isDecor ? 1 : -1;
2825 return llp.position - rlp.position;
2829 public class ScrollerCustomDuration extends Scroller {
2831 private double mScrollFactor = 1;
2833 public ScrollerCustomDuration(Context context) {
2837 public ScrollerCustomDuration(Context context,
2838 Interpolator interpolator) {
2839 super(context, interpolator);
2842 @SuppressLint("NewApi")
2843 public ScrollerCustomDuration(Context context,
2844 Interpolator interpolator, boolean flywheel) {
2845 super(context, interpolator, flywheel);
2849 * Set the factor by which the duration will change
2851 public void setScrollDurationFactor(double scrollFactor) {
2852 mScrollFactor = scrollFactor;
2856 public void startScroll(int startX, int startY, int dx, int dy, int duration) {
2857 super.startScroll(startX, startY, dx, dy, (int) (duration * mScrollFactor));