PagerAdapter闪屏坑的修复

背景

最近在填前同事的一个坑时,不小心遇到另外一个坑。 在一个礼物面板,原实现是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;
        }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容