RecyclerView 實現(xiàn)瀑布流,關(guān)鍵是用StaggeredGridLayoutManager這個類。原以為很簡單,用了之后才發(fā)現(xiàn)有很多的問題。
- item亂跳
- 滑動時有空白出現(xiàn)
- 如果item高度不固定得時候,item內(nèi)容不變的時候,可能出現(xiàn)同一個item高度可能會出現(xiàn)不同的值
1. item亂跳問題
StaggeredGridLayoutManager設(shè)置空隙處理方式為 不處理。
setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE)
2.滑動空白的問題
設(shè)置了StaggeredGridLayoutManager不處理空白之后,發(fā)現(xiàn)反復(fù)滑動列表時,頂部item上邊會出現(xiàn)空白。網(wǎng)絡(luò)很多都是講 監(jiān)聽onScrollListener,然后調(diào)用
invalidateSpanAssignments();
這個方法會重繪視圖,在scroll中調(diào)用會顯得非常頻繁,然后引起界面卡頓,滑動不流暢等問題。
本人優(yōu)化了一下,在OnScrollStateChange方法中,但列表處于SCROLL_STATE_IDLE的時候才去調(diào)用這個方法,感覺卡頓方面好很多,但是偶爾還是會出現(xiàn)頂部空白的現(xiàn)象。所以這個不能從根本上解決問題,充其量算是一種彌補之法。
其實產(chǎn)生這個問題的根本原因在于Item的高度,尤其是高度設(shè)置為 wrap_content這種不固定的狀態(tài)。
有很多人包括網(wǎng)上都說用map保存item的高度,尤其是當(dāng)圖片瀑布流不知道圖片大小的時候,第一次保存起來,后面就直接從map里取值然后設(shè)置對應(yīng)控件的高度。本人嘗試之后,發(fā)現(xiàn)表面上看起來好像能解決問題,但是StaggeredGridLayoutManager布局跟其他的布局有點不一樣的地方就是 橫向的 item對應(yīng)的position不確定,并不是像GridLayout那種從上到下,從左至右,position依次遞增。假如列表為2列,那么有可能第二行的左邊的position是2,右邊是3。當(dāng)你反復(fù)滑動幾次之后,其實就是notiftyDataChanged幾次之后,有可能會發(fā)現(xiàn)第二行的左邊是3,右邊是2。所以保存高度這種方式也不是很靠譜。
折騰了兩天之后,萬般無賴之下,發(fā)現(xiàn)只有從接口傳回的圖片數(shù)據(jù)帶上原始寬高,才能完美解決問題。
在已經(jīng)圖片高度的情況下,一切都好辦了,根據(jù)屏幕寬度計算出固定的item寬度,然后對原始圖片進(jìn)行等比縮放高度,然后在onBindViewHolder中設(shè)置動態(tài)設(shè)置ImageView 的高度就好了,這時候也不用map保存什么,也不需要調(diào)用invalidateSpanAssignments方法去重繪,因為已經(jīng)不會出現(xiàn)空白了。
3.RecyclerView設(shè)置item間隔問題
剛才已經(jīng)提到,StaggeredGridLayoutManager不能根據(jù)item 的 position來判斷一個item是靠在左邊還是右邊。所以之間定義的SpaceItemDecoration不能用了,現(xiàn)在的解決辦法是 定義一個簡單的SpaceItemDecoration,代碼如下:
public class SpaceItemDecoration extends RecyclerView.ItemDecoration {
private int spanCount;
private int space;
private boolean includeEdge;
public SpaceItemDecoration(int spanCount, int space) {
this.spanCount = spanCount;
this.space = space;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view); // item position
outRect.left = space;
outRect.right = space;
if(position!=0 && position!=1){
outRect.top = 2*space;
}else{
outRect.top = space;
}
}
}
這樣會發(fā)現(xiàn)兩列中間的間隔是 邊緣的兩倍。我的解決辦法是 給RecyclerView設(shè)置一定的padding,讓視圖看起來,四周,中間 的間隔看起來都一樣大。相當(dāng)于SpaceItemDecoration,不夠,還需RecyclerView補一刀。
當(dāng)然網(wǎng)上也有人用變量把Item是左邊還是右邊這種數(shù)據(jù)存起來,我覺的有點麻煩,而且第一次布局怎么辦?;蛟S還有更好更完美的辦法等著我們?nèi)グl(fā)現(xiàn)。
4.上拉加載問題
因為StaggeredGridLayoutManager 布局item 的position特殊性,就連findLastVisibleItemPositions方法的參數(shù)和返回值都不一樣,這個是返回一個position 數(shù)組。廢話不多說,本人自定義了一個StaggerRecyclerView專門針對StaggeredGridLayoutManager布局。代碼如下:
public class StaggerRecyclerView extends RecyclerView {
private OnLoadMoreListener onLoadMoreListener;
private boolean isLoadingMore = false;
private static final int TOLAST = 6;
public StaggerRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.addOnScrollListener(new OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
StaggeredGridLayoutManager layoutManager = null ;
if(recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager){
layoutManager = (StaggeredGridLayoutManager) recyclerView.getLayoutManager();
}else{
return;
}
int[] positions = null;
int[] into = layoutManager.findLastCompletelyVisibleItemPositions(positions);
int lastPositon = Math.max(into[0],into[1]);
for(int i = 0;i<into.length;i++){
Log.d("home","lastPositon ="+lastPositon +" | itemcount ="+layoutManager.getItemCount()+" | dx = "+dx+" | dy = "+dy);
}
if(!isLoadingMore && dy>0 && layoutManager.getItemCount()-lastPositon<=TOLAST){
//load more
isLoadingMore = true;
if(onLoadMoreListener!=null){
onLoadMoreListener.onLoadMore();
}
}
}
});
}
public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
this.onLoadMoreListener = onLoadMoreListener;
}
public void setLoadingMoreComplete(){
isLoadingMore = false;
}
public interface OnLoadMoreListener{
void onLoadMore();
}
}
其中 TOLAST是我定義的一個常亮,主要是決定什么時候開始加載,數(shù)字越大越提前加載,它表示提前幾個item去加載。相對于最后一個而言。往往當(dāng)我們上拉的時候,如果等到最后一個item可見的時候才去加載,可能會因為加載需要時間,造成短暫的停留,體驗不好。瀑布流嘛,最好讓用戶感知不到你的加載動作,讓他能一直順暢的滑下去。