RecyclerView 實(shí)現(xiàn)瀑布流,關(guān)鍵是用StaggeredGridLayoutManager這個(gè)類癣籽。原以為很簡(jiǎn)單敬辣,用了之后才發(fā)現(xiàn)有很多的問題。
- item亂跳
- 滑動(dòng)時(shí)有空白出現(xiàn)
- 如果item高度不固定得時(shí)候陋气,item內(nèi)容不變的時(shí)候,可能出現(xiàn)同一個(gè)item高度可能會(huì)出現(xiàn)不同的值
1. item亂跳問題
StaggeredGridLayoutManager設(shè)置空隙處理方式為 不處理引润。
setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE)
2.滑動(dòng)空白的問題
設(shè)置了StaggeredGridLayoutManager不處理空白之后巩趁,發(fā)現(xiàn)反復(fù)滑動(dòng)列表時(shí),頂部item上邊會(huì)出現(xiàn)空白淳附。網(wǎng)絡(luò)很多都是講 監(jiān)聽onScrollListener议慰,然后調(diào)用
invalidateSpanAssignments();
這個(gè)方法會(huì)重繪視圖,在scroll中調(diào)用會(huì)顯得非常頻繁奴曙,然后引起界面卡頓别凹,滑動(dòng)不流暢等問題。
本人優(yōu)化了一下洽糟,在OnScrollStateChange方法中炉菲,但列表處于SCROLL_STATE_IDLE的時(shí)候才去調(diào)用這個(gè)方法,感覺卡頓方面好很多坤溃,但是偶爾還是會(huì)出現(xiàn)頂部空白的現(xiàn)象拍霜。所以這個(gè)不能從根本上解決問題,充其量算是一種彌補(bǔ)之法薪介。
其實(shí)產(chǎn)生這個(gè)問題的根本原因在于Item的高度祠饺,尤其是高度設(shè)置為 wrap_content這種不固定的狀態(tài)。
有很多人包括網(wǎng)上都說(shuō)用map保存item的高度汁政,尤其是當(dāng)圖片瀑布流不知道圖片大小的時(shí)候道偷,第一次保存起來(lái),后面就直接從map里取值然后設(shè)置對(duì)應(yīng)控件的高度烂完。本人嘗試之后试疙,發(fā)現(xiàn)表面上看起來(lái)好像能解決問題,但是StaggeredGridLayoutManager布局跟其他的布局有點(diǎn)不一樣的地方就是 橫向的 item對(duì)應(yīng)的position不確定抠蚣,并不是像GridLayout那種從上到下祝旷,從左至右,position依次遞增。假如列表為2列怀跛,那么有可能第二行的左邊的position是2距贷,右邊是3。當(dāng)你反復(fù)滑動(dòng)幾次之后吻谋,其實(shí)就是notiftyDataChanged幾次之后忠蝗,有可能會(huì)發(fā)現(xiàn)第二行的左邊是3,右邊是2漓拾。所以保存高度這種方式也不是很靠譜阁最。
折騰了兩天之后,萬(wàn)般無(wú)賴之下骇两,發(fā)現(xiàn)只有從接口傳回的圖片數(shù)據(jù)帶上原始寬高速种,才能完美解決問題。
在已經(jīng)圖片高度的情況下低千,一切都好辦了配阵,根據(jù)屏幕寬度計(jì)算出固定的item寬度,然后對(duì)原始圖片進(jìn)行等比縮放高度示血,然后在onBindViewHolder中設(shè)置動(dòng)態(tài)設(shè)置ImageView 的高度就好了棋傍,這時(shí)候也不用map保存什么,也不需要調(diào)用invalidateSpanAssignments方法去重繪难审,因?yàn)橐呀?jīng)不會(huì)出現(xiàn)空白了瘫拣。
3.RecyclerView設(shè)置item間隔問題
剛才已經(jīng)提到,StaggeredGridLayoutManager不能根據(jù)item 的 position來(lái)判斷一個(gè)item是靠在左邊還是右邊剔宪。所以之間定義的SpaceItemDecoration不能用了拂铡,現(xiàn)在的解決辦法是 定義一個(gè)簡(jiǎ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;
}
}
}
這樣會(huì)發(fā)現(xiàn)兩列中間的間隔是 邊緣的兩倍葱绒。我的解決辦法是 給RecyclerView設(shè)置一定的padding,讓視圖看起來(lái)斗锭,四周地淀,中間 的間隔看起來(lái)都一樣大。相當(dāng)于SpaceItemDecoration岖是,不夠帮毁,還需RecyclerView補(bǔ)一刀。
當(dāng)然網(wǎng)上也有人用變量把Item是左邊還是右邊這種數(shù)據(jù)存起來(lái)豺撑,我覺的有點(diǎn)麻煩烈疚,而且第一次布局怎么辦〈辖危或許還有更好更完美的辦法等著我們?nèi)グl(fā)現(xiàn)爷肝。
4.上拉加載問題
因?yàn)镾taggeredGridLayoutManager 布局item 的position特殊性,就連findLastVisibleItemPositions方法的參數(shù)和返回值都不一樣,這個(gè)是返回一個(gè)position 數(shù)組灯抛。廢話不多說(shuō)金赦,本人自定義了一個(gè)StaggerRecyclerView專門針對(duì)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是我定義的一個(gè)常亮对嚼,主要是決定什么時(shí)候開始加載夹抗,數(shù)字越大越提前加載,它表示提前幾個(gè)item去加載纵竖。相對(duì)于最后一個(gè)而言漠烧。往往當(dāng)我們上拉的時(shí)候,如果等到最后一個(gè)item可見的時(shí)候才去加載靡砌,可能會(huì)因?yàn)榧虞d需要時(shí)間沽甥,造成短暫的停留,體驗(yàn)不好乏奥。瀑布流嘛摆舟,最好讓用戶感知不到你的加載動(dòng)作,讓他能一直順暢的滑下去邓了。