RecyclerView實現(xiàn)瀑布流的各種坑

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可見的時候才去加載,可能會因為加載需要時間,造成短暫的停留,體驗不好。瀑布流嘛,最好讓用戶感知不到你的加載動作,讓他能一直順暢的滑下去。

總結(jié)一點:實現(xiàn)瀑布流最關(guān)鍵的就是高度問題,完美的解決方案就是傳回圖片的時候順便把高度也傳回來。

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

相關(guān)閱讀更多精彩內(nèi)容

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