PagerAdapter闪屏坑的修复

来源:互联网 发布:小爱和花儿知乎 编辑:程序博客网 时间:2024/06/06 01:41

详见 http://www.jianshu.com/p/29b708c62b33

背景

最近在填前同事的一个坑时,不小心遇到另外一个坑。 在一个礼物面板,原实现是gridView + ViewPager实现的(有几页礼物),在送用户免费礼物时,刷新ViewPager里面的item时,出现了闪屏。

其实很多童鞋知道,PagerAdapter在调用notifyDataSetChanged(), 如果使用默认的会不起作用

点进notifyDataSetChanged()

/**     * This method should be called by the application if the data backing this adapter has changed     * and associated views should update.     */    public void notifyDataSetChanged() {        synchronized (this) {            if (mViewPagerObserver != null) {                mViewPagerObserver.onChanged();            }        }        mObservable.notifyChanged();    }

可以看到

  1. mViewPagerObserver 是怎么传进来的呢?该类实际实现类是啥?

搜索全类只有一处赋值

  void setViewPagerObserver(DataSetObserver observer) {        synchronized (this) {            mViewPagerObserver = observer;        }    }

image.png

可以看出是PagerObserver类,有ViewPager类初始化setAdapter(PagerAdapter adapter)的时候传过来。
回到刚才的 mViewPagerObserver.onChanged();PagerObserver的实现如下

 @Override        public void onChanged() {            dataSetChanged();        }

恩,所以这里dataSetChanged()才是真正的实现:

void dataSetChanged() {        // This method only gets called if our observer is attached, so mAdapter is non-null.        final int adapterCount = mAdapter.getCount();        mExpectedAdapterCount = adapterCount;        boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&                mItems.size() < adapterCount;        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, adapterCount - 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();        }    }

这里的代码:

       if (newPos == PagerAdapter.POSITION_UNCHANGED) {                continue;            }            if (newPos == PagerAdapter.POSITION_NONE) {         }

恩,明显是根据PagerAdapter.POSITION_NONE、PagerAdapter.POSITION_UNCHANGED来判断是否进行更新操作。 PagerAdapter.POSITION_UNCHANGED是什么时候打上标签的呢?


image.png

哎呀,getItemPosition方法返回的,于是有了解决方法1.

  1. 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();       }   }}
    好吧这里是逐个通知Observer调用onChanged();

解决方案如下:

   public int getItemPosition(Object object) {           return PagerAdapter.POSITION_NONE;   }

game over了么?当然没有。

上述解决方法只是解决了一个问题,注意测试的话,就会发觉引入了本文标题中提到的闪屏问题~~
到底是哪里出现的问题呢?前面的我们源码都读的没有问题,唯一没注意的就是最后更新的逻辑了。我们再次仔细看看:


image.png

注意标箭头的地方,原来这里是把整个item remove掉了,难怪会出现闪屏。 事实上我们也可以通过断点或打log的方式,看本文提到的gridView刷新时是否复用。
知道了这里,本文的解决方法如下,使用一个SparseArray来存储,然后手动刷新。

class MyPagerAdapter extends PagerAdapter {        private MyGridViewAdapter mGridAdapter;        private SparseArray<GridView> mViews = new SparseArray<>();        @Override        public int getCount() {            if (mInnerAdapter == null || mMaxRows == 0 || mColumns == 0) {                return 0;            }            return (int) Math.ceil(mInnerAdapter.getCount() / (double) (mMaxRows * mColumns));        }        // Remove a page for the given position.        @Override        public void destroyItem(ViewGroup container, int position, Object object) {            container.removeView((View) object);            mViews.remove(position);        }        // Determines whether a page View is associated with a specific key object as returned by instantiateItem(ViewGroup, int).        @Override        public boolean isViewFromObject(View view, Object object) {            return view == object;        }        /**         * PagerAdapter.POSITION_NONE 会导致调用notifyDataSetChanged         * 调用 destroyItem 导致重新添加item,闪屏的出现         * 但是这里系统的实现bug, 见ViewPager$PagerObserver         * 默认是POSITION_UNCHANGED 即不刷新, 调用notifyDataSetChanged无反应,         * 这里使用手动刷新         *         * @param object         * @return         */        @Override        public int getItemPosition(Object object) {            int index = -1;            if (mViews != null) {                index = mViews.indexOfValue((GridView) object);            }            return index != -1 ? index : PagerAdapter.POSITION_NONE;        }        @Override        public void notifyDataSetChanged() {            GridView view;            int size = mViews.size();            for (int index = 0; index < size; index++) {                view = mViews.valueAt(index);                if (view != null) {                    ((MyGridViewAdapter) view.getAdapter()).notifyDataSetChanged();                }            }            super.notifyDataSetChanged();        }        // Create the page for the given position.        @Override        public Object instantiateItem(ViewGroup container, int position) {            GridView mGridView = new GridView(mContext);            ....            mGridAdapter = new MyGridViewAdapter(mInnerAdapter, position);            mGridView.setAdapter(mGridAdapter);            container.addView(mGridView);            mViews.put(position, mGridView);            return mGridView;        }    }