RecyclerView 可以很轻松实现瀑布流,使用StaggeredGridLayoutManager 即可,但也暴露出了很多问题。
1.列表重新排序
大家应该都遇到过下面的问题,列表滚动出现了左右item位置切换的,这个问题解决倒不难。
layoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
GAP_HANDLING_NONE表示不对任何空白间隙做处理。
但也由此引发了下面的问题。
当列表出现空白间隙时,StaggeredGridLayoutManager其实是会对列表重排序来消除间隙,设置GAP_HANDLING_NONE后,屏蔽了这种机制,导致了顶部item空白的出现。
2.滑动到列表顶部时顶部留白
如下图所示
image.png
首先google了百度的一番,网上有一些解决办法,
layoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);//设置不对空白间隙处理
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
staggeredGridLayoutManager.invalidateSpanAssignments();//重新布局
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
public void invalidateSpanAssignments() {
// 将spanIndex数组清空,进行重绘,后面会讲解mLazySpanLookup的作用
mLazySpanLookup.clear();
requestLayout();
}
这种方式确实是“解决了”空白的问题,但还是有一些问题
1.造成图片闪烁
invalidateSpanAssignments实现是重新绘制一次,由于是在滚动状态发生变化时调用,每次滚动都会造成至少2-3次的重绘,资源浪费。
在这里说一个更好的解决方案:
使用notifyItemRangeChanged(或者notifyItemRangeInsert流程和notifyItemRangeChanged一样,感谢Magic丶海提醒)替代notifyDataSetChanged进行列表刷新。
造成空白的原因
大家再看一下下面这张图,左右两张图中item的位置变化。
image.png
左侧图片是默认情况下初始化时的展示,右侧图是加载了分页数据,重新回滚到顶部时的展示,显然右侧图的位置较之前出现了错乱。
我们的RecycleView一般都是有分页的,新增数据源之后,需要调用notifyDataSetChanged进行列表的刷新,如果我们列表只有一页不会出现空白的问题,当有分页数据时候刷新列表时就会出现空白,这是由于分页刷新列表造成的,但其最根本的原因是刷新时spanIndex索引出现了变化。下面将分析源码是如何造成这种情况的。我们先看下RecyclewView的测量布局流程。
image.png
上面的流程图忽略了一部分判断条件,为了更直观的分析,我们再简化一下:
瀑布流使用的是StaggeredGridLayoutManager,StaggeredGridLayoutManager中isAutoMeasureEnabled的实现是设置GAP_HANDLING_NONE时,才为false,此处没有设置则为true。
RecycleView 设置的layout_width和layout_height都是match_parent, 测量模式为EXACTLY,所以measureSpecModeIsExactly 为true.
那么流程图简化为如下图:
image.png
LayoutManger负责的item的布局,因为是瀑布流,我们去看一下在StaggeredGridLayoutManager中是如何对处理item v