Android RecyclerView adapter notify*** 的几个方法分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ITxiaodong/article/details/80560042

现在项目中大部分的列表都是使用的 RecyclerView ,而且第三方的 Adapter 库也很多,极大的方便了我们的使用,但是在列表刷新的时候 RecyclerView 为什么要给出那么多的刷新方式呢?它们有什么区别呢?这里就不得不说我在使用 RecyclerView 实现瀑布流时遇到的一个刷新有关的问题。

先来看看 RecyclerView 都提供了那些刷新方法给我们

  • notifyItemInserted(int position)
    这个方法会调用notifyItemRangeInserted

  • notifyItemRangeInserted(int positionStart, int itemCount)
    ,而notifyItemRangeInserted的实现源码如下:

 public void notifyItemRangeInserted(int positionStart, int itemCount) {
    // since onItemRangeInserted() is implemented by theapp, 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).onItemRangeInserted(positionStart, itemCount);
    }
}

渣渣翻译:就是说当调用了这个方法后,会通过 mObservers 观察者来移除自身,而且如果在数组中使用迭代器,为了避免出现问题,可以使用该方法来刷新数据。

  • notifyItemChanged(int position)
    notifyItemChanged(int position) 会调用 notifyItemRangeChanged(int positionStart, int itemCount)
  • notifyItemRangeChanged(int positionStart, int itemCount)
    - notifyItemRangeChanged(int positionStart, int itemCount)会调用- notifyItemRangeChanged(int positionStart, int itemCount, Object payload) 所以我们只要研究最后一个方法即可。
  • notifyItemRangeChanged(int positionStart, int itemCount, Object payload)
    在这个方法中会进行循环
// since onItemRangeChanged() 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).onItemRangeChanged(positionStart, itemCount, payload);
}

渣渣翻译:就是说当调用了这个方法后,会通过 mObservers 观察者来移除自身,而且如果在数组中使用迭代器,为了避免出现问题,可以使用该方法来刷新数据。

onItemRangeChangedAdapterDataObserver中的静态抽象方法,在具体实现中会实现该方法并进行界面的重绘,继承该抽象类源码如下:

private class RecyclerViewDataObserver extends AdapterDataObserver {
        RecyclerViewDataObserver() {
        }

        @Override
        public void onChanged() {
            assertNotInLayoutOrScroll(null);
            mState.mStructureChanged = true;

            setDataSetChangedAfterLayout();
            if (!mAdapterHelper.hasPendingUpdates()) {
                requestLayout();
            }
        }
        /**
        * 刷新方法的具体实现
        */
        @Override
        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
            assertNotInLayoutOrScroll(null);
            if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
                triggerUpdateProcessor();
            }
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            assertNotInLayoutOrScroll(null);
            if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
                triggerUpdateProcessor();
            }
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            assertNotInLayoutOrScroll(null);
            if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
                triggerUpdateProcessor();
            }
        }

        @Override
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
            assertNotInLayoutOrScroll(null);
            if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
                triggerUpdateProcessor();
            }
        }

        void triggerUpdateProcessor() {
            if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
                ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
            } else {
                mAdapterUpdateDuringMeasure = true;
                // 最后知道是进行界面的重绘操作,这就不往下细究。
                requestLayout();
            }
        }
    }

从上面的源码来看 notifyItemRangeInsertednotifyItemRangeChanged的实现上没有上面区别,二者的区别在于子视图被更新时发生排队操作的不同,一个是UpdateOp.UPDATE一个是UpdateOp.ADD,所以子视图在更新的时候使用notifyItemRangeChanged去刷新,而在数据插入的时候使用notifyItemRangeInserted去刷新。

  • notifyItemMoved(int fromPosition, toPosition)
 /**
         * Notify any registered observers that the item reflected at <code>fromPosition</code>
         * has been moved to <code>toPosition</code>.
         *
         * <p>This is a structural change event. Representations of other existing items in the
         * data set are still considered up to date and will not be rebound, though their
         * positions may be altered.</p>
         *
         * @param fromPosition Previous position of the item.
         * @param toPosition New position of the item.
         */
        public final void notifyItemMoved(int fromPosition, int toPosition) {
            mObservable.notifyItemMoved(fromPosition, toPosition);
        }

渣渣翻译: 从移除开始位置到移除结束的位置进行刷新,这是一个结构性变化事件。数据集中其他现有项目的表示仍被认为是最新的,不会被反弹,尽管它们的位置可能会改变。就是说使用该方法移除数据后界面可能已经改变了,但是数据集不一定是最新的数据,只是被认为是最新的(有点绕口,这里也没看明白)

  • notifyItemRangeRemoved(int positionStart, int itemCount)
    这里就和上面的 notifyItemRangeInsertednotifyItemRangeChanged相似了,就是在一个范围内刷新数据,这里是移除后刷新,以上两个,第一个是插入刷新,第二个是数据改变刷新。
  • notifyDataSetChanged()
 /**
         * Notify any registered observers that the data set has changed.
         *
         * <p>There are two different classes of data change events, item changes and structural
         * changes. Item changes are when a single item has its data updated but no positional
         * changes have occurred. Structural changes are when items are inserted, removed or moved
         * within the data set.</p>
         *
         * <p>This event does not specify what about the data set has changed, forcing
         * any observers to assume that all existing items and structure may no longer be valid.
         * LayoutManagers will be forced to fully rebind and relayout all visible views.</p>
         *
         * <p><code>RecyclerView</code> will attempt to synthesize visible structural change events
         * for adapters that report that they have {@link #hasStableIds() stable IDs} when
         * this method is used. This can help for the purposes of animation and visual
         * object persistence but individual item views will still need to be rebound
         * and relaid out.</p>
         *
         * <p>If you are writing an adapter it will always be more efficient to use the more
         * specific change events if you can. Rely on <code>notifyDataSetChanged()</code>
         * as a last resort.</p>
         *
         * @see #notifyItemChanged(int)
         * @see #notifyItemInserted(int)
         * @see #notifyItemRemoved(int)
         * @see #notifyItemRangeChanged(int, int)
         * @see #notifyItemRangeInserted(int, int)
         * @see #notifyItemRangeRemoved(int, int)
         */
        public final void notifyDataSetChanged() {
            mObservable.notifyChanged();
        }

渣渣翻译:刷新所有已经注册观察者的数据。最重要的是最后一段的翻译,adapter 的刷新不推荐直接使用 notifyDataSetChanged而是推荐使用对应的刷新方式,会提高更多的效率。

解决在RecyclerView实现的瀑布流中出现的Item跳动问题、顶部留白问题

这些问题的原因是瀑布流的position不是固定的,所以在刷新的时候使用notifyDataSetChanged是会全部重绘布局,之前的position就会改变,而且每个 item的高度又是不固定的,所以会出现跳动和顶部留白的问题。

那么这时候我们只需要在刷新的时候使用notifyItemInserted就可以了,因为这个刷新是只刷新最后添加进来的数据,只重绘后来添加进来的布局。

如果在刷新前清空数据,在添加数据,再使用notifyItemInserted刷新可能会出现 adapter 报错的 bug,这时候可以重写该瀑布流的 layoutmanager 捕捉该异常即可完美解决!

下面是重写的 LayoutManager

自定义一个 layoutManager 捕获异常
/**
 * 捕获position异常的问题
 */
public class MQStaggeredGridLayoutManager extends StaggeredGridLayoutManager {

    public MQStaggeredGridLayoutManager(int spanCount, int orientation) {
        super(spanCount, orientation);
    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        try {
            super.onLayoutChildren(recycler, state);
        } catch (IndexOutOfBoundsException e) {
            e.printStackTrace();
        }
    }
}
阅读更多

扫码向博主提问

龙衣倩

买的到的知识都是不值钱的
  • 擅长领域:
  • Android
去开通我的Chat快问
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页