RecyclerView的布局排列主要通過布局管理器——LayoutManager去實現,不同的布局能為使得RecyclerView擁有不同的排列效果。StaggeredGridLayoutManager是其中的一種布局管理器,它能夠實現類似於瀑布流的效果。
實現瀑布流的代碼很簡單,只要創建StaggeredGridLayoutManager實例,並將該實例綁定為RecyclerView的布局管理器:
// 創建StaggeredGridLayoutManager實例
StaggeredGridLayoutManager layoutManager =
new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
// 綁定布局管理器
rv.setLayoutManager(layoutManager);
效果如下:
可以看出,當滑到底部的時候,最后一個item等待一段時間才顯示出來。這是RecyclerView使用瀑布流布局時自帶的動畫效果,而這個動畫效果看起來不怎么舒服,一般都會去掉。需要調用RecyclerView的setAnimator將Animator設置為null,並調用StaggeredLayoutManager的setGapStrategy方法設置為StaggeredGridLayoutManager.GAP_HANDLING_NONE:
layoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
rv.setItemAnimator(null);
這樣就不會出現item要等待一段時間才顯示出來的問題了。如果像上述瀑布流那樣在item之間添加間隔,那么就要繼承RecyclerView的ItemDecoration類,並復寫其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為奇數位,設置其左間隔為5dp
outRect.left = interval;
}
// 下方間隔
outRect.bottom = interval;
}
}
在RecyclerView中添加itemDecoration:
rv.addItemDecoration(new StaggeredDividerItemDecoration(this, 5))
如果像上例一樣,第一個item比第二個item的高度低的話,那么上述代碼是沒有問題的,但是第一個item比第二item高的話,就會出現下面的問題:
可以看到,下方的間隔是沒有問題的,但是中間的間隔發生錯亂。這是因為瀑布流的item不是根據下標的順序去排列,而是根據上方的兩個item的高度差確定位置。上方哪個item預留的空間比較高,就會排列到這個item的下方:
可見,當第二個item高度比第一個item的低時,第三個item將排列在第二個item的下方,那么通過position判斷設置間隔的方法就不成立了。這就導致item間隔的錯亂。
那么,要怎樣解決這個問題呢?在參考了大神的博客以后,才發現StaggeredLayoutManager.LayoutParams類中有一個getSpanIndex()的方法,可以獲取item在span中的下標。那么,問題就很容易解決了,只要將position替換為span的下標判斷item的位置,就不會出現間隔錯亂的問題了:
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中的下標
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為奇數位,設置其左間隔為5dp
outRect.left = interval;
}
// 下方間隔
outRect.bottom = interval;
}
}
如果RecyclerView中有使用上拉加載或下拉刷新的話,那么會存在兩個問題。其中一個問題是進行上拉加載時,使用notifyItemDataSetChanged刷新列表,可能會出現閃動且間距錯亂,解決辦法是使用notifyItemRangeInserted代替刷新。
int start = list.size();
list.addAll(newItems);
adapter.notifyItemInserted(start, list.size());
而進行下拉刷新時,也建議用notifyItemRangeChanged代替notifyDataSetChanged
list.clear();
list.addAll(newList);
adapter.notifyItemRangeChanged(0, list.size());
另外一個問題是,從底部滾動到頂部時,會發現頂部item上方偶爾會出現一大片間隔,解決辦法是給RecyclerView添加OnScrollListener,並在其onScrollStateChanged方法中監聽RecyclerView是否滑到頂部,若滑到頂部,則調用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錯亂的問題就解決了。
參考博客:https://blog.csdn.net/tobevan/article/details/78924338