最近有一个奇葩的需要,具体场景是移动端要实现根据服务端配置的数据动态展示不同的布局,也就是说客户端界面中的布局不能写死,而是要动态添加展示,相信大家要去实现也不复杂,然而奇葩之处是其中能够动态展示的布局要包含瀑布流效果的布局,那么对于一个界面来说我们是不确定需要加载哪些布局的,也不知道有多少数据需要展示,那么我们肯定会使用scrollview作为根布局,对于瀑布流效果使用RecyclerView的StaggeredGridLayoutManager布局管理器就可以很容易实现。
那么你会发现这样的话问题就来了,scrollview中使用RecyclerView必然会出现类似于scrollview中使用listview等一样的各种问题,假如你使用如下代码实现瀑布流效果,你会发现根本没有效果,什么都不会显示。
View recyclerViewLayout =LayoutInflater.from(context).inflate(R.layout.layout_recycleview, null);
RecyclerView recyclerView =(RecyclerView) recyclerViewLayout.findViewById(R.id.layout_recycle_view);
recyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setAdapter(adapter);
那怎么办呢?不要慌。。。咱们只需要重写StaggeredGridLayoutManager,然后重写它的onMeasure方法重新测量子View即可,完整的代码在文章结尾GitHub中可自行下载,这里贴出核心代码如下:
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
......
int[] maxHeight = new int[spanCount];//存储每一列中各View的height总和
for (int i = 0; i < adapterItemCount; i++) {//循环计算每一列View的总高度
int position = i % spanCount;//spanCount 为列数
if (i < spanCount) {//第一排的View将height存到各自的maxHeight 中
maxHeight[position] += childColumnDimensions[i];//childColumnDimensions存储了所有View的height
} else if (position < spanCount) {//第二排开始需要将接下来的View排到高度最小的一列
int mixHeight = maxHeight[0];
int mixPosition = 0;
for (int j = 0; j < spanCount; j++) {
if (mixHeight > maxHeight[j]) {
mixHeight = maxHeight[j];
mixPosition = j;
}
}
maxHeight[mixPosition] += childColumnDimensions[i];
}
}
for (int i = 0; i < spanCount; i++) {//将所得的maxHeight的数据进行排序(降序)
for (int j = 0; j < spanCount - i - 1; j++) {
if (maxHeight[j] < maxHeight[j + 1]) {
int temp = maxHeight[j];
maxHeight[j] = maxHeight[j + 1];
maxHeight[j + 1] = temp;
}
}
}
height = maxHeight[0];//获取到最后排列好后最大的高度作为RecyclerView最终的高度
......
}
说明:其中注释写的也比较详细可参考,需要说明的一点时,RecyclerView瀑布流中子View的排列顺序是先按顺序排好第一排,从第二排起接下来的View先排到第一排中高度最小的View底部,然后依次这么排列下去,如果有一个子View的高度很高,那么它底部不会有View排列,直到其他位置的子View的总高度比它高时才会有子View排在它底部,这个规则在我们计算RecyclerView的子View的总高度时要特别注意,否则就会出现最终RecyclerView子View都正常显示了,但是底部会出现一块空白区域的问题。
自定义好了之后使用的话很简单,直接将StaggeredGridLayoutManager替换为FullyStaggeredGridLayoutManager就可以了,其他的不需要变。
View recyclerViewLayout =LayoutInflater.from(context).inflate(R.layout.layout_recycleview, null);
RecyclerView recyclerView =(RecyclerView) recyclerViewLayout.findViewById(R.id.layout_recycle_view);
recyclerView.setLayoutManager(new FullyStaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setAdapter(adapter);
对于scrollview中嵌套RecyclerView实现如listview效果的我们也可以自定义LinearLayoutManager就能解决,RecyclerView的优势就是特别灵活,一些炫酷的效果我们也可以直接继承RecyclerView.LayoutManager来实现,具体的可以根据自己的需要实现。下面是完整代码的传送门。