RecyclerView瀑布流的那些坑

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);

效果如下:

RecyclerView瀑布流.gif

可以看出,當(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);
去除item的動(dòng)畫.gif

這樣就不會(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)下面的問題:

第一個(gè)item比第二個(gè)item高時(shí),間隔出現(xiàn)錯(cuò)亂.gif

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

image.png

可見,當(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間隔問題

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容