RecyclerView的布局排列主要通過布局管理器——LayoutManager去實(shí)現(xiàn),不同的布局能為使得RecyclerView擁有不同的排列效果。StaggeredGridLayoutManager是其中的一種布局管理器,它能夠?qū)崿F(xiàn)類似于瀑布流的效果。
實(shí)現(xiàn)瀑布流的代碼很簡(jiǎn)單,只要?jiǎng)?chuàng)建StaggeredGridLayoutManager實(shí)例,并將該實(shí)例綁定為RecyclerView的布局管理器:
// 創(chuàng)建StaggeredGridLayoutManager實(shí)例
StaggeredGridLayoutManager layoutManager =
new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
// 綁定布局管理器
rv.setLayoutManager(layoutManager);
效果如下:

可以看出,當(dāng)滑到底部的時(shí)候,最后一個(gè)item等待一段時(shí)間才顯示出來(lái)。這是RecyclerView使用瀑布流布局時(shí)自帶的動(dòng)畫效果,而這個(gè)動(dòng)畫效果看起來(lái)不怎么舒服,一般都會(huì)去掉。需要調(diào)用RecyclerView的setAnimator將Animator設(shè)置為null,并調(diào)用StaggeredLayoutManager的setGapStrategy方法設(shè)置為StaggeredGridLayoutManager.GAP_HANDLING_NONE:
layoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
rv.setItemAnimator(null);

這樣就不會(huì)出現(xiàn)item要等待一段時(shí)間才顯示出來(lái)的問題了。如果像上述瀑布流那樣在item之間添加間隔,那么就要繼承RecyclerView的ItemDecoration類,并復(fù)寫其getItemOffsets方法:
package cn.axen.mvp.tools;
import android.content.Context;
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.TypedValue;
import android.view.View;
public class StaggeredDividerItemDecoration extends RecyclerView.ItemDecoration {
private Context context;
private int interval;
public StaggeredDividerItemDecoration(Context context, int interval) {
this.context = context;
this.interval = interval;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
int interval = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
this.interval, context.getResources().getDisplayMetrics());
// 中間間隔
if (position % 2 == 0) {
outRect.left = 0;
} else {
// item為奇數(shù)位,設(shè)置其左間隔為5dp
outRect.left = interval;
}
// 下方間隔
outRect.bottom = interval;
}
}
在RecyclerView中添加itemDecoration:
rv.addItemDecoration(new StaggeredDividerItemDecoration(this, 5))
如果像上例一樣,第一個(gè)item比第二個(gè)item的高度低的話,那么上述代碼是沒有問題的,但是第一個(gè)item比第二item高的話,就會(huì)出現(xiàn)下面的問題:

可以看到,下方的間隔是沒有問題的,但是中間的間隔發(fā)生錯(cuò)亂。這是因?yàn)槠俨剂鞯膇tem不是根據(jù)下標(biāo)的順序去排列,而是根據(jù)上方的兩個(gè)item的高度差確定位置。上方哪個(gè)item預(yù)留的空間比較高,就會(huì)排列到這個(gè)item的下方:


可見,當(dāng)?shù)诙€(gè)item高度比第一個(gè)item的低時(shí),第三個(gè)item將排列在第二個(gè)item的下方,那么通過position判斷設(shè)置間隔的方法就不成立了。這就導(dǎo)致item間隔的錯(cuò)亂。
那么,要怎樣解決這個(gè)問題呢?在參考了大神的博客以后,才發(fā)現(xiàn)StaggeredLayoutManager.LayoutParams類中有一個(gè)getSpanIndex()的方法,可以獲取item在span中的下標(biāo)。那么,問題就很容易解決了,只要將position替換為span的下標(biāo)判斷item的位置,就不會(huì)出現(xiàn)間隔錯(cuò)亂的問題了:
package cn.axen.mvp.tools;
import android.content.Context;
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.TypedValue;
import android.view.View;
public class StaggeredDividerItemDecoration extends RecyclerView.ItemDecoration {
private Context context;
private int interval;
public StaggeredDividerItemDecoration(Context context, int interval) {
this.context = context;
this.interval = interval;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
// int position = parent.getChildAdapterPosition(view);
StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
// 獲取item在span中的下標(biāo)
int spanIndex = params.getSpanIndex();
int interval = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
this.interval, context.getResources().getDisplayMetrics());
// 中間間隔
if (spanIndex % 2 == 0) {
outRect.left = 0;
} else {
// item為奇數(shù)位,設(shè)置其左間隔為5dp
outRect.left = interval;
}
// 下方間隔
outRect.bottom = interval;
}
}
如果RecyclerView中有使用上拉加載或下拉刷新的話,那么會(huì)存在兩個(gè)問題。其中一個(gè)問題是進(jìn)行上拉加載時(shí),使用notifyItemDataSetChanged刷新列表,可能會(huì)出現(xiàn)閃動(dòng)且間距錯(cuò)亂,解決辦法是使用notifyItemRangeInserted代替刷新。
int start = list.size();
list.addAll(newItems);
adapter.notifyItemInserted(start, list.size());
而進(jìn)行下拉刷新時(shí),也建議用notifyItemRangeChanged代替notifyDataSetChanged
list.clear();
list.addAll(newList);
adapter.notifyItemRangeChanged(0, list.size());
另外一個(gè)問題是,從底部滾動(dòng)到頂部時(shí),會(huì)發(fā)現(xiàn)頂部item上方偶爾會(huì)出現(xiàn)一大片間隔,解決辦法是給RecyclerView添加OnScrollListener,并在其onScrollStateChanged方法中監(jiān)聽RecyclerView是否滑到頂部,若滑到頂部,則調(diào)用StaggeredLayoutManager的invalidateSpanAssignments方法刷新間隔。
int spanCount = 2
rv.addOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
int[] first = new int[spanCount];
layoutManager.findFirstCompletelyVisibleItemPositions(first);
if (newState == RecyclerView.SCROLL_STATE_IDLE && (first[0] == 1 || first[1] == 1)) {
layoutManager.invalidateSpanAssignments();
}
}
})
至此,item錯(cuò)亂的問題就解決了。
參考博客:
Android瀑布流Item間隔問題