前言
- 隨著RecyclerView的越來越流行,我看著項(xiàng)目里L(fēng)istView罩抗、GridView陷入沉思拉庵,是時(shí)候開始改變了!(認(rèn)真臉)我決定將項(xiàng)目中的這些控件都改用RecyclerView套蒂。然而钞支,像下拉刷新等功能是必不可少的,雖然有很多現(xiàn)成的可以用操刀,但是烁挟,我毅然決定自己動(dòng)手。
思路
- 下定決心了骨坑,那么接下來就是考慮該怎么實(shí)現(xiàn)了撼嗓。
- 由于RecyclerView并沒有像ListView一樣為我們提供方便的addHeaderView()、addFooterView()方法來添加頭布局和腳布局(這也是我們要實(shí)現(xiàn)的)欢唾,所以就不能像ListView一樣通過添加頭布局且警、腳布局實(shí)現(xiàn)下拉刷新、上拉加載更多功能了礁遣。
- 而在RecyclerView里斑芜,展示多少條數(shù)據(jù),有多少條目祟霍,這些杏头,都是由適配器控制的盈包,所以,要想實(shí)現(xiàn)以上的功能大州,就要從適配器入手了续语。
實(shí)現(xiàn)
思路有了,那么接下來就是如何實(shí)現(xiàn)了厦画。
首先疮茄,自然是新建RLRecyclerView繼承RecyclerView,實(shí)現(xiàn)構(gòu)造方法根暑。
-
接著力试,重寫setAdapter()方法,將傳遞進(jìn)來的適配器對象保存下來排嫌,實(shí)際上設(shè)置的是封裝好的實(shí)現(xiàn)以上功能的適配器畸裳。
@Override public void setAdapter(Adapter adapter) { // 保存設(shè)置的適配器 mAdapter = adapter; // 設(shè)置封裝的適配器 innerAdapter = new InsideAdapter(); super.setAdapter(innerAdapter); // 注冊觀察者 mAdapter.registerAdapterDataObserver(mObserver); }
-
至于下面的注冊觀察者,我們晚點(diǎn)再說淳地,接下來就是這個(gè) InsideAdapter怖糊,直接以內(nèi)部類形式定義在RLRecyclerView中,繼承RecyclerView.Adapter
/** * 添加了頭布局颇象、腳布局伍伤、下拉刷新、上拉加載更多功能的適配器類 */ class InsideAdapter extends Adapter { /** 布局類型-刷新布局 */ private static final int VIEW_TYPE_REFRESH = 0; /** 布局類型-頭布局 */ private static final int VIEW_TYPE_HEADER = 1; /** 布局類型-普通布局 */ private static final int VIEW_TYPE_NORMAL = 2; /** 布局類型-腳布局 */ private static final int VIEW_TYPE_FOOTER = 3; /** 布局類型-加載更多布局 */ private static final int VIEW_TYPE_LOADMORE = 4; @Override public int getItemViewType(int position) { // 重寫方法遣钳,根據(jù)下標(biāo)判斷布局類型 if (isRefresh(position)) { return VIEW_TYPE_REFRESH; } else if (isHeader(position)) { return VIEW_TYPE_HEADER; } else if (isFooter(position)) { return VIEW_TYPE_FOOTER; } else if (isLoadMore(position)) { return VIEW_TYPE_LOADMORE; } else { return VIEW_TYPE_NORMAL; } } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { ViewHolder holder; // 根據(jù)布局類型扰魂,返回不同的ViewHolder對象,SimpleViewHolder不做任何操作 switch (viewType) { case VIEW_TYPE_REFRESH: holder = new SimpleViewHolder(mRefresh); break; case VIEW_TYPE_HEADER: holder = new SimpleViewHolder(mHeaders.get(headerPosition++)); break; case VIEW_TYPE_NORMAL: // 普通布局類型返回設(shè)置的Adpter的ViewHolder對象 holder = mAdapter.onCreateViewHolder(parent, viewType); break; case VIEW_TYPE_FOOTER: holder = new SimpleViewHolder(mFooters.get(footerPosition++)); break; case VIEW_TYPE_LOADMORE: holder = new SimpleViewHolder(mLoadMore); break; default: holder = new SimpleViewHolder(null); break; } return holder; } @Override public void onBindViewHolder(ViewHolder holder, int position) { // 刷新布局蕴茴、加載更多劝评、頭布局、腳布局不做處理 if (isRefresh(position) || isLoadMore(position) || isHeader(position) || isFooter(position)) { return; } mAdapter.onBindViewHolder(holder, realPosition(position)); } @Override public int getItemCount() { // 根據(jù)功能開啟情況以及頭布局腳布局返回實(shí)際的條目數(shù) if (REFRESH_MODE_BOTH.equals(mode)) { return mAdapter.getItemCount() + mHeaders.size() + mFooters.size() + 2; } else if (REFRESH_MODE_REFRESH.equals(mode) || REFRESH_MODE_LOADMORE.equals(mode)) { return mAdapter.getItemCount() + mHeaders.size() + mFooters.size() + 1; } else { return mAdapter.getItemCount() + mHeaders.size() + mFooters.size(); } } class SimpleViewHolder extends RecyclerView.ViewHolder { SimpleViewHolder(View itemView) { super(itemView); } } }
-
先不說下拉刷新倦淀、上拉加載控件需要隱藏蒋畜,實(shí)際運(yùn)行起來,你會(huì)發(fā)現(xiàn)撞叽,如果使用 LinnerLayoutManager 是沒有問題的百侧,但是如果使用的是 GridLayoutManager 或者是 StaggeredGridLayoutManager 你就會(huì)發(fā)現(xiàn)并沒有達(dá)到想象中的效果,這是因?yàn)槲覀兊拇a中實(shí)際上只是在設(shè)置適配器的時(shí)候能扒,添加了幾條數(shù)據(jù)佣渴,并沒有改變他的展示效果。而 RecyclerView 把布局展示的工作都交給了 LayoutManager初斑,所以這個(gè)時(shí)候辛润,為了能夠?qū)崿F(xiàn)頭布局等能在 GridLayoutManager 和 StaggeredGridLayoutManager 下寬度也能MATCH_PARENT,我們就需要在 InsideAdapter 中重寫 onAttachedToRecyclerView 和 onViewAttachedToWindow 兩個(gè)方法:
@Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); if (manager instanceof GridLayoutManager) { // 如果是Grid布局 final GridLayoutManager gridManager = ((GridLayoutManager) manager); gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { // 這個(gè)方法是返回當(dāng)前對象所在行有幾列 return (isRefresh(position) || isLoadMore(position) || isHeader(position) || isFooter(position)) ? gridManager.getSpanCount() : 1; // 如果是刷新、加載更多或頭布局砂竖、腳布局獨(dú)占一行真椿,否則按照設(shè)置展示 } }); } } @Override public void onViewAttachedToWindow(ViewHolder holder) { ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams && (isRefresh(holder.getLayoutPosition()) || isLoadMore(holder.getLayoutPosition()) || isHeader(holder.getLayoutPosition()) || isFooter(holder.getLayoutPosition()))) { StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; // 如果是刷新、加載更多或頭布局乎澄、腳布局獨(dú)占一行突硝,否則按照設(shè)置展示 p.setFullSpan(true); // 設(shè)置獨(dú)占一行 } }
這樣,即使布局為 GridLayoutManager 或者 StaggeredGridLayoutManager 刷新置济、加載更多解恰、頭布局、腳布局都是單獨(dú)的一行了浙于。
接下來要實(shí)現(xiàn)的就是下拉刷新护盈、上拉加載更多的功能了,這個(gè)實(shí)現(xiàn)思路其實(shí)和ListView下拉刷新是一致的羞酗,同樣通過設(shè)置刷新布局和加載更多布局的margin值來實(shí)現(xiàn)腐宋,其核心就是設(shè)置觸摸事件監(jiān)聽,然后在 onTouch 方法中判斷不同的情況檀轨,做不同的處理胸竞。由于這部分內(nèi)容比較復(fù)雜,筆者就不在這里細(xì)說了参萄,有興趣的朋友可以在文章最后找到Github地址查看源碼卫枝,源碼里都有詳細(xì)注釋。
在前文有說明一段代碼后面解釋拧揽,那就是 mAdapter.registerAdapterDataObserver(mObserver)
-
這是給使用者設(shè)置適配器的時(shí)候同時(shí)給這個(gè)適配器注冊了一個(gè)觀察者:
private final RecyclerView.AdapterDataObserver mObserver = new RecyclerView.AdapterDataObserver() { @Override public void onChanged() { innerAdapter.notifyDataSetChanged(); } @Override public void onItemRangeInserted(int positionStart, int itemCount) { innerAdapter.notifyItemRangeInserted(positionStart, itemCount); } @Override public void onItemRangeChanged(int positionStart, int itemCount) { innerAdapter.notifyItemRangeChanged(positionStart, itemCount); } @Override public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { innerAdapter.notifyItemRangeChanged(positionStart, itemCount, payload); } @Override public void onItemRangeRemoved(int positionStart, int itemCount) { innerAdapter.notifyItemRangeRemoved(positionStart, itemCount); } @Override public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { innerAdapter.notifyItemMoved(fromPosition, toPosition); } };
這個(gè)觀察者所做的就是在使用者調(diào)用適配器的notifyDataSetChanged方法時(shí)剃盾,同步調(diào)用InnerAdapter的方法腺占,因?yàn)橥ㄟ^setAdapter方法設(shè)置的適配器實(shí)際上是我們封裝的InnerAdapter淤袜,所以,當(dāng)數(shù)據(jù)變更時(shí)衰伯,需要調(diào)用InnerAdapter的方法才能同步更新界面铡羡。
總結(jié)
- 說到這,RecyclerView的刷新意鲸、加載更多的功能就差不多都實(shí)現(xiàn)了烦周,由于文章篇幅原因,很多東西都沒有詳細(xì)的寫怎顾,大家如果有興趣的读慎,這里貼上Github地址:RLRecyclerView ,歡迎Star槐雾。
- 最后夭委,在這里感謝前輩們的無私分享,項(xiàng)目中借鑒了郭霖大大的 Android下拉刷新完全解析募强,教你如何一分鐘實(shí)現(xiàn)下拉刷新功能以及XRecyclerView的部分源碼株灸,歡迎大家討論學(xué)習(xí)崇摄!