Android进阶系列之ViewPager的刷新问题分析

ViewPager是Android应用开发中非常常用的一个控件,是一个可以让View左右翻页滑动的管理布局,需要和PagerAdapter配合使用,来创建每一页的View并显示。

ViewPager的使用其实是比较简单的,但是有一个比较重要的问题,就是ViewPager的数据刷新。PagerAdapter有一个notifyDataSetChanged()方法,根据Android官方文档的介绍
PagerAdapter支持数据的更改,但是数据的更改必须在主线程中进行,并且要调用notifyDataSetChanged()这个方法。我们知道所谓数据的更改,无非是页面的添加,删除和页面位置的变化。

按照官方文档的介绍,我们在改变PagerAdapter的数据源之后,然后调用一下notifyDataSetChanged()方法,应该就可以看到页面的变化,然而事情并没有那么简单,你会发现页面并没有任何的变化。那这个到底是什么情况。我们来看一下PagerAdapter的notifyDataSetChanged()这个方法都干了些什么

  public void notifyDataSetChanged() {
        synchronized(this) {
            if (this.mViewPagerObserver != null) {
                this.mViewPagerObserver.onChanged();
            }
        }

        this.mObservable.notifyChanged();
    }

我们可以看到这里调用了this,mViewPagerObserver.onChanged(),这里实际上是观察者模式,去通知ViewPager数据发生了改变。我们再看下ViewPager的ViewPagerObserver 的onChanged ()方法又做了什么,

 private class PagerObserver extends DataSetObserver {
        PagerObserver() {
        }

        public void onChanged() {
            ViewPager.this.dataSetChanged();
        }

        public void onInvalidated() {
            ViewPager.this.dataSetChanged();
        }
    }

ViewPagerObserver的onChanged()方法里调用了ViewPager的dataSetChanged()方法

 void dataSetChanged() {
        int adapterCount = this.mAdapter.getCount();
        this.mExpectedAdapterCount = adapterCount;
        boolean needPopulate = this.mItems.size() < this.mOffscreenPageLimit * 2 + 1 && this.mItems.size() < adapterCount;
        int newCurrItem = this.mCurItem;
        boolean isUpdating = false;

        int childCount;
        for(childCount = 0; childCount < this.mItems.size(); ++childCount) {
            ViewPager.ItemInfo ii = (ViewPager.ItemInfo)this.mItems.get(childCount);
            int newPos = this.mAdapter.getItemPosition(ii.object);
            if (newPos != -1) {
                if (newPos == -2) {
                    this.mItems.remove(childCount);
                    --childCount;
                    if (!isUpdating) {
                        this.mAdapter.startUpdate(this);
                        isUpdating = true;
                    }

                    this.mAdapter.destroyItem(this, ii.position, ii.object);
                    needPopulate = true;
                    if (this.mCurItem == ii.position) {
                        newCurrItem = Math.max(0, Math.min(this.mCurItem, adapterCount - 1));
                        needPopulate = true;
                    }
                } else if (ii.position != newPos) {
                    if (ii.position == this.mCurItem) {
                        newCurrItem = newPos;
                    }

                    ii.position = newPos;
                    needPopulate = true;
                }
            }
        }

        if (isUpdating) {
            this.mAdapter.finishUpdate(this);
        }

        Collections.sort(this.mItems, COMPARATOR);
        if (needPopulate) {
            childCount = this.getChildCount();

            for(int i = 0; i < childCount; ++i) {
                View child = this.getChildAt(i);
                ViewPager.LayoutParams lp = (ViewPager.LayoutParams)child.getLayoutParams();
                if (!lp.isDecor) {
                    lp.widthFactor = 0.0F;
                }
            }

            this.setCurrentItemInternal(newCurrItem, false, true);
            this.requestLayout();
        }

    }

我们可以看到dataSetChanged()方法,中

int newPos = this.mAdapter.getItemPosition(ii.object);
            if (newPos != -1) { 
                  .......
             }

newPos不等于-1才会进入刷新数据的逻辑,newPos来自于PagerAapter的getItemPosition()方法,我们再看一下PagerAdapter的getItemPosition()方法。

  public int getItemPosition(@NonNull Object object) {
        return -1;
    }

你会惊讶的发现,这特码的直接返回-1,调用PagerAdapter的notifyDataSetChanged()
ViewPager肯定不会有任何刷新啊。也是醉了。这算是Google工程师给的坑?。回到正题,那怎么办呢,我们应该需要重写PagerAdapter的getItemPosition()方法。PagerAdapter中实际上定义了两个常量。

 public static final int POSITION_UNCHANGED = -1;
 public static final int POSITION_NONE = -2;

默认的返回的是POSITION_UNCHANGED,我们重写getItemPosition()方法返回POSITION_NONE就可以实现数据的刷新了。

public int getItemPosition(@NonNull Object object) {
        return POSITION_NONE ;
    }

但是这样做有个严重缺点就是,所有的getItemPosition()都返回POSITION_NONE的话,
每次都要移除所有的View,再重新添加到ViewPager中,这样效率太低。

有一个比较好的优化方案是:首先在instantiateItem()方法中给每个View设置一个Tag,就是View在数据列表也就是ViewPager中的索引位置,然后在getItemPosition()判断,当前View数据列表中是否有该View,索引位置有没有发生该变,如果当前数据列表没有该View int newsPos=mViewList.indexOf(object),也即newPos为-1,返回POSITION_NONE即可;如果当前数据列表中有该View,但是索引位置发生了改变也即 int oldPosition= (int) ((View) object).getTag();
newPosition!=oldPosition ,直接返回新的索引newPosition,如果位置没有发生改变
则返回POSITION_UNCHANGED。

  @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        mViewList.get(position).setTag(position); //设置Tag
        container.addView(mViewList.get(position));
        return mViewList.get(position);
    }
@Override
    public int getItemPosition(@NonNull Object object) {
        int oldPosition= (int) ((View) object).getTag();
        int newsPos=mViewList.indexOf(object);

        if (newsPos>0&&newsPos!=oldPosition){
            ((View) object).setTag(newsPos);
            return newsPos;
        }else{
           if (newsPos<0){
                return  POSITION_NONE;
            }else{
                return  POSITION_UNCHANGED;
            }
        }

    }

以上就是ViewPager中数据刷新的问题的分析及解决。

你可能感兴趣的:(Android开发)