自己動手寫RecyclerView的上拉載入

wizardev發表於2017-12-28

前言:上一篇文章自己動手寫RecyclerView的下拉重新整理寫過之後大家真是各種批評呀!耦合度高、考慮的情況單一什麼的.....在這裡說明一下,為了能夠讓更大家清楚的瞭解RecyclerView下拉重新整理的這種原理,所以程式碼的耦合度就高了一些!本篇文章將會為大家講解一下怎樣實現RecyclerView的上拉載入,為了講明白原理,文中程式碼的依然會緊耦合。

  如果你閱讀過自己動手寫RecyclerView的下拉重新整理這篇文章,那麼你也一定知道了怎樣在RecyclerView中顯示不同的佈局了,本篇文章講的主要內容有以下兩點

  1. 為RecyclerView新增FooterView作為載入更多時顯示的檢視。
  2. 監聽RecyclerView的滑動狀態,根據不同的狀態來改變FooterView的顯示狀態。

為RecyclerView新增載入更多時的檢視

  上篇講過的知識這裡就不再講了,首先看下FooterView的佈局檔案,如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/loading_view"
    android:layout_width="match_parent"
    android:layout_height="@dimen/dp_60"
    android:gravity="center"
    android:orientation="vertical">

    <ViewStub
        android:id="@+id/loading_viewstub"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_60"
        android:layout="@layout/layout_recyclerview_footer_loading" />

    <ViewStub
        android:id="@+id/end_viewstub"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_60"
        android:layout="@layout/layout_recyclerview_footer_end" />

</LinearLayout>
複製程式碼

這裡為了節省記憶體資源,用了ViewStub,ViewStub是一個輕量級的View,它是一個看不見的,不佔佈局位置,佔用資源非常小的控制元件。使用ViewStub可以方便的在執行時控制展示那個佈局。

在Adapter中載入FooterView

  在RecyclerView中載入不同的佈局的方法,上篇已經講解過了,這裡就直接看關鍵的程式碼

@Override
    public int getItemViewType(int position) {
        if (isFooter(position)) {
            return FOOTER_VIEW_TYPE;
        }
        return super.getItemViewType(position);
    }
    
    public boolean isFooter(int position) {
        int lastPosition = getItemCount() - getFooterViewCount();
        return getFooterViewCount() > 0 && position >= lastPosition;
    }
複製程式碼

上面程式碼中isFooter方法就是判斷在position這個位置的檢視是否是FooterView。

控制FooterView的顯示隱藏

  文章上部分已經講了怎樣在RecyclerView中新增FooterView,下面就重點將下FooterView在什麼時候隱藏和顯示。我們肯定知道RecyclerView在正常的狀態下FooterView也就是底部重新整理的檢視是不可見的,在我們下滑到RecyclerView的最後一個條目時才顯示底部重新整理的檢視,同時,在RecyclerView沒有將螢幕鋪滿時不會顯示底部重新整理的檢視。把這句話整理一下就是下面三點

  1. RecyclerView在正常狀態下,底部重新整理的檢視不可見。
  2. 在滑動RecyclerView時,最後一個可見的item大於或等於RecyclerView總的條目數時,顯示底部重新整理的檢視。
  3. 當RecyclerView的條目數未佔滿螢幕時,不會顯示底部重新整理檢視。

好了,知道了什麼時候顯示和隱藏底部重新整理的檢視,下面面臨的問題就是我們怎樣獲取RecyclerView的最後一個可見的item的position?我們知道RecyclerView總的條目數可以利用LayoutManager的getItemCount方法獲取,那麼LayoutManager中是否有獲取最後一個可見的item的position的方法呢!答案是肯定的,我們可以通過LayoutManager中的方法獲取最後一個可見的item的position,但是LayoutManager又有LinearLayoutManagerGridLayoutManagerStaggeredGridLayoutManager,怎樣分別在這三個LayoutManager中獲取最後一個可見的item的position呢,答案在以下程式碼中

 LayoutManager layoutManager = getLayoutManager();//獲取LayoutManager

        if (layoutManagerType == null) {
            if (layoutManager instanceof LinearLayoutManager) {
                layoutManagerType = LayoutManagerType.LinearLayout;
            } else if (layoutManager instanceof GridLayoutManager) {
                layoutManagerType = LayoutManagerType.GridLayout;
            } else if (layoutManager instanceof StaggeredGridLayoutManager) {
                layoutManagerType = LayoutManagerType.StaggeredGridLayout;
            } else {
                throw new RuntimeException("LayoutManager不符合規範!");
            }
        }

        switch (layoutManagerType) {
            case LinearLayout:
                mLastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
                break;
            case GridLayout:
                mLastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
                break;
            case StaggeredGridLayout:
                StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
                if (lastPositions == null) {
                    lastPositions = new int[staggeredGridLayoutManager.getSpanCount()];
                    staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions);
                }
                mLastVisibleItemPosition = findMax(lastPositions);
                break;
        }
複製程式碼

看過程式碼之後,可以發現這裡比較特殊的是StaggeredGridLayoutManager,由於它的特殊性,可能導致LastVisibleItemPositions有多個,這裡的處理辦法是將LastVisibleItemPositions放進陣列中,然後取出陣列中最大的數作為LastVisibleItemPosition。因為LastVisibleItemPosition根據滑動的位置而變化的,所以需要把上面的程式碼放進RecyclerView的onScrolled方法中。

  文章到了這裡,我們已經知道了怎樣獲取RecyclerView的LastVisibleItemPosition和總的條目數,那什麼時候來比較這兩個數值的大小呢?當然是RecyclerView停止滾動時來比較了,用過ListView的應該知道ListView有一個監聽滑動狀態的方法,同樣RecyclerView也有這個方法,就是onScrollStateChanged(int state),RecyclerView的滑動狀態分別對應RecyclerView.SCROLL_STATE_DRAGGINGRecyclerView.SCROLL_STATE_SETTLINGRecyclerView.SCROLL_STATE_IDLE三個欄位,這個三個欄位分別代表手指在螢幕上拖動、拖動後的慣性滑動和拖動之後停止滑動。講明白之後,下面看程式碼

@Override
    public void onScrollStateChanged(int state) {
        //手指滑動後離開螢幕的狀態
        if (state == RecyclerView.SCROLL_STATE_IDLE) {
            if (mLoadMoreEnabled) {
                LayoutManager layoutManager = getLayoutManager();
                int visibleItemCount = layoutManager.getChildCount();
                int totalItemCount = layoutManager.getItemCount();
                //mLastVisibleItemPosition >= totalItemCount-1是因為新增了一個FooterView
                //totalItemCount>visibleItemCount是限制當totalItemCount沒鋪滿螢幕時不顯示FooterView
                if (visibleItemCount > 0
                        && mLastVisibleItemPosition >= totalItemCount-1
                        && totalItemCount>visibleItemCount
                        && !isNoMore) {
                    mFootView.setVisibility(View.VISIBLE);//這裡顯示FootView
                    if (mLoadingData) {
                        return;
                    } else {
                        mLoadingData = true;
                        mILoadMoreFooter.onLoading();//方法回撥
                        if (mLoadMoreListener != null) {
                            mLoadMoreListener.onLoadMore();
                        }
                    }
                }
            }
        }
    }
複製程式碼

程式碼中一些可能比較繞的地方已經進行了註釋,這裡就不再講解了,已經知道了怎樣控制FooterView的顯示隱藏,下一步就是改變FooterView的狀態了。

改變FooterView的狀態

  因為是在RecyclerView中來改變FooterView的狀態的,所以這裡需要定義一個回撥介面,如下

public interface ILoadMoreFooter {

    void onReset();//正常的狀態下

    void onLoading();//正在載入中的狀態

    void onComplete();//已經載入完成

    void LoadNoMore();//沒有更多資料

    View getFooterView();//獲取FooterView

}
複製程式碼

再看下FooterView的程式碼

public class LoadMoreFooter extends RelativeLayout implements ILoadMoreFooter{
    private State mState;
    private View mLoadingView; //正在載入的圖示
    private View mTheEndView; //載入全部的圖示
  
    //此處省略部分程式碼

    public LoadMoreFooter(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);

    }

    private void init(Context context) {
        inflate(context, R.layout.layout_recyclerview_footer, this);
        onReset();
    }

    @Override
    public void onReset() {
        onComplete();
    }

    @Override
    public void onLoading() {
        setState(State.Loading);
    }

    @Override
    public void onComplete() {
        setState(State.Normal);
    }

    @Override
    public void LoadNoMore() {
        setState(State.NoMore);
    }

    private void setState(State state) {
        if (mState == state) {
            return;
        }
        mState = state;
        switch (state) {
            case Normal:
                if (mLoadingView != null) {
                    mLoadingView.setVisibility(GONE);
                }

                if (mTheEndView != null) {
                    mTheEndView.setVisibility(GONE);
                }

                break;
            case Loading:

                if (mTheEndView != null) {
                    mTheEndView.setVisibility(GONE);
                }
                
                if (mLoadingView == null) {
                    ViewStub viewStub = findViewById(R.id.loading_viewstub);
                    mLoadingView= viewStub.inflate();
                }
                mLoadingView.setVisibility(VISIBLE);

                break;
            case NoMore:
                if (mLoadingView != null) {
                    mLoadingView.setVisibility(GONE);
                }
                if (mTheEndView == null) {
                    ViewStub viewStub = findViewById(R.id.end_viewstub);
                    mTheEndView= viewStub.inflate();
                }
                mTheEndView.setVisibility(VISIBLE);
                break;
        }
    }

    @Override
    public View getFooterView() {
        return this;
    }

    public enum State {
        Normal,
        Loading,
        NoMore,
    }
}
複製程式碼

可以看到,這裡面的主要程式碼就是setState方法中的程式碼,setState方法中的程式碼也比較簡單就是控制佈局的顯示隱藏。文章到了這裡,已經將實現RecyclerView載入更多的功能中的主要的部分講解完了,下面看下效果圖

自己動手寫RecyclerView的上拉載入

結束語

  文中只是貼出了一些比較重要的程式碼,獲取完整的程式碼,點選這裡

ps: 歷史文章中有乾貨哦!

轉載請註明出處:www.wizardev.com

歡迎關注我的公眾號
掃碼關注公眾號,回覆“獲取資料”有驚喜

相關文章