Added Android code
[wl-app.git] / Android / folioreader / src / main / java / com / folioreader / view / DirectionalViewpager.java
1 package com.folioreader.view;\r
2 \r
3 /**\r
4  * Created by mobisys on 10/10/2016.\r
5  */\r
6 \r
7 \r
8 import android.content.Context;\r
9 import android.content.res.Resources;\r
10 import android.content.res.TypedArray;\r
11 import android.database.DataSetObserver;\r
12 import android.graphics.Canvas;\r
13 import android.graphics.Rect;\r
14 import android.graphics.drawable.Drawable;\r
15 import android.os.Build;\r
16 import android.os.Bundle;\r
17 import android.os.Parcel;\r
18 import android.os.Parcelable;\r
19 import android.os.SystemClock;\r
20 import android.support.annotation.CallSuper;\r
21 import android.support.annotation.DrawableRes;\r
22 import android.support.v4.os.ParcelableCompat;\r
23 import android.support.v4.os.ParcelableCompatCreatorCallbacks;\r
24 import android.support.v4.view.AccessibilityDelegateCompat;\r
25 import android.support.v4.view.MotionEventCompat;\r
26 import android.support.v4.view.PagerAdapter;\r
27 import android.support.v4.view.VelocityTrackerCompat;\r
28 import android.support.v4.view.ViewCompat;\r
29 import android.support.v4.view.ViewConfigurationCompat;\r
30 import android.support.v4.view.WindowInsetsCompat;\r
31 import android.support.v4.view.accessibility.AccessibilityEventCompat;\r
32 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;\r
33 import android.support.v4.view.accessibility.AccessibilityRecordCompat;\r
34 import android.support.v4.widget.EdgeEffectCompat;\r
35 import android.util.AttributeSet;\r
36 import android.util.Log;\r
37 import android.view.FocusFinder;\r
38 import android.view.Gravity;\r
39 import android.view.KeyEvent;\r
40 import android.view.MotionEvent;\r
41 import android.view.SoundEffectConstants;\r
42 import android.view.VelocityTracker;\r
43 import android.view.View;\r
44 import android.view.ViewConfiguration;\r
45 import android.view.ViewGroup;\r
46 import android.view.ViewParent;\r
47 import android.view.accessibility.AccessibilityEvent;\r
48 import android.view.animation.Interpolator;\r
49 import android.widget.Scroller;\r
50 \r
51 import com.folioreader.R;\r
52 \r
53 import java.lang.reflect.Method;\r
54 import java.util.ArrayList;\r
55 import java.util.Collections;\r
56 import java.util.Comparator;\r
57 import java.util.List;\r
58 \r
59 public class DirectionalViewpager extends ViewGroup {\r
60     private static final String TAG = "ViewPager";\r
61     private static final boolean DEBUG = false;\r
62 \r
63     private static final boolean USE_CACHE = false;\r
64 \r
65     private static final int DEFAULT_OFFSCREEN_PAGES = 1;\r
66     private static final int MAX_SETTLE_DURATION = 600; // ms\r
67     private static final int MIN_DISTANCE_FOR_FLING = 25; // dips\r
68 \r
69     private static final int DEFAULT_GUTTER_SIZE = 16; // dips\r
70 \r
71     private static final int MIN_FLING_VELOCITY = 400; // dips\r
72 \r
73     private static final int[] LAYOUT_ATTRS = new int[]{\r
74             android.R.attr.layout_gravity\r
75     };\r
76 \r
77     public static enum Direction {\r
78         HORIZONTAL,\r
79         VERTICAL,\r
80     }\r
81 \r
82     /**\r
83      * Used to track what the expected number of items in the adapter should be.\r
84      * If the app changes this when we don't expect it, we'll throw a big obnoxious exception.\r
85      */\r
86     private int mExpectedAdapterCount;\r
87     public String mDirection = Direction.VERTICAL.name();\r
88 \r
89     static class ItemInfo {\r
90         Object object;\r
91         int position;\r
92         boolean scrolling;\r
93         float widthFactor;\r
94         float heightFactor;\r
95         float offset;\r
96     }\r
97 \r
98     private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>() {\r
99         @Override\r
100         public int compare(ItemInfo lhs, ItemInfo rhs) {\r
101             return lhs.position - rhs.position;\r
102         }\r
103     };\r
104 \r
105     private static final Interpolator sInterpolator = new Interpolator() {\r
106         public float getInterpolation(float t) {\r
107             t -= 1.0f;\r
108             return t * t * t * t * t + 1.0f;\r
109         }\r
110     };\r
111 \r
112     private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();\r
113     private final ItemInfo mTempItem = new ItemInfo();\r
114 \r
115     private final Rect mTempRect = new Rect();\r
116 \r
117     private PagerAdapter mAdapter;\r
118     private int mCurItem;   // Index of currently displayed page.\r
119     private int mRestoredCurItem = -1;\r
120     private Parcelable mRestoredAdapterState = null;\r
121     private ClassLoader mRestoredClassLoader = null;\r
122 \r
123     private Scroller mScroller;\r
124     private boolean mIsScrollStarted;\r
125 \r
126     private PagerObserver mObserver;\r
127 \r
128     private int mPageMargin;\r
129     private Drawable mMarginDrawable;\r
130     private int mTopPageBounds;\r
131     private int mBottomPageBounds;\r
132     private int mLeftPageBounds;\r
133     private int mRightPageBounds;\r
134 \r
135     // Offsets of the first and last items, if known.\r
136     // Set during population, used to determine if we are at the beginning\r
137     // or end of the pager data set during touch scrolling.\r
138     private float mFirstOffset = -Float.MAX_VALUE;\r
139     private float mLastOffset = Float.MAX_VALUE;\r
140 \r
141     private int mChildWidthMeasureSpec;\r
142     private int mChildHeightMeasureSpec;\r
143     private boolean mInLayout;\r
144 \r
145     private boolean mScrollingCacheEnabled;\r
146 \r
147     private boolean mPopulatePending;\r
148     private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;\r
149 \r
150     private boolean mIsBeingDragged;\r
151     private boolean mIsUnableToDrag;\r
152     private boolean mIgnoreGutter;\r
153     private int mDefaultGutterSize;\r
154     private int mGutterSize;\r
155     private int mTouchSlop;\r
156     /**\r
157      * Position of the last motion event.\r
158      */\r
159     private float mLastMotionX;\r
160     private float mLastMotionY;\r
161     private float mInitialMotionX;\r
162     private float mInitialMotionY;\r
163     /**\r
164      * ID of the active pointer. This is used to retain consistency during\r
165      * drags/flings if multiple pointers are used.\r
166      */\r
167     private int mActivePointerId = INVALID_POINTER;\r
168     /**\r
169      * Sentinel value for no current active pointer.\r
170      * Used by {@link #mActivePointerId}.\r
171      */\r
172     private static final int INVALID_POINTER = -1;\r
173 \r
174     /**\r
175      * Determines speed during touch scrolling\r
176      */\r
177     private VelocityTracker mVelocityTracker;\r
178     private int mMinimumVelocity;\r
179     private int mMaximumVelocity;\r
180     private int mFlingDistance;\r
181     private int mCloseEnough;\r
182 \r
183     // If the pager is at least this close to its final position, complete the scroll\r
184     // on touch down and let the user interact with the content inside instead of\r
185     // "catching" the flinging pager.\r
186     private static final int CLOSE_ENOUGH = 2; // dp\r
187 \r
188     private boolean mFakeDragging;\r
189     private long mFakeDragBeginTime;\r
190 \r
191     private EdgeEffectCompat mLeftEdge;\r
192     private EdgeEffectCompat mRightEdge;\r
193     private EdgeEffectCompat mTopEdge;\r
194     private EdgeEffectCompat mBottomEdge;\r
195 \r
196     private boolean mFirstLayout = true;\r
197     private boolean mNeedCalculatePageOffsets = false;\r
198     private boolean mCalledSuper;\r
199     private int mDecorChildCount;\r
200 \r
201     private List<OnPageChangeListener> mOnPageChangeListeners;\r
202     private OnPageChangeListener mOnPageChangeListener;\r
203     private OnPageChangeListener mInternalPageChangeListener;\r
204     private OnAdapterChangeListener mAdapterChangeListener;\r
205     private PageTransformer mPageTransformer;\r
206     private Method mSetChildrenDrawingOrderEnabled;\r
207 \r
208     private static final int DRAW_ORDER_DEFAULT = 0;\r
209     private static final int DRAW_ORDER_FORWARD = 1;\r
210     private static final int DRAW_ORDER_REVERSE = 2;\r
211     private int mDrawingOrder;\r
212     private ArrayList<View> mDrawingOrderedChildren;\r
213     private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator();\r
214 \r
215     /**\r
216      * Indicates that the pager is in an idle, settled state. The current page\r
217      * is fully in view and no animation is in progress.\r
218      */\r
219     public static final int SCROLL_STATE_IDLE = 0;\r
220 \r
221     /**\r
222      * Indicates that the pager is currently being dragged by the user.\r
223      */\r
224     public static final int SCROLL_STATE_DRAGGING = 1;\r
225 \r
226     /**\r
227      * Indicates that the pager is in the process of settling to a final position.\r
228      */\r
229     public static final int SCROLL_STATE_SETTLING = 2;\r
230 \r
231     private final Runnable mEndScrollRunnable = new Runnable() {\r
232         public void run() {\r
233             setScrollState(SCROLL_STATE_IDLE);\r
234             populate();\r
235         }\r
236     };\r
237 \r
238     private int mScrollState = SCROLL_STATE_IDLE;\r
239 \r
240     /**\r
241      * Callback interface for responding to changing state of the selected page.\r
242      */\r
243     public interface OnPageChangeListener {\r
244 \r
245         /**\r
246          * This method will be invoked when the current\r
247          * page is scrolled, either as part\r
248          * of a programmatically initiated\r
249          * smooth scroll or a user initiated touch scroll.\r
250          *\r
251          * @param position             Position index of the first page currently being displayed.\r
252          *                             <p>\r
253          *                             Page position+1 will be visible if positionOffset is nonzero.\r
254          * @param positionOffset\r
255          * Value from [0, 1) indicating the offset from the page at position.\r
256          * @param positionOffsetPixels\r
257          * Value in pixels indicating the offset from position.\r
258          */\r
259         public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);\r
260 \r
261         /**\r
262          * This method will be invoked when a new page becomes selected. Animation is not\r
263          * necessarily complete.\r
264          *\r
265          * @param position Position index of the new selected page.\r
266          */\r
267         public void onPageSelected(int position);\r
268 \r
269         /**\r
270          * Called when the scroll state changes. Useful for discovering when the user\r
271          * begins dragging, when the pager is automatically settling to the current page,\r
272          * or when it is fully stopped/idle.\r
273          *\r
274          * @param state The new scroll state.\r
275          * @see ViewPager#SCROLL_STATE_IDLE\r
276          * @see ViewPager#SCROLL_STATE_DRAGGING\r
277          * @see ViewPager#SCROLL_STATE_SETTLING\r
278          */\r
279         public void onPageScrollStateChanged(int state);\r
280     }\r
281 \r
282     /**\r
283      * Simple implementation of the {@link OnPageChangeListener}\r
284      * interface with stub\r
285      * implementations of each method.\r
286      * Extend this if you do not intend to override\r
287      * every method of {@link OnPageChangeListener}.\r
288      */\r
289     public static class SimpleOnPageChangeListener implements OnPageChangeListener {\r
290         @Override\r
291         public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {\r
292             // This space for rent\r
293         }\r
294 \r
295         @Override\r
296         public void onPageSelected(int position) {\r
297             // This space for rent\r
298         }\r
299 \r
300         @Override\r
301         public void onPageScrollStateChanged(int state) {\r
302             // This space for rent\r
303         }\r
304     }\r
305 \r
306     /**\r
307      * A PageTransformer is invoked whenever a visible/attached page is scrolled.\r
308      * This offers an opportunity for the application to apply a custom transformation\r
309      * to the page views using animation properties.\r
310      * <p>\r
311      * <p>As property animation is only supported as of Android 3.0 and forward,\r
312      * setting a PageTransformer on a ViewPager on earlier platform versions will\r
313      * be ignored.</p>\r
314      */\r
315     public interface PageTransformer {\r
316         /**\r
317          * Apply a property transformation to the given page.\r
318          *\r
319          * @param page     Apply the transformation to this page\r
320          * @param position Position of page relative to the current front-and-center\r
321          *                 position of the pager. 0 is front and center. 1 is one full\r
322          *                 page position to the right, and -1 is one page position to the left.\r
323          */\r
324         public void transformPage(View page, float position);\r
325     }\r
326 \r
327     /**\r
328      * Used internally to monitor when adapters are switched.\r
329      */\r
330     interface OnAdapterChangeListener {\r
331         public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);\r
332     }\r
333 \r
334     /**\r
335      * Used internally to tag special types of child views that should be added as\r
336      * pager decorations by default.\r
337      */\r
338     interface Decor {\r
339     }\r
340 \r
341     public DirectionalViewpager(Context context) {\r
342         super(context);\r
343         initViewPager();\r
344     }\r
345 \r
346     public DirectionalViewpager(Context context, AttributeSet attrs) {\r
347         super(context, attrs);\r
348         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DirectionalViewpager);\r
349         if (a.getString(R.styleable.DirectionalViewpager_direction) != null) {\r
350             mDirection = a.getString(R.styleable.DirectionalViewpager_direction);\r
351         }\r
352         initViewPager();\r
353     }\r
354 \r
355     void initViewPager() {\r
356         setWillNotDraw(false);\r
357         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);\r
358         setFocusable(true);\r
359         final Context context = getContext();\r
360         mScroller = new Scroller(context, sInterpolator);\r
361         final ViewConfiguration configuration = ViewConfiguration.get(context);\r
362         final float density = context.getResources().getDisplayMetrics().density;\r
363 \r
364         mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);\r
365         mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);\r
366         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();\r
367         mLeftEdge = new EdgeEffectCompat(context);\r
368         mRightEdge = new EdgeEffectCompat(context);\r
369         mTopEdge = new EdgeEffectCompat(context);\r
370         mBottomEdge = new EdgeEffectCompat(context);\r
371 \r
372         mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);\r
373         mCloseEnough = (int) (CLOSE_ENOUGH * density);\r
374         mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);\r
375 \r
376         ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate());\r
377 \r
378         if (ViewCompat.getImportantForAccessibility(this)\r
379                 == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {\r
380             ViewCompat.setImportantForAccessibility(this,\r
381                     ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);\r
382         }\r
383 \r
384         ViewCompat.setOnApplyWindowInsetsListener(this,\r
385                 new android.support\r
386                         .v4.view.OnApplyWindowInsetsListener() {\r
387                     private final Rect mTempRect = new Rect();\r
388 \r
389                     @Override\r
390                     public WindowInsetsCompat\r
391                             onApplyWindowInsets(final View v,\r
392                                         final WindowInsetsCompat originalInsets) {\r
393                         // First let the ViewPager itself try and consume them...\r
394                         final WindowInsetsCompat applied =\r
395                                     ViewCompat.onApplyWindowInsets(v, originalInsets);\r
396                         if (applied.isConsumed()) {\r
397                             // If the ViewPager consumed all insets, return now\r
398                             return applied;\r
399                         }\r
400 \r
401                         // Now we'll manually dispatch the insets to our children. Since ViewPager\r
402                         // children are always full-height, we do not want to use the standard\r
403                         // ViewGroup dispatchApplyWindowInsets since if child 0 consumes them,\r
404                         // the rest of the children will not receive any insets. To workaround this\r
405                         // we manually dispatch the applied insets, not allowing children to\r
406                         // consume them from each other. We do however keep track of any insets\r
407                         // which are consumed, returning the union of our children's consumption\r
408                         final Rect res = mTempRect;\r
409                         res.left = applied.getSystemWindowInsetLeft();\r
410                         res.top = applied.getSystemWindowInsetTop();\r
411                         res.right = applied.getSystemWindowInsetRight();\r
412                         res.bottom = applied.getSystemWindowInsetBottom();\r
413 \r
414                         for (int i = 0, count = getChildCount(); i < count; i++) {\r
415                             final WindowInsetsCompat childInsets = ViewCompat\r
416                                     .dispatchApplyWindowInsets(getChildAt(i), applied);\r
417                             // Now keep track of any consumed by tracking each dimension's min\r
418                             // value\r
419                             res.left\r
420                                     = Math.min(childInsets.getSystemWindowInsetLeft(),\r
421                                     res.left);\r
422                             res.top = Math.min(childInsets.getSystemWindowInsetTop(),\r
423                                     res.top);\r
424                             res.right = Math.min(childInsets.getSystemWindowInsetRight(),\r
425                                     res.right);\r
426                             res.bottom = Math.min(childInsets.getSystemWindowInsetBottom(),\r
427                                     res.bottom);\r
428                         }\r
429 \r
430                         // Now return a new WindowInsets, using the consumed window insets\r
431                         return applied.replaceSystemWindowInsets(\r
432                                 res.left, res.top, res.right, res.bottom);\r
433                     }\r
434                 });\r
435     }\r
436 \r
437     @Override\r
438     protected void onDetachedFromWindow() {\r
439         removeCallbacks(mEndScrollRunnable);\r
440         // To be on the safe side, abort the scroller\r
441         if ((mScroller != null) && !mScroller.isFinished()) {\r
442             mScroller.abortAnimation();\r
443         }\r
444         super.onDetachedFromWindow();\r
445     }\r
446 \r
447     private void setScrollState(int newState) {\r
448         if (mScrollState == newState) {\r
449             return;\r
450         }\r
451 \r
452         mScrollState = newState;\r
453         if (mPageTransformer != null) {\r
454             // PageTransformers can do complex things that benefit from hardware layers.\r
455             enableLayers(newState != SCROLL_STATE_IDLE);\r
456         }\r
457         dispatchOnScrollStateChanged(newState);\r
458     }\r
459 \r
460     /**\r
461      * Set a PagerAdapter that will supply views for this pager as needed.\r
462      *\r
463      * @param adapter Adapter to use\r
464      */\r
465     public void setAdapter(PagerAdapter adapter) {\r
466         if (mAdapter != null) {\r
467             mAdapter.unregisterDataSetObserver(mObserver);\r
468             mAdapter.startUpdate(this);\r
469             for (int i = 0; i < mItems.size(); i++) {\r
470                 final ItemInfo ii = mItems.get(i);\r
471                 mAdapter.destroyItem(this, ii.position, ii.object);\r
472             }\r
473             mAdapter.finishUpdate(this);\r
474             mItems.clear();\r
475             removeNonDecorViews();\r
476             mCurItem = 0;\r
477             scrollTo(0, 0);\r
478         }\r
479 \r
480         final PagerAdapter oldAdapter = mAdapter;\r
481         mAdapter = adapter;\r
482         mExpectedAdapterCount = 0;\r
483 \r
484         if (mAdapter != null) {\r
485             if (mObserver == null) {\r
486                 mObserver = new PagerObserver();\r
487             }\r
488             mAdapter.registerDataSetObserver(mObserver);\r
489             mPopulatePending = false;\r
490             final boolean wasFirstLayout = mFirstLayout;\r
491             mFirstLayout = true;\r
492             mExpectedAdapterCount = mAdapter.getCount();\r
493             if (mRestoredCurItem >= 0) {\r
494                 mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);\r
495                 setCurrentItemInternal(mRestoredCurItem, false, true);\r
496                 mRestoredCurItem = -1;\r
497                 mRestoredAdapterState = null;\r
498                 mRestoredClassLoader = null;\r
499             } else if (!wasFirstLayout) {\r
500                 populate();\r
501             } else {\r
502                 requestLayout();\r
503             }\r
504         }\r
505 \r
506         if (mAdapterChangeListener != null && oldAdapter != adapter) {\r
507             mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);\r
508         }\r
509     }\r
510 \r
511     private void removeNonDecorViews() {\r
512         for (int i = 0; i < getChildCount(); i++) {\r
513             final View child = getChildAt(i);\r
514             final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
515             if (!lp.isDecor) {\r
516                 removeViewAt(i);\r
517                 i--;\r
518             }\r
519         }\r
520     }\r
521 \r
522     /**\r
523      * Retrieve the current adapter supplying pages.\r
524      *\r
525      * @return The currently registered PagerAdapter\r
526      */\r
527     public PagerAdapter getAdapter() {\r
528         return mAdapter;\r
529     }\r
530 \r
531     void setOnAdapterChangeListener(OnAdapterChangeListener listener) {\r
532         mAdapterChangeListener = listener;\r
533     }\r
534 \r
535     private int getClientWidth() {\r
536         return getMeasuredWidth() - getPaddingLeft() - getPaddingRight();\r
537     }\r
538 \r
539     private int getClientHeight() {\r
540         return getMeasuredHeight() - getPaddingTop() - getPaddingBottom();\r
541     }\r
542 \r
543     /**\r
544      * Set the currently selected page. If the ViewPager has already been through its first\r
545      * layout with its current adapter there will be a smooth animated transition between\r
546      * the current item and the specified item.\r
547      *\r
548      * @param item Item index to select\r
549      */\r
550     public void setCurrentItem(int item) {\r
551         mPopulatePending = false;\r
552         setCurrentItemInternal(item, !mFirstLayout, false);\r
553     }\r
554 \r
555     /**\r
556      * Set the currently selected page.\r
557      *\r
558      * @param item         Item index to select\r
559      * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately\r
560      */\r
561     public void setCurrentItem(int item, boolean smoothScroll) {\r
562         mPopulatePending = false;\r
563         setCurrentItemInternal(item, smoothScroll, false);\r
564     }\r
565 \r
566     public int getCurrentItem() {\r
567         return mCurItem;\r
568     }\r
569 \r
570     public int getExpectedAdapterCount() {\r
571         return mExpectedAdapterCount;\r
572     }\r
573 \r
574     void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {\r
575         setCurrentItemInternal(item, smoothScroll, always, 0);\r
576     }\r
577 \r
578     void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {\r
579         if (mAdapter == null || mAdapter.getCount() <= 0) {\r
580             setScrollingCacheEnabled(false);\r
581             return;\r
582         }\r
583         if (!always && mCurItem == item && mItems.size() != 0) {\r
584             setScrollingCacheEnabled(false);\r
585             return;\r
586         }\r
587 \r
588         if (item < 0) {\r
589             item = 0;\r
590         } else if (item >= mAdapter.getCount()) {\r
591             item = mAdapter.getCount() - 1;\r
592         }\r
593         final int pageLimit = mOffscreenPageLimit;\r
594         if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {\r
595             // We are doing a jump by more than one page.  To avoid\r
596             // glitches, we want to keep all current pages in the view\r
597             // until the scroll ends.\r
598             for (int i = 0; i < mItems.size(); i++) {\r
599                 mItems.get(i).scrolling = true;\r
600             }\r
601         }\r
602         final boolean dispatchSelected = mCurItem != item;\r
603 \r
604         if (mFirstLayout) {\r
605             // We don't have any idea how big we are yet and shouldn't have any pages either.\r
606             // Just set things up and let the pending layout handle things.\r
607             mCurItem = item;\r
608             if (dispatchSelected) {\r
609                 dispatchOnPageSelected(item);\r
610             }\r
611             requestLayout();\r
612         } else {\r
613             populate(item);\r
614             scrollToItem(item, smoothScroll, velocity, dispatchSelected);\r
615         }\r
616     }\r
617 \r
618     private void scrollToItem(int item, boolean smoothScroll, int velocity,\r
619                               boolean dispatchSelected) {\r
620         final ItemInfo curInfo = infoForPosition(item);\r
621         int destX = 0;\r
622         int destY = 0;\r
623         if (isHorizontal()) {\r
624             if (curInfo != null) {\r
625                 final int width = getClientWidth();\r
626                 destX = (int) (width * Math.max(mFirstOffset,\r
627                         Math.min(curInfo.offset, mLastOffset)));\r
628             }\r
629             if (smoothScroll) {\r
630                 smoothScrollTo(destX, 0, velocity);\r
631                 if (dispatchSelected) {\r
632                     dispatchOnPageSelected(item);\r
633                 }\r
634             } else {\r
635                 if (dispatchSelected) {\r
636                     dispatchOnPageSelected(item);\r
637                 }\r
638                 completeScroll(false);\r
639                 scrollTo(destX, 0);\r
640                 pageScrolled(destX, 0);\r
641             }\r
642         } else {\r
643             if (curInfo != null) {\r
644                 final int height = getClientHeight();\r
645                 destY = (int) (height * Math.max(mFirstOffset,\r
646                         Math.min(curInfo.offset, mLastOffset)));\r
647             }\r
648             if (smoothScroll) {\r
649                 smoothScrollTo(0, destY, velocity);\r
650                 if (dispatchSelected && mOnPageChangeListener != null) {\r
651                     mOnPageChangeListener.onPageSelected(item);\r
652                 }\r
653                 if (dispatchSelected && mInternalPageChangeListener != null) {\r
654                     mInternalPageChangeListener.onPageSelected(item);\r
655                 }\r
656             } else {\r
657                 if (dispatchSelected && mOnPageChangeListener != null) {\r
658                     mOnPageChangeListener.onPageSelected(item);\r
659                 }\r
660                 if (dispatchSelected && mInternalPageChangeListener != null) {\r
661                     mInternalPageChangeListener.onPageSelected(item);\r
662                 }\r
663                 completeScroll(false);\r
664                 scrollTo(0, destY);\r
665                 pageScrolled(0, destY);\r
666             }\r
667         }\r
668     }\r
669 \r
670     /**\r
671      * Set a listener that will be invoked whenever the page changes or is incrementally\r
672      * scrolled. See {@link OnPageChangeListener}.\r
673      *\r
674      * @param listener Listener to set\r
675      * @deprecated Use {@link #addOnPageChangeListener(OnPageChangeListener)}\r
676      * and {@link #removeOnPageChangeListener(OnPageChangeListener)} instead.\r
677      */\r
678     @Deprecated\r
679     public void setOnPageChangeListener(OnPageChangeListener listener) {\r
680         mOnPageChangeListener = listener;\r
681     }\r
682 \r
683     /**\r
684      * Add a listener that will be invoked whenever the page changes or is incrementally\r
685      * scrolled. See {@link OnPageChangeListener}.\r
686      * <p>\r
687      * <p>Components that add a listener should take care to remove it when finished.\r
688      * Other components that take ownership of a view may call {@link #clearOnPageChangeListeners()}\r
689      * to remove all attached listeners.</p>\r
690      *\r
691      * @param listener listener to add\r
692      */\r
693     public void addOnPageChangeListener(OnPageChangeListener listener) {\r
694         if (mOnPageChangeListeners == null) {\r
695             mOnPageChangeListeners = new ArrayList<>();\r
696         }\r
697         mOnPageChangeListeners.add(listener);\r
698     }\r
699 \r
700     /**\r
701      * Remove a listener that was previously added via\r
702      * {@link #addOnPageChangeListener(OnPageChangeListener)}.\r
703      *\r
704      * @param listener listener to remove\r
705      */\r
706     public void removeOnPageChangeListener(OnPageChangeListener listener) {\r
707         if (mOnPageChangeListeners != null) {\r
708             mOnPageChangeListeners.remove(listener);\r
709         }\r
710     }\r
711 \r
712     /**\r
713      * Remove all listeners that are notified of any changes in scroll state or position.\r
714      */\r
715     public void clearOnPageChangeListeners() {\r
716         if (mOnPageChangeListeners != null) {\r
717             mOnPageChangeListeners.clear();\r
718         }\r
719     }\r
720 \r
721     /**\r
722      * Set a {@link PageTransformer} that will be called for each attached page whenever\r
723      * the scroll position is changed. This allows the application to apply custom property\r
724      * transformations to each page, overriding the default sliding look and feel.\r
725      * <p>\r
726      * <p><em>Note:</em> Prior to Android 3.0 the property animation APIs did not exist.\r
727      * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.</p>\r
728      *\r
729      * @param reverseDrawingOrder true if the supplied PageTransformer requires page views\r
730      *                            to be drawn from last to first instead of first to last.\r
731      * @param transformer         PageTransformer that will modify each page's animation properties\r
732      */\r
733     public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) {\r
734         if (Build.VERSION.SDK_INT >= 11) {\r
735             final boolean hasTransformer = transformer != null;\r
736             final boolean needsPopulate = hasTransformer != (mPageTransformer != null);\r
737             mPageTransformer = transformer;\r
738             setChildrenDrawingOrderEnabledCompat(hasTransformer);\r
739             if (hasTransformer) {\r
740                 mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD;\r
741             } else {\r
742                 mDrawingOrder = DRAW_ORDER_DEFAULT;\r
743             }\r
744             if (needsPopulate) populate();\r
745         }\r
746     }\r
747 \r
748     void setChildrenDrawingOrderEnabledCompat(boolean enable) {\r
749         if (Build.VERSION.SDK_INT >= 7) {\r
750             if (mSetChildrenDrawingOrderEnabled == null) {\r
751                 try {\r
752                     mSetChildrenDrawingOrderEnabled = ViewGroup.class.getDeclaredMethod(\r
753                             "setChildrenDrawingOrderEnabled", new Class[]{Boolean.TYPE});\r
754                 } catch (NoSuchMethodException e) {\r
755                     Log.e(TAG, "Can't find setChildrenDrawingOrderEnabled", e);\r
756                 }\r
757             }\r
758             try {\r
759                 mSetChildrenDrawingOrderEnabled\r
760                         .invoke(this, enable);\r
761             } catch (Exception e) {\r
762                 Log.e(TAG, "Error changing children drawing order", e);\r
763             }\r
764         }\r
765     }\r
766 \r
767     @Override\r
768     protected int getChildDrawingOrder(int childCount, int i) {\r
769         final int index = mDrawingOrder\r
770                 == DRAW_ORDER_REVERSE ? childCount - 1 - i : i;\r
771         final int result\r
772                 = ((LayoutParams) mDrawingOrderedChildren.get(index).getLayoutParams()).childIndex;\r
773         return result;\r
774     }\r
775 \r
776     /**\r
777      * Set a separate OnPageChangeListener for internal use by the support library.\r
778      *\r
779      * @param listener Listener to set\r
780      * @return The old listener that was set, if any.\r
781      */\r
782     OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) {\r
783         OnPageChangeListener oldListener = mInternalPageChangeListener;\r
784         mInternalPageChangeListener = listener;\r
785         return oldListener;\r
786     }\r
787 \r
788     /**\r
789      * Returns the number of pages that will be retained to either side of the\r
790      * current page in the view hierarchy in an idle state. Defaults to 1.\r
791      *\r
792      * @return How many pages will be kept offscreen on either side\r
793      * @see #setOffscreenPageLimit(int)\r
794      */\r
795     public int getOffscreenPageLimit() {\r
796         return mOffscreenPageLimit;\r
797     }\r
798 \r
799     /**\r
800      * Set the number of pages that should be\r
801      * retained to either side of the\r
802      * current page in the view hierarchy\r
803      * in an idle state. Pages beyond this\r
804      * limit will be recreated from the adapter when needed.\r
805      * <p>\r
806      * <p>This is offered as an optimization.\r
807      * If you know in advance the number\r
808      * of pages you will need to support or\r
809      * have lazy-loading mechanisms in place\r
810      * on your pages, tweaking this setting\r
811      * can have benefits in perceived smoothness\r
812      * of paging animations and interaction.\r
813      * If you have a small number of pages (3-4)\r
814      * that you can keep active all at once,\r
815      * less time will be spent in layout for\r
816      * newly created view subtrees as the\r
817      * user pages back and forth.</p>\r
818      * <p>\r
819      * <p>You should keep this limit low,\r
820      * especially if your pages have complex layouts.\r
821      * This setting defaults to 1.</p>\r
822      *\r
823      * @param limit How many pages will be kept offscreen in an idle state.\r
824      */\r
825     public void setOffscreenPageLimit(int limit) {\r
826         if (limit < DEFAULT_OFFSCREEN_PAGES) {\r
827             Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +\r
828                     DEFAULT_OFFSCREEN_PAGES);\r
829             limit = DEFAULT_OFFSCREEN_PAGES;\r
830         }\r
831         if (limit != mOffscreenPageLimit) {\r
832             mOffscreenPageLimit = limit;\r
833             populate();\r
834         }\r
835     }\r
836 \r
837     /**\r
838      * Set the margin between pages.\r
839      *\r
840      * @param marginPixels Distance between adjacent pages in pixels\r
841      * @see #getPageMargin()\r
842      * @see #setPageMarginDrawable(Drawable)\r
843      * @see #setPageMarginDrawable(int)\r
844      */\r
845     public void setPageMargin(int marginPixels) {\r
846         final int oldMargin = mPageMargin;\r
847         mPageMargin = marginPixels;\r
848 \r
849         if (isHorizontal()) {\r
850             int width = getWidth();\r
851             recomputeScrollPosition(width, width, marginPixels, oldMargin, 0, 0);\r
852         } else {\r
853             int height = getHeight();\r
854             recomputeScrollPosition(0, 0, marginPixels, oldMargin, height, height);\r
855         }\r
856 \r
857         requestLayout();\r
858     }\r
859 \r
860     /**\r
861      * Return the margin between pages.\r
862      *\r
863      * @return The size of the margin in pixels\r
864      */\r
865     public int getPageMargin() {\r
866         return mPageMargin;\r
867     }\r
868 \r
869     /**\r
870      * Set a drawable that will be used to fill the margin between pages.\r
871      *\r
872      * @param d Drawable to display between pages\r
873      */\r
874     public void setPageMarginDrawable(Drawable d) {\r
875         mMarginDrawable = d;\r
876         if (d != null) refreshDrawableState();\r
877         setWillNotDraw(d == null);\r
878         invalidate();\r
879     }\r
880 \r
881     /**\r
882      * Set a drawable that will be used to fill the margin between pages.\r
883      *\r
884      * @param resId Resource ID of a drawable to display between pages\r
885      */\r
886     public void setPageMarginDrawable(@DrawableRes int resId) {\r
887         setPageMarginDrawable(getContext().getResources().getDrawable(resId));\r
888     }\r
889 \r
890     @Override\r
891     protected boolean verifyDrawable(Drawable who) {\r
892         return super.verifyDrawable(who) || who == mMarginDrawable;\r
893     }\r
894 \r
895     @Override\r
896     protected void drawableStateChanged() {\r
897         super.drawableStateChanged();\r
898         final Drawable d = mMarginDrawable;\r
899         if (d != null && d.isStateful()) {\r
900             d.setState(getDrawableState());\r
901         }\r
902     }\r
903 \r
904     // We want the duration of the page snap animation to be influenced by the distance that\r
905     // the screen has to travel, however, we don't want this duration to be effected in a\r
906     // purely linear fashion. Instead, we use this method to moderate the effect that the distance\r
907     // of travel has on the overall snap duration.\r
908     float distanceInfluenceForSnapDuration(float f) {\r
909         f -= 0.5f; // center the values about 0.\r
910         f *= 0.3f * Math.PI / 2.0f;\r
911         return (float) Math.sin(f);\r
912     }\r
913 \r
914     /**\r
915      * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.\r
916      *\r
917      * @param x the number of pixels to scroll by on the X axis\r
918      * @param y the number of pixels to scroll by on the Y axis\r
919      */\r
920     void smoothScrollTo(int x, int y) {\r
921         smoothScrollTo(x, y, 0);\r
922     }\r
923 \r
924     /**\r
925      * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.\r
926      *\r
927      * @param x        the number of pixels to scroll by on the X axis\r
928      * @param y        the number of pixels to scroll by on the Y axis\r
929      * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)\r
930      */\r
931     void smoothScrollTo(int x, int y, int velocity) {\r
932         if (getChildCount() == 0) {\r
933             // Nothing to do.\r
934             setScrollingCacheEnabled(false);\r
935             return;\r
936         }\r
937 \r
938         int sx;\r
939         if (isHorizontal()) {\r
940             boolean wasScrolling = (mScroller != null) && !mScroller.isFinished();\r
941             if (wasScrolling) {\r
942                 // We're in the middle of a previously initiated scrolling. Check to see\r
943                 // whether that scrolling has actually started (if we always call getStartX\r
944                 // we can get a stale value from the scroller if it hadn't yet had its first\r
945                 // computeScrollOffset call) to decide what is the current scrolling position.\r
946                 sx = mIsScrollStarted ? mScroller.getCurrX() : mScroller.getStartX();\r
947                 // And abort the current scrolling.\r
948                 mScroller.abortAnimation();\r
949                 setScrollingCacheEnabled(false);\r
950             } else {\r
951                 sx = getScrollX();\r
952             }\r
953         } else {\r
954             sx = getScrollX();\r
955         }\r
956         int sy = getScrollY();\r
957         int dx = x - sx;\r
958         int dy = y - sy;\r
959         if (dx == 0 && dy == 0) {\r
960             completeScroll(false);\r
961             populate();\r
962             setScrollState(SCROLL_STATE_IDLE);\r
963             return;\r
964         }\r
965 \r
966         setScrollingCacheEnabled(true);\r
967         setScrollState(SCROLL_STATE_SETTLING);\r
968         int duration = 0;\r
969         if (isHorizontal()) {\r
970             final int width = getClientWidth();\r
971             final int halfWidth = width / 2;\r
972             final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);\r
973             final float distance = halfWidth + halfWidth *\r
974                     distanceInfluenceForSnapDuration(distanceRatio);\r
975             velocity = Math.abs(velocity);\r
976             if (velocity > 0) {\r
977                 duration = 4 * Math.round(1000 * Math.abs(distance / velocity));\r
978             } else {\r
979                 final float pageWidth = width * mAdapter.getPageWidth(mCurItem);\r
980                 final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin);\r
981                 duration = (int) ((pageDelta + 1) * 100);\r
982             }\r
983         } else {\r
984             final int height = getClientHeight();\r
985             final int halfHeight = height / 2;\r
986             final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / height);\r
987             final float distance = halfHeight + halfHeight *\r
988                     distanceInfluenceForSnapDuration(distanceRatio);\r
989 \r
990             duration = 0;\r
991             velocity = Math.abs(velocity);\r
992             if (velocity > 0) {\r
993                 duration = 4 * Math.round(1000 * Math.abs(distance / velocity));\r
994             } else {\r
995                 final float pageHeight = height * mAdapter.getPageWidth(mCurItem);\r
996                 final float pageDelta = (float) Math.abs(dx) / (pageHeight + mPageMargin);\r
997                 duration = (int) ((pageDelta + 1) * 100);\r
998             }\r
999         }\r
1000         duration = Math.min(duration, MAX_SETTLE_DURATION);\r
1001 \r
1002         // Reset the "scroll started" flag. It will be flipped to true in all places\r
1003         // where we call computeScrollOffset().\r
1004         if (isHorizontal()) {\r
1005             mIsScrollStarted = false;\r
1006         }\r
1007         mScroller.startScroll(sx, sy, dx, dy, duration);\r
1008         ViewCompat.postInvalidateOnAnimation(this);\r
1009     }\r
1010 \r
1011     private boolean isHorizontal() {\r
1012         return mDirection.equalsIgnoreCase(Direction.HORIZONTAL.name());\r
1013     }\r
1014 \r
1015     ItemInfo addNewItem(int position, int index) {\r
1016         ItemInfo ii = new ItemInfo();\r
1017         ii.position = position;\r
1018         ii.object = mAdapter.instantiateItem(this, position);\r
1019         if (isHorizontal()) {\r
1020             ii.widthFactor = mAdapter.getPageWidth(position);\r
1021         } else {\r
1022             ii.heightFactor = mAdapter.getPageWidth(position);\r
1023         }\r
1024         if (index < 0 || index >= mItems.size()) {\r
1025             mItems.add(ii);\r
1026         } else {\r
1027             mItems.add(index, ii);\r
1028         }\r
1029         return ii;\r
1030     }\r
1031 \r
1032     void dataSetChanged() {\r
1033         // This method only gets called if our observer is attached, so mAdapter is non-null.\r
1034 \r
1035         final int adapterCount = mAdapter.getCount();\r
1036         mExpectedAdapterCount = adapterCount;\r
1037         boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&\r
1038                 mItems.size() < adapterCount;\r
1039         int newCurrItem = mCurItem;\r
1040 \r
1041         boolean isUpdating = false;\r
1042         for (int i = 0; i < mItems.size(); i++) {\r
1043             final ItemInfo ii = mItems.get(i);\r
1044             final int newPos = mAdapter.getItemPosition(ii.object);\r
1045 \r
1046             if (newPos == PagerAdapter.POSITION_UNCHANGED) {\r
1047                 continue;\r
1048             }\r
1049 \r
1050             if (newPos == PagerAdapter.POSITION_NONE) {\r
1051                 mItems.remove(i);\r
1052                 i--;\r
1053 \r
1054                 if (!isUpdating) {\r
1055                     mAdapter.startUpdate(this);\r
1056                     isUpdating = true;\r
1057                 }\r
1058 \r
1059                 mAdapter.destroyItem(this, ii.position, ii.object);\r
1060                 needPopulate = true;\r
1061 \r
1062                 if (mCurItem == ii.position) {\r
1063                     // Keep the current item in the valid range\r
1064                     newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));\r
1065                     needPopulate = true;\r
1066                 }\r
1067                 continue;\r
1068             }\r
1069 \r
1070             if (ii.position != newPos) {\r
1071                 if (ii.position == mCurItem) {\r
1072                     // Our current item changed position. Follow it.\r
1073                     newCurrItem = newPos;\r
1074                 }\r
1075 \r
1076                 ii.position = newPos;\r
1077                 needPopulate = true;\r
1078             }\r
1079         }\r
1080 \r
1081         if (isUpdating) {\r
1082             mAdapter.finishUpdate(this);\r
1083         }\r
1084 \r
1085         Collections.sort(mItems, COMPARATOR);\r
1086 \r
1087         if (needPopulate) {\r
1088             // Reset our known page widths; populate will recompute them.\r
1089             final int childCount = getChildCount();\r
1090             for (int i = 0; i < childCount; i++) {\r
1091                 final View child = getChildAt(i);\r
1092                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
1093                 if (!lp.isDecor) {\r
1094                     if (isHorizontal()) {\r
1095                         lp.widthFactor = 0.f;\r
1096                     } else {\r
1097                         lp.heightFactor = 0.f;\r
1098                     }\r
1099                 }\r
1100             }\r
1101 \r
1102             setCurrentItemInternal(newCurrItem, false, true);\r
1103             requestLayout();\r
1104         }\r
1105     }\r
1106 \r
1107     void populate() {\r
1108         populate(mCurItem);\r
1109     }\r
1110 \r
1111     void populate(int newCurrentItem) {\r
1112         ItemInfo oldCurInfo = null;\r
1113         int focusDirection = View.FOCUS_FORWARD;\r
1114         if (mCurItem != newCurrentItem) {\r
1115             focusDirection = mCurItem < newCurrentItem ? View.FOCUS_DOWN : View.FOCUS_UP;\r
1116             oldCurInfo = infoForPosition(mCurItem);\r
1117             mCurItem = newCurrentItem;\r
1118         }\r
1119 \r
1120         if (mAdapter == null) {\r
1121             sortChildDrawingOrder();\r
1122             return;\r
1123         }\r
1124 \r
1125         // Bail now if we are waiting to populate.  This is to hold off\r
1126         // on creating views from the time the user releases their finger to\r
1127         // fling to a new position until we have finished the scroll to\r
1128         // that position, avoiding glitches from happening at that point.\r
1129         if (mPopulatePending) {\r
1130             if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");\r
1131             sortChildDrawingOrder();\r
1132             return;\r
1133         }\r
1134 \r
1135         // Also, don't populate until we are attached to a window.  This is to\r
1136         // avoid trying to populate before we have restored our view hierarchy\r
1137         // state and conflicting with what is restored.\r
1138         if (getWindowToken() == null) {\r
1139             return;\r
1140         }\r
1141 \r
1142         mAdapter.startUpdate(this);\r
1143 \r
1144         final int pageLimit = mOffscreenPageLimit;\r
1145         final int startPos = Math.max(0, mCurItem - pageLimit);\r
1146         final int N = mAdapter.getCount();\r
1147         final int endPos = Math.min(N - 1, mCurItem + pageLimit);\r
1148 \r
1149         if (N != mExpectedAdapterCount) {\r
1150             String resName;\r
1151             try {\r
1152                 resName = getResources().getResourceName(getId());\r
1153             } catch (Resources.NotFoundException e) {\r
1154                 resName = Integer.toHexString(getId());\r
1155             }\r
1156             throw new IllegalStateException("The application's PagerAdapter changed the adapter's" +\r
1157                     " contents without calling PagerAdapter#notifyDataSetChanged!" +\r
1158                     " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N +\r
1159                     " Pager id: " + resName +\r
1160                     " Pager class: " + getClass() +\r
1161                     " Problematic adapter: " + mAdapter.getClass());\r
1162         }\r
1163 \r
1164         // Locate the currently focused item or add it if needed.\r
1165         int curIndex = -1;\r
1166         ItemInfo curItem = null;\r
1167         for (curIndex = 0; curIndex < mItems.size(); curIndex++) {\r
1168             final ItemInfo ii = mItems.get(curIndex);\r
1169             if (ii.position >= mCurItem) {\r
1170                 if (ii.position == mCurItem) curItem = ii;\r
1171                 break;\r
1172             }\r
1173         }\r
1174 \r
1175         if (curItem == null && N > 0) {\r
1176             curItem = addNewItem(mCurItem, curIndex);\r
1177         }\r
1178 \r
1179         // Fill 3x the available width or up to the number of offscreen\r
1180         // pages requested to either side, whichever is larger.\r
1181         // If we have no current item we have no work to do.\r
1182         if (curItem != null) {\r
1183             if (isHorizontal()) {\r
1184                 float extraWidthLeft = 0.f;\r
1185                 int itemIndex = curIndex - 1;\r
1186                 ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;\r
1187                 final int clientWidth = getClientWidth();\r
1188                 final float leftWidthNeeded = clientWidth <= 0 ? 0 :\r
1189                         2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;\r
1190                 for (int pos = mCurItem - 1; pos >= 0; pos--) {\r
1191                     if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {\r
1192                         if (ii == null) {\r
1193                             break;\r
1194                         }\r
1195                         if (pos == ii.position && !ii.scrolling) {\r
1196                             mItems.remove(itemIndex);\r
1197                             mAdapter.destroyItem(this, pos, ii.object);\r
1198                             if (DEBUG) {\r
1199                                 Log.i(TAG, logDestroyItem(pos, ((View) ii.object)));\r
1200                             }\r
1201                             itemIndex--;\r
1202                             curIndex--;\r
1203                             ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;\r
1204                         }\r
1205                     } else if (ii != null && pos == ii.position) {\r
1206                         extraWidthLeft += ii.widthFactor;\r
1207                         itemIndex--;\r
1208                         ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;\r
1209                     } else {\r
1210                         ii = addNewItem(pos, itemIndex + 1);\r
1211                         extraWidthLeft += ii.widthFactor;\r
1212                         curIndex++;\r
1213                         ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;\r
1214                     }\r
1215                 }\r
1216 \r
1217                 float extraWidthRight = curItem.widthFactor;\r
1218                 itemIndex = curIndex + 1;\r
1219                 if (extraWidthRight < 2.f) {\r
1220                     ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;\r
1221                     final float rightWidthNeeded = clientWidth <= 0 ? 0 :\r
1222                             (float) getPaddingRight() / (float) clientWidth + 2.f;\r
1223                     for (int pos = mCurItem + 1; pos < N; pos++) {\r
1224                         if (extraWidthRight >= rightWidthNeeded && pos > endPos) {\r
1225                             if (ii == null) {\r
1226                                 break;\r
1227                             }\r
1228                             if (pos == ii.position && !ii.scrolling) {\r
1229                                 mItems.remove(itemIndex);\r
1230                                 mAdapter.destroyItem(this, pos, ii.object);\r
1231                                 if (DEBUG) {\r
1232                                     Log.i(TAG, logDestroyItem(pos, ((View) ii.object)));\r
1233                                 }\r
1234                                 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;\r
1235                             }\r
1236                         } else if (ii != null && pos == ii.position) {\r
1237                             extraWidthRight += ii.widthFactor;\r
1238                             itemIndex++;\r
1239                             ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;\r
1240                         } else {\r
1241                             ii = addNewItem(pos, itemIndex);\r
1242                             itemIndex++;\r
1243                             extraWidthRight += ii.widthFactor;\r
1244                             ii = itemIndex < mItems.size()\r
1245                                     ? mItems.get(itemIndex) : null;\r
1246                         }\r
1247                     }\r
1248                 }\r
1249             } else {\r
1250                 float extraHeightTop = 0.f;\r
1251                 int itemIndex = curIndex - 1;\r
1252                 ItemInfo ii\r
1253                         = itemIndex >= 0 ? mItems.get(itemIndex) : null;\r
1254                 final int clientHeight = getClientHeight();\r
1255                 final float topHeightNeeded = clientHeight <= 0 ? 0 :\r
1256                         2.f - curItem.heightFactor\r
1257                                 + (float) getPaddingLeft() / (float) clientHeight;\r
1258                 for (int pos = mCurItem - 1; pos >= 0; pos--) {\r
1259                     if (extraHeightTop >= topHeightNeeded && pos < startPos) {\r
1260                         if (ii == null) {\r
1261                             break;\r
1262                         }\r
1263                         if (pos == ii.position && !ii.scrolling) {\r
1264                             mItems.remove(itemIndex);\r
1265                             mAdapter.destroyItem(this, pos, ii.object);\r
1266                             if (DEBUG) {\r
1267                                 Log.i(TAG, logDestroyItem(pos, ((View) ii.object)));\r
1268                             }\r
1269                             itemIndex--;\r
1270                             curIndex--;\r
1271                             ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;\r
1272                         }\r
1273                     } else if (ii != null && pos == ii.position) {\r
1274                         extraHeightTop += ii.heightFactor;\r
1275                         itemIndex--;\r
1276                         ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;\r
1277                     } else {\r
1278                         ii = addNewItem(pos, itemIndex + 1);\r
1279                         extraHeightTop += ii.heightFactor;\r
1280                         curIndex++;\r
1281                         ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;\r
1282                     }\r
1283                 }\r
1284 \r
1285                 float extraHeightBottom = curItem.heightFactor;\r
1286                 itemIndex = curIndex + 1;\r
1287                 if (extraHeightBottom < 2.f) {\r
1288                     ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;\r
1289                     final float bottomHeightNeeded = clientHeight <= 0 ? 0 :\r
1290                             (float) getPaddingRight() / (float) clientHeight + 2.f;\r
1291                     for (int pos = mCurItem + 1; pos < N; pos++) {\r
1292                         if (extraHeightBottom >= bottomHeightNeeded && pos > endPos) {\r
1293                             if (ii == null) {\r
1294                                 break;\r
1295                             }\r
1296                             if (pos == ii.position && !ii.scrolling) {\r
1297                                 mItems.remove(itemIndex);\r
1298                                 mAdapter.destroyItem(this, pos, ii.object);\r
1299                                 if (DEBUG) {\r
1300                                     Log.i(TAG, logDestroyItem(pos, ((View) ii.object)));\r
1301                                 }\r
1302                                 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;\r
1303                             }\r
1304                         } else if (ii != null && pos == ii.position) {\r
1305                             extraHeightBottom += ii.heightFactor;\r
1306                             itemIndex++;\r
1307                             ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;\r
1308                         } else {\r
1309                             ii = addNewItem(pos, itemIndex);\r
1310                             itemIndex++;\r
1311                             extraHeightBottom += ii.heightFactor;\r
1312                             ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;\r
1313                         }\r
1314                     }\r
1315                 }\r
1316             }\r
1317 \r
1318             calculatePageOffsets(curItem, curIndex, oldCurInfo);\r
1319         }\r
1320 \r
1321         if (DEBUG) {\r
1322             Log.i(TAG, "Current page list:");\r
1323             for (int i = 0; i < mItems.size(); i++) {\r
1324                 Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);\r
1325             }\r
1326         }\r
1327 \r
1328         mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);\r
1329 \r
1330         mAdapter.finishUpdate(this);\r
1331 \r
1332         // Check width measurement of current pages and drawing sort order.\r
1333         // Update LayoutParams as needed.\r
1334         final int childCount = getChildCount();\r
1335         if (isHorizontal()) {\r
1336             for (int i = 0; i < childCount; i++) {\r
1337                 final View child = getChildAt(i);\r
1338                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
1339                 lp.childIndex = i;\r
1340                 if (!lp.isDecor && lp.widthFactor == 0.f) {\r
1341                     // 0 means requery the adapter for this, it doesn't have a valid width.\r
1342                     final ItemInfo ii = infoForChild(child);\r
1343                     if (ii != null) {\r
1344                         lp.widthFactor = ii.widthFactor;\r
1345                         lp.position = ii.position;\r
1346                     }\r
1347                 }\r
1348             }\r
1349             sortChildDrawingOrder();\r
1350 \r
1351             if (hasFocus()) {\r
1352                 View currentFocused = findFocus();\r
1353                 ItemInfo ii\r
1354                         = currentFocused != null ? infoForAnyChild(currentFocused) : null;\r
1355                 if (ii == null || ii.position != mCurItem) {\r
1356                     for (int i = 0; i < getChildCount(); i++) {\r
1357                         View child = getChildAt(i);\r
1358                         ii = infoForChild(child);\r
1359                         if (ii != null\r
1360                                 && ii.position == mCurItem &&\r
1361                                 child.requestFocus(View.FOCUS_FORWARD)) {\r
1362                             break;\r
1363                         }\r
1364                     }\r
1365                 }\r
1366             }\r
1367         } else {\r
1368             for (int i = 0; i < childCount; i++) {\r
1369                 final View child = getChildAt(i);\r
1370                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
1371                 lp.childIndex = i;\r
1372                 if (!lp.isDecor && lp.heightFactor == 0.f) {\r
1373                     final ItemInfo ii = infoForChild(child);\r
1374                     if (ii != null) {\r
1375                         lp.heightFactor = ii.heightFactor;\r
1376                         lp.position = ii.position;\r
1377                     }\r
1378                 }\r
1379             }\r
1380             sortChildDrawingOrder();\r
1381 \r
1382             if (hasFocus()) {\r
1383                 View currentFocused = findFocus();\r
1384                 ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;\r
1385                 if (ii == null || ii.position != mCurItem) {\r
1386                     for (int i = 0; i < getChildCount(); i++) {\r
1387                         View child = getChildAt(i);\r
1388                         ii = infoForChild(child);\r
1389                         if (ii != null && ii.position == mCurItem\r
1390                                 && child.requestFocus(focusDirection)) {\r
1391 //                        if (child.requestFocus(focusDirection)) {\r
1392                             break;\r
1393                             // }\r
1394                         }\r
1395                     }\r
1396                 }\r
1397             }\r
1398         }\r
1399     }\r
1400 \r
1401     private void sortChildDrawingOrder() {\r
1402         if (mDrawingOrder != DRAW_ORDER_DEFAULT) {\r
1403             if (mDrawingOrderedChildren == null) {\r
1404                 mDrawingOrderedChildren = new ArrayList<View>();\r
1405             } else {\r
1406                 mDrawingOrderedChildren.clear();\r
1407             }\r
1408             final int childCount = getChildCount();\r
1409             for (int i = 0; i < childCount; i++) {\r
1410                 final View child = getChildAt(i);\r
1411                 mDrawingOrderedChildren.add(child);\r
1412             }\r
1413             Collections.sort(mDrawingOrderedChildren, sPositionComparator);\r
1414         }\r
1415     }\r
1416 \r
1417     private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {\r
1418         final int N = mAdapter.getCount();\r
1419         if (isHorizontal()) {\r
1420             final int width = getClientWidth();\r
1421             final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;\r
1422             // Fix up offsets for later layout.\r
1423             if (oldCurInfo != null) {\r
1424                 final int oldCurPosition = oldCurInfo.position;\r
1425                 // Base offsets off of oldCurInfo.\r
1426                 if (oldCurPosition < curItem.position) {\r
1427                     int itemIndex = 0;\r
1428                     ItemInfo ii = null;\r
1429                     float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset;\r
1430                     for (int pos = oldCurPosition + 1;\r
1431                              pos <= curItem.position && itemIndex < mItems.size(); pos++) {\r
1432                         ii = mItems.get(itemIndex);\r
1433                         while (pos > ii.position && itemIndex < mItems.size() - 1) {\r
1434                             itemIndex++;\r
1435                             ii = mItems.get(itemIndex);\r
1436                         }\r
1437                         while (pos < ii.position) {\r
1438                             // We don't have an item populated for this,\r
1439                             // ask the adapter for an offset.\r
1440                             offset += mAdapter.getPageWidth(pos) + marginOffset;\r
1441                             pos++;\r
1442                         }\r
1443                         ii.offset = offset;\r
1444                         offset += ii.widthFactor + marginOffset;\r
1445                     }\r
1446                 } else if (oldCurPosition > curItem.position) {\r
1447                     int itemIndex = mItems.size() - 1;\r
1448                     ItemInfo ii = null;\r
1449                     float offset = oldCurInfo.offset;\r
1450                     for (int pos = oldCurPosition - 1;\r
1451                             pos >= curItem.position && itemIndex >= 0; pos--) {\r
1452                         ii = mItems.get(itemIndex);\r
1453                         while (pos < ii.position && itemIndex > 0) {\r
1454                             itemIndex--;\r
1455                             ii = mItems.get(itemIndex);\r
1456                         }\r
1457                         while (pos > ii.position) {\r
1458                             // We don't have an item populated for this,\r
1459                             // ask the adapter for an offset.\r
1460                             offset -= mAdapter.getPageWidth(pos) + marginOffset;\r
1461                             pos--;\r
1462                         }\r
1463                         offset -= ii.widthFactor + marginOffset;\r
1464                         ii.offset = offset;\r
1465                     }\r
1466                 }\r
1467             }\r
1468 \r
1469             // Base all offsets off of curItem.\r
1470             final int itemCount = mItems.size();\r
1471             float offset = curItem.offset;\r
1472             int pos = curItem.position - 1;\r
1473             mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;\r
1474             mLastOffset = curItem.position == N - 1 ?\r
1475                     curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE;\r
1476             // Previous pages\r
1477             for (int i = curIndex - 1; i >= 0; i--, pos--) {\r
1478                 final ItemInfo ii = mItems.get(i);\r
1479                 while (pos > ii.position) {\r
1480                     offset -= mAdapter.getPageWidth(pos--) + marginOffset;\r
1481                 }\r
1482                 offset -= ii.widthFactor + marginOffset;\r
1483                 ii.offset = offset;\r
1484                 if (ii.position == 0) mFirstOffset = offset;\r
1485             }\r
1486             offset = curItem.offset + curItem.widthFactor + marginOffset;\r
1487             pos = curItem.position + 1;\r
1488             // Next pages\r
1489             for (int i = curIndex + 1; i < itemCount; i++, pos++) {\r
1490                 final ItemInfo ii = mItems.get(i);\r
1491                 while (pos < ii.position) {\r
1492                     offset += mAdapter.getPageWidth(pos++) + marginOffset;\r
1493                 }\r
1494                 if (ii.position == N - 1) {\r
1495                     mLastOffset = offset + ii.widthFactor - 1;\r
1496                 }\r
1497                 ii.offset = offset;\r
1498                 offset += ii.widthFactor + marginOffset;\r
1499             }\r
1500         } else {\r
1501             final int height = getClientHeight();\r
1502             final float marginOffset = height > 0 ? (float) mPageMargin / height : 0;\r
1503             // Fix up offsets for later layout.\r
1504             if (oldCurInfo != null) {\r
1505                 final int oldCurPosition = oldCurInfo.position;\r
1506                 // Base offsets off of oldCurInfo.\r
1507                 if (oldCurPosition < curItem.position) {\r
1508                     int itemIndex = 0;\r
1509                     ItemInfo ii = null;\r
1510                     float offset = oldCurInfo.offset + oldCurInfo.heightFactor + marginOffset;\r
1511                     for (int pos = oldCurPosition + 1;\r
1512                             pos <= curItem.position && itemIndex < mItems.size(); pos++) {\r
1513                         ii = mItems.get(itemIndex);\r
1514                         while (pos > ii.position && itemIndex < mItems.size() - 1) {\r
1515                             itemIndex++;\r
1516                             ii = mItems.get(itemIndex);\r
1517                         }\r
1518                         while (pos < ii.position) {\r
1519                             // We don't have an item populated for this,\r
1520                             // ask the adapter for an offset.\r
1521                             offset += mAdapter.getPageWidth(pos) + marginOffset;\r
1522                             pos++;\r
1523                         }\r
1524                         ii.offset = offset;\r
1525                         offset += ii.heightFactor + marginOffset;\r
1526                     }\r
1527                 } else if (oldCurPosition > curItem.position) {\r
1528                     int itemIndex = mItems.size() - 1;\r
1529                     ItemInfo ii = null;\r
1530                     float offset = oldCurInfo.offset;\r
1531                     for (int pos = oldCurPosition - 1;\r
1532                             pos >= curItem.position && itemIndex >= 0;\r
1533                                 pos--) {\r
1534                         ii = mItems.get(itemIndex);\r
1535                         while (pos < ii.position && itemIndex > 0) {\r
1536                             itemIndex--;\r
1537                             ii = mItems.get(itemIndex);\r
1538                         }\r
1539                         while (pos > ii.position) {\r
1540                             // We don't have an item populated for this,\r
1541                             // ask the adapter for an offset.\r
1542                             offset -= mAdapter.getPageWidth(pos) + marginOffset;\r
1543                             pos--;\r
1544                         }\r
1545                         offset -= ii.heightFactor + marginOffset;\r
1546                         ii.offset = offset;\r
1547                     }\r
1548                 }\r
1549             }\r
1550 \r
1551             // Base all offsets off of curItem.\r
1552             final int itemCount = mItems.size();\r
1553             float offset = curItem.offset;\r
1554             int pos = curItem.position - 1;\r
1555             mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;\r
1556             mLastOffset = curItem.position == N - 1 ?\r
1557                     curItem.offset + curItem.heightFactor - 1 : Float.MAX_VALUE;\r
1558             // Previous pages\r
1559             for (int i = curIndex - 1; i >= 0; i--, pos--) {\r
1560                 final ItemInfo ii = mItems.get(i);\r
1561                 while (pos > ii.position) {\r
1562                     offset -= mAdapter.getPageWidth(pos--) + marginOffset;\r
1563                 }\r
1564                 offset -= ii.heightFactor + marginOffset;\r
1565                 ii.offset = offset;\r
1566                 if (ii.position == 0) mFirstOffset = offset;\r
1567             }\r
1568             offset = curItem.offset + curItem.heightFactor + marginOffset;\r
1569             pos = curItem.position + 1;\r
1570             // Next pages\r
1571             for (int i = curIndex + 1; i < itemCount; i++, pos++) {\r
1572                 final ItemInfo ii = mItems.get(i);\r
1573                 while (pos < ii.position) {\r
1574                     offset += mAdapter.getPageWidth(pos++) + marginOffset;\r
1575                 }\r
1576                 if (ii.position == N - 1) {\r
1577                     mLastOffset = offset + ii.heightFactor - 1;\r
1578                 }\r
1579                 ii.offset = offset;\r
1580                 offset += ii.heightFactor + marginOffset;\r
1581             }\r
1582         }\r
1583 \r
1584         mNeedCalculatePageOffsets = false;\r
1585     }\r
1586 \r
1587     /**\r
1588      * This is the persistent state that is saved by ViewPager.  Only needed\r
1589      * if you are creating a sublass of ViewPager that must save its own\r
1590      * state, in which case it should implement a subclass of this which\r
1591      * contains that state.\r
1592      */\r
1593     public static class SavedState extends BaseSavedState {\r
1594         int position;\r
1595         Parcelable adapterState;\r
1596         ClassLoader loader;\r
1597 \r
1598         public SavedState(Parcelable superState) {\r
1599             super(superState);\r
1600         }\r
1601 \r
1602         @Override\r
1603         public void writeToParcel(Parcel out, int flags) {\r
1604             super.writeToParcel(out, flags);\r
1605             out.writeInt(position);\r
1606             out.writeParcelable(adapterState, flags);\r
1607         }\r
1608 \r
1609         @Override\r
1610         public String toString() {\r
1611             return "FragmentPager.SavedState{"\r
1612                     + Integer.toHexString(System.identityHashCode(this))\r
1613                     + " position=" + position + "}";\r
1614         }\r
1615 \r
1616         public static final Parcelable.Creator<SavedState> CREATOR\r
1617                 = ParcelableCompat\r
1618                 .newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {\r
1619                     @Override\r
1620                     public SavedState\r
1621                             createFromParcel(Parcel in, ClassLoader loader) {\r
1622                                 return new SavedState(in, loader);\r
1623                             }\r
1624 \r
1625                             @Override\r
1626                             public SavedState[] newArray(int size) {\r
1627                                 return new SavedState[size];\r
1628                             }\r
1629                 });\r
1630 \r
1631         SavedState(Parcel in, ClassLoader loader) {\r
1632             super(in);\r
1633             if (loader == null) {\r
1634                 loader = getClass().getClassLoader();\r
1635             }\r
1636             position = in.readInt();\r
1637             adapterState = in.readParcelable(loader);\r
1638             this.loader = loader;\r
1639         }\r
1640     }\r
1641 \r
1642     @Override\r
1643     public Parcelable onSaveInstanceState() {\r
1644         Parcelable superState = super.onSaveInstanceState();\r
1645         SavedState ss = new SavedState(superState);\r
1646         ss.position = mCurItem;\r
1647         if (mAdapter != null) {\r
1648             ss.adapterState = mAdapter.saveState();\r
1649         }\r
1650         return ss;\r
1651     }\r
1652 \r
1653     @Override\r
1654     public void onRestoreInstanceState(Parcelable state) {\r
1655         if (!(state instanceof SavedState)) {\r
1656             super.onRestoreInstanceState(state);\r
1657             return;\r
1658         }\r
1659 \r
1660         SavedState ss = (SavedState) state;\r
1661         super.onRestoreInstanceState(ss.getSuperState());\r
1662 \r
1663         if (mAdapter != null) {\r
1664             mAdapter.restoreState(ss.adapterState, ss.loader);\r
1665             setCurrentItemInternal(ss.position, false, true);\r
1666         } else {\r
1667             mRestoredCurItem = ss.position;\r
1668             mRestoredAdapterState = ss.adapterState;\r
1669             mRestoredClassLoader = ss.loader;\r
1670         }\r
1671     }\r
1672 \r
1673     @Override\r
1674     public void addView(View child, int index, ViewGroup.LayoutParams params) {\r
1675         if (!checkLayoutParams(params)) {\r
1676             params = generateLayoutParams(params);\r
1677         }\r
1678         final LayoutParams lp = (LayoutParams) params;\r
1679         lp.isDecor |= child instanceof Decor;\r
1680         if (mInLayout) {\r
1681             if (lp != null && lp.isDecor) {\r
1682                 throw new IllegalStateException("Cannot add pager decor view during layout");\r
1683             }\r
1684             lp.needsMeasure = true;\r
1685             addViewInLayout(child, index, params);\r
1686         } else {\r
1687             super.addView(child, index, params);\r
1688         }\r
1689 \r
1690         if (USE_CACHE) {\r
1691             if (child.getVisibility() != GONE) {\r
1692                 child.setDrawingCacheEnabled(mScrollingCacheEnabled);\r
1693             } else {\r
1694                 child.setDrawingCacheEnabled(false);\r
1695             }\r
1696         }\r
1697     }\r
1698 \r
1699     @Override\r
1700     public void removeView(View view) {\r
1701         if (mInLayout) {\r
1702             removeViewInLayout(view);\r
1703         } else {\r
1704             super.removeView(view);\r
1705         }\r
1706     }\r
1707 \r
1708     ItemInfo infoForChild(View child) {\r
1709         for (int i = 0; i < mItems.size(); i++) {\r
1710             ItemInfo ii = mItems.get(i);\r
1711             if (mAdapter.isViewFromObject(child, ii.object)) {\r
1712                 return ii;\r
1713             }\r
1714         }\r
1715         return null;\r
1716     }\r
1717 \r
1718     ItemInfo infoForAnyChild(View child) {\r
1719         ViewParent parent;\r
1720         while ((parent = child.getParent()) != this) {\r
1721             if (parent == null || !(parent instanceof View)) {\r
1722                 return null;\r
1723             }\r
1724             child = (View) parent;\r
1725         }\r
1726         return infoForChild(child);\r
1727     }\r
1728 \r
1729     ItemInfo infoForPosition(int position) {\r
1730         for (int i = 0; i < mItems.size(); i++) {\r
1731             ItemInfo ii = mItems.get(i);\r
1732             if (ii.position == position) {\r
1733                 return ii;\r
1734             }\r
1735         }\r
1736         return null;\r
1737     }\r
1738 \r
1739     @Override\r
1740     protected void onAttachedToWindow() {\r
1741         super.onAttachedToWindow();\r
1742         mFirstLayout = true;\r
1743     }\r
1744 \r
1745     @Override\r
1746     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\r
1747         // For simple implementation, our internal size is always 0.\r
1748         // We depend on the container to specify the layout size of\r
1749         // our view.  We can't really know what it is since we will be\r
1750         // adding and removing different arbitrary views and do not\r
1751         // want the layout to change as this happens.\r
1752         setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),\r
1753                 getDefaultSize(0, heightMeasureSpec));\r
1754 \r
1755         int childWidthSize = 0;\r
1756         int childHeightSize = 0;\r
1757         if (isHorizontal()) {\r
1758             int measuredWidth = getMeasuredWidth();\r
1759             int maxGutterSize = measuredWidth / 10;\r
1760             mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);\r
1761 \r
1762             // Children are just made to fill our space.\r
1763             childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();\r
1764             childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();\r
1765         } else {\r
1766             int measuredHeight = getMeasuredHeight();\r
1767             int maxGutterSize = measuredHeight / 10;\r
1768             mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);\r
1769 \r
1770             // Children are just made to fill our space.\r
1771             childWidthSize = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();\r
1772             childHeightSize = measuredHeight - getPaddingTop() - getPaddingBottom();\r
1773         }\r
1774 \r
1775         /*\r
1776          * Make sure all children have been properly measured. Decor views first.\r
1777          * Right now we cheat and make this less complicated by assuming decor\r
1778          * views won't intersect. We will pin to edges based on gravity.\r
1779          */\r
1780         int size = getChildCount();\r
1781         for (int i = 0; i < size; ++i) {\r
1782             final View child = getChildAt(i);\r
1783             if (child.getVisibility() != GONE) {\r
1784                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
1785                 if (lp != null && lp.isDecor) {\r
1786                     final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;\r
1787                     final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;\r
1788                     int widthMode = MeasureSpec.AT_MOST;\r
1789                     int heightMode = MeasureSpec.AT_MOST;\r
1790                     boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;\r
1791                     boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;\r
1792 \r
1793                     if (consumeVertical) {\r
1794                         widthMode = MeasureSpec.EXACTLY;\r
1795                     } else if (consumeHorizontal) {\r
1796                         heightMode = MeasureSpec.EXACTLY;\r
1797                     }\r
1798 \r
1799                     int widthSize = childWidthSize;\r
1800                     int heightSize = childHeightSize;\r
1801                     if (lp.width != LayoutParams.WRAP_CONTENT) {\r
1802                         widthMode = MeasureSpec.EXACTLY;\r
1803                         if (lp.width != LayoutParams.FILL_PARENT) {\r
1804                             widthSize = lp.width;\r
1805                         }\r
1806                     }\r
1807                     if (lp.height != LayoutParams.WRAP_CONTENT) {\r
1808                         heightMode = MeasureSpec.EXACTLY;\r
1809                         if (lp.height != LayoutParams.FILL_PARENT) {\r
1810                             heightSize = lp.height;\r
1811                         }\r
1812                     }\r
1813                     final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);\r
1814                     final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);\r
1815                     child.measure(widthSpec, heightSpec);\r
1816 \r
1817                     if (consumeVertical) {\r
1818                         childHeightSize -= child.getMeasuredHeight();\r
1819                     } else if (consumeHorizontal) {\r
1820                         childWidthSize -= child.getMeasuredWidth();\r
1821                     }\r
1822                 }\r
1823             }\r
1824         }\r
1825 \r
1826         mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);\r
1827         mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);\r
1828 \r
1829         // Make sure we have created all fragments that we need to have shown.\r
1830         mInLayout = true;\r
1831         populate();\r
1832         mInLayout = false;\r
1833 \r
1834         // Page views next.\r
1835         size = getChildCount();\r
1836         for (int i = 0; i < size; ++i) {\r
1837             final View child = getChildAt(i);\r
1838             if (child.getVisibility() != GONE) {\r
1839                 if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child\r
1840                         + ": " + mChildWidthMeasureSpec);\r
1841 \r
1842                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
1843                 if (lp == null || !lp.isDecor) {\r
1844                     if (isHorizontal()) {\r
1845                         int widthSpec = MeasureSpec.makeMeasureSpec(\r
1846                                 (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);\r
1847                         child.measure(widthSpec, mChildHeightMeasureSpec);\r
1848                     } else {\r
1849                         int heightSpec = MeasureSpec.makeMeasureSpec(\r
1850                                 (int) (childHeightSize * lp.heightFactor), MeasureSpec.EXACTLY);\r
1851                         child.measure(mChildWidthMeasureSpec, heightSpec);\r
1852                     }\r
1853                 }\r
1854 \r
1855             }\r
1856         }\r
1857     }\r
1858 \r
1859     @Override\r
1860     protected void onSizeChanged(int w, int h, int oldw, int oldh) {\r
1861         super.onSizeChanged(w, h, oldw, oldh);\r
1862 \r
1863         // Make sure scroll position is set correctly.\r
1864 \r
1865         if (isHorizontal()) {\r
1866             if (w != oldw) {\r
1867                 recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin, 0, 0);\r
1868             }\r
1869         } else {\r
1870             if (h != oldh) {\r
1871                 recomputeScrollPosition(0, 0, mPageMargin, mPageMargin, h, oldh);\r
1872             }\r
1873         }\r
1874     }\r
1875 \r
1876 \r
1877     private void recomputeScrollPosition(int width, int oldWidth, int margin,\r
1878                                                 int oldMargin, int height, int oldHeight) {\r
1879         if (isHorizontal()) {\r
1880             if (oldWidth > 0 && !mItems.isEmpty()) {\r
1881                 if (!mScroller.isFinished()) {\r
1882                     mScroller.setFinalX(\r
1883                             getCurrentItem() * getClientWidth());\r
1884                 } else {\r
1885                     final int widthWithMargin\r
1886                             = width - getPaddingLeft() - getPaddingRight() + margin;\r
1887                     final int oldWidthWithMargin\r
1888                             = oldWidth - getPaddingLeft() - getPaddingRight()\r
1889                                 + oldMargin;\r
1890                     final int xpos = getScrollX();\r
1891                     final float pageOffset = (float) xpos / oldWidthWithMargin;\r
1892                     final int newOffsetPixels = (int) (pageOffset * widthWithMargin);\r
1893 \r
1894                     scrollTo(newOffsetPixels, getScrollY());\r
1895                 }\r
1896             } else {\r
1897                 final ItemInfo ii = infoForPosition(mCurItem);\r
1898                 final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;\r
1899                 final int scrollPos = (int) (scrollOffset *\r
1900                         (width - getPaddingLeft() - getPaddingRight()));\r
1901                 if (scrollPos != getScrollX()) {\r
1902                     completeScroll(false);\r
1903                     scrollTo(scrollPos, getScrollY());\r
1904                 }\r
1905             }\r
1906         } else {\r
1907             final int heightWithMargin = height - getPaddingTop() - getPaddingBottom() + margin;\r
1908             final int oldHeightWithMargin = oldHeight - getPaddingTop() - getPaddingBottom()\r
1909                     + oldMargin;\r
1910             final int ypos = getScrollY();\r
1911             final float pageOffset = (float) ypos / oldHeightWithMargin;\r
1912             final int newOffsetPixels = (int) (pageOffset * heightWithMargin);\r
1913 \r
1914             scrollTo(getScrollX(), newOffsetPixels);\r
1915             if (!mScroller.isFinished()) {\r
1916                 // We now return to your regularly scheduled scroll, already in progress.\r
1917                 final int newDuration = mScroller.getDuration() - mScroller.timePassed();\r
1918                 ItemInfo targetInfo = infoForPosition(mCurItem);\r
1919                 mScroller.startScroll(0, newOffsetPixels,\r
1920                         0, (int) (targetInfo.offset * height), newDuration);\r
1921             } else {\r
1922                 final ItemInfo ii = infoForPosition(mCurItem);\r
1923                 final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;\r
1924                 final int scrollPos = (int) (scrollOffset *\r
1925                         (height - getPaddingTop() - getPaddingBottom()));\r
1926                 if (scrollPos != getScrollY()) {\r
1927                     completeScroll(false);\r
1928                     scrollTo(getScrollX(), scrollPos);\r
1929                 }\r
1930             }\r
1931         }\r
1932 \r
1933     }\r
1934 \r
1935     @Override\r
1936     protected void onLayout(boolean changed, int l, int t, int r, int b) {\r
1937         final int count = getChildCount();\r
1938         int width = r - l;\r
1939         int height = b - t;\r
1940         int paddingLeft = getPaddingLeft();\r
1941         int paddingTop = getPaddingTop();\r
1942         int paddingRight = getPaddingRight();\r
1943         int paddingBottom = getPaddingBottom();\r
1944         final int scrollX = getScrollX();\r
1945         final int scrollY = getScrollY();\r
1946 \r
1947         int decorCount = 0;\r
1948 \r
1949         // First pass - decor views. We need to do this in two passes so that\r
1950         // we have the proper offsets for non-decor views later.\r
1951         for (int i = 0; i < count; i++) {\r
1952             final View child = getChildAt(i);\r
1953             if (child.getVisibility() != GONE) {\r
1954                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
1955                 int childLeft = 0;\r
1956                 int childTop = 0;\r
1957                 if (lp.isDecor) {\r
1958                     final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;\r
1959                     final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;\r
1960                     switch (hgrav) {\r
1961                         default:\r
1962                             childLeft = paddingLeft;\r
1963                             break;\r
1964                         case Gravity.LEFT:\r
1965                             childLeft = paddingLeft;\r
1966                             paddingLeft += child.getMeasuredWidth();\r
1967                             break;\r
1968                         case Gravity.CENTER_HORIZONTAL:\r
1969                             childLeft = Math.max((width - child.getMeasuredWidth()) / 2,\r
1970                                     paddingLeft);\r
1971                             break;\r
1972                         case Gravity.RIGHT:\r
1973                             childLeft = width - paddingRight - child.getMeasuredWidth();\r
1974                             paddingRight += child.getMeasuredWidth();\r
1975                             break;\r
1976                     }\r
1977                     switch (vgrav) {\r
1978                         default:\r
1979                             childTop = paddingTop;\r
1980                             break;\r
1981                         case Gravity.TOP:\r
1982                             childTop = paddingTop;\r
1983                             paddingTop += child.getMeasuredHeight();\r
1984                             break;\r
1985                         case Gravity.CENTER_VERTICAL:\r
1986                             childTop = Math.max((height - child.getMeasuredHeight()) / 2,\r
1987                                     paddingTop);\r
1988                             break;\r
1989                         case Gravity.BOTTOM:\r
1990                             childTop = height - paddingBottom - child.getMeasuredHeight();\r
1991                             paddingBottom += child.getMeasuredHeight();\r
1992                             break;\r
1993                     }\r
1994                     if (isHorizontal()) {\r
1995                         childLeft += scrollX;\r
1996                     } else {\r
1997                         childTop += scrollY;\r
1998                     }\r
1999                     child.layout(childLeft, childTop,\r
2000                             childLeft + child.getMeasuredWidth(),\r
2001                             childTop + child.getMeasuredHeight());\r
2002                     decorCount++;\r
2003                 }\r
2004             }\r
2005         }\r
2006 \r
2007         if (isHorizontal()) {\r
2008             final int childWidth = width - paddingLeft - paddingRight;\r
2009             // Page views. Do this once we have the right padding offsets from above.\r
2010             for (int i = 0; i < count; i++) {\r
2011                 final View child = getChildAt(i);\r
2012                 if (child.getVisibility() != GONE) {\r
2013                     final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
2014                     ItemInfo ii;\r
2015                     if (!lp.isDecor && (ii = infoForChild(child)) != null) {\r
2016                         int loff = (int) (childWidth * ii.offset);\r
2017                         int childLeft = paddingLeft + loff;\r
2018                         int childTop = paddingTop;\r
2019                         if (lp.needsMeasure) {\r
2020                             // This was added during layout and needs measurement.\r
2021                             // Do it now that we know what we're working with.\r
2022                             lp.needsMeasure = false;\r
2023                             final int widthSpec = MeasureSpec.makeMeasureSpec(\r
2024                                     (int) (childWidth * lp.widthFactor),\r
2025                                     MeasureSpec.EXACTLY);\r
2026                             final int heightSpec = MeasureSpec.makeMeasureSpec(\r
2027                                     (int) (height - paddingTop - paddingBottom),\r
2028                                     MeasureSpec.EXACTLY);\r
2029                             child.measure(widthSpec, heightSpec);\r
2030                         }\r
2031                         if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object\r
2032                                 + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()\r
2033                                 + "x" + child.getMeasuredHeight());\r
2034                         child.layout(childLeft, childTop,\r
2035                                 childLeft + child.getMeasuredWidth(),\r
2036                                 childTop + child.getMeasuredHeight());\r
2037                     }\r
2038                 }\r
2039             }\r
2040             mTopPageBounds = paddingTop;\r
2041             mBottomPageBounds = height - paddingBottom;\r
2042         } else {\r
2043             final int childHeight = height - paddingTop - paddingBottom;\r
2044             // Page views. Do this once we have the right padding offsets from above.\r
2045             for (int i = 0; i < count; i++) {\r
2046                 final View child = getChildAt(i);\r
2047                 if (child.getVisibility() != GONE) {\r
2048                     final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
2049                     ItemInfo ii;\r
2050                     if (!lp.isDecor && (ii = infoForChild(child)) != null) {\r
2051                         int toff = (int) (childHeight * ii.offset);\r
2052                         int childLeft = paddingLeft;\r
2053                         int childTop = paddingTop + toff;\r
2054                         if (lp.needsMeasure) {\r
2055                             // This was added during layout and needs measurement.\r
2056                             // Do it now that we know what we're working with.\r
2057                             lp.needsMeasure = false;\r
2058                             final int widthSpec = MeasureSpec.makeMeasureSpec(\r
2059                                     (int) (width - paddingLeft - paddingRight),\r
2060                                     MeasureSpec.EXACTLY);\r
2061                             final int heightSpec = MeasureSpec.makeMeasureSpec(\r
2062                                     (int) (childHeight * lp.heightFactor),\r
2063                                     MeasureSpec.EXACTLY);\r
2064                             child.measure(widthSpec, heightSpec);\r
2065                         }\r
2066                         if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object\r
2067                                 + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()\r
2068                                 + "x" + child.getMeasuredHeight());\r
2069                         child.layout(childLeft, childTop,\r
2070                                 childLeft + child.getMeasuredWidth(),\r
2071                                 childTop + child.getMeasuredHeight());\r
2072                     }\r
2073                 }\r
2074             }\r
2075             mLeftPageBounds = paddingLeft;\r
2076             mRightPageBounds = width - paddingRight;\r
2077         }\r
2078         mDecorChildCount = decorCount;\r
2079 \r
2080         if (mFirstLayout) {\r
2081             scrollToItem(mCurItem, false, 0, false);\r
2082         }\r
2083         mFirstLayout = false;\r
2084     }\r
2085 \r
2086     @Override\r
2087     public void computeScroll() {\r
2088         mIsScrollStarted = true;\r
2089         if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {\r
2090             int oldX = getScrollX();\r
2091             int oldY = getScrollY();\r
2092             int x = mScroller.getCurrX();\r
2093             int y = mScroller.getCurrY();\r
2094 \r
2095             if (oldX != x || oldY != y) {\r
2096                 scrollTo(x, y);\r
2097                 if (isHorizontal()) {\r
2098                     if (!pageScrolled(x, 0)) {\r
2099                         mScroller.abortAnimation();\r
2100                         scrollTo(0, y);\r
2101                     }\r
2102                 } else {\r
2103                     if (!pageScrolled(0, y)) {\r
2104                         mScroller.abortAnimation();\r
2105                         scrollTo(x, 0);\r
2106                     }\r
2107                 }\r
2108             }\r
2109 \r
2110             // Keep on drawing until the animation has finished.\r
2111             ViewCompat.postInvalidateOnAnimation(this);\r
2112             return;\r
2113         }\r
2114 \r
2115         // Done with scroll, clean up state.\r
2116         completeScroll(true);\r
2117     }\r
2118 \r
2119     private boolean pageScrolled(int xpos, int ypos) {\r
2120         if (mItems.size() == 0) {\r
2121             if (mFirstLayout) {\r
2122                 // If we haven't been laid out yet, we probably just haven't been populated yet.\r
2123                 // Let's skip this call since it doesn't make sense in this state\r
2124                 return false;\r
2125             }\r
2126             mCalledSuper = false;\r
2127             onPageScrolled(0, 0, 0);\r
2128             if (!mCalledSuper) {\r
2129                 throw new IllegalStateException(\r
2130                         "onPageScrolled did not call superclass implementation");\r
2131             }\r
2132             return false;\r
2133         }\r
2134         final ItemInfo ii = infoForCurrentScrollPosition();\r
2135         int currentPage = 0;\r
2136         float pageOffset = 0;\r
2137         int offsetPixels = 0;\r
2138         if (isHorizontal()) {\r
2139             int width = getClientWidth();\r
2140             int widthWithMargin = width + mPageMargin;\r
2141             float marginOffset = (float) mPageMargin / width;\r
2142             currentPage = ii.position;\r
2143             pageOffset = (((float) xpos / width) - ii.offset) /\r
2144                     (ii.widthFactor + marginOffset);\r
2145             offsetPixels = (int) (pageOffset * widthWithMargin);\r
2146         } else {\r
2147             int height = getClientHeight();\r
2148             int heightWithMargin = height + mPageMargin;\r
2149             float marginOffset = (float) mPageMargin / height;\r
2150             currentPage = ii.position;\r
2151             pageOffset = (((float) ypos / height) - ii.offset) /\r
2152                     (ii.heightFactor + marginOffset);\r
2153             offsetPixels = (int) (pageOffset * heightWithMargin);\r
2154         }\r
2155 \r
2156         mCalledSuper = false;\r
2157         onPageScrolled(currentPage, pageOffset, offsetPixels);\r
2158         if (!mCalledSuper) {\r
2159             throw new IllegalStateException(\r
2160                     "onPageScrolled did not call superclass implementation");\r
2161         }\r
2162         return true;\r
2163     }\r
2164 \r
2165     /**\r
2166      * This method will be invoked when the current page is scrolled, either as part\r
2167      * of a programmatically initiated smooth scroll or a user initiated touch scroll.\r
2168      * If you override this method you must call through to the superclass implementation\r
2169      * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled\r
2170      * returns.\r
2171      *\r
2172      * @param position     Position index of the first page currently being displayed.\r
2173      *                     Page position+1 will be visible if positionOffset is nonzero.\r
2174      * @param offset       Value from [0, 1) indicating the offset from the page at position.\r
2175      * @param offsetPixels Value in pixels indicating the offset from position.\r
2176      */\r
2177     @CallSuper\r
2178     protected void onPageScrolled(int position, float offset, int offsetPixels) {\r
2179         // Offset any decor views if needed - keep them on-screen at all times.\r
2180         if (isHorizontal()) {\r
2181             if (mDecorChildCount > 0) {\r
2182                 final int scrollX = getScrollX();\r
2183                 int paddingLeft = getPaddingLeft();\r
2184                 int paddingRight = getPaddingRight();\r
2185                 final int width = getWidth();\r
2186                 final int childCount = getChildCount();\r
2187                 for (int i = 0; i < childCount; i++) {\r
2188                     final View child = getChildAt(i);\r
2189                     final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
2190                     if (!lp.isDecor) continue;\r
2191 \r
2192                     final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;\r
2193                     int childLeft = 0;\r
2194                     switch (hgrav) {\r
2195                         default:\r
2196                             childLeft = paddingLeft;\r
2197                             break;\r
2198                         case Gravity.LEFT:\r
2199                             childLeft = paddingLeft;\r
2200                             paddingLeft += child.getWidth();\r
2201                             break;\r
2202                         case Gravity.CENTER_HORIZONTAL:\r
2203                             childLeft = Math.max((width - child.getMeasuredWidth()) / 2,\r
2204                                     paddingLeft);\r
2205                             break;\r
2206                         case Gravity.RIGHT:\r
2207                             childLeft = width - paddingRight - child.getMeasuredWidth();\r
2208                             paddingRight += child.getMeasuredWidth();\r
2209                             break;\r
2210                     }\r
2211                     childLeft += scrollX;\r
2212 \r
2213                     final int childOffset = childLeft - child.getLeft();\r
2214                     if (childOffset != 0) {\r
2215                         child.offsetLeftAndRight(childOffset);\r
2216                     }\r
2217                 }\r
2218             }\r
2219 \r
2220             dispatchOnPageScrolled(position, offset, offsetPixels);\r
2221 \r
2222             if (mPageTransformer != null) {\r
2223                 final int scrollX = getScrollX();\r
2224                 final int childCount = getChildCount();\r
2225                 for (int i = 0; i < childCount; i++) {\r
2226                     final View child = getChildAt(i);\r
2227                     final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
2228 \r
2229                     if (lp.isDecor) continue;\r
2230                     final float transformPos\r
2231                             = (float) (child.getLeft() - scrollX) / getClientWidth();\r
2232                     mPageTransformer.transformPage(child, transformPos);\r
2233                 }\r
2234             }\r
2235         } else {\r
2236             if (mDecorChildCount > 0) {\r
2237                 final int scrollY = getScrollY();\r
2238                 int paddingTop = getPaddingTop();\r
2239                 int paddingBottom = getPaddingBottom();\r
2240                 final int height = getHeight();\r
2241                 final int childCount = getChildCount();\r
2242                 for (int i = 0; i < childCount; i++) {\r
2243                     final View child = getChildAt(i);\r
2244                     final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
2245                     if (!lp.isDecor) continue;\r
2246 \r
2247                     final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;\r
2248                     int childTop = 0;\r
2249                     switch (vgrav) {\r
2250                         default:\r
2251                             childTop = paddingTop;\r
2252                             break;\r
2253                         case Gravity.TOP:\r
2254                             childTop = paddingTop;\r
2255                             paddingTop += child.getHeight();\r
2256                             break;\r
2257                         case Gravity.CENTER_VERTICAL:\r
2258                             childTop = Math.max((height - child.getMeasuredHeight()) / 2,\r
2259                                     paddingTop);\r
2260                             break;\r
2261                         case Gravity.BOTTOM:\r
2262                             childTop = height - paddingBottom - child.getMeasuredHeight();\r
2263                             paddingBottom += child.getMeasuredHeight();\r
2264                             break;\r
2265                     }\r
2266                     childTop += scrollY;\r
2267 \r
2268                     final int childOffset = childTop - child.getTop();\r
2269                     if (childOffset != 0) {\r
2270                         child.offsetTopAndBottom(childOffset);\r
2271                     }\r
2272                 }\r
2273             }\r
2274 \r
2275             if (mOnPageChangeListener != null) {\r
2276                 mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);\r
2277             }\r
2278             if (mInternalPageChangeListener != null) {\r
2279                 mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);\r
2280             }\r
2281 \r
2282             if (mPageTransformer != null) {\r
2283                 final int scrollY = getScrollY();\r
2284                 final int childCount = getChildCount();\r
2285                 for (int i = 0; i < childCount; i++) {\r
2286                     final View child = getChildAt(i);\r
2287                     final LayoutParams lp = (LayoutParams) child.getLayoutParams();\r
2288 \r
2289                     if (lp.isDecor) continue;\r
2290 \r
2291                     final float transformPos\r
2292                             = (float) (child.getTop() - scrollY) / getClientHeight();\r
2293                     mPageTransformer.transformPage(child, transformPos);\r
2294                 }\r
2295             }\r
2296         }\r
2297 \r
2298         mCalledSuper = true;\r
2299     }\r
2300 \r
2301     private void dispatchOnPageScrolled(int position, float offset, int offsetPixels) {\r
2302         if (mOnPageChangeListener != null) {\r
2303             mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);\r
2304         }\r
2305         if (mOnPageChangeListeners != null) {\r
2306             for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) {\r
2307                 OnPageChangeListener listener = mOnPageChangeListeners.get(i);\r
2308                 if (listener != null) {\r
2309                     listener.onPageScrolled(position, offset, offsetPixels);\r
2310                 }\r
2311             }\r
2312         }\r
2313         if (mInternalPageChangeListener != null) {\r
2314             mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);\r
2315         }\r
2316     }\r
2317 \r
2318     private void dispatchOnPageSelected(int position) {\r
2319         if (mOnPageChangeListener != null) {\r
2320             mOnPageChangeListener.onPageSelected(position);\r
2321         }\r
2322         if (mOnPageChangeListeners != null) {\r
2323             for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) {\r
2324                 OnPageChangeListener listener = mOnPageChangeListeners.get(i);\r
2325                 if (listener != null) {\r
2326                     listener.onPageSelected(position);\r
2327                 }\r
2328             }\r
2329         }\r
2330         if (mInternalPageChangeListener != null) {\r
2331             mInternalPageChangeListener.onPageSelected(position);\r
2332         }\r
2333     }\r
2334 \r
2335     private void dispatchOnScrollStateChanged(int state) {\r
2336         if (mOnPageChangeListener != null) {\r
2337             mOnPageChangeListener.onPageScrollStateChanged(state);\r
2338         }\r
2339         if (mOnPageChangeListeners != null) {\r
2340             for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) {\r
2341                 OnPageChangeListener listener = mOnPageChangeListeners.get(i);\r
2342                 if (listener != null) {\r
2343                     listener.onPageScrollStateChanged(state);\r
2344                 }\r
2345             }\r
2346         }\r
2347         if (mInternalPageChangeListener != null) {\r
2348             mInternalPageChangeListener.onPageScrollStateChanged(state);\r
2349         }\r
2350     }\r
2351 \r
2352     private void completeScroll(boolean postEvents) {\r
2353         boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING;\r
2354         if (needPopulate) {\r
2355             // Done with scroll, no longer want to cache view drawing.\r
2356             setScrollingCacheEnabled(false);\r
2357             boolean wasScrolling = !mScroller.isFinished();\r
2358             if (wasScrolling) {\r
2359                 mScroller.abortAnimation();\r
2360                 int oldX = getScrollX();\r
2361                 int oldY = getScrollY();\r
2362                 int x = mScroller.getCurrX();\r
2363                 int y = mScroller.getCurrY();\r
2364                 if (oldX != x || oldY != y) {\r
2365                     scrollTo(x, y);\r
2366                     if (isHorizontal() && x != oldX) {\r
2367                         pageScrolled(x, 0);\r
2368                     }\r
2369                 }\r
2370             }\r
2371         }\r
2372         mPopulatePending = false;\r
2373         for (int i = 0; i < mItems.size(); i++) {\r
2374             ItemInfo ii = mItems.get(i);\r
2375             if (ii.scrolling) {\r
2376                 needPopulate = true;\r
2377                 ii.scrolling = false;\r
2378             }\r
2379         }\r
2380         if (needPopulate) {\r
2381             if (postEvents) {\r
2382                 ViewCompat.postOnAnimation(this, mEndScrollRunnable);\r
2383             } else {\r
2384                 mEndScrollRunnable.run();\r
2385             }\r
2386         }\r
2387     }\r
2388 \r
2389     private boolean isGutterDrag(float x, float dx, float y, float dy) {\r
2390         if (isHorizontal()) {\r
2391             return (x < mGutterSize && dx > 0) || (x > getWidth() - mGutterSize && dx < 0);\r
2392         } else {\r
2393             return (y < mGutterSize && dy > 0) || (y > getHeight() - mGutterSize && dy < 0);\r
2394         }\r
2395     }\r
2396 \r
2397     private void enableLayers(boolean enable) {\r
2398         final int childCount = getChildCount();\r
2399         for (int i = 0; i < childCount; i++) {\r
2400             final int layerType = enable ?\r
2401                     ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE;\r
2402             ViewCompat.setLayerType(getChildAt(i), layerType, null);\r
2403         }\r
2404     }\r
2405 \r
2406     @Override\r
2407     public boolean onInterceptTouchEvent(MotionEvent ev) {\r
2408         /*\r
2409          * This method JUST determines whether we want to intercept the motion.\r
2410          * If we return true, onMotionEvent will be called and we do the actual\r
2411          * scrolling there.\r
2412          */\r
2413 \r
2414         final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;\r
2415 \r
2416         // Always take care of the touch gesture being complete.\r
2417         if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {\r
2418             // Release the drag.\r
2419             if (DEBUG) Log.v(TAG, "Intercept done!");\r
2420             if (isHorizontal()) {\r
2421                 resetTouch();\r
2422             } else {\r
2423                 mIsBeingDragged = false;\r
2424                 mIsUnableToDrag = false;\r
2425                 mActivePointerId = INVALID_POINTER;\r
2426                 if (mVelocityTracker != null) {\r
2427                     mVelocityTracker.recycle();\r
2428                     mVelocityTracker = null;\r
2429                 }\r
2430             }\r
2431             return false;\r
2432         }\r
2433 \r
2434         // Nothing more to do here if we have decided whether or not we\r
2435         // are dragging.\r
2436         if (action != MotionEvent.ACTION_DOWN) {\r
2437             if (mIsBeingDragged) {\r
2438                 if (DEBUG) Log.v(TAG, "Intercept returning true!");\r
2439                 return true;\r
2440             }\r
2441             if (mIsUnableToDrag) {\r
2442                 if (DEBUG) Log.v(TAG, "Intercept returning false!");\r
2443                 return false;\r
2444             }\r
2445         }\r
2446 \r
2447         if (isHorizontal()) {\r
2448 \r
2449             switch (action) {\r
2450                 case MotionEvent.ACTION_MOVE: {\r
2451                 /*\r
2452                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check\r
2453                  * whether the user has moved far enough from his original down touch.\r
2454                  */\r
2455 \r
2456                 /*\r
2457                 * Locally do absolute value. mLastMotionY is set to the y value\r
2458                 * of the down event.\r
2459                 */\r
2460                     final int activePointerId = mActivePointerId;\r
2461                     if (activePointerId == INVALID_POINTER) {\r
2462                         break;\r
2463                     }\r
2464 \r
2465                     final int pointerIndex\r
2466                             = MotionEventCompat.findPointerIndex(ev, activePointerId);\r
2467                     final float x = MotionEventCompat.getX(ev, pointerIndex);\r
2468                     final float dx = x - mLastMotionX;\r
2469                     final float xDiff = Math.abs(dx);\r
2470                     final float y = MotionEventCompat.getY(ev, pointerIndex);\r
2471                     final float yDiff = Math.abs(y - mInitialMotionY);\r
2472 \r
2473                     if (dx != 0 && !isGutterDrag(mLastMotionX, dx, 0, 0) &&\r
2474                             canScroll(this, false, (int) dx, 0, (int) x, (int) y)) {\r
2475                         // Nested view has scrollable\r
2476                         // area under this point. Let it be handled there.\r
2477                         mLastMotionX = x;\r
2478                         mLastMotionY = y;\r
2479                         mIsUnableToDrag = true;\r
2480                         return false;\r
2481                     }\r
2482                     if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {\r
2483                         if (DEBUG) Log.v(TAG, getContext().getString(R.string.debug_start_drag));\r
2484                         mIsBeingDragged = true;\r
2485                         requestParentDisallowInterceptTouchEvent(true);\r
2486                         setScrollState(SCROLL_STATE_DRAGGING);\r
2487                         mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :\r
2488                                 mInitialMotionX - mTouchSlop;\r
2489                         mLastMotionY = y;\r
2490                         setScrollingCacheEnabled(true);\r
2491                     } else if (yDiff > mTouchSlop) {\r
2492                         // The finger has moved enough in the vertical\r
2493                         // direction to be counted as a drag...  abort\r
2494                         // any attempt to drag horizontally, to work correctly\r
2495                         // with children that have scrolling containers.\r
2496                         if (DEBUG)\r
2497                             Log.v(TAG, getContext().getString(R.string.debug_start_unable_drag));\r
2498                         mIsUnableToDrag = true;\r
2499                     }\r
2500                     if (mIsBeingDragged && performDrag(x, 0)) {\r
2501                         // Scroll to follow the motion event\r
2502                         ViewCompat.postInvalidateOnAnimation(this);\r
2503                     }\r
2504                     break;\r
2505                 }\r
2506 \r
2507                 case MotionEvent.ACTION_DOWN: {\r
2508                 /*\r
2509                  * Remember location of down touch.\r
2510                  * ACTION_DOWN always refers to pointer index 0.\r
2511                  */\r
2512                     mLastMotionX = mInitialMotionX = ev.getX();\r
2513                     mLastMotionY = mInitialMotionY = ev.getY();\r
2514                     mActivePointerId = MotionEventCompat.getPointerId(ev, 0);\r
2515                     mIsUnableToDrag = false;\r
2516 \r
2517                     mIsScrollStarted = true;\r
2518                     mScroller.computeScrollOffset();\r
2519                     if (mScrollState == SCROLL_STATE_SETTLING &&\r
2520                             Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {\r
2521                         // Let the user 'catch' the pager as it animates.\r
2522                         mScroller.abortAnimation();\r
2523                         mPopulatePending = false;\r
2524                         populate();\r
2525                         mIsBeingDragged = true;\r
2526                         requestParentDisallowInterceptTouchEvent(true);\r
2527                         setScrollState(SCROLL_STATE_DRAGGING);\r
2528                     } else {\r
2529                         completeScroll(false);\r
2530                         mIsBeingDragged = false;\r
2531                     }\r
2532 \r
2533                     if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY\r
2534                             + " mIsBeingDragged=" + mIsBeingDragged\r
2535                             + "mIsUnableToDrag=" + mIsUnableToDrag);\r
2536                     break;\r
2537                 }\r
2538 \r
2539                 case MotionEventCompat.ACTION_POINTER_UP:\r
2540                     onSecondaryPointerUp(ev);\r
2541                     break;\r
2542             }\r
2543 \r
2544            /* if (mVelocityTracker == null) {\r
2545                 mVelocityTracker = VelocityTracker.obtain();\r
2546             }\r
2547             mVelocityTracker.addMovement(ev);\r
2548 */\r
2549         /*\r
2550          * The only time we want to intercept motion events is if we are in the\r
2551          * drag mode.\r
2552          */\r
2553         } else {\r
2554             switch (action) {\r
2555                 case MotionEvent.ACTION_MOVE: {\r
2556                 /*\r
2557                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check\r
2558                  * whether the user has moved far enough from his original down touch.\r
2559                  */\r
2560 \r
2561                 /*\r
2562                 * Locally do absolute value. mLastMotionY is set to the y value\r
2563                 * of the down event.\r
2564                 */\r
2565                     final int activePointerId = mActivePointerId;\r
2566                     if (activePointerId == INVALID_POINTER) {\r
2567                         // If we don't have a valid id, the touch down wasn't on content.\r
2568                         break;\r
2569                     }\r
2570 \r
2571                     final int pointerIndex\r
2572                             = MotionEventCompat.findPointerIndex(ev, activePointerId);\r
2573                     final float y = MotionEventCompat.getY(ev, pointerIndex);\r
2574                     final float dy = y - mLastMotionY;\r
2575                     final float yDiff = Math.abs(dy);\r
2576                     final float x\r
2577                                 = MotionEventCompat.getX(ev, pointerIndex);\r
2578                     final float xDiff = Math.abs(x - mInitialMotionX);\r
2579 \r
2580                     if (dy != 0 && !isGutterDrag(0, 0, mLastMotionY, dy) &&\r
2581                             canScroll(this, false, 0,\r
2582                                     (int) dy, (int) x, (int) y)) {\r
2583                         // Nested view has scrollable\r
2584                         // area under this point.\r
2585                         // Let it be handled there.\r
2586                         mLastMotionX = x;\r
2587                         mLastMotionY = y;\r
2588                         mIsUnableToDrag = true;\r
2589                         return false;\r
2590                     }\r
2591                     if (yDiff > mTouchSlop && yDiff * 0.5f > xDiff) {\r
2592                         if (DEBUG) Log.v(TAG, getContext().getString(R.string.debug_start_drag));\r
2593                         mIsBeingDragged = true;\r
2594                         requestParentDisallowInterceptTouchEvent(true);\r
2595                         setScrollState(SCROLL_STATE_DRAGGING);\r
2596                         mLastMotionY = dy > 0 ? mInitialMotionY + mTouchSlop :\r
2597                                 mInitialMotionY - mTouchSlop;\r
2598                         mLastMotionX = x;\r
2599                         setScrollingCacheEnabled(true);\r
2600                     } else if (xDiff > mTouchSlop) {\r
2601                         // The finger has moved enough in the vertical\r
2602                         // direction to be counted as a drag...  abort\r
2603                         // any attempt to drag horizontally, to work correctly\r
2604                         // with children that have scrolling containers.\r
2605                         if (DEBUG)\r
2606                             Log.v(TAG, getContext().getString(R.string.debug_start_unable_drag));\r
2607                         mIsUnableToDrag = true;\r
2608                     }\r
2609                     if (mIsBeingDragged && performDrag(0, y)) {\r
2610                         // Scroll to follow the motion event\r
2611                         ViewCompat.postInvalidateOnAnimation(this);\r
2612                     }\r
2613                     break;\r
2614                 }\r
2615 \r
2616                 case MotionEvent.ACTION_DOWN: {\r
2617                 /*\r
2618                  * Remember location of down touch.\r
2619                  * ACTION_DOWN always refers to pointer index 0.\r
2620                  */\r
2621                     mLastMotionX = mInitialMotionX = ev.getX();\r
2622                     mLastMotionY = mInitialMotionY = ev.getY();\r
2623                     mActivePointerId = MotionEventCompat.getPointerId(ev, 0);\r
2624                     mIsUnableToDrag = false;\r
2625 \r
2626                     mScroller.computeScrollOffset();\r
2627                     if (mScrollState == SCROLL_STATE_SETTLING &&\r
2628                             Math.abs(mScroller.getFinalY() - mScroller.getCurrY()) > mCloseEnough) {\r
2629                         // Let the user 'catch' the pager as it animates.\r
2630                         mScroller.abortAnimation();\r
2631                         mPopulatePending = false;\r
2632                         populate();\r
2633                         mIsBeingDragged = true;\r
2634                         requestParentDisallowInterceptTouchEvent(true);\r
2635                         setScrollState(SCROLL_STATE_DRAGGING);\r
2636                     } else {\r
2637                         completeScroll(false);\r
2638                         mIsBeingDragged = false;\r
2639                     }\r
2640 \r
2641                     if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY\r
2642                             + " mIsBeingDragged=" + mIsBeingDragged\r
2643                             + "mIsUnableToDrag=" + mIsUnableToDrag);\r
2644                     break;\r
2645                 }\r
2646 \r
2647                 case MotionEventCompat.ACTION_POINTER_UP:\r
2648                     onSecondaryPointerUp(ev);\r
2649                     break;\r
2650             }\r
2651 \r
2652         }\r
2653         if (mVelocityTracker == null) {\r
2654             mVelocityTracker = VelocityTracker.obtain();\r
2655         }\r
2656         mVelocityTracker.addMovement(ev);\r
2657         return mIsBeingDragged;\r
2658     }\r
2659 \r
2660     @Override\r
2661     public boolean onTouchEvent(MotionEvent ev) {\r
2662         if (mFakeDragging) {\r
2663             // A fake drag is in progress already, ignore this real one\r
2664             // but still eat the touch events.\r
2665             // (It is likely that the user is multi-touching the screen.)\r
2666             return true;\r
2667         }\r
2668 \r
2669         if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {\r
2670             // Don't handle edge touches immediately -- they may actually belong to one of our\r
2671             // descendants.\r
2672             return false;\r
2673         }\r
2674 \r
2675         if (mAdapter == null || mAdapter.getCount() == 0) {\r
2676             // Nothing to present or scroll; nothing to touch.\r
2677             return false;\r
2678         }\r
2679 \r
2680         if (mVelocityTracker == null) {\r
2681             mVelocityTracker = VelocityTracker.obtain();\r
2682         }\r
2683         mVelocityTracker.addMovement(ev);\r
2684 \r
2685         final int action = ev.getAction();\r
2686         boolean needsInvalidate = false;\r
2687 \r
2688         if (isHorizontal()) {\r
2689             switch (action & MotionEventCompat.ACTION_MASK) {\r
2690                 case MotionEvent.ACTION_DOWN: {\r
2691                     mScroller.abortAnimation();\r
2692                     mPopulatePending = false;\r
2693                     populate();\r
2694 \r
2695                     // Remember where the motion event started\r
2696                     mLastMotionX = mInitialMotionX = ev.getX();\r
2697                     mLastMotionY = mInitialMotionY = ev.getY();\r
2698                     mActivePointerId = MotionEventCompat.getPointerId(ev, 0);\r
2699                     break;\r
2700                 }\r
2701                 case MotionEvent.ACTION_MOVE:\r
2702                     if (!mIsBeingDragged) {\r
2703                         final int pointerIndex\r
2704                                 = MotionEventCompat.findPointerIndex(ev,\r
2705                                     mActivePointerId);\r
2706                         if (pointerIndex == -1) {\r
2707                             // A child has consumed some\r
2708                             // touch events and put us into an inconsistent state.\r
2709                             needsInvalidate = resetTouch();\r
2710                             break;\r
2711                         }\r
2712                         final float x = MotionEventCompat.getX(ev, pointerIndex);\r
2713                         final float xDiff = Math.abs(x - mLastMotionX);\r
2714                         final float y\r
2715                                 = MotionEventCompat.getY(ev,\r
2716                                     pointerIndex);\r
2717                         final float yDiff = Math.abs(y - mLastMotionY);\r
2718                         if (xDiff > mTouchSlop && xDiff > yDiff) {\r
2719                             if (DEBUG)\r
2720                                 Log.v(TAG, getContext().getString(R.string.debug_start_drag));\r
2721                             mIsBeingDragged = true;\r
2722                             requestParentDisallowInterceptTouchEvent(true);\r
2723                             mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :\r
2724                                     mInitialMotionX - mTouchSlop;\r
2725                             mLastMotionY = y;\r
2726                             setScrollState(SCROLL_STATE_DRAGGING);\r
2727                             setScrollingCacheEnabled(true);\r
2728 \r
2729                             // Disallow Parent Intercept, just in case\r
2730                             ViewParent parent = getParent();\r
2731                             if (parent != null) {\r
2732                                 parent.requestDisallowInterceptTouchEvent(true);\r
2733                             }\r
2734                         }\r
2735                     }\r
2736                     // Not else! Note that mIsBeingDragged can be set above.\r
2737                     if (mIsBeingDragged) {\r
2738                         // Scroll to follow the motion event\r
2739                         final int activePointerIndex = MotionEventCompat.findPointerIndex(\r
2740                                 ev, mActivePointerId);\r
2741                         final float x = MotionEventCompat.getX(ev, activePointerIndex);\r
2742                         needsInvalidate |= performDrag(x, 0);\r
2743                     }\r
2744                     break;\r
2745                 case MotionEvent.ACTION_UP:\r
2746                     if (mIsBeingDragged) {\r
2747                         final VelocityTracker velocityTracker = mVelocityTracker;\r
2748                         velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);\r
2749                         int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(\r
2750                                 velocityTracker, mActivePointerId);\r
2751                         mPopulatePending = true;\r
2752                         final int width = getClientWidth();\r
2753                         final int scrollX = getScrollX();\r
2754                         final ItemInfo ii = infoForCurrentScrollPosition();\r
2755                         final float marginOffset = (float) mPageMargin / width;\r
2756                         final int currentPage = ii.position;\r
2757                         final float pageOffset = (((float) scrollX / width) - ii.offset)\r
2758                                 / (ii.widthFactor + marginOffset);\r
2759                         final int activePointerIndex =\r
2760                                 MotionEventCompat.findPointerIndex(ev, mActivePointerId);\r
2761                         final float x = MotionEventCompat.getX(ev, activePointerIndex);\r
2762                         final int totalDelta = (int) (x - mInitialMotionX);\r
2763                         int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,\r
2764                                 totalDelta, 0);\r
2765                         setCurrentItemInternal(nextPage, true, true, initialVelocity);\r
2766 \r
2767                         needsInvalidate = resetTouch();\r
2768                     }\r
2769                     break;\r
2770                 case MotionEvent.ACTION_CANCEL:\r
2771                     if (mIsBeingDragged) {\r
2772                         scrollToItem(mCurItem, true, 0, false);\r
2773                         needsInvalidate = resetTouch();\r
2774                     }\r
2775                     break;\r
2776                 case MotionEventCompat.ACTION_POINTER_DOWN: {\r
2777                     final int index = MotionEventCompat.getActionIndex(ev);\r
2778                     final float x = MotionEventCompat.getX(ev, index);\r
2779                     mLastMotionX = x;\r
2780                     mActivePointerId = MotionEventCompat.getPointerId(ev, index);\r
2781                     break;\r
2782                 }\r
2783                 case MotionEventCompat.ACTION_POINTER_UP:\r
2784                     onSecondaryPointerUp(ev);\r
2785                     mLastMotionX = MotionEventCompat.getX(ev,\r
2786                             MotionEventCompat.findPointerIndex(ev, mActivePointerId));\r
2787                     break;\r
2788             }\r
2789         } else {\r
2790             switch (action & MotionEventCompat.ACTION_MASK) {\r
2791                 case MotionEvent.ACTION_DOWN: {\r
2792                     mScroller.abortAnimation();\r
2793                     mPopulatePending = false;\r
2794                     populate();\r
2795 \r
2796                     // Remember where the motion event started\r
2797                     mLastMotionX = mInitialMotionX = ev.getX();\r
2798                     mLastMotionY = mInitialMotionY = ev.getY();\r
2799                     mActivePointerId = MotionEventCompat.getPointerId(ev, 0);\r
2800                     break;\r
2801                 }\r
2802                 case MotionEvent.ACTION_MOVE:\r
2803                     if (!mIsBeingDragged) {\r
2804                         final int pointerIndex =\r
2805                                 MotionEventCompat.findPointerIndex(ev, mActivePointerId);\r
2806                         final float y = MotionEventCompat.getY(ev, pointerIndex);\r
2807                         final float yDiff\r
2808                                 = Math.abs(y - mLastMotionY);\r
2809                         final float x\r
2810                                 = MotionEventCompat.getX(ev, pointerIndex);\r
2811                         final float xDiff = Math.abs(x - mLastMotionX);\r
2812 \r
2813                         if (yDiff > mTouchSlop && yDiff > xDiff) {\r
2814                             if (DEBUG)\r
2815                                 Log.v(TAG, getContext().getString(R.string.debug_start_drag));\r
2816                             mIsBeingDragged = true;\r
2817                             requestParentDisallowInterceptTouchEvent(true);\r
2818                             mLastMotionY = y - mInitialMotionY > 0 ? mInitialMotionY + mTouchSlop :\r
2819                                     mInitialMotionY - mTouchSlop;\r
2820                             mLastMotionX = x;\r
2821                             setScrollState(SCROLL_STATE_DRAGGING);\r
2822                             setScrollingCacheEnabled(true);\r
2823 \r
2824                             // Disallow Parent Intercept, just in case\r
2825                             ViewParent parent = getParent();\r
2826                             if (parent != null) {\r
2827                                 parent.requestDisallowInterceptTouchEvent(true);\r
2828                             }\r
2829                         }\r
2830                     }\r
2831                     // Not else! Note that mIsBeingDragged can be set above.\r
2832                     if (mIsBeingDragged) {\r
2833                         // Scroll to follow the motion event\r
2834                         final int activePointerIndex = MotionEventCompat.findPointerIndex(\r
2835                                 ev, mActivePointerId);\r
2836                         final float y = MotionEventCompat.getY(ev, activePointerIndex);\r
2837                         needsInvalidate |= performDrag(0, y);\r
2838                     }\r
2839                     break;\r
2840                 case MotionEvent.ACTION_UP:\r
2841                     if (mIsBeingDragged) {\r
2842                         final VelocityTracker velocityTracker = mVelocityTracker;\r
2843                         velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);\r
2844                         int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(\r
2845                                 velocityTracker, mActivePointerId);\r
2846                         mPopulatePending = true;\r
2847                         final int height = getClientHeight();\r
2848                         final int scrollY = getScrollY();\r
2849                         final ItemInfo ii = infoForCurrentScrollPosition();\r
2850                         final int currentPage = ii.position;\r
2851                         final float pageOffset =\r
2852                                 (((float) scrollY / height) - ii.offset) / ii.heightFactor;\r
2853                         final int activePointerIndex =\r
2854                                 MotionEventCompat.findPointerIndex(ev, mActivePointerId);\r
2855                         final float y = MotionEventCompat.getY(ev, activePointerIndex);\r
2856                         final int totalDelta = (int) (y - mInitialMotionY);\r
2857                         int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,\r
2858                                 0, totalDelta);\r
2859                         setCurrentItemInternal(nextPage, true, true, initialVelocity);\r
2860 \r
2861                         mActivePointerId = INVALID_POINTER;\r
2862                         endDrag();\r
2863                         needsInvalidate = mTopEdge.onRelease() | mBottomEdge.onRelease();\r
2864                     }\r
2865                     break;\r
2866                 case MotionEvent.ACTION_CANCEL:\r
2867                     if (mIsBeingDragged) {\r
2868                         scrollToItem(mCurItem, true, 0, false);\r
2869                         mActivePointerId = INVALID_POINTER;\r
2870                         endDrag();\r
2871                         needsInvalidate = mTopEdge.onRelease() | mBottomEdge.onRelease();\r
2872                     }\r
2873                     break;\r
2874                 case MotionEventCompat.ACTION_POINTER_DOWN: {\r
2875                     final int index = MotionEventCompat.getActionIndex(ev);\r
2876                     final float y = MotionEventCompat.getY(ev, index);\r
2877                     mLastMotionY = y;\r
2878                     mActivePointerId = MotionEventCompat.getPointerId(ev, index);\r
2879                     break;\r
2880                 }\r
2881                 case MotionEventCompat.ACTION_POINTER_UP:\r
2882                     onSecondaryPointerUp(ev);\r
2883                     mLastMotionY = MotionEventCompat.getY(ev,\r
2884                             MotionEventCompat.findPointerIndex(ev, mActivePointerId));\r
2885                     break;\r
2886             }\r
2887         }\r
2888         if (needsInvalidate) {\r
2889             ViewCompat.postInvalidateOnAnimation(this);\r
2890         }\r
2891         return true;\r
2892     }\r
2893 \r
2894     private boolean resetTouch() {\r
2895         boolean needsInvalidate;\r
2896         mActivePointerId = INVALID_POINTER;\r
2897         endDrag();\r
2898         needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();\r
2899         return needsInvalidate;\r
2900     }\r
2901 \r
2902     private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {\r
2903         final ViewParent parent = getParent();\r
2904         if (parent != null) {\r
2905             parent.requestDisallowInterceptTouchEvent(disallowIntercept);\r
2906         }\r
2907     }\r
2908 \r
2909     private boolean performDrag(float x, float y) {\r
2910         boolean needsInvalidate = false;\r
2911         if (isHorizontal()) {\r
2912             final float deltaX = mLastMotionX - x;\r
2913             mLastMotionX = x;\r
2914 \r
2915             float oldScrollX = getScrollX();\r
2916             float scrollX = oldScrollX + deltaX;\r
2917             final int width = getClientWidth();\r
2918 \r
2919             float leftBound = width * mFirstOffset;\r
2920             float rightBound = width * mLastOffset;\r
2921             boolean leftAbsolute = true;\r
2922             boolean rightAbsolute = true;\r
2923 \r
2924             final ItemInfo firstItem = mItems.get(0);\r
2925             final ItemInfo lastItem = mItems.get(mItems.size() - 1);\r
2926             if (firstItem.position != 0) {\r
2927                 leftAbsolute = false;\r
2928                 leftBound = firstItem.offset * width;\r
2929             }\r
2930             if (lastItem.position != mAdapter.getCount() - 1) {\r
2931                 rightAbsolute = false;\r
2932                 rightBound = lastItem.offset * width;\r
2933             }\r
2934 \r
2935             if (scrollX < leftBound) {\r
2936                 if (leftAbsolute) {\r
2937                     float over = leftBound - scrollX;\r
2938                     needsInvalidate = mLeftEdge.onPull(Math.abs(over) / width);\r
2939                 }\r
2940                 scrollX = leftBound;\r
2941             } else if (scrollX > rightBound) {\r
2942                 if (rightAbsolute) {\r
2943                     float over = scrollX - rightBound;\r
2944                     needsInvalidate = mRightEdge.onPull(Math.abs(over) / width);\r
2945                 }\r
2946                 scrollX = rightBound;\r
2947             }\r
2948             // Don't lose the rounded component\r
2949             mLastMotionX += scrollX - (int) scrollX;\r
2950             scrollTo((int) scrollX, getScrollY());\r
2951             pageScrolled((int) scrollX, 0);\r
2952         } else {\r
2953 \r
2954             final float deltaY = mLastMotionY - y;\r
2955             mLastMotionY = y;\r
2956 \r
2957             float oldScrollY = getScrollY();\r
2958             float scrollY = oldScrollY + deltaY;\r
2959             final int height = getClientHeight();\r
2960 \r
2961             float topBound = height * mFirstOffset;\r
2962             float bottomBound = height * mLastOffset;\r
2963             boolean topAbsolute = true;\r
2964             boolean bottomAbsolute = true;\r
2965 \r
2966             final ItemInfo firstItem = mItems.get(0);\r
2967             final ItemInfo lastItem = mItems.get(mItems.size() - 1);\r
2968             if (firstItem.position != 0) {\r
2969                 topAbsolute = false;\r
2970                 topBound = firstItem.offset * height;\r
2971             }\r
2972             if (lastItem.position != mAdapter.getCount() - 1) {\r
2973                 bottomAbsolute = false;\r
2974                 bottomBound = lastItem.offset * height;\r
2975             }\r
2976 \r
2977             if (scrollY < topBound) {\r
2978                 if (topAbsolute) {\r
2979                     float over = topBound - scrollY;\r
2980                     needsInvalidate = mTopEdge.onPull(Math.abs(over) / height);\r
2981                 }\r
2982                 scrollY = topBound;\r
2983             } else if (scrollY > bottomBound) {\r
2984                 if (bottomAbsolute) {\r
2985                     float over = scrollY - bottomBound;\r
2986                     needsInvalidate = mBottomEdge.onPull(Math.abs(over) / height);\r
2987                 }\r
2988                 scrollY = bottomBound;\r
2989             }\r
2990             // Don't lose the rounded component\r
2991             mLastMotionX += scrollY - (int) scrollY;\r
2992             scrollTo(getScrollX(), (int) scrollY);\r
2993             pageScrolled(0, (int) scrollY);\r
2994         }\r
2995 \r
2996         return needsInvalidate;\r
2997     }\r
2998 \r
2999     /**\r
3000      * @return Info about the page at the current scroll position.\r
3001      * This can be synthetic for a missing middle page; the 'object' field can be null.\r
3002      */\r
3003     private ItemInfo infoForCurrentScrollPosition() {\r
3004         ItemInfo lastItem = null;\r
3005         if (isHorizontal()) {\r
3006             final int width = getClientWidth();\r
3007             final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0;\r
3008             final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;\r
3009             int lastPos = -1;\r
3010             float lastOffset = 0.f;\r
3011             float lastWidth = 0.f;\r
3012             boolean first = true;\r
3013 \r
3014             for (int i = 0; i < mItems.size(); i++) {\r
3015                 ItemInfo ii = mItems.get(i);\r
3016                 float offset;\r
3017                 if (!first && ii.position != lastPos + 1) {\r
3018                     // Create a synthetic item for a missing page.\r
3019                     ii = mTempItem;\r
3020                     ii.offset = lastOffset + lastWidth + marginOffset;\r
3021                     ii.position = lastPos + 1;\r
3022                     ii.widthFactor = mAdapter.getPageWidth(ii.position);\r
3023                     i--;\r
3024                 }\r
3025                 offset = ii.offset;\r
3026 \r
3027                 final float leftBound = offset;\r
3028                 final float rightBound = offset + ii.widthFactor + marginOffset;\r
3029                 if (first || scrollOffset >= leftBound) {\r
3030                     if (scrollOffset < rightBound || i == mItems.size() - 1) {\r
3031                         return ii;\r
3032                     }\r
3033                 } else {\r
3034                     return lastItem;\r
3035                 }\r
3036                 first = false;\r
3037                 lastPos = ii.position;\r
3038                 lastOffset = offset;\r
3039                 lastWidth = ii.widthFactor;\r
3040                 lastItem = ii;\r
3041             }\r
3042         } else {\r
3043             final int height = getClientHeight();\r
3044             final float scrollOffset = height > 0 ? (float) getScrollY() / height : 0;\r
3045             final float marginOffset = height > 0 ? (float) mPageMargin / height : 0;\r
3046             int lastPos = -1;\r
3047             float lastOffset = 0.f;\r
3048             float lastHeight = 0.f;\r
3049             boolean first = true;\r
3050 \r
3051             for (int i = 0; i < mItems.size(); i++) {\r
3052                 ItemInfo ii = mItems.get(i);\r
3053                 float offset;\r
3054                 if (!first && ii.position != lastPos + 1) {\r
3055                     // Create a synthetic item for a missing page.\r
3056                     ii = mTempItem;\r
3057                     ii.offset = lastOffset + lastHeight + marginOffset;\r
3058                     ii.position = lastPos + 1;\r
3059                     ii.heightFactor = mAdapter.getPageWidth(ii.position);\r
3060                     i--;\r
3061                 }\r
3062                 offset = ii.offset;\r
3063 \r
3064                 final float topBound = offset;\r
3065                 final float bottomBound = offset + ii.heightFactor + marginOffset;\r
3066                 if (first || scrollOffset >= topBound) {\r
3067                     if (scrollOffset < bottomBound || i == mItems.size() - 1) {\r
3068                         return ii;\r
3069                     }\r
3070                 } else {\r
3071                     return lastItem;\r
3072                 }\r
3073                 first = false;\r
3074                 lastPos = ii.position;\r
3075                 lastOffset = offset;\r
3076                 lastHeight = ii.heightFactor;\r
3077                 lastItem = ii;\r
3078             }\r
3079         }\r
3080 \r
3081         return lastItem;\r
3082     }\r
3083 \r
3084     private int determineTargetPage(int currentPage, float pageOffset,\r
3085                                         int velocity, int deltaX, int deltaY) {\r
3086         int targetPage;\r
3087         if (isHorizontal()) {\r
3088             if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {\r
3089                 targetPage = velocity > 0 ? currentPage : currentPage + 1;\r
3090             } else {\r
3091                 final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;\r
3092                 targetPage = (int) (currentPage + pageOffset + truncator);\r
3093             }\r
3094         } else {\r
3095             if (Math.abs(deltaY) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {\r
3096                 targetPage = velocity > 0 ? currentPage : currentPage + 1;\r
3097             } else {\r
3098                 final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;\r
3099                 targetPage = (int) (currentPage + pageOffset + truncator);\r
3100             }\r
3101         }\r
3102 \r
3103         if (mItems.size() > 0) {\r
3104             final ItemInfo firstItem = mItems.get(0);\r
3105             final ItemInfo lastItem = mItems.get(mItems.size() - 1);\r
3106 \r
3107             // Only let the user target pages we have items for\r
3108             targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));\r
3109         }\r
3110 \r
3111         return targetPage;\r
3112     }\r
3113 \r
3114     @Override\r
3115     public void draw(Canvas canvas) {\r
3116         super.draw(canvas);\r
3117         boolean needsInvalidate = false;\r
3118 \r
3119         final int overScrollMode = ViewCompat.getOverScrollMode(this);\r
3120         if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||\r
3121                 (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&\r
3122                         mAdapter != null && mAdapter.getCount() > 1)) {\r
3123             if (isHorizontal()) {\r
3124                 if (!mLeftEdge.isFinished()) {\r
3125                     final int restoreCount = canvas.save();\r
3126                     final int height = getHeight() - getPaddingTop() - getPaddingBottom();\r
3127                     final int width = getWidth();\r
3128 \r
3129                     canvas.rotate(270);\r
3130                     canvas.translate(-height + getPaddingTop(), mFirstOffset * width);\r
3131                     mLeftEdge.setSize(height, width);\r
3132                     needsInvalidate |= mLeftEdge.draw(canvas);\r
3133                     canvas.restoreToCount(restoreCount);\r
3134                 }\r
3135                 if (!mRightEdge.isFinished()) {\r
3136                     final int restoreCount = canvas.save();\r
3137                     final int width = getWidth();\r
3138                     final int height = getHeight() - getPaddingTop() - getPaddingBottom();\r
3139 \r
3140                     canvas.rotate(90);\r
3141                     canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width);\r
3142                     mRightEdge.setSize(height, width);\r
3143                     needsInvalidate |= mRightEdge.draw(canvas);\r
3144                     canvas.restoreToCount(restoreCount);\r
3145                 } else {\r
3146                     mLeftEdge.finish();\r
3147                     mRightEdge.finish();\r
3148                 }\r
3149             } else {\r
3150                 if (!mTopEdge.isFinished()) {\r
3151                     final int restoreCount = canvas.save();\r
3152                     final int height = getHeight();\r
3153                     final int width = getWidth() - getPaddingLeft() - getPaddingRight();\r
3154 \r
3155                     canvas.translate(getPaddingLeft(), mFirstOffset * height);\r
3156                     mTopEdge.setSize(width, height);\r
3157                     needsInvalidate |= mTopEdge.draw(canvas);\r
3158                     canvas.restoreToCount(restoreCount);\r
3159                 }\r
3160                 if (!mBottomEdge.isFinished()) {\r
3161                     final int restoreCount = canvas.save();\r
3162                     final int height = getHeight();\r
3163                     final int width = getWidth() - getPaddingLeft() - getPaddingRight();\r
3164 \r
3165                     canvas.rotate(180);\r
3166                     canvas.translate(-width - getPaddingLeft(), -(mLastOffset + 1) * height);\r
3167                     mBottomEdge.setSize(width, height);\r
3168                     needsInvalidate |= mBottomEdge.draw(canvas);\r
3169                     canvas.restoreToCount(restoreCount);\r
3170                 } else {\r
3171                     mTopEdge.finish();\r
3172                     mBottomEdge.finish();\r
3173                 }\r
3174             }\r
3175         }\r
3176 \r
3177 \r
3178         if (needsInvalidate) {\r
3179             // Keep animating\r
3180             ViewCompat.postInvalidateOnAnimation(this);\r
3181         }\r
3182     }\r
3183 \r
3184     @Override\r
3185     protected void onDraw(Canvas canvas) {\r
3186         super.onDraw(canvas);\r
3187 \r
3188         // Draw the margin drawable between pages if needed.\r
3189         if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) {\r
3190             if (isHorizontal()) {\r
3191                 final int scrollX = getScrollX();\r
3192                 final int width = getWidth();\r
3193 \r
3194                 final float marginOffset = (float) mPageMargin / width;\r
3195                 int itemIndex = 0;\r
3196                 ItemInfo ii = mItems.get(0);\r
3197                 float offset = ii.offset;\r
3198                 final int itemCount = mItems.size();\r
3199                 final int firstPos = ii.position;\r
3200                 final int lastPos = mItems.get(itemCount - 1).position;\r
3201                 for (int pos = firstPos; pos < lastPos; pos++) {\r
3202                     while (pos > ii.position && itemIndex < itemCount) {\r
3203                         ii = mItems.get(++itemIndex);\r
3204                     }\r
3205 \r
3206                     float drawAt;\r
3207                     if (pos == ii.position) {\r
3208                         drawAt = (ii.offset + ii.widthFactor) * width;\r
3209                         offset = ii.offset + ii.widthFactor + marginOffset;\r
3210                     } else {\r
3211                         float widthFactor = mAdapter.getPageWidth(pos);\r
3212                         drawAt = (offset + widthFactor) * width;\r
3213                         offset += widthFactor + marginOffset;\r
3214                     }\r
3215 \r
3216                     if (drawAt + mPageMargin > scrollX) {\r
3217                         mMarginDrawable.setBounds(Math.round(drawAt), mTopPageBounds,\r
3218                                 Math.round(drawAt + mPageMargin), mBottomPageBounds);\r
3219                         mMarginDrawable.draw(canvas);\r
3220                     }\r
3221 \r
3222                     if (drawAt > scrollX + width) {\r
3223                         break; // No more visible, no sense in continuing\r
3224                     }\r
3225                 }\r
3226             } else {\r
3227                 final int scrollY = getScrollY();\r
3228                 final int height = getHeight();\r
3229 \r
3230                 final float marginOffset = (float) mPageMargin / height;\r
3231                 int itemIndex = 0;\r
3232                 ItemInfo ii = mItems.get(0);\r
3233                 float offset = ii.offset;\r
3234                 final int itemCount = mItems.size();\r
3235                 final int firstPos = ii.position;\r
3236                 final int lastPos = mItems.get(itemCount - 1).position;\r
3237                 for (int pos = firstPos; pos < lastPos; pos++) {\r
3238                     while (pos > ii.position && itemIndex < itemCount) {\r
3239                         ii = mItems.get(++itemIndex);\r
3240                     }\r
3241 \r
3242                     float drawAt;\r
3243                     if (pos == ii.position) {\r
3244                         drawAt = (ii.offset + ii.heightFactor) * height;\r
3245                         offset = ii.offset + ii.heightFactor + marginOffset;\r
3246                     } else {\r
3247                         float heightFactor = mAdapter.getPageWidth(pos);\r
3248                         drawAt = (offset + heightFactor) * height;\r
3249                         offset += heightFactor + marginOffset;\r
3250                     }\r
3251 \r
3252                     if (drawAt + mPageMargin > scrollY) {\r
3253                         mMarginDrawable.setBounds(mLeftPageBounds, (int) drawAt,\r
3254                                 mRightPageBounds, (int) (drawAt + mPageMargin + 0.5f));\r
3255                         mMarginDrawable.draw(canvas);\r
3256                     }\r
3257 \r
3258                     if (drawAt > scrollY + height) {\r
3259                         break; // No more visible, no sense in continuing\r
3260                     }\r
3261                 }\r
3262             }\r
3263         }\r
3264     }\r
3265 \r
3266     /**\r
3267      * Start a fake drag of the pager.\r
3268      * <p>\r
3269      * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager\r
3270      * with the touch scrolling of another view, while still letting the ViewPager\r
3271      * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)\r
3272      * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call\r
3273      * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.\r
3274      * <p>\r
3275      * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag\r
3276      * is already in progress, this method will return false.\r
3277      *\r
3278      * @return true if the fake drag began successfully, false if it could not be started.\r
3279      * @see #fakeDragBy(float)\r
3280      * @see #endFakeDrag()\r
3281      */\r
3282     public boolean beginFakeDrag() {\r
3283         if (mIsBeingDragged) {\r
3284             return false;\r
3285         }\r
3286         mFakeDragging = true;\r
3287         setScrollState(SCROLL_STATE_DRAGGING);\r
3288         if (isHorizontal()) {\r
3289             mInitialMotionX = mLastMotionX = 0;\r
3290         } else {\r
3291             mInitialMotionY = mLastMotionY = 0;\r
3292         }\r
3293         if (mVelocityTracker == null) {\r
3294             mVelocityTracker = VelocityTracker.obtain();\r
3295         } else {\r
3296             mVelocityTracker.clear();\r
3297         }\r
3298         final long time = SystemClock.uptimeMillis();\r
3299         final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);\r
3300         mVelocityTracker.addMovement(ev);\r
3301         ev.recycle();\r
3302         mFakeDragBeginTime = time;\r
3303         return true;\r
3304     }\r
3305 \r
3306     /**\r
3307      * End a fake drag of the pager.\r
3308      *\r
3309      * @see #beginFakeDrag()\r
3310      * @see #endFakeDrag()\r
3311      */\r
3312     public void endFakeDrag() {\r
3313         if (!mFakeDragging) {\r
3314             throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");\r
3315         }\r
3316 \r
3317         if (mAdapter != null) {\r
3318             if (isHorizontal()) {\r
3319                 final VelocityTracker velocityTracker = mVelocityTracker;\r
3320                 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);\r
3321                 int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(\r
3322                         velocityTracker, mActivePointerId);\r
3323                 mPopulatePending = true;\r
3324                 final int width = getClientWidth();\r
3325                 final int scrollX = getScrollX();\r
3326                 final ItemInfo ii = infoForCurrentScrollPosition();\r
3327                 final int currentPage = ii.position;\r
3328                 final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;\r
3329                 final int totalDelta = (int) (mLastMotionX - mInitialMotionX);\r
3330                 int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,\r
3331                         totalDelta, 0);\r
3332                 setCurrentItemInternal(nextPage, true, true, initialVelocity);\r
3333             } else {\r
3334                 final VelocityTracker velocityTracker = mVelocityTracker;\r
3335                 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);\r
3336                 int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(\r
3337                         velocityTracker, mActivePointerId);\r
3338                 mPopulatePending = true;\r
3339                 final int height = getClientHeight();\r
3340                 final int scrollY = getScrollY();\r
3341                 final ItemInfo ii = infoForCurrentScrollPosition();\r
3342                 final int currentPage = ii.position;\r
3343                 final float pageOffset = (((float) scrollY / height) - ii.offset) / ii.heightFactor;\r
3344                 final int totalDelta = (int) (mLastMotionY - mInitialMotionY);\r
3345                 int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,\r
3346                         0, totalDelta);\r
3347                 setCurrentItemInternal(nextPage, true, true, initialVelocity);\r
3348             }\r
3349         }\r
3350         endDrag();\r
3351 \r
3352         mFakeDragging = false;\r
3353     }\r
3354 \r
3355     /**\r
3356      * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.\r
3357      *\r
3358      * @param xOffset Offset in pixels to drag by.\r
3359      * @see #beginFakeDrag()\r
3360      * @see #endFakeDrag()\r
3361      */\r
3362     public void fakeDragBy(float xOffset, float yOffset) {\r
3363         MotionEvent ev = null;\r
3364         if (!mFakeDragging) {\r
3365             throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");\r
3366         }\r
3367 \r
3368         if (mAdapter == null) {\r
3369             return;\r
3370         }\r
3371 \r
3372         if (isHorizontal()) {\r
3373             mLastMotionX += xOffset;\r
3374 \r
3375             float oldScrollX = getScrollX();\r
3376             float scrollX = oldScrollX - xOffset;\r
3377             final int width = getClientWidth();\r
3378 \r
3379             float leftBound = width * mFirstOffset;\r
3380             float rightBound = width * mLastOffset;\r
3381 \r
3382             final ItemInfo firstItem = mItems.get(0);\r
3383             final ItemInfo lastItem = mItems.get(mItems.size() - 1);\r
3384             if (firstItem.position != 0) {\r
3385                 leftBound = firstItem.offset * width;\r
3386             }\r
3387             if (lastItem.position != mAdapter.getCount() - 1) {\r
3388                 rightBound = lastItem.offset * width;\r
3389             }\r
3390 \r
3391             if (scrollX < leftBound) {\r
3392                 scrollX = leftBound;\r
3393             } else if (scrollX > rightBound) {\r
3394                 scrollX = rightBound;\r
3395             }\r
3396             // Don't lose the rounded component\r
3397             mLastMotionX += scrollX - (int) scrollX;\r
3398             scrollTo((int) scrollX, getScrollY());\r
3399             pageScrolled((int) scrollX, 0);\r
3400 \r
3401             // Synthesize an event for the VelocityTracker.\r
3402             final long time = SystemClock.uptimeMillis();\r
3403             ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,\r
3404                     mLastMotionX, 0, 0);\r
3405         } else {\r
3406             mLastMotionY += yOffset;\r
3407 \r
3408             float oldScrollY = getScrollY();\r
3409             float scrollY = oldScrollY - yOffset;\r
3410             final int height = getClientHeight();\r
3411 \r
3412             float topBound = height * mFirstOffset;\r
3413             float bottomBound = height * mLastOffset;\r
3414 \r
3415             final ItemInfo firstItem = mItems.get(0);\r
3416             final ItemInfo lastItem = mItems.get(mItems.size() - 1);\r
3417             if (firstItem.position != 0) {\r
3418                 topBound = firstItem.offset * height;\r
3419             }\r
3420             if (lastItem.position != mAdapter.getCount() - 1) {\r
3421                 bottomBound = lastItem.offset * height;\r
3422             }\r
3423 \r
3424             if (scrollY < topBound) {\r
3425                 scrollY = topBound;\r
3426             } else if (scrollY > bottomBound) {\r
3427                 scrollY = bottomBound;\r
3428             }\r
3429             // Don't lose the rounded component\r
3430             mLastMotionY += scrollY - (int) scrollY;\r
3431             scrollTo(getScrollX(), (int) scrollY);\r
3432             pageScrolled(0, (int) scrollY);\r
3433 \r
3434             // Synthesize an event for the VelocityTracker.\r
3435             final long time = SystemClock.uptimeMillis();\r
3436             ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,\r
3437                     0, mLastMotionY, 0);\r
3438         }\r
3439         mVelocityTracker.addMovement(ev);\r
3440         ev.recycle();\r
3441     }\r
3442 \r
3443     /**\r
3444      * Returns true if a fake drag is in progress.\r
3445      *\r
3446      * @return true if currently in a fake drag, false otherwise.\r
3447      * @see #beginFakeDrag()\r
3448      * @see #fakeDragBy(float)\r
3449      * @see #endFakeDrag()\r
3450      */\r
3451     public boolean isFakeDragging() {\r
3452         return mFakeDragging;\r
3453     }\r
3454 \r
3455     private void onSecondaryPointerUp(MotionEvent ev) {\r
3456         final int pointerIndex = MotionEventCompat.getActionIndex(ev);\r
3457         final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);\r
3458         if (pointerId == mActivePointerId) {\r
3459             // This was our active pointer going up. Choose a new\r
3460             // active pointer and adjust accordingly.\r
3461             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;\r
3462             if (isHorizontal()) {\r
3463                 mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);\r
3464             } else {\r
3465                 mLastMotionY = MotionEventCompat.getY(ev, newPointerIndex);\r
3466             }\r
3467             mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);\r
3468             if (mVelocityTracker != null) {\r
3469                 mVelocityTracker.clear();\r
3470             }\r
3471         }\r
3472     }\r
3473 \r
3474     private void endDrag() {\r
3475         mIsBeingDragged = false;\r
3476         mIsUnableToDrag = false;\r
3477 \r
3478         if (mVelocityTracker != null) {\r
3479             mVelocityTracker.recycle();\r
3480             mVelocityTracker = null;\r
3481         }\r
3482     }\r
3483 \r
3484     private void setScrollingCacheEnabled(boolean enabled) {\r
3485         if (mScrollingCacheEnabled != enabled) {\r
3486             mScrollingCacheEnabled = enabled;\r
3487             if (USE_CACHE) {\r
3488                 final int size = getChildCount();\r
3489                 for (int i = 0; i < size; ++i) {\r
3490                     final View child = getChildAt(i);\r
3491                     if (child.getVisibility() != GONE) {\r
3492                         child.setDrawingCacheEnabled(enabled);\r
3493                     }\r
3494                 }\r
3495             }\r
3496         }\r
3497     }\r
3498 \r
3499     public boolean canScrollHorizontally(int direction) {\r
3500         if (mAdapter == null) {\r
3501             return false;\r
3502         }\r
3503 \r
3504         final int width = getClientWidth();\r
3505         final int scrollX = getScrollX();\r
3506         if (direction < 0) {\r
3507             return (scrollX > (int) (width * mFirstOffset));\r
3508         } else if (direction > 0) {\r
3509             return (scrollX < (int) (width * mLastOffset));\r
3510         } else {\r
3511             return false;\r
3512         }\r
3513     }\r
3514 \r
3515     public boolean internalCanScrollVertically(int direction) {\r
3516         if (mAdapter == null) {\r
3517             return false;\r
3518         }\r
3519 \r
3520         final int height = getClientHeight();\r
3521         final int scrollY = getScrollY();\r
3522         if (direction < 0) {\r
3523             return (scrollY > (int) (height * mFirstOffset));\r
3524         } else if (direction > 0) {\r
3525             return (scrollY < (int) (height * mLastOffset));\r
3526         } else {\r
3527             return false;\r
3528         }\r
3529     }\r
3530 \r
3531     /**\r
3532      * Tests scrollability within child views of v given a delta of dx.\r
3533      *\r
3534      * @param v      View to test for horizontal scrollability\r
3535      * @param checkV Whether the view v passed should itself be checked for scrollability (true),\r
3536      *               or just its children (false).\r
3537      * @param dx     Delta scrolled in pixels\r
3538      * @param x      X coordinate of the active touch point\r
3539      * @param y      Y coordinate of the active touch point\r
3540      * @return true if child views of v can be scrolled by delta of dx.\r
3541      */\r
3542     protected boolean canScroll(View v, boolean checkV, int dx, int dy, int x, int y) {\r
3543         if (v instanceof ViewGroup) {\r
3544             if (isHorizontal()) {\r
3545                 final ViewGroup group = (ViewGroup) v;\r
3546                 final int scrollX = v.getScrollX();\r
3547                 final int scrollY = v.getScrollY();\r
3548                 final int count = group.getChildCount();\r
3549                 // Count backwards - let topmost views consume scroll distance first.\r
3550                 for (int i = count - 1; i >= 0; i--) {\r
3551                     // TODO: Add versioned support here for transformed views.\r
3552                     // This will not work for transformed views in Honeycomb+\r
3553                     final View child = group.getChildAt(i);\r
3554                     if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&\r
3555                             y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&\r
3556                             canScroll(child, true, dx, 0, x + scrollX - child.getLeft(),\r
3557                                     y + scrollY - child.getTop())) {\r
3558                         return true;\r
3559                     }\r
3560                 }\r
3561                 return checkV && ViewCompat.canScrollHorizontally(v, -dx);\r
3562             } else {\r
3563                 final ViewGroup group = (ViewGroup) v;\r
3564                 final int scrollX = v.getScrollX();\r
3565                 final int scrollY = v.getScrollY();\r
3566                 final int count = group.getChildCount();\r
3567                 // Count backwards - let topmost views consume scroll distance first.\r
3568                 for (int i = count - 1; i >= 0; i--) {\r
3569                     // TODO: Add versioned support here for transformed views.\r
3570                     // This will not work for transformed views in Honeycomb+\r
3571                     final View child = group.getChildAt(i);\r
3572                     if (y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&\r
3573                             x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&\r
3574                             canScroll(child, true, 0, dy, x + scrollX - child.getLeft(),\r
3575                                     y + scrollY - child.getTop())) {\r
3576                         return true;\r
3577                     }\r
3578                 }\r
3579 \r
3580                 return checkV && ViewCompat.canScrollVertically(v, -dy);\r
3581 \r
3582             }\r
3583         }\r
3584         return false;\r
3585     }\r
3586 \r
3587     @Override\r
3588     public boolean dispatchKeyEvent(KeyEvent event) {\r
3589         // Let the focused view and/or our descendants get the key first\r
3590         return super.dispatchKeyEvent(event) || executeKeyEvent(event);\r
3591     }\r
3592 \r
3593     /**\r
3594      * You can call this function yourself to have the scroll view perform\r
3595      * scrolling from a key event, just as if the event had been dispatched to\r
3596      * it by the view hierarchy.\r
3597      *\r
3598      * @param event The key event to execute.\r
3599      * @return Return true if the event was handled, else false.\r
3600      */\r
3601     public boolean executeKeyEvent(KeyEvent event) {\r
3602         boolean handled = false;\r
3603         if (event.getAction() == KeyEvent.ACTION_DOWN) {\r
3604             switch (event.getKeyCode()) {\r
3605                 case KeyEvent.KEYCODE_DPAD_LEFT:\r
3606                     handled = arrowScroll(FOCUS_LEFT);\r
3607                     break;\r
3608                 case KeyEvent.KEYCODE_DPAD_RIGHT:\r
3609                     handled = arrowScroll(FOCUS_RIGHT);\r
3610                     break;\r
3611                 case KeyEvent.KEYCODE_TAB:\r
3612                     if (Build.VERSION.SDK_INT >= 11) {\r
3613                         // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD\r
3614                         // before Android 3.0. Ignore the tab key on those devices.\r
3615                         if (KeyEvent.metaStateHasNoModifiers(event.getMetaState())) {\r
3616                             handled = arrowScroll(FOCUS_FORWARD);\r
3617                         } else if (KeyEvent.metaStateHasNoModifiers(event.getMetaState())) {\r
3618                             handled = arrowScroll(FOCUS_BACKWARD);\r
3619                         }\r
3620                     }\r
3621                     break;\r
3622             }\r
3623         }\r
3624         return handled;\r
3625     }\r
3626 \r
3627     public boolean arrowScroll(int direction) {\r
3628         View currentFocused = findFocus();\r
3629         if (currentFocused == this) {\r
3630             currentFocused = null;\r
3631         } else if (currentFocused != null) {\r
3632             boolean isChild = false;\r
3633             for (ViewParent parent\r
3634                     = currentFocused.getParent();\r
3635                         parent instanceof ViewGroup;\r
3636                             parent = parent.getParent()) {\r
3637                 if (parent == this) {\r
3638                     isChild = true;\r
3639                     break;\r
3640                 }\r
3641             }\r
3642             if (!isChild) {\r
3643                 // This would cause the focus search down below to fail in fun ways.\r
3644                 final StringBuilder sb = new StringBuilder();\r
3645                 sb.append(currentFocused.getClass().getSimpleName());\r
3646                 for (ViewParent parent\r
3647                         = currentFocused.getParent();\r
3648                                 parent instanceof ViewGroup;\r
3649                                    parent = parent.getParent()) {\r
3650                     sb.append(" => ").append(parent.getClass().getSimpleName());\r
3651                 }\r
3652                 Log.e(TAG, "arrowScroll tried to find focus based on non-child " +\r
3653                         "current focused view " + sb.toString());\r
3654                 currentFocused = null;\r
3655             }\r
3656         }\r
3657 \r
3658         boolean handled = false;\r
3659 \r
3660         View nextFocused\r
3661                     = FocusFinder.getInstance().findNextFocus(this, currentFocused,\r
3662                         direction);\r
3663         if (nextFocused != null && nextFocused != currentFocused) {\r
3664             if (isHorizontal()) {\r
3665                 if (direction == View.FOCUS_LEFT) {\r
3666                     // If there is nothing\r
3667                     // to the left, or this is causing us to\r
3668                     // jump to the right,\r
3669                     // then what we really want to do is page left.\r
3670                     final int nextLeft\r
3671                             = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;\r
3672                     final int currLeft\r
3673                             = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;\r
3674                     if (currentFocused != null && nextLeft >= currLeft) {\r
3675                         handled = pageLeft();\r
3676                     } else {\r
3677                         handled = nextFocused.requestFocus();\r
3678                     }\r
3679                 } else if (direction == View.FOCUS_RIGHT) {\r
3680                     // If there is nothing to the right,\r
3681                     // or this is causing us to\r
3682                     // jump to the left,\r
3683                     // then what we really\r
3684                     // want to do is page right.\r
3685                     final int nextLeft\r
3686                             = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;\r
3687                     final int currLeft\r
3688                             = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;\r
3689                     if (currentFocused != null && nextLeft <= currLeft) {\r
3690                         handled = pageRight();\r
3691                     } else {\r
3692                         handled = nextFocused.requestFocus();\r
3693                     }\r
3694                 } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {\r
3695                     // Trying to move left and nothing there; try to page.\r
3696                     handled = pageLeft();\r
3697                 } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {\r
3698                     // Trying to move right and nothing there; try to page.\r
3699                     handled = pageRight();\r
3700                 }\r
3701             } else {\r
3702                 if (direction == View.FOCUS_UP) {\r
3703                     // If there is nothing to the left,\r
3704                     // or this is causing us to\r
3705                     // jump to the right,\r
3706                     // then what we really want to do is page left.\r
3707                     final int nextTop\r
3708                             = getChildRectInPagerCoordinates(mTempRect, nextFocused).top;\r
3709                     final int currTop\r
3710                             = getChildRectInPagerCoordinates(mTempRect, currentFocused).top;\r
3711                     if (currentFocused != null && nextTop >= currTop) {\r
3712                         handled = pageUp();\r
3713                     } else {\r
3714                         handled = nextFocused.requestFocus();\r
3715                     }\r
3716                 } else if (direction == View.FOCUS_DOWN) {\r
3717                     final int nextDown =\r
3718                             getChildRectInPagerCoordinates(mTempRect, nextFocused).bottom;\r
3719                     final int currDown =\r
3720                             getChildRectInPagerCoordinates(mTempRect, currentFocused).bottom;\r
3721                     if (currentFocused != null && nextDown <= currDown) {\r
3722                         handled = pageDown();\r
3723                     } else {\r
3724                         handled = nextFocused.requestFocus();\r
3725                     }\r
3726                 } else if (direction == FOCUS_UP || direction == FOCUS_BACKWARD) {\r
3727                     // Trying to move left and nothing there; try to page.\r
3728                     handled = pageUp();\r
3729                 } else if (direction == FOCUS_DOWN || direction == FOCUS_FORWARD) {\r
3730                     // Trying to move right and nothing there; try to page.\r
3731                     handled = pageDown();\r
3732 \r
3733                 }\r
3734             }\r
3735             if (handled) {\r
3736                 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));\r
3737             }\r
3738             return handled;\r
3739         }\r
3740         return handled;\r
3741     }\r
3742 \r
3743 \r
3744     private Rect getChildRectInPagerCoordinates(Rect outRect, View child) {\r
3745         if (outRect == null) {\r
3746             outRect = new Rect();\r
3747         }\r
3748         if (child == null) {\r
3749             outRect.set(0, 0, 0, 0);\r
3750             return outRect;\r
3751         }\r
3752         outRect.left = child.getLeft();\r
3753         outRect.right = child.getRight();\r
3754         outRect.top = child.getTop();\r
3755         outRect.bottom = child.getBottom();\r
3756 \r
3757         ViewParent parent = child.getParent();\r
3758         while (parent instanceof ViewGroup && parent != this) {\r
3759             final ViewGroup group = (ViewGroup) parent;\r
3760             outRect.left += group.getLeft();\r
3761             outRect.right += group.getRight();\r
3762             outRect.top += group.getTop();\r
3763             outRect.bottom += group.getBottom();\r
3764 \r
3765             parent = group.getParent();\r
3766         }\r
3767         return outRect;\r
3768     }\r
3769 \r
3770     boolean pageLeft() {\r
3771         if (mCurItem > 0) {\r
3772             setCurrentItem(mCurItem - 1, true);\r
3773             return true;\r
3774         }\r
3775         return false;\r
3776     }\r
3777 \r
3778     boolean pageRight() {\r
3779         if (mAdapter != null && mCurItem < (mAdapter.getCount() - 1)) {\r
3780             setCurrentItem(mCurItem + 1, true);\r
3781             return true;\r
3782         }\r
3783         return false;\r
3784     }\r
3785 \r
3786     boolean pageUp() {\r
3787         if (mCurItem > 0) {\r
3788             setCurrentItem(mCurItem - 1, true);\r
3789             return true;\r
3790         }\r
3791         return false;\r
3792     }\r
3793 \r
3794     boolean pageDown() {\r
3795         if (mAdapter != null && mCurItem < (mAdapter.getCount() - 1)) {\r
3796             setCurrentItem(mCurItem + 1, true);\r
3797             return true;\r
3798         }\r
3799         return false;\r
3800     }\r
3801 \r
3802     /**\r
3803      * We only want the current page that is being shown to be focusable.\r
3804      */\r
3805     @Override\r
3806     public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {\r
3807         final int focusableCount = views.size();\r
3808 \r
3809         final int descendantFocusability = getDescendantFocusability();\r
3810 \r
3811         if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {\r
3812             for (int i = 0; i < getChildCount(); i++) {\r
3813                 final View child = getChildAt(i);\r
3814                 if (child.getVisibility() == VISIBLE) {\r
3815                     ItemInfo ii = infoForChild(child);\r
3816                     if (ii != null && ii.position == mCurItem) {\r
3817                         child.addFocusables(views, direction, focusableMode);\r
3818                     }\r
3819                 }\r
3820             }\r
3821         }\r
3822 \r
3823         // we add ourselves (if focusable) in all cases except for when we are\r
3824         // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.  this is\r
3825         // to avoid the focus search finding layouts when a more precise search\r
3826         // among the focusable children would be more interesting.\r
3827         if (\r
3828                 descendantFocusability != FOCUS_AFTER_DESCENDANTS ||\r
3829                         // No focusable descendants\r
3830                         (focusableCount == views.size())) {\r
3831             // Note that we can't call the superclass here, because it will\r
3832             // add all views in.  So we need to do the same thing View does.\r
3833             if (!isFocusable()) {\r
3834                 return;\r
3835             }\r
3836             if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&\r
3837                     isInTouchMode() && !isFocusableInTouchMode()) {\r
3838                 return;\r
3839             }\r
3840             if (views != null) {\r
3841                 views.add(this);\r
3842             }\r
3843         }\r
3844     }\r
3845 \r
3846     /**\r
3847      * We only want the current page that is being shown to be touchable.\r
3848      */\r
3849     @Override\r
3850     public void addTouchables(ArrayList<View> views) {\r
3851         // Note that we don't call super.addTouchables(), which means that\r
3852         // we don't call View.addTouchables().  This is okay because a ViewPager\r
3853         // is itself not touchable.\r
3854         for (int i = 0; i < getChildCount(); i++) {\r
3855             final View child = getChildAt(i);\r
3856             if (child.getVisibility() == VISIBLE) {\r
3857                 ItemInfo ii = infoForChild(child);\r
3858                 if (ii != null && ii.position == mCurItem) {\r
3859                     child.addTouchables(views);\r
3860                 }\r
3861             }\r
3862         }\r
3863     }\r
3864 \r
3865     /**\r
3866      * We only want the current page that is being shown to be focusable.\r
3867      */\r
3868     @Override\r
3869     protected boolean onRequestFocusInDescendants(int direction,\r
3870                                                   Rect previouslyFocusedRect) {\r
3871         int index;\r
3872         int increment;\r
3873         int end;\r
3874         int count = getChildCount();\r
3875         if ((direction & FOCUS_FORWARD) != 0) {\r
3876             index = 0;\r
3877             increment = 1;\r
3878             end = count;\r
3879         } else {\r
3880             index = count - 1;\r
3881             increment = -1;\r
3882             end = -1;\r
3883         }\r
3884         for (int i = index; i != end; i += increment) {\r
3885             View child = getChildAt(i);\r
3886             if (child.getVisibility() == VISIBLE) {\r
3887                 ItemInfo ii = infoForChild(child);\r
3888                 if (ii != null && ii.position ==\r
3889                             mCurItem && child.requestFocus(direction, previouslyFocusedRect)) {\r
3890                     return true;\r
3891                 }\r
3892             }\r
3893         }\r
3894         return false;\r
3895     }\r
3896 \r
3897     @Override\r
3898     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {\r
3899         // Dispatch scroll events from this ViewPager.\r
3900         if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED) {\r
3901             return super.dispatchPopulateAccessibilityEvent(event);\r
3902         }\r
3903 \r
3904         // Dispatch all other accessibility events from the current page.\r
3905         final int childCount = getChildCount();\r
3906         for (int i = 0; i < childCount; i++) {\r
3907             final View child = getChildAt(i);\r
3908             if (child.getVisibility() == VISIBLE) {\r
3909                 final ItemInfo ii = infoForChild(child);\r
3910                 if (ii != null && ii.position == mCurItem &&\r
3911                         child.dispatchPopulateAccessibilityEvent(event)) {\r
3912                     return true;\r
3913                 }\r
3914             }\r
3915         }\r
3916 \r
3917         return false;\r
3918     }\r
3919 \r
3920     @Override\r
3921     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {\r
3922         return new LayoutParams();\r
3923     }\r
3924 \r
3925     @Override\r
3926     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {\r
3927         return generateDefaultLayoutParams();\r
3928     }\r
3929 \r
3930     @Override\r
3931     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {\r
3932         return p instanceof LayoutParams && super.checkLayoutParams(p);\r
3933     }\r
3934 \r
3935     @Override\r
3936     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {\r
3937         return new LayoutParams(getContext(), attrs);\r
3938     }\r
3939 \r
3940     class MyAccessibilityDelegate extends AccessibilityDelegateCompat {\r
3941 \r
3942         @Override\r
3943         public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {\r
3944             super.onInitializeAccessibilityEvent(host, event);\r
3945             event.setClassName(DirectionalViewpager.class.getName());\r
3946             AccessibilityRecordCompat recordCompat = null;\r
3947             if (isHorizontal()) {\r
3948                 recordCompat =\r
3949                         AccessibilityEventCompat.asRecord(event);\r
3950             } else {\r
3951                 recordCompat = AccessibilityRecordCompat.obtain();\r
3952             }\r
3953             recordCompat.setScrollable(canScroll());\r
3954             if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED\r
3955                     && mAdapter != null) {\r
3956                 recordCompat.setItemCount(mAdapter.getCount());\r
3957                 recordCompat.setFromIndex(mCurItem);\r
3958                 recordCompat.setToIndex(mCurItem);\r
3959             }\r
3960         }\r
3961 \r
3962         @Override\r
3963         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {\r
3964             super.onInitializeAccessibilityNodeInfo(host, info);\r
3965             info.setClassName(DirectionalViewpager.class.getName());\r
3966             info.setScrollable(canScroll());\r
3967             if (isHorizontal()) {\r
3968                 if (canScrollHorizontally(1)) {\r
3969                     info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);\r
3970                 }\r
3971                 if (canScrollHorizontally(-1)) {\r
3972                     info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);\r
3973                 }\r
3974             } else {\r
3975                 if (internalCanScrollVertically(1)) {\r
3976                     info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);\r
3977                 }\r
3978                 if (internalCanScrollVertically(-1)) {\r
3979                     info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);\r
3980                 }\r
3981             }\r
3982         }\r
3983 \r
3984         @Override\r
3985         public boolean performAccessibilityAction(View host, int action, Bundle args) {\r
3986             if (super.performAccessibilityAction(host, action, args)) {\r
3987                 return true;\r
3988             }\r
3989 \r
3990             if (isHorizontal()) {\r
3991                 switch (action) {\r
3992                     case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {\r
3993                         if (canScrollHorizontally(1)) {\r
3994                             setCurrentItem(mCurItem + 1);\r
3995                             return true;\r
3996                         }\r
3997                     }\r
3998                     return false;\r
3999                     case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {\r
4000                         if (canScrollHorizontally(-1)) {\r
4001                             setCurrentItem(mCurItem - 1);\r
4002                             return true;\r
4003                         }\r
4004                     }\r
4005                     return false;\r
4006                 }\r
4007             } else {\r
4008                 switch (action) {\r
4009                     case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {\r
4010                         if (internalCanScrollVertically(1)) {\r
4011                             setCurrentItem(mCurItem + 1);\r
4012                             return true;\r
4013                         }\r
4014                     }\r
4015                     return false;\r
4016                     case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {\r
4017                         if (internalCanScrollVertically(-1)) {\r
4018                             setCurrentItem(mCurItem - 1);\r
4019                             return true;\r
4020                         }\r
4021                     }\r
4022                     return false;\r
4023                 }\r
4024             }\r
4025             return false;\r
4026         }\r
4027 \r
4028         private boolean canScroll() {\r
4029             return (mAdapter != null) && (mAdapter.getCount() > 1);\r
4030         }\r
4031     }\r
4032 \r
4033     private class PagerObserver extends DataSetObserver {\r
4034         @Override\r
4035         public void onChanged() {\r
4036             dataSetChanged();\r
4037         }\r
4038 \r
4039         @Override\r
4040         public void onInvalidated() {\r
4041             dataSetChanged();\r
4042         }\r
4043     }\r
4044 \r
4045     /**\r
4046      * Layout parameters that should be supplied for views added to a\r
4047      * ViewPager.\r
4048      */\r
4049     public static class LayoutParams extends ViewGroup.LayoutParams {\r
4050         /**\r
4051          * true if this view is a decoration on the pager itself and not\r
4052          * a view supplied by the adapter.\r
4053          */\r
4054         public boolean isDecor;\r
4055 \r
4056         /**\r
4057          * Gravity setting for use on decor views only:\r
4058          * Where to position the view page within the overall ViewPager\r
4059          * container; constants are defined in {@link android.view.Gravity}.\r
4060          */\r
4061         public int gravity;\r
4062 \r
4063         /**\r
4064          * Width as a 0-1 multiplier of the measured pager width\r
4065          */\r
4066         float widthFactor = 0.f;\r
4067 \r
4068         float heightFactor = 0.f;\r
4069 \r
4070         /**\r
4071          * true if this view was added during layout and needs to be measured\r
4072          * before being positioned.\r
4073          */\r
4074         boolean needsMeasure;\r
4075 \r
4076         /**\r
4077          * Adapter position this view is for if !isDecor\r
4078          */\r
4079         int position;\r
4080 \r
4081         /**\r
4082          * Current child index within the ViewPager that this view occupies\r
4083          */\r
4084         int childIndex;\r
4085 \r
4086         public LayoutParams() {\r
4087             super(FILL_PARENT, FILL_PARENT);\r
4088         }\r
4089 \r
4090         public LayoutParams(Context context, AttributeSet attrs) {\r
4091             super(context, attrs);\r
4092 \r
4093             final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);\r
4094             gravity = a.getInteger(0, Gravity.TOP);\r
4095             a.recycle();\r
4096         }\r
4097     }\r
4098 \r
4099     static class ViewPositionComparator implements Comparator<View> {\r
4100         @Override\r
4101         public int compare(View lhs, View rhs) {\r
4102             final LayoutParams llp = (LayoutParams) lhs.getLayoutParams();\r
4103             final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams();\r
4104             if (llp.isDecor != rlp.isDecor) {\r
4105                 return llp.isDecor ? 1 : -1;\r
4106             }\r
4107             return llp.position - rlp.position;\r
4108         }\r
4109     }\r
4110 \r
4111     public void setDirection(Direction direction) {\r
4112         mDirection = direction.name();\r
4113         initViewPager();\r
4114     }\r
4115 \r
4116     private String logDestroyItem(int pos, View object) {\r
4117         return "populate() - destroyItem() with pos: " + pos + " view: " + object;\r
4118     }\r
4119 }\r