RecyclerView瀑布流的那些坑


RecyclerView的布局排列主要通過布局管理器——LayoutManager去實現,不同的布局能為使得RecyclerView擁有不同的排列效果。StaggeredGridLayoutManager是其中的一種布局管理器,它能夠實現類似於瀑布流的效果。
實現瀑布流的代碼很簡單,只要創建StaggeredGridLayoutManager實例,並將該實例綁定為RecyclerView的布局管理器:

// 創建StaggeredGridLayoutManager實例
StaggeredGridLayoutManager layoutManager = 
new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
// 綁定布局管理器
rv.setLayoutManager(layoutManager);

效果如下:
RecyclerView瀑布流.gif
可以看出,當滑到底部的時候,最后一個item等待一段時間才顯示出來。這是RecyclerView使用瀑布流布局時自帶的動畫效果,而這個動畫效果看起來不怎么舒服,一般都會去掉。需要調用RecyclerView的setAnimator將Animator設置為null,並調用StaggeredLayoutManager的setGapStrategy方法設置為StaggeredGridLayoutManager.GAP_HANDLING_NONE:

layoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
rv.setItemAnimator(null);

去除item的動畫.gif
這樣就不會出現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高時,間隔出現錯亂.gif
可以看到,下方的間隔是沒有問題的,但是中間的間隔發生錯亂。這是因為瀑布流的item不是根據下標的順序去排列,而是根據上方的兩個item的高度差確定位置。上方哪個item預留的空間比較高,就會排列到這個item的下方:
image.png
image.png
可見,當第二個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

本文轉自:https://www.jianshu.com/p/4e142909b824


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM