1. ViewPager 的页面重置问题
当我们使用ViewPager控件时,假设我们的ViewPager有三页,当我们第一次启动ViewPager显示第一页的时候,ViewPager会预加载第二页,这样当我们向第二页滑动的时候就可以看见第二页的内容了,因为第二页的内容在第一页内容显示的时候就已经加载了。当我们滑动到第二页时,ViewPager会预加载第三页,这时ViewPager中已经保存了三页的内容:第一页,第二页和第三页,这样当我们在第二页向左或向右滑动ViewPager时能看到这三页的内容。
我在使用ViewPager控件时遇到过这样的问题,像上面说明的那样,我在把ViewPager从第一页滑动到第三页的时候看到了这三页的内容,但是假设我在第一页中做了一些操作改变了第一页的内容,而我在第三页中也做了一些操作改变了第三页的内容,可是当我从第三页滑动到第一页时发现第一页的内容还原了,而当我再次从第一页滑动到第三页时发现第三页的内容也还原了。
问题的根源在于ViewPage的加载页数,就是说你可以指定ViewPager预加载的页数,如果不指定,ViewPager默认加载一页。在这种情况下,ViewPage会加载当前显示页面相邻的还没有加载的页面。比如说,当前显示的是首页,因为首页的左边是没有页面的,所以ViewPager会加载第二页的内容;当ViewPager滑动到第二页的时候,因为第一页已经加载,所以不会重复加载,但是第三页还没有加载,所以ViewPager会加载第三页的内容;当ViewPager滑动到第三页的时候(假设ViewPager只有三页),因为第二页已经加载过了,而第三页的右边没有任何页面了,所以ViewPager不会加载任何界面。当从第三页滑动到第二页时,因为第三页已经加载过,所以不会重新加载,虽然第一页之前加载过,但是,ViewPager的默认加载为一页,而当ViewPager在第三页的时候第一页在默认加载之外,所以从第三页滑动到第二页的时候,ViewPager会重新加载第一页,而再从第一页滑动到第三页时是同样的道理,所以会出现我上面描述的情况。
为了避免上述情况的发生我们可以通过调用ViewPager的setOffscreenPageLimit方法来指定加载的页数,从而得到我们想要的结果。
2. 调用setCurrentItem方法无动画问题
在使用setCurrentItem时,我们可以通过反射机制设置Viewpager的滑动过渡时间。
具体的实现方式如下:
/** * ViewPager 滚动速度设置 * */ public class ViewPagerScroller extends Scroller { private int mScrollDuration = 2000; // 滑动速度 /** * 设置速度速度 * @param duration */ public void setScrollDuration(int duration){ this.mScrollDuration = duration; } public ViewPagerScroller(Context context) { super(context); } public ViewPagerScroller(Context context, Interpolator interpolator) { super(context, interpolator); } public ViewPagerScroller(Context context, Interpolator interpolator, boolean flywheel) { super(context, interpolator, flywheel); } @Override public void startScroll(int startX, int startY, int dx, int dy, int duration) { super.startScroll(startX, startY, dx, dy, mScrollDuration); } @Override public void startScroll(int startX, int startY, int dx, int dy) { super.startScroll(startX, startY, dx, dy, mScrollDuration); } public void initViewPagerScroll(ViewPager viewPager) { try { Field mScroller = ViewPager.class.getDeclaredField("mScroller"); mScroller.setAccessible(true); mScroller.set(viewPager, this); } catch(Exception e) { e.printStackTrace(); } } } //这个是设置viewPager切换过度时间的类 ViewPagerScroller scroller = new ViewPagerScroller(context); scroller.setScrollDuration(0); scroller.initViewPagerScroll(viewPager); //这个是设置切换过渡时间为0毫秒 ViewPagerScroller scroller = new ViewPagerScroller(context); scroller.setScrollDuration(2000); scroller.initViewPagerScroll(viewPager);//这个是设置切换过渡时间为2秒
如果需要设置其他类型的动画效果,可以调用 ViewPager.setPageTransformer 来设置。
3. ViewPager调用notifyDataSetChanged() 刷新问题解决方案
问题说明:
ViewPager控件很大程度上满足了开发者开发页面左右移动切换的功能,使用非常方便。但是使用中发现,在删除或者修改数据的时候,PagerAdapter无法像BaseAdapter那样仅通过notifyDataSetChanged方法通知刷新View。有人提出一种解决方案:给Viewpager重新设置一遍适配器adapter,来达到刷新数据的目的。但是这种方法在大多数情况下,是存在问题的。
问题分析:
为什么调用数据更新的方法,Viewpager却没有更新呢,我们跟进该方法的源代码看一下。
/** * This method should be called by the application if the data backing this adapter has changed * and associated views should update. */ public void notifyDataSetChanged() { mObservable.notifyChanged(); }
注释里说到,当附加在适配器上的数据发生变化时,应该调用该方法刷新数据。该方法调用了一个mObservable .notifyChanged():
/** * Invokes {@link DataSetObserver#onChanged} on each observer. * Called when the contents of the data set have changed. The recipient * will obtain the new contents the next time it queries the data set. */ public void notifyChanged() { synchronized(mObservers ) { // since onChanged() is implemented by the app, it could do anything, including // removing itself from {@link mObservers} - and that could cause problems if // an iterator is used on the ArrayList {@link mObservers}. // to avoid such problems, just march thru the list in the reverse order. for (int i = mObservers .size() - 1; i >= 0; i--) { mObservers.get(i).onChanged(); } } }
这都不是重点,重点我们来看这个mObservers的类型是一个抽象类DataSetObserver,里面只有两个未实现的方法,都有谁使用了这个抽象类呢,快捷键 ctrl + alt + H ,在众多的调用者当中,我们发现了Viewpager的身影。进入Viewpager,我们终于找到了Viewpager中控制数据变更的重点方法dataSetChanged ,这个方法如下:
void dataSetChanged () { // This method only gets called if our observer is attached, so mAdapter is non-null. boolean needPopulate = mItems .size() < mOffscreenPageLimit * 2 + 1 && mItems.size() < mAdapter.getCount(); int newCurrItem = mCurItem ; boolean isUpdating = false; for (int i = 0; i < mItems.size(); i++) { final ItemInfo ii = mItems .get(i); final int newPos = mAdapter.getItemPosition(ii.object ); if (newPos == PagerAdapter.POSITION_UNCHANGED ) { continue; } if (newPos == PagerAdapter.POSITION_NONE) { mItems.remove(i); i--; if (!isUpdating) { mAdapter.startUpdate( this); isUpdating = true; } mAdapter.destroyItem( this, ii.position , ii.object); needPopulate = true; if (mCurItem == ii.position ) { // Keep the current item in the valid range newCurrItem = Math. max(0, Math.min(mCurItem, mAdapter.getCount() - 1)); needPopulate = true; } continue; } if (ii.position != newPos) { if (ii.position == mCurItem ) { // Our current item changed position. Follow it. newCurrItem = newPos; } ii. position = newPos; needPopulate = true; } } if (isUpdating) { mAdapter.finishUpdate( this); } Collections. sort(mItems, COMPARATOR); if (needPopulate) { // Reset our known page widths; populate will recompute them. final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (!lp.isDecor ) { lp. widthFactor = 0.f; } } setCurrentItemInternal(newCurrItem, false, true); requestLayout(); } }
重点看这样一行代码:
final int newPos = mAdapter.getItemPosition(ii.object); if (newPos == PagerAdapter.POSITION_UNCHANGED) { continue ; }
到这里我们就找到了解决这个问题的核心方法:getItemPosition()。官方对getItemPosition()的解释是:
Called when the host view is attempting to determine if an item’s position has changed.
Returns POSITION_UNCHANGED if the position of the given item has not changed orPOSITION_NONE if the item is no longer present in the adapter.
The default implementation assumes that items will never change position and always returns POSITION_UNCHANGED.
意思是如果item的位置如果没有发生变化,则返回POSITION_UNCHANGED。如果返回了POSITION_NONE,表示该位置的item已经不存在了。默认的实现是假设item的位置永远不会发生变化,而返回POSITION_UNCHANGED。
解决方案:
根据上面的分析,我们可以尝试着修改适配器的写法,覆盖getItemPosition()方法,当调用notifyDataSetChanged时,让getItemPosition方法人为的返回POSITION_NONE,从而达到强制Viewpager重绘所有item的目的。
@Override public int getItemPosition(Object object) { return POSITION_NONE; }