史上最全的使用RecyclerView實現下拉重新整理和上拉載入更多
前言:
縱觀多數App,下拉重新整理和上拉載入更多是很常見的功能,但是谷歌官方只有一個SwipeRefreshLayout用來下拉重新整理,上拉載入更多還要自己做。
本篇文章基於RecyclerView簡單封裝了這兩個操作,下拉重新整理支援LinearLayoutManager、GridLayoutManager和StaggeredGridLayoutManager;上拉載入更多隻支援前兩者。
概述:
關於此篇文章,你需要提前瞭解: 1.實際專案開發過程中,能使用到下拉重新整理和上拉載入更多功能的往往都和網路請求有關,即,下拉重新整理和上拉載入更多需要有後臺伺服器的支援。
2.不管是下拉重新整理還是上拉載入更多,本質上都是通過監聽事件的觸發而已。
3.如果是單純的只是使用下拉重新整理(不使用上拉載入更多),我們完全可以在每次觸發下拉重新整理操作時,直接將從後臺伺服器上請求下來的資料全部通過RecyclerView+Adapter進行展示即可,無須分頁,也無須規定當前頁展示多少條資料。
//虛擬碼
.....
mListData.clear();
mListData.addAll(value.getResults());
adapter.notifyDataSetChanged();
.........
但是如果專案中同時整合下拉重新整理和上拉載入更多的話,則不管是下拉重新整理還是上拉載入更過的介面中必然要涉及一下兩個重要的引數:
parm 1:每頁請求的資料
parm 2:當前頁數
原因很簡:如果不設定這兩個引數的話,在進行下來重新整理時完全可以將所有的資料一起展示,上拉時也就不會觸發什麼載入更多的操作了。
說在前面:
之前想研究下拉重新整理和上拉載入更多的示例程式碼。從網上找了好多,關於上拉載入更多的邏輯存在各種各樣bug,簡單有下面幾類: 1.簡單的模擬,沒有後臺伺服器支援,邏輯走不通。 2.在上拉載入更多的最後的一個item的處理上存在邏輯混亂。雖然嘗試做了更改,仍然不能使其正常執行。 3.對資料請求失敗的處理不完善。 4.不支援GridLayoutManager對應的多行多列的實現。
其中講述了下拉重新整理和上拉載入更多的使用步驟,以及需要重點關注的邏輯。因此本文就不再贅述其中上述部落格已經存在的使用步驟,而是從專案結構的角度幫助大家更好理解和實現對上拉載入更多功能。
效果演示:
上圖說話:
說在中間:
在分析專案結構之前呢,先把專案中用到相關知識點或者模式跟大家羅列一下,如果大家對列舉的這些內容都已經瞭解或者已經掌握,則可以繼續往下看,如果好多知識你都不曾接觸過。那麼你繼續往下看的話會感到很迷茫,看不懂。(哈哈,有句話說的好,當你感到迷茫的時候,正是促使你學習的最佳時機,不要多想,行動起來吧。)
本專案用到的知識點羅列: 1.專案中用到的設計模式是標準的MVP模式(Model-View-Presenter)。 mvp速學通道:MVP模式使用示例詳解
3.ButterKnife. ButterKnife是一個專注於Android系統View的注入框架,讓你從煩人的findViewById中解脫出來。專案中使用的是版本比較老的ButterKnife,所以在獲取控制元件例項時候,的註釋欄位可能會有些區別,eg.老本本使用的是InjectView,新版本使用的是bindView。這個比較簡單。 速學通道:Android Butterknife使用方法總結
好了,上面列舉的就是本專案中用到的且會影響到程式碼研讀的知識點,提前先列出來。如果對上面的知識點有了基本的認知或者更深入的理解,才可以進入到下一步。
順便說一下,上面列舉的相關知識點,也是目前進行進行android專案開發時用到的最新最熱的知識點。不要感覺這些東西很超前,相反,他們已經出來好幾年了。霍霍,是不是意識到了點什麼。。。。。
專案結構與分析:
分析:
adapter包:
1.AdapterWrapper.java
/**
* 1. 顯示或隱藏"滑動載入更多條目"
* 2. 設定"滑動載入更多條目的狀態" 提示載入或正在載入
* 3. 末尾item由wapper新增, 其他item由原生adapter新增
* 4. 隱藏"滑動載入更多條目"時, item數量減1
* 5. 更新操作呼叫原生adapter的notifyDataSetChanged()
* 6. 不支援StaggeredGridLayoutManager
*/
public class AdapterWrapper extends RecyclerView.Adapter {
..........
...........
2.GankNewsAdapter.java
/**
* "乾貨"/"福利"的adapter介面卡
*/
public class GankNewsAdapter extends RecyclerView.Adapter<GankNewsAdapter.MyViewHolder> {
..........
...........
3.GankViewPagerAdapter.java
/**
* 用於fragment中"android乾貨"和“福利”tab的切換的介面卡
*/
public class GankViewPagerAdapter extends FragmentPagerAdapter {
..........
...........
bean包:
1.GankNewsBean.java
/**
* 用於展示“android乾貨”的實體類物件
*/
public class GankNewsBean implements Serializable {
/**
* 使用Gson解析json成物件時預設的是將json裡對應欄位的值解析到java物件裡對應欄位的屬性裡面
*/
@SerializedName("desc")
private String mDesc;
@SerializedName("images")
private List<String> mImages;
@SerializedName("publishedAt")
private Date mPublishedAt;
@SerializedName("url")
private String mUrl;
public String getDesc() {
return mDesc;
}
public List<String> getImages() {
return mImages;
}
public Date getPublishedAt() {
return mPublishedAt;
}
public String getUrl() {
return mUrl;
}
}
2.GankResponseBean.java
/**
* 封裝的Retrofit返回的資料類
* 使用Gson解析json成物件,預設的是將json裡對應欄位的值解析到java物件裡對應欄位的屬性裡面
*/
public class GankResponseBean implements Serializable {
@SerializedName("results")
private List<GankNewsBean> mResults;
public List<GankNewsBean> getResults() {
return mResults;
}
}
contract包:
1.GankContract.java
/**
* Presenter層介面---控制Model層的資料操作及呼叫View層的UI操作來完成“中間人”工作
*/
public interface GankContract {
/**
* 用於view(Activity或者Fragment)中進行的ui操作
*/
interface View {
void setPageState(boolean isLoading);
void setListData(List<GankNewsBean> listData);
void onRefreshComplete();
void onLoadMoreComplete();
void onInitLoadFailed();
}
/**
* Presenter是連線model和view的紐帶,因此其要提供獲取model的介面
*/
interface Presenter {
void onViewCreate();
void onRefresh();
void onLoadMore();
}
}
2.GankPresenter.java
/**
* GankPresenter是連線view和model的紐帶,此處職能:通過view的呼叫去初始化view的相關邏輯,
* 同時呼叫mode的相關介面去獲得資料,並將資料返回給presenter
*/
public class GankPresenter implements GankContract.Presenter {
.............
.............
3.GankURLService.java
/**
* 使用Rxjava和Retrofit封裝的資料請求介面
*/
public interface GankURLService {
String BASE_URL = "http://gank.io/api/";
@GET("data/{category}/{pagecount}/{page}")
Observable<GankResponseBean> requestData(
@Path("category") String category,
@Path("pagecount") int countPerPage,
@Path("page") int page);
}
4.NullOnEmptyConverterFactory.java
/**
* 上面兩個介面從服務端獲取了資料,通過GsonConverterFactory將服務端相應內容解析成對應的實體類。
* 在介面正常響應時(有資料返回),並沒有什麼異常發生,但當介面請求的資料為空,我們的服務端人員並不是返回理論意義上的空,
* null或者[](資料集合空),而是返回沒有響應體body,只有響應頭header,content-length為0的Response
*/
public class NullOnEmptyConverterFactory extends Converter.Factory {
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
final Converter<ResponseBody, ?> delegate = retrofit.nextResponseBodyConverter(this, type, annotations);
return new Converter<ResponseBody, Object>() {
@Override
public Object convert(ResponseBody body) throws IOException {
if (body.contentLength() == 0) return null;
return delegate.convert(body);
}
};
}
}
好了,主程式程式碼就不貼出來了,大家通過上面的專案結構先對整體功能結構劃分有個瞭解。然後大家結合本專案的原來出處::::
說在最後:
文章最後,我會貼出此專案的示例原始碼。我在原來示例的基礎上,添加了更為詳細的註釋,同時也對原來示例原始碼中描述明顯有誤的地方做了修改。當然了,你也可以直接下載原來博文中的原始碼示例。重點是要好好分析。 原始碼中有些比較難理解的地方,可能一時半會理解不過來。沒關係,一步步來: 1.先學會如何使用,做到稍加修改就能滿足自己的專案需求。 2.在今後再次遇到下拉重新整理和上拉載入更多的需求的時候,繼續研究。相信隨著研究的深入以及相關問題的不斷解決。我們一定能完全消化,化為己用。
示例程式碼下載:
好了,至此完結,同時感謝原示例程式碼作者,小夥伴如果有疑問請留言,願我們相互探討,共同進步。