记ViewPager在使用中踩过的坑(刷新bug,空白页bug)

ViewPager是现在比较常用的图片轮播容器,本人在项目中做循环轮播时,踩了不少坑,特开一贴记录。

ViewPagerAdapter刷新问题

这是个老生常谈的问题了,网上的解决方案也一堆,这里主要记录一下问题产生的原因。

每次调用notifyDataSetChanged()时,都会触发getItemPosition(Object object)。该方法会遍历viewpager的所有item,为每个item返回一个状态值(POSITION_NONE/POSITION_UNCHANGED)

如果item的位置如果没有发生变化,则返回POSITION_UNCHANGED。如果返回了POSITION_NONE,表示该位置的item已经不存在了。默认的实现是假设item的位置永远不会发生变化,而返回POSITION_UNCHANGED。 所以如果我们不重写getItemPosition(Object object),就无法看到刷新效果。

对于这两种状态:
如果是POSITION_NONE,那么该item会被destroyItem(…)方法remove掉,然后重新加载;

如果是POSITION_UNCHANGED,就不会重新加载。

所以我们解决的方案也明了了:初始化item时,把itemposition作为tag与它绑定,然后重写getItemPosition,遇到当前位置上的item就把它的状态置为POSITION_NONE

@Override
    public Object instantiateItem(View collection, int position) {
        final int count = getSize();
        if (count > 0 && position >= count) {
            position = position % count;
        }
        ViewPager viewPager = ((ViewPager) collection);
        View childView = getView(cachedView, mList.get(position), position);
        viewPager.addView(childView);
        childView.setTag(KEY_TAG_CURRENT_POS, position);
        return childView;
    }

  @Override
    public int getItemPosition(Object object) {
        View view = (View) object;
        Object o = view.getTag(KEY_TAG_CURRENT_POS);
        if (o != null) {
            final int curPos = (Integer) o;
            //只刷新当前页面
            if (mCurrentPosition != curPos) {
                return POSITION_UNCHANGED;
            }
        }
        return POSITION_NONE;
    }

/**
  * 外部调用,当viewPager滑动页面时,保存当前位置
  **/
public void setCurrentPosition(int mCurrentPosition) {
        this.mCurrentPosition = mCurrentPosition;
    }

ViewPager子元素少于3时的bug

ViewPager被设计成预加载的形式,默认会维护三个页面:当前页面,下一页面和上一页面。每向前滑动一页,会destroy掉最后面的一项,然后预加载前面一项,如图:

记ViewPager在使用中踩过的坑(刷新bug,空白页bug)_第1张图片

因此如果元素个数大于3时,不会有什么问题,而一旦元素小于等于3,就会出现一系列问题。

我在项目中设计循环轮播时,想把所有的item都缓存下来,省的每次都重新new新对象,况且我的item里还有图,不加缓存,时间久了很容易OOM,代码如下:


 @Override
    public Object instantiateItem(View collection, int position) {
        final int count = getSize();
        if (count > 0 && position >= count) {
            position = position % count;
        }
        ViewPager viewPager = ((ViewPager) collection);
        View cachedView = null;
        if (mCachedViewList != null) {
            cachedView = mCachedViewList.get(position);
        }
        View childView = getView(cachedView, mList.get(position), position);
        if (cachedView == null) {
            mCachedViewList.put(position, childView);
        }
        viewPager.addView(childView);
        childView.setTag(KEY_TAG_CURRENT_POS, position);
        return childView;
    }

当只有三个元素,向正向滑动再反方向滑动时,会报错:

java.lang.IllegalArgumentException: parameter must be a descendant of this view

错在两次在同一位置调用instantiateItem,重复添加了子View

 viewPager.addView(childView);

下面分析一下:

如果定位到ViewPager的源码,直接看populate(int newCurrentItem),这里就不贴源码了。

ViewPager在滑动过程中都是从左到右,根据位置分析当前是销毁(调用destroyItem),还是使用缓存,还是重新初始化item(调用instantiateItem)。

假如当时在位置2,正向滑动一次,到了位置3,此时先destory位置1,预加载位置4,由于是循环滑动,修正后会重新加载位置1;

接着反向滑动,到了位置2,此时需要先预加载位置1,再次调用了instantiateItem,此时就会再次添加item 1,结果就报错了…

除此之外,如果页面数量少于3,比如只有两个页面,会出现,滑着滑着就一堆空白页面,这也是ViewPager子数量太少引起的。

怎么解决呢?

核心问题就在于,子元素太少,所以网上的通用方案是当子元素小于3时,再次添加一遍子元素,让子元素数量强制大于3。

渲染indicator时,要用真实数量,所以还要在ViewPagerAdapter中保存真实数量。

你可能感兴趣的:(Android疑难杂症)