现在项目中大部分的列表都是使用的 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 观察者来移除自身,而且如果在数组中使用迭代器,为了避免出现问题,可以使用该方法来刷新数据。
onItemRangeChanged
是AdapterDataObserver
中的静态抽象方法,在具体实现中会实现该方法并进行界面的重绘,继承该抽象类源码如下:
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();
}
}
}
从上面的源码来看
notifyItemRangeInserted
和notifyItemRangeChanged
的实现上没有上面区别,二者的区别在于子视图被更新时发生排队操作的不同,一个是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)
这里就和上面的notifyItemRangeInserted
和notifyItemRangeChanged
相似了,就是在一个范围内刷新数据,这里是移除后刷新,以上两个,第一个是插入刷新,第二个是数据改变刷新。 - 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();
}
}
}