Android RecyclerView下拉刷新 & 上拉加載更多

GitHub傳送門

1.寫在前面

本文主要實現的是上拉加載更多功能,下拉刷新使用的是Google官方的SwipeRefreshLayout控件,因爲在實現這個功能的時候走了不少彎路,所以在此記錄下來分享給大家,先看下效果圖:

上拉加載更多

2.實現

上拉加載更多功能實際上就是給RecyclerView增加一個FooterView,然後通過判斷是否滑動到了最後一條Item,來控制FooterView的顯示和隱藏,接下來我們來看下如何實現:

Adapter添加FooterView

小二,上代碼:

public class LoadMoreAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private List<String> dataList;

    // 普通佈局
    private final int TYPE_ITEM = 1;
    // 腳佈局
    private final int TYPE_FOOTER = 2;
    // 當前加載狀態,默認爲加載完成
    private int loadState = 2;
    // 正在加載
    public final int LOADING = 1;
    // 加載完成
    public final int LOADING_COMPLETE = 2;
    // 加載到底
    public final int LOADING_END = 3;

    public LoadMoreAdapter(List<String> dataList) {
        this.dataList = dataList;
    }

    @Override
    public int getItemViewType(int position) {
        // 最後一個item設置爲FooterView
        if (position + 1 == getItemCount()) {
            return TYPE_FOOTER;
        } else {
            return TYPE_ITEM;
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //進行判斷顯示類型,來創建返回不同的View
        if (viewType == TYPE_ITEM) {
            View view = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.adapter_recyclerview, parent, false);
            return new RecyclerViewHolder(view);

        } else if (viewType == TYPE_FOOTER) {
            View view = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.layout_refresh_footer, parent, false);
            return new FootViewHolder(view);
        }
        return null;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof RecyclerViewHolder) {
            RecyclerViewHolder recyclerViewHolder = (RecyclerViewHolder) holder;
            recyclerViewHolder.tvItem.setText(dataList.get(position));

        } else if (holder instanceof FootViewHolder) {
            FootViewHolder footViewHolder = (FootViewHolder) holder;
            switch (loadState) {
                case LOADING: // 正在加載
                    footViewHolder.pbLoading.setVisibility(View.VISIBLE);
                    footViewHolder.tvLoading.setVisibility(View.VISIBLE);
                    footViewHolder.llEnd.setVisibility(View.GONE);
                    break;

                case LOADING_COMPLETE: // 加載完成
                    footViewHolder.pbLoading.setVisibility(View.INVISIBLE);
                    footViewHolder.tvLoading.setVisibility(View.INVISIBLE);
                    footViewHolder.llEnd.setVisibility(View.GONE);
                    break;

                case LOADING_END: // 加載到底
                    footViewHolder.pbLoading.setVisibility(View.GONE);
                    footViewHolder.tvLoading.setVisibility(View.GONE);
                    footViewHolder.llEnd.setVisibility(View.VISIBLE);
                    break;

                default:
                    break;
            }
        }
    }

    @Override
    public int getItemCount() {
        return dataList.size() + 1;
    }

    private class RecyclerViewHolder extends RecyclerView.ViewHolder {

        TextView tvItem;

        RecyclerViewHolder(View itemView) {
            super(itemView);
            tvItem = (TextView) itemView.findViewById(R.id.tv_item);
        }
    }

    private class FootViewHolder extends RecyclerView.ViewHolder {

        ProgressBar pbLoading;
        TextView tvLoading;
        LinearLayout llEnd;

        FootViewHolder(View itemView) {
            super(itemView);
            pbLoading = (ProgressBar) itemView.findViewById(R.id.pb_loading);
            tvLoading = (TextView) itemView.findViewById(R.id.tv_loading);
            llEnd = (LinearLayout) itemView.findViewById(R.id.ll_end);
        }
    }

    /**
     * 設置上拉加載狀態
     *
     * @param loadState 0.正在加載 1.加載完成 2.加載到底
     */
    public void setLoadState(int loadState) {
        this.loadState = loadState;
        notifyDataSetChanged();
    }
}

首先定義了佈局和數據加載狀態的一些標誌,然後在getItemViewType方法中設置最後一個Item爲FooterView,在onCreateViewHolder方法中根據viewType來加載不同的佈局,最後在onBindViewHolder方法中設置一下加載的狀態顯示就OK了,對了,由於多了一個FooterView,所以要記得在getItemCount方法的返回值中加上1。

到這裏一個線性佈局列表的Adapter就完成了,注意,是線性佈局列表(只有一列的那種),那網格佈局怎麼辦,先看下這個Adapter在網格佈局中使用會發生什麼:

網格佈局

可以看到加載更多的進度條顯示在了一個Item上,如果想要正常顯示的話,進度條需要橫跨兩個Item,這該怎麼辦呢,別擔心,繼續往下看:

@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
	super.onAttachedToRecyclerView(recyclerView);
	RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
	if (manager instanceof GridLayoutManager) {
		final GridLayoutManager gridManager = ((GridLayoutManager) manager);
		gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
			@Override
			public int getSpanSize(int position) {
				// 如果當前是footer的位置,那麼該item佔據2個單元格,正常情況下佔據1個單元格
				return getItemViewType(position) == TYPE_FOOTER ? gridManager.getSpanCount() : 1;
			}
		});
	}
}

在Adapter中重寫onAttachedToRecyclerView方法,首先判斷當前是否爲網格佈局,然後給GridLayoutManager設置一個SpanSizeLookup,這是一個抽象類,裏面有一個抽象方法getSpanSize,這個方法的返回值決定了每個Item佔據的單元格數。

以上文爲例,是一個兩列的網格佈局,如果當前Item是FooterView的話需要佔據兩個單元格才能橫向充滿屏幕,所以需要返回2(GridLayoutManager的getSpanCount方法獲取到的是當前一行中單元格的數量),正常情況下每個Item佔據一個單元格。

RecyclerView設置滑動監聽

設置好FooterView之後,我們還需要判斷一下什麼時候顯示出來,這就需要對RecyclerView設置一下滑動監聽,當滑動到最後一個Item的時候,顯示加載更多UI並且開始請求下一頁列表的數據,看下代碼:

public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {

    //用來標記是否正在向上滑動
    private boolean isSlidingUpward = false;

    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        LinearLayoutManager manager = (LinearLayoutManager) recyclerView.getLayoutManager();
        // 當不滑動時
        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
            //獲取最後一個完全顯示的itemPosition
            int lastItemPosition = manager.findLastCompletelyVisibleItemPosition();
            int itemCount = manager.getItemCount();

            // 判斷是否滑動到了最後一個item,並且是向上滑動
            if (lastItemPosition == (itemCount - 1) && isSlidingUpward) {
                //加載更多
                onLoadMore();
            }
        }
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        // 大於0表示正在向上滑動,小於等於0表示停止或向下滑動
        isSlidingUpward = dy > 0;
    }

    /**
     * 加載更多回調
     */
    public abstract void onLoadMore();
}

代碼中已經寫了很全的註釋,重點看下onScrolled這個回調方法,裏面有dx、dy這兩個參數,當向上滑動的時候dy是大於0的,向左滑動的時候dx是大於0的,反方向滑動則小於0,所以這段代碼稍稍修改一下就可以適用於橫向滑動列表的監聽。

在Activity中使用

準備工作已經完成了,接下來看看如何使用吧:

public class LoadMoreActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private LoadMoreAdapter loadMoreAdapter;
    private List<String> dataList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recyclerview);

        init();
    }

    private void init() {
        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        // 模擬獲取數據
        getData();
        loadMoreAdapter = new LoadMoreAdapter(dataList);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(loadMoreAdapter);

        // 設置加載更多監聽
        recyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener() {
            @Override
            public void onLoadMore() {
                loadMoreAdapter.setLoadState(loadMoreAdapter.LOADING);

                if (dataList.size() < 52) {
                    // 模擬獲取網絡數據,延時1s
                    new Timer().schedule(new TimerTask() {
                        @Override
                        public void run() {
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    getData();
                                    loadMoreAdapter.setLoadState(loadMoreAdapter.LOADING_COMPLETE);
                                }
                            });
                        }
                    }, 1000);
                } else {
                    // 顯示加載到底的提示
                    loadMoreAdapter.setLoadState(loadMoreAdapter.LOADING_END);
                }
            }
        });
    }

    private void getData() {
        char letter = 'A';
        for (int i = 0; i < 26; i++) {
            dataList.add(String.valueOf(letter));
            letter++;
        }
    }
}

調用RecyclerView的addOnScrollListener方法設置一下加載更多監聽,在onLoadMore回調方法中,首先顯示正在加載進度UI,然後模擬獲取網絡數據,完成之後隱藏加載進度UI,加載完兩頁數據之後顯示到底了的提示。

3.封裝

到這裏,我們已經完成了RecyclerView的上拉加載更多功能,但是大部分的邏輯都寫在了Adapter中,這樣每寫一個Adapter都要寫一遍加載邏輯,這是很不優雅的,接下來我們對加載更多功能做一個封裝,使其和Adapter完全解構,看下代碼:

public class LoadMoreWrapper extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private RecyclerView.Adapter adapter;

    // 普通佈局
    private final int TYPE_ITEM = 1;
    // 腳佈局
    private final int TYPE_FOOTER = 2;
    // 當前加載狀態,默認爲加載完成
    private int loadState = 2;
    // 正在加載
    public final int LOADING = 1;
    // 加載完成
    public final int LOADING_COMPLETE = 2;
    // 加載到底
    public final int LOADING_END = 3;

    public LoadMoreWrapper(RecyclerView.Adapter adapter) {
        this.adapter = adapter;
    }

    @Override
    public int getItemViewType(int position) {
        // 最後一個item設置爲FooterView
        if (position + 1 == getItemCount()) {
            return TYPE_FOOTER;
        } else {
            return TYPE_ITEM;
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //進行判斷顯示類型,來創建返回不同的View
        if (viewType == TYPE_FOOTER) {
            View view = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.layout_refresh_footer, parent, false);
            return new FootViewHolder(view);
        } else {
            return adapter.onCreateViewHolder(parent, viewType);
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof FootViewHolder) {
            FootViewHolder footViewHolder = (FootViewHolder) holder;
            switch (loadState) {
                case LOADING: // 正在加載
                    footViewHolder.pbLoading.setVisibility(View.VISIBLE);
                    footViewHolder.tvLoading.setVisibility(View.VISIBLE);
                    footViewHolder.llEnd.setVisibility(View.GONE);
                    break;

                case LOADING_COMPLETE: // 加載完成
                    footViewHolder.pbLoading.setVisibility(View.INVISIBLE);
                    footViewHolder.tvLoading.setVisibility(View.INVISIBLE);
                    footViewHolder.llEnd.setVisibility(View.GONE);
                    break;

                case LOADING_END: // 加載到底
                    footViewHolder.pbLoading.setVisibility(View.GONE);
                    footViewHolder.tvLoading.setVisibility(View.GONE);
                    footViewHolder.llEnd.setVisibility(View.VISIBLE);
                    break;

                default:
                    break;
            }
        } else {
            adapter.onBindViewHolder(holder, position);
        }
    }

    @Override
    public int getItemCount() {
        return adapter.getItemCount() + 1;
    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        if (manager instanceof GridLayoutManager) {
            final GridLayoutManager gridManager = ((GridLayoutManager) manager);
            gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    // 如果當前是footer的位置,那麼該item佔據2個單元格,正常情況下佔據1個單元格
                    return getItemViewType(position) == TYPE_FOOTER ? gridManager.getSpanCount() : 1;
                }
            });
        }
    }

    private class FootViewHolder extends RecyclerView.ViewHolder {

        ProgressBar pbLoading;
        TextView tvLoading;
        LinearLayout llEnd;

        FootViewHolder(View itemView) {
            super(itemView);
            pbLoading = (ProgressBar) itemView.findViewById(R.id.pb_loading);
            tvLoading = (TextView) itemView.findViewById(R.id.tv_loading);
            llEnd = (LinearLayout) itemView.findViewById(R.id.ll_end);
        }
    }

    /**
     * 設置上拉加載狀態
     *
     * @param loadState 0.正在加載 1.加載完成 2.加載到底
     */
    public void setLoadState(int loadState) {
        this.loadState = loadState;
        notifyDataSetChanged();
    }
}

乍一看好像和上文中的LoadMoreAdapter沒什麼區別,都是繼承了RecyclerView.Adapter並實現了其中的一些方法,但是仔細看會發現,構造方法中的參數變成了RecyclerView.Adapter,在LoadMoreWrapper中我們只處理加載更多功能相關的邏輯,其他邏輯交由Adapter本身處理,相當於擴展了Adapter的一些功能,嗯,這種方式還有一個學名,叫【裝飾者模式】。

封裝之後,Adapter中的代碼變成了這樣:

public class LoadMoreWrapperAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private List<String> dataList;

    public LoadMoreWrapperAdapter(List<String> dataList) {
        this.dataList = dataList;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.adapter_recyclerview, parent, false);
        return new RecyclerViewHolder(view);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        RecyclerViewHolder recyclerViewHolder = (RecyclerViewHolder) holder;
        recyclerViewHolder.tvItem.setText(dataList.get(position));
    }

    @Override
    public int getItemCount() {
        return dataList.size();
    }

    private class RecyclerViewHolder extends RecyclerView.ViewHolder {

        TextView tvItem;

        RecyclerViewHolder(View itemView) {
            super(itemView);
            tvItem = (TextView) itemView.findViewById(R.id.tv_item);
        }
    }
}

瞬間減少了一大半,使用起來也很簡單,在原有Adapter的基礎上包上一層就可以了:

LoadMoreWrapperAdapter loadMoreWrapperAdapter = new LoadMoreWrapperAdapter(dataList);
LoadMoreWrapper loadMoreWrapper = new LoadMoreWrapper(loadMoreWrapperAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(loadMoreWrapper);

4.寫在最後

源碼已經上傳到GitHub上了,歡迎Fork,覺得還不錯就Start一下吧!

GitHub傳送門

點我下載本文Demo的Apk

功能已集成至:
《Android開源項目 RecyclerViewHelper 上拉加載更多/頭尾佈局/拖拽排序/側滑刪除/側滑選擇/萬能分割線》

RecyclerViewHelper:https://github.com/alidili/RecyclerViewHelper

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章