• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.support.v4.view;
18 
19 import android.content.Context;
20 import android.graphics.Canvas;
21 import android.graphics.Rect;
22 import android.graphics.drawable.Drawable;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 import android.os.SystemClock;
26 import android.support.v4.os.ParcelableCompat;
27 import android.support.v4.os.ParcelableCompatCreatorCallbacks;
28 import android.support.v4.widget.EdgeEffectCompat;
29 import android.util.AttributeSet;
30 import android.util.Log;
31 import android.view.FocusFinder;
32 import android.view.KeyEvent;
33 import android.view.MotionEvent;
34 import android.view.SoundEffectConstants;
35 import android.view.VelocityTracker;
36 import android.view.View;
37 import android.view.ViewConfiguration;
38 import android.view.ViewGroup;
39 import android.view.ViewParent;
40 import android.view.accessibility.AccessibilityEvent;
41 import android.view.animation.Interpolator;
42 import android.widget.Scroller;
43 
44 import java.util.ArrayList;
45 import java.util.Collections;
46 import java.util.Comparator;
47 
48 /**
49  * Layout manager that allows the user to flip left and right
50  * through pages of data.  You supply an implementation of a
51  * {@link PagerAdapter} to generate the pages that the view shows.
52  *
53  * <p>Note this class is currently under early design and
54  * development.  The API will likely change in later updates of
55  * the compatibility library, requiring changes to the source code
56  * of apps when they are compiled against the newer version.</p>
57  */
58 public class ViewPager extends ViewGroup {
59     private static final String TAG = "ViewPager";
60     private static final boolean DEBUG = false;
61 
62     private static final boolean USE_CACHE = false;
63 
64     private static final int DEFAULT_OFFSCREEN_PAGES = 1;
65     private static final int MAX_SETTLE_DURATION = 600; // ms
66 
67     static class ItemInfo {
68         Object object;
69         int position;
70         boolean scrolling;
71     }
72 
73     private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>(){
74         @Override
75         public int compare(ItemInfo lhs, ItemInfo rhs) {
76             return lhs.position - rhs.position;
77         }};
78 
79     private static final Interpolator sInterpolator = new Interpolator() {
80         public float getInterpolation(float t) {
81             // _o(t) = t * t * ((tension + 1) * t + tension)
82             // o(t) = _o(t - 1) + 1
83             t -= 1.0f;
84             return t * t * t + 1.0f;
85         }
86     };
87 
88     private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
89 
90     private PagerAdapter mAdapter;
91     private int mCurItem;   // Index of currently displayed page.
92     private int mRestoredCurItem = -1;
93     private Parcelable mRestoredAdapterState = null;
94     private ClassLoader mRestoredClassLoader = null;
95     private Scroller mScroller;
96     private PagerAdapter.DataSetObserver mObserver;
97 
98     private int mPageMargin;
99     private Drawable mMarginDrawable;
100 
101     private int mChildWidthMeasureSpec;
102     private int mChildHeightMeasureSpec;
103     private boolean mInLayout;
104 
105     private boolean mScrollingCacheEnabled;
106 
107     private boolean mPopulatePending;
108     private boolean mScrolling;
109     private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
110 
111     private boolean mIsBeingDragged;
112     private boolean mIsUnableToDrag;
113     private int mTouchSlop;
114     private float mInitialMotionX;
115     /**
116      * Position of the last motion event.
117      */
118     private float mLastMotionX;
119     private float mLastMotionY;
120     /**
121      * ID of the active pointer. This is used to retain consistency during
122      * drags/flings if multiple pointers are used.
123      */
124     private int mActivePointerId = INVALID_POINTER;
125     /**
126      * Sentinel value for no current active pointer.
127      * Used by {@link #mActivePointerId}.
128      */
129     private static final int INVALID_POINTER = -1;
130 
131     /**
132      * Determines speed during touch scrolling
133      */
134     private VelocityTracker mVelocityTracker;
135     private int mMinimumVelocity;
136     private int mMaximumVelocity;
137     private float mBaseLineFlingVelocity;
138     private float mFlingVelocityInfluence;
139 
140     private boolean mFakeDragging;
141     private long mFakeDragBeginTime;
142 
143     private EdgeEffectCompat mLeftEdge;
144     private EdgeEffectCompat mRightEdge;
145 
146     private boolean mFirstLayout = true;
147 
148     private OnPageChangeListener mOnPageChangeListener;
149 
150     /**
151      * Indicates that the pager is in an idle, settled state. The current page
152      * is fully in view and no animation is in progress.
153      */
154     public static final int SCROLL_STATE_IDLE = 0;
155 
156     /**
157      * Indicates that the pager is currently being dragged by the user.
158      */
159     public static final int SCROLL_STATE_DRAGGING = 1;
160 
161     /**
162      * Indicates that the pager is in the process of settling to a final position.
163      */
164     public static final int SCROLL_STATE_SETTLING = 2;
165 
166     private int mScrollState = SCROLL_STATE_IDLE;
167 
168     /**
169      * Callback interface for responding to changing state of the selected page.
170      */
171     public interface OnPageChangeListener {
172 
173         /**
174          * This method will be invoked when the current page is scrolled, either as part
175          * of a programmatically initiated smooth scroll or a user initiated touch scroll.
176          *
177          * @param position Position index of the first page currently being displayed.
178          *                 Page position+1 will be visible if positionOffset is nonzero.
179          * @param positionOffset Value from [0, 1) indicating the offset from the page at position.
180          * @param positionOffsetPixels Value in pixels indicating the offset from position.
181          */
onPageScrolled(int position, float positionOffset, int positionOffsetPixels)182         public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
183 
184         /**
185          * This method will be invoked when a new page becomes selected. Animation is not
186          * necessarily complete.
187          *
188          * @param position Position index of the new selected page.
189          */
onPageSelected(int position)190         public void onPageSelected(int position);
191 
192         /**
193          * Called when the scroll state changes. Useful for discovering when the user
194          * begins dragging, when the pager is automatically settling to the current page,
195          * or when it is fully stopped/idle.
196          *
197          * @param state The new scroll state.
198          * @see ViewPager#SCROLL_STATE_IDLE
199          * @see ViewPager#SCROLL_STATE_DRAGGING
200          * @see ViewPager#SCROLL_STATE_SETTLING
201          */
onPageScrollStateChanged(int state)202         public void onPageScrollStateChanged(int state);
203     }
204 
205     /**
206      * Simple implementation of the {@link OnPageChangeListener} interface with stub
207      * implementations of each method. Extend this if you do not intend to override
208      * every method of {@link OnPageChangeListener}.
209      */
210     public static class SimpleOnPageChangeListener implements OnPageChangeListener {
211         @Override
onPageScrolled(int position, float positionOffset, int positionOffsetPixels)212         public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
213             // This space for rent
214         }
215 
216         @Override
onPageSelected(int position)217         public void onPageSelected(int position) {
218             // This space for rent
219         }
220 
221         @Override
onPageScrollStateChanged(int state)222         public void onPageScrollStateChanged(int state) {
223             // This space for rent
224         }
225     }
226 
ViewPager(Context context)227     public ViewPager(Context context) {
228         super(context);
229         initViewPager();
230     }
231 
ViewPager(Context context, AttributeSet attrs)232     public ViewPager(Context context, AttributeSet attrs) {
233         super(context, attrs);
234         initViewPager();
235     }
236 
initViewPager()237     void initViewPager() {
238         setWillNotDraw(false);
239         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
240         setFocusable(true);
241         final Context context = getContext();
242         mScroller = new Scroller(context, sInterpolator);
243         final ViewConfiguration configuration = ViewConfiguration.get(context);
244         mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
245         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
246         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
247         mLeftEdge = new EdgeEffectCompat(context);
248         mRightEdge = new EdgeEffectCompat(context);
249 
250         float density = context.getResources().getDisplayMetrics().density;
251         mBaseLineFlingVelocity = 2500.0f * density;
252         mFlingVelocityInfluence = 0.4f;
253     }
254 
setScrollState(int newState)255     private void setScrollState(int newState) {
256         if (mScrollState == newState) {
257             return;
258         }
259 
260         mScrollState = newState;
261         if (mOnPageChangeListener != null) {
262             mOnPageChangeListener.onPageScrollStateChanged(newState);
263         }
264     }
265 
setAdapter(PagerAdapter adapter)266     public void setAdapter(PagerAdapter adapter) {
267         if (mAdapter != null) {
268             mAdapter.setDataSetObserver(null);
269             mAdapter.startUpdate(this);
270             for (int i = 0; i < mItems.size(); i++) {
271                 final ItemInfo ii = mItems.get(i);
272                 mAdapter.destroyItem(this, ii.position, ii.object);
273             }
274             mAdapter.finishUpdate(this);
275             mItems.clear();
276             removeAllViews();
277             mCurItem = 0;
278             scrollTo(0, 0);
279         }
280 
281         mAdapter = adapter;
282 
283         if (mAdapter != null) {
284             if (mObserver == null) {
285                 mObserver = new DataSetObserver();
286             }
287             mAdapter.setDataSetObserver(mObserver);
288             mPopulatePending = false;
289             if (mRestoredCurItem >= 0) {
290                 mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
291                 setCurrentItemInternal(mRestoredCurItem, false, true);
292                 mRestoredCurItem = -1;
293                 mRestoredAdapterState = null;
294                 mRestoredClassLoader = null;
295             } else {
296                 populate();
297             }
298         }
299     }
300 
getAdapter()301     public PagerAdapter getAdapter() {
302         return mAdapter;
303     }
304 
305     /**
306      * Set the currently selected page. If the ViewPager has already been through its first
307      * layout there will be a smooth animated transition between the current item and the
308      * specified item.
309      *
310      * @param item Item index to select
311      */
setCurrentItem(int item)312     public void setCurrentItem(int item) {
313         mPopulatePending = false;
314         setCurrentItemInternal(item, !mFirstLayout, false);
315     }
316 
317     /**
318      * Set the currently selected page.
319      *
320      * @param item Item index to select
321      * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
322      */
setCurrentItem(int item, boolean smoothScroll)323     public void setCurrentItem(int item, boolean smoothScroll) {
324         mPopulatePending = false;
325         setCurrentItemInternal(item, smoothScroll, false);
326     }
327 
getCurrentItem()328     public int getCurrentItem() {
329         return mCurItem;
330     }
331 
setCurrentItemInternal(int item, boolean smoothScroll, boolean always)332     void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
333         setCurrentItemInternal(item, smoothScroll, always, 0);
334     }
335 
setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity)336     void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
337         if (mAdapter == null || mAdapter.getCount() <= 0) {
338             setScrollingCacheEnabled(false);
339             return;
340         }
341         if (!always && mCurItem == item && mItems.size() != 0) {
342             setScrollingCacheEnabled(false);
343             return;
344         }
345         if (item < 0) {
346             item = 0;
347         } else if (item >= mAdapter.getCount()) {
348             item = mAdapter.getCount() - 1;
349         }
350         final int pageLimit = mOffscreenPageLimit;
351         if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
352             // We are doing a jump by more than one page.  To avoid
353             // glitches, we want to keep all current pages in the view
354             // until the scroll ends.
355             for (int i=0; i<mItems.size(); i++) {
356                 mItems.get(i).scrolling = true;
357             }
358         }
359         final boolean dispatchSelected = mCurItem != item;
360         mCurItem = item;
361         populate();
362         final int destX = (getWidth() + mPageMargin) * item;
363         if (smoothScroll) {
364             smoothScrollTo(destX, 0, velocity);
365             if (dispatchSelected && mOnPageChangeListener != null) {
366                 mOnPageChangeListener.onPageSelected(item);
367             }
368         } else {
369             if (dispatchSelected && mOnPageChangeListener != null) {
370                 mOnPageChangeListener.onPageSelected(item);
371             }
372             completeScroll();
373             scrollTo(destX, 0);
374         }
375     }
376 
setOnPageChangeListener(OnPageChangeListener listener)377     public void setOnPageChangeListener(OnPageChangeListener listener) {
378         mOnPageChangeListener = listener;
379     }
380 
381     /**
382      * Returns the number of pages that will be retained to either side of the
383      * current page in the view hierarchy in an idle state. Defaults to 1.
384      *
385      * @return How many pages will be kept offscreen on either side
386      * @see #setOffscreenPageLimit(int)
387      */
getOffscreenPageLimit()388     public int getOffscreenPageLimit() {
389         return mOffscreenPageLimit;
390     }
391 
392     /**
393      * Set the number of pages that should be retained to either side of the
394      * current page in the view hierarchy in an idle state. Pages beyond this
395      * limit will be recreated from the adapter when needed.
396      *
397      * <p>This is offered as an optimization. If you know in advance the number
398      * of pages you will need to support or have lazy-loading mechanisms in place
399      * on your pages, tweaking this setting can have benefits in perceived smoothness
400      * of paging animations and interaction. If you have a small number of pages (3-4)
401      * that you can keep active all at once, less time will be spent in layout for
402      * newly created view subtrees as the user pages back and forth.</p>
403      *
404      * <p>You should keep this limit low, especially if your pages have complex layouts.
405      * This setting defaults to 1.</p>
406      *
407      * @param limit How many pages will be kept offscreen in an idle state.
408      */
setOffscreenPageLimit(int limit)409     public void setOffscreenPageLimit(int limit) {
410         if (limit < DEFAULT_OFFSCREEN_PAGES) {
411             Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
412                     DEFAULT_OFFSCREEN_PAGES);
413             limit = DEFAULT_OFFSCREEN_PAGES;
414         }
415         if (limit != mOffscreenPageLimit) {
416             mOffscreenPageLimit = limit;
417             populate();
418         }
419     }
420 
421     /**
422      * Set the margin between pages.
423      *
424      * @param marginPixels Distance between adjacent pages in pixels
425      * @see #getPageMargin()
426      * @see #setPageMarginDrawable(Drawable)
427      * @see #setPageMarginDrawable(int)
428      */
setPageMargin(int marginPixels)429     public void setPageMargin(int marginPixels) {
430         final int oldMargin = mPageMargin;
431         mPageMargin = marginPixels;
432 
433         final int width = getWidth();
434         recomputeScrollPosition(width, width, marginPixels, oldMargin);
435 
436         requestLayout();
437     }
438 
439     /**
440      * Return the margin between pages.
441      *
442      * @return The size of the margin in pixels
443      */
getPageMargin()444     public int getPageMargin() {
445         return mPageMargin;
446     }
447 
448     /**
449      * Set a drawable that will be used to fill the margin between pages.
450      *
451      * @param d Drawable to display between pages
452      */
setPageMarginDrawable(Drawable d)453     public void setPageMarginDrawable(Drawable d) {
454         mMarginDrawable = d;
455         if (d != null) refreshDrawableState();
456         setWillNotDraw(d == null);
457         invalidate();
458     }
459 
460     /**
461      * Set a drawable that will be used to fill the margin between pages.
462      *
463      * @param resId Resource ID of a drawable to display between pages
464      */
setPageMarginDrawable(int resId)465     public void setPageMarginDrawable(int resId) {
466         setPageMarginDrawable(getContext().getResources().getDrawable(resId));
467     }
468 
469     @Override
verifyDrawable(Drawable who)470     protected boolean verifyDrawable(Drawable who) {
471         return super.verifyDrawable(who) || who == mMarginDrawable;
472     }
473 
474     @Override
drawableStateChanged()475     protected void drawableStateChanged() {
476         super.drawableStateChanged();
477         final Drawable d = mMarginDrawable;
478         if (d != null && d.isStateful()) {
479             d.setState(getDrawableState());
480         }
481     }
482 
483     // We want the duration of the page snap animation to be influenced by the distance that
484     // the screen has to travel, however, we don't want this duration to be effected in a
485     // purely linear fashion. Instead, we use this method to moderate the effect that the distance
486     // of travel has on the overall snap duration.
distanceInfluenceForSnapDuration(float f)487     float distanceInfluenceForSnapDuration(float f) {
488         f -= 0.5f; // center the values about 0.
489         f *= 0.3f * Math.PI / 2.0f;
490         return (float) Math.sin(f);
491     }
492 
493     /**
494      * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
495      *
496      * @param x the number of pixels to scroll by on the X axis
497      * @param y the number of pixels to scroll by on the Y axis
498      */
smoothScrollTo(int x, int y)499     void smoothScrollTo(int x, int y) {
500         smoothScrollTo(x, y, 0);
501     }
502 
503     /**
504      * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
505      *
506      * @param x the number of pixels to scroll by on the X axis
507      * @param y the number of pixels to scroll by on the Y axis
508      * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
509      */
smoothScrollTo(int x, int y, int velocity)510     void smoothScrollTo(int x, int y, int velocity) {
511         if (getChildCount() == 0) {
512             // Nothing to do.
513             setScrollingCacheEnabled(false);
514             return;
515         }
516         int sx = getScrollX();
517         int sy = getScrollY();
518         int dx = x - sx;
519         int dy = y - sy;
520         if (dx == 0 && dy == 0) {
521             completeScroll();
522             setScrollState(SCROLL_STATE_IDLE);
523             return;
524         }
525 
526         setScrollingCacheEnabled(true);
527         mScrolling = true;
528         setScrollState(SCROLL_STATE_SETTLING);
529 
530         final float pageDelta = (float) Math.abs(dx) / (getWidth() + mPageMargin);
531         int duration = (int) (pageDelta * 100);
532 
533         velocity = Math.abs(velocity);
534         if (velocity > 0) {
535             duration += (duration / (velocity / mBaseLineFlingVelocity)) * mFlingVelocityInfluence;
536         } else {
537             duration += 100;
538         }
539         duration = Math.min(duration, MAX_SETTLE_DURATION);
540 
541         mScroller.startScroll(sx, sy, dx, dy, duration);
542         invalidate();
543     }
544 
addNewItem(int position, int index)545     void addNewItem(int position, int index) {
546         ItemInfo ii = new ItemInfo();
547         ii.position = position;
548         ii.object = mAdapter.instantiateItem(this, position);
549         if (index < 0) {
550             mItems.add(ii);
551         } else {
552             mItems.add(index, ii);
553         }
554     }
555 
dataSetChanged()556     void dataSetChanged() {
557         // This method only gets called if our observer is attached, so mAdapter is non-null.
558 
559         boolean needPopulate = mItems.size() < 3 && mItems.size() < mAdapter.getCount();
560         int newCurrItem = -1;
561 
562         for (int i = 0; i < mItems.size(); i++) {
563             final ItemInfo ii = mItems.get(i);
564             final int newPos = mAdapter.getItemPosition(ii.object);
565 
566             if (newPos == PagerAdapter.POSITION_UNCHANGED) {
567                 continue;
568             }
569 
570             if (newPos == PagerAdapter.POSITION_NONE) {
571                 mItems.remove(i);
572                 i--;
573                 mAdapter.destroyItem(this, ii.position, ii.object);
574                 needPopulate = true;
575 
576                 if (mCurItem == ii.position) {
577                     // Keep the current item in the valid range
578                     newCurrItem = Math.max(0, Math.min(mCurItem, mAdapter.getCount() - 1));
579                 }
580                 continue;
581             }
582 
583             if (ii.position != newPos) {
584                 if (ii.position == mCurItem) {
585                     // Our current item changed position. Follow it.
586                     newCurrItem = newPos;
587                 }
588 
589                 ii.position = newPos;
590                 needPopulate = true;
591             }
592         }
593 
594         Collections.sort(mItems, COMPARATOR);
595 
596         if (newCurrItem >= 0) {
597             // TODO This currently causes a jump.
598             setCurrentItemInternal(newCurrItem, false, true);
599             needPopulate = true;
600         }
601         if (needPopulate) {
602             populate();
603             requestLayout();
604         }
605     }
606 
607     void populate() {
608         if (mAdapter == null) {
609             return;
610         }
611 
612         // Bail now if we are waiting to populate.  This is to hold off
613         // on creating views from the time the user releases their finger to
614         // fling to a new position until we have finished the scroll to
615         // that position, avoiding glitches from happening at that point.
616         if (mPopulatePending) {
617             if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
618             return;
619         }
620 
621         // Also, don't populate until we are attached to a window.  This is to
622         // avoid trying to populate before we have restored our view hierarchy
623         // state and conflicting with what is restored.
624         if (getWindowToken() == null) {
625             return;
626         }
627 
628         mAdapter.startUpdate(this);
629 
630         final int pageLimit = mOffscreenPageLimit;
631         final int startPos = Math.max(0, mCurItem - pageLimit);
632         final int N = mAdapter.getCount();
633         final int endPos = Math.min(N-1, mCurItem + pageLimit);
634 
635         if (DEBUG) Log.v(TAG, "populating: startPos=" + startPos + " endPos=" + endPos);
636 
637         // Add and remove pages in the existing list.
638         int lastPos = -1;
639         for (int i=0; i<mItems.size(); i++) {
640             ItemInfo ii = mItems.get(i);
641             if ((ii.position < startPos || ii.position > endPos) && !ii.scrolling) {
642                 if (DEBUG) Log.i(TAG, "removing: " + ii.position + " @ " + i);
643                 mItems.remove(i);
644                 i--;
645                 mAdapter.destroyItem(this, ii.position, ii.object);
646             } else if (lastPos < endPos && ii.position > startPos) {
647                 // The next item is outside of our range, but we have a gap
648                 // between it and the last item where we want to have a page
649                 // shown.  Fill in the gap.
650                 lastPos++;
651                 if (lastPos < startPos) {
652                     lastPos = startPos;
653                 }
654                 while (lastPos <= endPos && lastPos < ii.position) {
655                     if (DEBUG) Log.i(TAG, "inserting: " + lastPos + " @ " + i);
656                     addNewItem(lastPos, i);
657                     lastPos++;
658                     i++;
659                 }
660             }
661             lastPos = ii.position;
662         }
663 
664         // Add any new pages we need at the end.
665         lastPos = mItems.size() > 0 ? mItems.get(mItems.size()-1).position : -1;
666         if (lastPos < endPos) {
667             lastPos++;
668             lastPos = lastPos > startPos ? lastPos : startPos;
669             while (lastPos <= endPos) {
670                 if (DEBUG) Log.i(TAG, "appending: " + lastPos);
671                 addNewItem(lastPos, -1);
672                 lastPos++;
673             }
674         }
675 
676         if (DEBUG) {
677             Log.i(TAG, "Current page list:");
678             for (int i=0; i<mItems.size(); i++) {
679                 Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
680             }
681         }
682 
683         ItemInfo curItem = null;
684         for (int i=0; i<mItems.size(); i++) {
685             if (mItems.get(i).position == mCurItem) {
686                 curItem = mItems.get(i);
687                 break;
688             }
689         }
690         mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
691 
692         mAdapter.finishUpdate(this);
693 
694         if (hasFocus()) {
695             View currentFocused = findFocus();
696             ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
697             if (ii == null || ii.position != mCurItem) {
698                 for (int i=0; i<getChildCount(); i++) {
699                     View child = getChildAt(i);
700                     ii = infoForChild(child);
701                     if (ii != null && ii.position == mCurItem) {
702                         if (child.requestFocus(FOCUS_FORWARD)) {
703                             break;
704                         }
705                     }
706                 }
707             }
708         }
709     }
710 
711     public static class SavedState extends BaseSavedState {
712         int position;
713         Parcelable adapterState;
714         ClassLoader loader;
715 
716         public SavedState(Parcelable superState) {
717             super(superState);
718         }
719 
720         @Override
721         public void writeToParcel(Parcel out, int flags) {
722             super.writeToParcel(out, flags);
723             out.writeInt(position);
724             out.writeParcelable(adapterState, flags);
725         }
726 
727         @Override
728         public String toString() {
729             return "FragmentPager.SavedState{"
730                     + Integer.toHexString(System.identityHashCode(this))
731                     + " position=" + position + "}";
732         }
733 
734         public static final Parcelable.Creator<SavedState> CREATOR
735                 = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
736                     @Override
737                     public SavedState createFromParcel(Parcel in, ClassLoader loader) {
738                         return new SavedState(in, loader);
739                     }
740                     @Override
741                     public SavedState[] newArray(int size) {
742                         return new SavedState[size];
743                     }
744                 });
745 
746         SavedState(Parcel in, ClassLoader loader) {
747             super(in);
748             if (loader == null) {
749                 loader = getClass().getClassLoader();
750             }
751             position = in.readInt();
752             adapterState = in.readParcelable(loader);
753             this.loader = loader;
754         }
755     }
756 
757     @Override
758     public Parcelable onSaveInstanceState() {
759         Parcelable superState = super.onSaveInstanceState();
760         SavedState ss = new SavedState(superState);
761         ss.position = mCurItem;
762         if (mAdapter != null) {
763             ss.adapterState = mAdapter.saveState();
764         }
765         return ss;
766     }
767 
768     @Override
769     public void onRestoreInstanceState(Parcelable state) {
770         if (!(state instanceof SavedState)) {
771             super.onRestoreInstanceState(state);
772             return;
773         }
774 
775         SavedState ss = (SavedState)state;
776         super.onRestoreInstanceState(ss.getSuperState());
777 
778         if (mAdapter != null) {
779             mAdapter.restoreState(ss.adapterState, ss.loader);
780             setCurrentItemInternal(ss.position, false, true);
781         } else {
782             mRestoredCurItem = ss.position;
783             mRestoredAdapterState = ss.adapterState;
784             mRestoredClassLoader = ss.loader;
785         }
786     }
787 
788     @Override
789     public void addView(View child, int index, LayoutParams params) {
790         if (mInLayout) {
791             addViewInLayout(child, index, params);
792             child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
793         } else {
794             super.addView(child, index, params);
795         }
796 
797         if (USE_CACHE) {
798             if (child.getVisibility() != GONE) {
799                 child.setDrawingCacheEnabled(mScrollingCacheEnabled);
800             } else {
801                 child.setDrawingCacheEnabled(false);
802             }
803         }
804     }
805 
806     ItemInfo infoForChild(View child) {
807         for (int i=0; i<mItems.size(); i++) {
808             ItemInfo ii = mItems.get(i);
809             if (mAdapter.isViewFromObject(child, ii.object)) {
810                 return ii;
811             }
812         }
813         return null;
814     }
815 
816     ItemInfo infoForAnyChild(View child) {
817         ViewParent parent;
818         while ((parent=child.getParent()) != this) {
819             if (parent == null || !(parent instanceof View)) {
820                 return null;
821             }
822             child = (View)parent;
823         }
824         return infoForChild(child);
825     }
826 
827     @Override
828     protected void onAttachedToWindow() {
829         super.onAttachedToWindow();
830         mFirstLayout = true;
831     }
832 
833     @Override
834     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
835         // For simple implementation, or internal size is always 0.
836         // We depend on the container to specify the layout size of
837         // our view.  We can't really know what it is since we will be
838         // adding and removing different arbitrary views and do not
839         // want the layout to change as this happens.
840         setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
841                 getDefaultSize(0, heightMeasureSpec));
842 
843         // Children are just made to fill our space.
844         mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
845                 getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY);
846         mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
847                 getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY);
848 
849         // Make sure we have created all fragments that we need to have shown.
850         mInLayout = true;
851         populate();
852         mInLayout = false;
853 
854         // Make sure all children have been properly measured.
855         final int size = getChildCount();
856         for (int i = 0; i < size; ++i) {
857             final View child = getChildAt(i);
858             if (child.getVisibility() != GONE) {
859                 if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
860 		        + ": " + mChildWidthMeasureSpec);
861                 child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
862             }
863         }
864     }
865 
866     @Override
867     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
868         super.onSizeChanged(w, h, oldw, oldh);
869 
870         // Make sure scroll position is set correctly.
871         if (w != oldw) {
872             recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin);
873         }
874     }
875 
876     private void recomputeScrollPosition(int width, int oldWidth, int margin, int oldMargin) {
877         final int widthWithMargin = width + margin;
878         if (oldWidth > 0) {
879             final int oldScrollPos = getScrollX();
880             final int oldwwm = oldWidth + oldMargin;
881             final int oldScrollItem = oldScrollPos / oldwwm;
882             final float scrollOffset = (float) (oldScrollPos % oldwwm) / oldwwm;
883             final int scrollPos = (int) ((oldScrollItem + scrollOffset) * widthWithMargin);
884             scrollTo(scrollPos, getScrollY());
885             if (!mScroller.isFinished()) {
886                 // We now return to your regularly scheduled scroll, already in progress.
887                 final int newDuration = mScroller.getDuration() - mScroller.timePassed();
888                 mScroller.startScroll(scrollPos, 0, mCurItem * widthWithMargin, 0, newDuration);
889             }
890         } else {
891             int scrollPos = mCurItem * widthWithMargin;
892             if (scrollPos != getScrollX()) {
893                 completeScroll();
894                 scrollTo(scrollPos, getScrollY());
895             }
896         }
897     }
898 
899     @Override
900     protected void onLayout(boolean changed, int l, int t, int r, int b) {
901         mInLayout = true;
902         populate();
903         mInLayout = false;
904 
905         final int count = getChildCount();
906         final int width = r-l;
907 
908         for (int i = 0; i < count; i++) {
909             View child = getChildAt(i);
910             ItemInfo ii;
911             if (child.getVisibility() != GONE && (ii=infoForChild(child)) != null) {
912                 int loff = (width + mPageMargin) * ii.position;
913                 int childLeft = getPaddingLeft() + loff;
914                 int childTop = getPaddingTop();
915                 if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
916 		        + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
917 		        + "x" + child.getMeasuredHeight());
918                 child.layout(childLeft, childTop,
919                         childLeft + child.getMeasuredWidth(),
920                         childTop + child.getMeasuredHeight());
921             }
922         }
923         mFirstLayout = false;
924     }
925 
926     @Override
927     public void computeScroll() {
928         if (DEBUG) Log.i(TAG, "computeScroll: finished=" + mScroller.isFinished());
929         if (!mScroller.isFinished()) {
930             if (mScroller.computeScrollOffset()) {
931                 if (DEBUG) Log.i(TAG, "computeScroll: still scrolling");
932                 int oldX = getScrollX();
933                 int oldY = getScrollY();
934                 int x = mScroller.getCurrX();
935                 int y = mScroller.getCurrY();
936 
937                 if (oldX != x || oldY != y) {
938                     scrollTo(x, y);
939                 }
940 
941                 if (mOnPageChangeListener != null) {
942                     final int widthWithMargin = getWidth() + mPageMargin;
943                     final int position = x / widthWithMargin;
944                     final int offsetPixels = x % widthWithMargin;
945                     final float offset = (float) offsetPixels / widthWithMargin;
946                     mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
947                 }
948 
949                 // Keep on drawing until the animation has finished.
950                 invalidate();
951                 return;
952             }
953         }
954 
955         // Done with scroll, clean up state.
956         completeScroll();
957     }
958 
959     private void completeScroll() {
960         boolean needPopulate = mScrolling;
961         if (needPopulate) {
962             // Done with scroll, no longer want to cache view drawing.
963             setScrollingCacheEnabled(false);
964             mScroller.abortAnimation();
965             int oldX = getScrollX();
966             int oldY = getScrollY();
967             int x = mScroller.getCurrX();
968             int y = mScroller.getCurrY();
969             if (oldX != x || oldY != y) {
970                 scrollTo(x, y);
971             }
972             setScrollState(SCROLL_STATE_IDLE);
973         }
974         mPopulatePending = false;
975         mScrolling = false;
976         for (int i=0; i<mItems.size(); i++) {
977             ItemInfo ii = mItems.get(i);
978             if (ii.scrolling) {
979                 needPopulate = true;
980                 ii.scrolling = false;
981             }
982         }
983         if (needPopulate) {
984             populate();
985         }
986     }
987 
988     @Override
989     public boolean onInterceptTouchEvent(MotionEvent ev) {
990         /*
991          * This method JUST determines whether we want to intercept the motion.
992          * If we return true, onMotionEvent will be called and we do the actual
993          * scrolling there.
994          */
995 
996         final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
997 
998         // Always take care of the touch gesture being complete.
999         if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
1000             // Release the drag.
1001             if (DEBUG) Log.v(TAG, "Intercept done!");
1002             mIsBeingDragged = false;
1003             mIsUnableToDrag = false;
1004             mActivePointerId = INVALID_POINTER;
1005             return false;
1006         }
1007 
1008         // Nothing more to do here if we have decided whether or not we
1009         // are dragging.
1010         if (action != MotionEvent.ACTION_DOWN) {
1011             if (mIsBeingDragged) {
1012                 if (DEBUG) Log.v(TAG, "Intercept returning true!");
1013                 return true;
1014             }
1015             if (mIsUnableToDrag) {
1016                 if (DEBUG) Log.v(TAG, "Intercept returning false!");
1017                 return false;
1018             }
1019         }
1020 
1021         switch (action) {
1022             case MotionEvent.ACTION_MOVE: {
1023                 /*
1024                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1025                  * whether the user has moved far enough from his original down touch.
1026                  */
1027 
1028                 /*
1029                 * Locally do absolute value. mLastMotionY is set to the y value
1030                 * of the down event.
1031                 */
1032                 final int activePointerId = mActivePointerId;
1033                 if (activePointerId == INVALID_POINTER) {
1034                     // If we don't have a valid id, the touch down wasn't on content.
1035                     break;
1036                 }
1037 
1038                 final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
1039                 final float x = MotionEventCompat.getX(ev, pointerIndex);
1040                 final float dx = x - mLastMotionX;
1041                 final float xDiff = Math.abs(dx);
1042                 final float y = MotionEventCompat.getY(ev, pointerIndex);
1043                 final float yDiff = Math.abs(y - mLastMotionY);
1044                 final int scrollX = getScrollX();
1045                 final boolean atEdge = (dx > 0 && scrollX == 0) || (dx < 0 && mAdapter != null &&
1046                         scrollX >= (mAdapter.getCount() - 1) * getWidth() - 1);
1047                 if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
1048 
1049                 if (canScroll(this, false, (int) dx, (int) x, (int) y)) {
1050                     // Nested view has scrollable area under this point. Let it be handled there.
1051                     mInitialMotionX = mLastMotionX = x;
1052                     mLastMotionY = y;
1053                     return false;
1054                 }
1055                 if (xDiff > mTouchSlop && xDiff > yDiff) {
1056                     if (DEBUG) Log.v(TAG, "Starting drag!");
1057                     mIsBeingDragged = true;
1058                     setScrollState(SCROLL_STATE_DRAGGING);
1059                     mLastMotionX = x;
1060                     setScrollingCacheEnabled(true);
1061                 } else {
1062                     if (yDiff > mTouchSlop) {
1063                         // The finger has moved enough in the vertical
1064                         // direction to be counted as a drag...  abort
1065                         // any attempt to drag horizontally, to work correctly
1066                         // with children that have scrolling containers.
1067                         if (DEBUG) Log.v(TAG, "Starting unable to drag!");
1068                         mIsUnableToDrag = true;
1069                     }
1070                 }
1071                 break;
1072             }
1073 
1074             case MotionEvent.ACTION_DOWN: {
1075                 /*
1076                  * Remember location of down touch.
1077                  * ACTION_DOWN always refers to pointer index 0.
1078                  */
1079                 mLastMotionX = mInitialMotionX = ev.getX();
1080                 mLastMotionY = ev.getY();
1081                 mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
1082 
1083                 if (mScrollState == SCROLL_STATE_SETTLING) {
1084                     // Let the user 'catch' the pager as it animates.
1085                     mIsBeingDragged = true;
1086                     mIsUnableToDrag = false;
1087                     setScrollState(SCROLL_STATE_DRAGGING);
1088                 } else {
1089                     completeScroll();
1090                     mIsBeingDragged = false;
1091                     mIsUnableToDrag = false;
1092                 }
1093 
1094                 if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
1095                         + " mIsBeingDragged=" + mIsBeingDragged
1096                         + "mIsUnableToDrag=" + mIsUnableToDrag);
1097                 break;
1098             }
1099 
1100             case MotionEventCompat.ACTION_POINTER_UP:
1101                 onSecondaryPointerUp(ev);
1102                 break;
1103         }
1104 
1105         /*
1106         * The only time we want to intercept motion events is if we are in the
1107         * drag mode.
1108         */
1109         return mIsBeingDragged;
1110     }
1111 
1112     @Override
1113     public boolean onTouchEvent(MotionEvent ev) {
1114         if (mFakeDragging) {
1115             // A fake drag is in progress already, ignore this real one
1116             // but still eat the touch events.
1117             // (It is likely that the user is multi-touching the screen.)
1118             return true;
1119         }
1120 
1121         if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
1122             // Don't handle edge touches immediately -- they may actually belong to one of our
1123             // descendants.
1124             return false;
1125         }
1126 
1127         if (mAdapter == null || mAdapter.getCount() == 0) {
1128             // Nothing to present or scroll; nothing to touch.
1129             return false;
1130         }
1131 
1132         if (mVelocityTracker == null) {
1133             mVelocityTracker = VelocityTracker.obtain();
1134         }
1135         mVelocityTracker.addMovement(ev);
1136 
1137         final int action = ev.getAction();
1138         boolean needsInvalidate = false;
1139 
1140         switch (action & MotionEventCompat.ACTION_MASK) {
1141             case MotionEvent.ACTION_DOWN: {
1142                 /*
1143                  * If being flinged and user touches, stop the fling. isFinished
1144                  * will be false if being flinged.
1145                  */
1146                 completeScroll();
1147 
1148                 // Remember where the motion event started
1149                 mLastMotionX = mInitialMotionX = ev.getX();
1150                 mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
1151                 break;
1152             }
1153             case MotionEvent.ACTION_MOVE:
1154                 if (!mIsBeingDragged) {
1155                     final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
1156                     final float x = MotionEventCompat.getX(ev, pointerIndex);
1157                     final float xDiff = Math.abs(x - mLastMotionX);
1158                     final float y = MotionEventCompat.getY(ev, pointerIndex);
1159                     final float yDiff = Math.abs(y - mLastMotionY);
1160                     if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
1161                     if (xDiff > mTouchSlop && xDiff > yDiff) {
1162                         if (DEBUG) Log.v(TAG, "Starting drag!");
1163                         mIsBeingDragged = true;
1164                         mLastMotionX = x;
1165                         setScrollState(SCROLL_STATE_DRAGGING);
1166                         setScrollingCacheEnabled(true);
1167                     }
1168                 }
1169                 if (mIsBeingDragged) {
1170                     // Scroll to follow the motion event
1171                     final int activePointerIndex = MotionEventCompat.findPointerIndex(
1172                             ev, mActivePointerId);
1173                     final float x = MotionEventCompat.getX(ev, activePointerIndex);
1174                     final float deltaX = mLastMotionX - x;
1175                     mLastMotionX = x;
1176                     float oldScrollX = getScrollX();
1177                     float scrollX = oldScrollX + deltaX;
1178                     final int width = getWidth();
1179                     final int widthWithMargin = width + mPageMargin;
1180 
1181                     final int lastItemIndex = mAdapter.getCount() - 1;
1182                     final float leftBound = Math.max(0, (mCurItem - 1) * widthWithMargin);
1183                     final float rightBound =
1184                             Math.min(mCurItem + 1, lastItemIndex) * widthWithMargin;
1185                     if (scrollX < leftBound) {
1186                         if (leftBound == 0) {
1187                             float over = -scrollX;
1188                             needsInvalidate = mLeftEdge.onPull(over / width);
1189                         }
1190                         scrollX = leftBound;
1191                     } else if (scrollX > rightBound) {
1192                         if (rightBound == lastItemIndex * widthWithMargin) {
1193                             float over = scrollX - rightBound;
1194                             needsInvalidate = mRightEdge.onPull(over / width);
1195                         }
1196                         scrollX = rightBound;
1197                     }
1198                     // Don't lose the rounded component
1199                     mLastMotionX += scrollX - (int) scrollX;
1200                     scrollTo((int) scrollX, getScrollY());
1201                     if (mOnPageChangeListener != null) {
1202                         final int position = (int) scrollX / widthWithMargin;
1203                         final int positionOffsetPixels = (int) scrollX % widthWithMargin;
1204                         final float positionOffset = (float) positionOffsetPixels / widthWithMargin;
1205                         mOnPageChangeListener.onPageScrolled(position, positionOffset,
1206                                 positionOffsetPixels);
1207                     }
1208                 }
1209                 break;
1210             case MotionEvent.ACTION_UP:
1211                 if (mIsBeingDragged) {
1212                     final VelocityTracker velocityTracker = mVelocityTracker;
1213                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1214                     int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
1215                             velocityTracker, mActivePointerId);
1216                     mPopulatePending = true;
1217                     final int widthWithMargin = getWidth() + mPageMargin;
1218                     final int scrollX = getScrollX();
1219                     final int currentPage = scrollX / widthWithMargin;
1220                     int nextPage = initialVelocity > 0 ? currentPage : currentPage + 1;
1221                     setCurrentItemInternal(nextPage, true, true, initialVelocity);
1222 
1223                     mActivePointerId = INVALID_POINTER;
1224                     endDrag();
1225                     needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
1226                 }
1227                 break;
1228             case MotionEvent.ACTION_CANCEL:
1229                 if (mIsBeingDragged) {
1230                     setCurrentItemInternal(mCurItem, true, true);
1231                     mActivePointerId = INVALID_POINTER;
1232                     endDrag();
1233                     needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
1234                 }
1235                 break;
1236             case MotionEventCompat.ACTION_POINTER_DOWN: {
1237                 final int index = MotionEventCompat.getActionIndex(ev);
1238                 final float x = MotionEventCompat.getX(ev, index);
1239                 mLastMotionX = x;
1240                 mActivePointerId = MotionEventCompat.getPointerId(ev, index);
1241                 break;
1242             }
1243             case MotionEventCompat.ACTION_POINTER_UP:
1244                 onSecondaryPointerUp(ev);
1245                 mLastMotionX = MotionEventCompat.getX(ev,
1246                         MotionEventCompat.findPointerIndex(ev, mActivePointerId));
1247                 break;
1248         }
1249         if (needsInvalidate) {
1250             invalidate();
1251         }
1252         return true;
1253     }
1254 
1255     @Override
1256     public void draw(Canvas canvas) {
1257         super.draw(canvas);
1258         boolean needsInvalidate = false;
1259 
1260         final int overScrollMode = ViewCompat.getOverScrollMode(this);
1261         if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
1262                 (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&
1263                         mAdapter != null && mAdapter.getCount() > 1)) {
1264             if (!mLeftEdge.isFinished()) {
1265                 final int restoreCount = canvas.save();
1266                 final int height = getHeight() - getPaddingTop() - getPaddingBottom();
1267 
1268                 canvas.rotate(270);
1269                 canvas.translate(-height + getPaddingTop(), 0);
1270                 mLeftEdge.setSize(height, getWidth());
1271                 needsInvalidate |= mLeftEdge.draw(canvas);
1272                 canvas.restoreToCount(restoreCount);
1273             }
1274             if (!mRightEdge.isFinished()) {
1275                 final int restoreCount = canvas.save();
1276                 final int width = getWidth();
1277                 final int height = getHeight() - getPaddingTop() - getPaddingBottom();
1278                 final int itemCount = mAdapter != null ? mAdapter.getCount() : 1;
1279 
1280                 canvas.rotate(90);
1281                 canvas.translate(-getPaddingTop(),
1282                         -itemCount * (width + mPageMargin) + mPageMargin);
1283                 mRightEdge.setSize(height, width);
1284                 needsInvalidate |= mRightEdge.draw(canvas);
1285                 canvas.restoreToCount(restoreCount);
1286             }
1287         } else {
1288             mLeftEdge.finish();
1289             mRightEdge.finish();
1290         }
1291 
1292         if (needsInvalidate) {
1293             // Keep animating
1294             invalidate();
1295         }
1296     }
1297 
1298     @Override
1299     protected void onDraw(Canvas canvas) {
1300         super.onDraw(canvas);
1301 
1302         // Draw the margin drawable if needed.
1303         if (mPageMargin > 0 && mMarginDrawable != null) {
1304             final int scrollX = getScrollX();
1305             final int width = getWidth();
1306             final int offset = scrollX % (width + mPageMargin);
1307             if (offset != 0) {
1308                 // Pages fit completely when settled; we only need to draw when in between
1309                 final int left = scrollX - offset + width;
1310                 mMarginDrawable.setBounds(left, 0, left + mPageMargin, getHeight());
1311                 mMarginDrawable.draw(canvas);
1312             }
1313         }
1314     }
1315 
1316     /**
1317      * Start a fake drag of the pager.
1318      *
1319      * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
1320      * with the touch scrolling of another view, while still letting the ViewPager
1321      * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
1322      * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
1323      * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
1324      *
1325      * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
1326      * is already in progress, this method will return false.
1327      *
1328      * @return true if the fake drag began successfully, false if it could not be started.
1329      *
1330      * @see #fakeDragBy(float)
1331      * @see #endFakeDrag()
1332      */
1333     public boolean beginFakeDrag() {
1334         if (mIsBeingDragged) {
1335             return false;
1336         }
1337         mFakeDragging = true;
1338         setScrollState(SCROLL_STATE_DRAGGING);
1339         mInitialMotionX = mLastMotionX = 0;
1340         if (mVelocityTracker == null) {
1341             mVelocityTracker = VelocityTracker.obtain();
1342         } else {
1343             mVelocityTracker.clear();
1344         }
1345         final long time = SystemClock.uptimeMillis();
1346         final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
1347         mVelocityTracker.addMovement(ev);
1348         ev.recycle();
1349         mFakeDragBeginTime = time;
1350         return true;
1351     }
1352 
1353     /**
1354      * End a fake drag of the pager.
1355      *
1356      * @see #beginFakeDrag()
1357      * @see #fakeDragBy(float)
1358      */
1359     public void endFakeDrag() {
1360         if (!mFakeDragging) {
1361             throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
1362         }
1363 
1364         final VelocityTracker velocityTracker = mVelocityTracker;
1365         velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1366         int initialVelocity = (int)VelocityTrackerCompat.getYVelocity(
1367                 velocityTracker, mActivePointerId);
1368         mPopulatePending = true;
1369         if ((Math.abs(initialVelocity) > mMinimumVelocity)
1370                 || Math.abs(mInitialMotionX-mLastMotionX) >= (getWidth()/3)) {
1371             if (mLastMotionX > mInitialMotionX) {
1372                 setCurrentItemInternal(mCurItem-1, true, true);
1373             } else {
1374                 setCurrentItemInternal(mCurItem+1, true, true);
1375             }
1376         } else {
1377             setCurrentItemInternal(mCurItem, true, true);
1378         }
1379         endDrag();
1380 
1381         mFakeDragging = false;
1382     }
1383 
1384     /**
1385      * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
1386      *
1387      * @param xOffset Offset in pixels to drag by.
1388      * @see #beginFakeDrag()
1389      * @see #endFakeDrag()
1390      */
fakeDragBy(float xOffset)1391     public void fakeDragBy(float xOffset) {
1392         if (!mFakeDragging) {
1393             throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
1394         }
1395 
1396         mLastMotionX += xOffset;
1397         float scrollX = getScrollX() - xOffset;
1398         final int width = getWidth();
1399         final int widthWithMargin = width + mPageMargin;
1400 
1401         final float leftBound = Math.max(0, (mCurItem - 1) * widthWithMargin);
1402         final float rightBound =
1403                 Math.min(mCurItem + 1, mAdapter.getCount() - 1) * widthWithMargin;
1404         if (scrollX < leftBound) {
1405             scrollX = leftBound;
1406         } else if (scrollX > rightBound) {
1407             scrollX = rightBound;
1408         }
1409         // Don't lose the rounded component
1410         mLastMotionX += scrollX - (int) scrollX;
1411         scrollTo((int) scrollX, getScrollY());
1412         if (mOnPageChangeListener != null) {
1413             final int position = (int) scrollX / widthWithMargin;
1414             final int positionOffsetPixels = (int) scrollX % widthWithMargin;
1415             final float positionOffset = (float) positionOffsetPixels / widthWithMargin;
1416             mOnPageChangeListener.onPageScrolled(position, positionOffset,
1417                     positionOffsetPixels);
1418         }
1419 
1420         // Synthesize an event for the VelocityTracker.
1421         final long time = SystemClock.uptimeMillis();
1422         final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
1423                 mLastMotionX, 0, 0);
1424         mVelocityTracker.addMovement(ev);
1425         ev.recycle();
1426     }
1427 
1428     /**
1429      * Returns true if a fake drag is in progress.
1430      *
1431      * @return true if currently in a fake drag, false otherwise.
1432      *
1433      * @see #beginFakeDrag()
1434      * @see #fakeDragBy(float)
1435      * @see #endFakeDrag()
1436      */
isFakeDragging()1437     public boolean isFakeDragging() {
1438         return mFakeDragging;
1439     }
1440 
onSecondaryPointerUp(MotionEvent ev)1441     private void onSecondaryPointerUp(MotionEvent ev) {
1442         final int pointerIndex = MotionEventCompat.getActionIndex(ev);
1443         final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
1444         if (pointerId == mActivePointerId) {
1445             // This was our active pointer going up. Choose a new
1446             // active pointer and adjust accordingly.
1447             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1448             mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);
1449             mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
1450             if (mVelocityTracker != null) {
1451                 mVelocityTracker.clear();
1452             }
1453         }
1454     }
1455 
endDrag()1456     private void endDrag() {
1457         mIsBeingDragged = false;
1458         mIsUnableToDrag = false;
1459 
1460         if (mVelocityTracker != null) {
1461             mVelocityTracker.recycle();
1462             mVelocityTracker = null;
1463         }
1464     }
1465 
setScrollingCacheEnabled(boolean enabled)1466     private void setScrollingCacheEnabled(boolean enabled) {
1467         if (mScrollingCacheEnabled != enabled) {
1468             mScrollingCacheEnabled = enabled;
1469             if (USE_CACHE) {
1470                 final int size = getChildCount();
1471                 for (int i = 0; i < size; ++i) {
1472                     final View child = getChildAt(i);
1473                     if (child.getVisibility() != GONE) {
1474                         child.setDrawingCacheEnabled(enabled);
1475                     }
1476                 }
1477             }
1478         }
1479     }
1480 
1481     /**
1482      * Tests scrollability within child views of v given a delta of dx.
1483      *
1484      * @param v View to test for horizontal scrollability
1485      * @param checkV Whether the view v passed should itself be checked for scrollability (true),
1486      *               or just its children (false).
1487      * @param dx Delta scrolled in pixels
1488      * @param x X coordinate of the active touch point
1489      * @param y Y coordinate of the active touch point
1490      * @return true if child views of v can be scrolled by delta of dx.
1491      */
canScroll(View v, boolean checkV, int dx, int x, int y)1492     protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
1493         if (v instanceof ViewGroup) {
1494             final ViewGroup group = (ViewGroup) v;
1495             final int scrollX = v.getScrollX();
1496             final int scrollY = v.getScrollY();
1497             final int count = group.getChildCount();
1498             // Count backwards - let topmost views consume scroll distance first.
1499             for (int i = count - 1; i >= 0; i--) {
1500                 // TODO: Add versioned support here for transformed views.
1501                 // This will not work for transformed views in Honeycomb+
1502                 final View child = group.getChildAt(i);
1503                 if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
1504                         y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
1505                         canScroll(child, true, dx, x + scrollX - child.getLeft(),
1506                                 y + scrollY - child.getTop())) {
1507                     return true;
1508                 }
1509             }
1510         }
1511 
1512         return checkV && ViewCompat.canScrollHorizontally(v, -dx);
1513     }
1514 
1515     @Override
dispatchKeyEvent(KeyEvent event)1516     public boolean dispatchKeyEvent(KeyEvent event) {
1517         // Let the focused view and/or our descendants get the key first
1518         return super.dispatchKeyEvent(event) || executeKeyEvent(event);
1519     }
1520 
1521     /**
1522      * You can call this function yourself to have the scroll view perform
1523      * scrolling from a key event, just as if the event had been dispatched to
1524      * it by the view hierarchy.
1525      *
1526      * @param event The key event to execute.
1527      * @return Return true if the event was handled, else false.
1528      */
executeKeyEvent(KeyEvent event)1529     public boolean executeKeyEvent(KeyEvent event) {
1530         boolean handled = false;
1531         if (event.getAction() == KeyEvent.ACTION_DOWN) {
1532             switch (event.getKeyCode()) {
1533                 case KeyEvent.KEYCODE_DPAD_LEFT:
1534                     handled = arrowScroll(FOCUS_LEFT);
1535                     break;
1536                 case KeyEvent.KEYCODE_DPAD_RIGHT:
1537                     handled = arrowScroll(FOCUS_RIGHT);
1538                     break;
1539                 case KeyEvent.KEYCODE_TAB:
1540                     if (KeyEventCompat.hasNoModifiers(event)) {
1541                         handled = arrowScroll(FOCUS_FORWARD);
1542                     } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
1543                         handled = arrowScroll(FOCUS_BACKWARD);
1544                     }
1545                     break;
1546             }
1547         }
1548         return handled;
1549     }
1550 
arrowScroll(int direction)1551     public boolean arrowScroll(int direction) {
1552         View currentFocused = findFocus();
1553         if (currentFocused == this) currentFocused = null;
1554 
1555         boolean handled = false;
1556 
1557         View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
1558                 direction);
1559         if (nextFocused != null && nextFocused != currentFocused) {
1560             if (direction == View.FOCUS_LEFT) {
1561                 // If there is nothing to the left, or this is causing us to
1562                 // jump to the right, then what we really want to do is page left.
1563                 if (currentFocused != null && nextFocused.getLeft() >= currentFocused.getLeft()) {
1564                     handled = pageLeft();
1565                 } else {
1566                     handled = nextFocused.requestFocus();
1567                 }
1568             } else if (direction == View.FOCUS_RIGHT) {
1569                 // If there is nothing to the right, or this is causing us to
1570                 // jump to the left, then what we really want to do is page right.
1571                 if (currentFocused != null && nextFocused.getLeft() <= currentFocused.getLeft()) {
1572                     handled = pageRight();
1573                 } else {
1574                     handled = nextFocused.requestFocus();
1575                 }
1576             }
1577         } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {
1578             // Trying to move left and nothing there; try to page.
1579             handled = pageLeft();
1580         } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {
1581             // Trying to move right and nothing there; try to page.
1582             handled = pageRight();
1583         }
1584         if (handled) {
1585             playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
1586         }
1587         return handled;
1588     }
1589 
pageLeft()1590     boolean pageLeft() {
1591         if (mCurItem > 0) {
1592             setCurrentItem(mCurItem-1, true);
1593             return true;
1594         }
1595         return false;
1596     }
1597 
pageRight()1598     boolean pageRight() {
1599         if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) {
1600             setCurrentItem(mCurItem+1, true);
1601             return true;
1602         }
1603         return false;
1604     }
1605 
1606     /**
1607      * We only want the current page that is being shown to be focusable.
1608      */
1609     @Override
addFocusables(ArrayList<View> views, int direction, int focusableMode)1610     public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1611         final int focusableCount = views.size();
1612 
1613         final int descendantFocusability = getDescendantFocusability();
1614 
1615         if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
1616             for (int i = 0; i < getChildCount(); i++) {
1617                 final View child = getChildAt(i);
1618                 if (child.getVisibility() == VISIBLE) {
1619                     ItemInfo ii = infoForChild(child);
1620                     if (ii != null && ii.position == mCurItem) {
1621                         child.addFocusables(views, direction, focusableMode);
1622                     }
1623                 }
1624             }
1625         }
1626 
1627         // we add ourselves (if focusable) in all cases except for when we are
1628         // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.  this is
1629         // to avoid the focus search finding layouts when a more precise search
1630         // among the focusable children would be more interesting.
1631         if (
1632             descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
1633                 // No focusable descendants
1634                 (focusableCount == views.size())) {
1635             // Note that we can't call the superclass here, because it will
1636             // add all views in.  So we need to do the same thing View does.
1637             if (!isFocusable()) {
1638                 return;
1639             }
1640             if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
1641                     isInTouchMode() && !isFocusableInTouchMode()) {
1642                 return;
1643             }
1644             if (views != null) {
1645                 views.add(this);
1646             }
1647         }
1648     }
1649 
1650     /**
1651      * We only want the current page that is being shown to be touchable.
1652      */
1653     @Override
addTouchables(ArrayList<View> views)1654     public void addTouchables(ArrayList<View> views) {
1655         // Note that we don't call super.addTouchables(), which means that
1656         // we don't call View.addTouchables().  This is okay because a ViewPager
1657         // is itself not touchable.
1658         for (int i = 0; i < getChildCount(); i++) {
1659             final View child = getChildAt(i);
1660             if (child.getVisibility() == VISIBLE) {
1661                 ItemInfo ii = infoForChild(child);
1662                 if (ii != null && ii.position == mCurItem) {
1663                     child.addTouchables(views);
1664                 }
1665             }
1666         }
1667     }
1668 
1669     /**
1670      * We only want the current page that is being shown to be focusable.
1671      */
1672     @Override
onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)1673     protected boolean onRequestFocusInDescendants(int direction,
1674             Rect previouslyFocusedRect) {
1675         int index;
1676         int increment;
1677         int end;
1678         int count = getChildCount();
1679         if ((direction & FOCUS_FORWARD) != 0) {
1680             index = 0;
1681             increment = 1;
1682             end = count;
1683         } else {
1684             index = count - 1;
1685             increment = -1;
1686             end = -1;
1687         }
1688         for (int i = index; i != end; i += increment) {
1689             View child = getChildAt(i);
1690             if (child.getVisibility() == VISIBLE) {
1691                 ItemInfo ii = infoForChild(child);
1692                 if (ii != null && ii.position == mCurItem) {
1693                     if (child.requestFocus(direction, previouslyFocusedRect)) {
1694                         return true;
1695                     }
1696                 }
1697             }
1698         }
1699         return false;
1700     }
1701 
1702     @Override
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)1703     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
1704         // ViewPagers should only report accessibility info for the current page,
1705         // otherwise things get very confusing.
1706 
1707         // TODO: Should this note something about the paging container?
1708 
1709         final int childCount = getChildCount();
1710         for (int i = 0; i < childCount; i++) {
1711             final View child = getChildAt(i);
1712             if (child.getVisibility() == VISIBLE) {
1713                 final ItemInfo ii = infoForChild(child);
1714                 if (ii != null && ii.position == mCurItem &&
1715                         child.dispatchPopulateAccessibilityEvent(event)) {
1716                     return true;
1717                 }
1718             }
1719         }
1720 
1721         return false;
1722     }
1723 
1724     private class DataSetObserver implements PagerAdapter.DataSetObserver {
1725         @Override
onDataSetChanged()1726         public void onDataSetChanged() {
1727             dataSetChanged();
1728         }
1729     }
1730 }
1731