zoukankan      html  css  js  c++  java
  • 关于禁止ViewPager预加载问题【转】

    转自:http://blog.csdn.net/qq_21898059/article/details/51453938#comments

    我最近上班又遇到一个小难题了,就是如题所述:ViewPager预加载的问题。相信用过ViewPager的人大抵都有遇到过这种情况,网上的解决办法也就那么几个,终于在我自己不断试验之下,完美解决了(禁止了)ViewPager的预加载。

    好了,首先来说明一下,什么是ViewPager的预加载:ViewPager有一个 “预加载”的机制,默认会把ViewPager当前位置的左右相邻页面预先初始化(俗称的预加载),它的默认值是 1,这样做的好处就是ViewPager左右滑动会更加流畅。

    可是我的情况很特殊,因为我 5 个Fragment里有一个Fragment是有SurfaceView的,这样造成的问题就是,我ViewPager滑动到其相邻页面时,含有SurfaceView的页面就会被预先初始化,然后SurfaceView就开始预览了,只是我们看不到而已。同样的,当我们从含有SurfaceView的页面滑倒其相邻的页面时,SurfaceView并不会回调其surfaceDestory方法。于是这给我造成了极大的困扰。

    ok,下面言归正传,到底该怎么禁止ViewPager的这个预加载问题呢?

    方案1:网上大多数说法是 懒加载,即让ViewPager预加载初始化UI,而具体一些数据,网络访问请求等延迟加载。这是靠Fragment里有一个setUserVisibleHint(boolean isVisibleToUser)的方法,我们可以在这个方法里做判断,当其True可见时(即切换到某一个具体Fragment)时才去加载数据,这样可以省流量。但这里并不满足我的需求,因为某一个Fragment并不会在ViewPager滑动到其相邻的Fragment时销毁。这个只可以解决部分人问题。

    首先我们来深入了解下ViewPager的预加载机制:

    上文提到过,ViewPager默认预加载的数量是1,这一点我们可以在ViewPager源码里看到。

    DEFAULT_OFFSCREEN_PAGES 这里就定义了默认值是1, 所以网上 有种解决方案 说调用ViewPager的setOffscreenPageLimit(int limit),来设置ViewPager预加载的数量,但是这里很明确的告诉你,这种方案是不可行的,如下图ViewPager源码:

    我们可以看到,如果你调用该方法传进来的值小于1是无效的,会被强行的拽回1。而且DEFAULT_OFFSCREEN_PAGES 这个值是private的,子类继承ViewPager也是不可见的。

    方案2:然后网上有第二种说法,自定义一个ViewPager,把原生ViewPager全拷过来,修改这个DEFAULT_OFFSCREEN_PAGES 值为0。对,就是这种解决方案!!但是!!

    但是!!!但是什么呢?但是我试过,没用。为什么呢?接下来就是本文的重点了。

    因为现在Android都6.0了,版本都老高了,其实android虽然每个版本都有v4包,但是这些v4包是有差异的。这就造成高版本v4包里的ViewPager,即使你Copy它,将其DEFAULT_OFFSCREEN_PAGES的值改为0,还是不起作用的,其中的逻辑变了。具体哪里变了导致无效我也没有继续研究了,毕竟公司不会花时间让我研究这些啊。偷笑

    完美解决方案:ok,所以关于禁止ViewPager预加载的完美解决方案就是,使用低版本v4包里的ViewPager,完全copy一份,将其中的DEFAULT_OFFSCREEN_PAGES值改为0即可。博主亲测 API 14 即 Android 4.0的v4包里ViewPager 有效。

    当然,谷歌既然有这么一种ViewPager的机制肯定有它的道理,所以一般还是预加载的好。

    最后,因为低版本的源码越来越少的人会去下载,这里直接把这个禁止了预加载的ViewPager贴上来,需要的人就拿去吧。copy就能用了。

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