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