開始前總是要有廢話的
很早之前,上拉載入下拉重新整理這種互動方式一經推出,就火炸了。如果你在兩三年前就接觸過android開發,你一定聽說過PullToRefreshListView這個開源框架,使用起來很簡單,首先感謝偉大的作者開源這麼優秀的作品,但是對於新手來講,這個框架有些過於龐大了,類和方法實在太多,定製功能太複雜,並且不得不說,再使用過程中,這個框架的侷限性很大,說到底他只是個ListView,當你和其他可滑動控制元件一起套用時,就會出現各種的問題,並且這個框架的作者已經好幾年沒有維護過他了。
我在git上嘗試查詢能替代他的傢伙,萬幸這個傢伙被我成功發現了。雖然他的歲數也已經很大了,但到現在我仍然在使用並且也一直在維護他的程式碼。android-Ultra-Pull-To-Refresh,同樣感謝liaohuqiu貢獻這麼優秀的作品。我習慣叫這個框架為Ptr,至於為什麼到現在我還要推薦這個框架,請聽我慢慢道來:
- 首先他不是個ListView也不是個GridView,他是個ViewGroup,這意味著什麼?他意味著你的整個ViewGroup都是可以下拉的,你的ViewGroup裡可以裝任何內容,TextView,Button,ListView,RecycleView都可以,他不在侷限我只是個ListView,簡單說,下拉這個動作,不繫結任何控制元件,他是獨立的。這個優點我給滿分。
- 充分抽象解耦合,我們可以定義屬於我們自己的重新整理樣式,只要實現統一介面,定製你的樣式沒有那麼難,典型的物件導向思想。
- 類相對較少。你說這也是個優點?我說這是個大大的優勢,類少程式碼易讀,核心類集中,修改與定製會很方便。
那麼他沒有缺點嗎?當然有!
- 或許這也不算是個缺點,在作者公佈的開源專案中,並不支援上拉載入更多功能,至於為什麼,作者在他的issues裡已經回覆過了,大概意思是:下拉重新整理和載入更多,不是同一個層級的功能。載入更多不應該由 UltraPTR 去實現,而應該由 Content自己去實現。沒關係作者的另外一個開源庫,有實現這一功能,在這篇文章中,我們把他整合在一起!
- 作者原始碼中的實現使用的是ListView,這裡我們會換成RecycleView,畢竟要與時俱進嘛!
- 同樣在滑動巢狀中,會有衝突的問題。沒關係,程式碼中已經解決了一些。
看完這篇文章你會得到什麼?
我不會對作者的原始碼進行解析,畢竟網上的輪子已經很多了,重複造輪子是可恥的。也不會把修改原始碼的過程進行講解,為啥?因為很早之前就改好了,到現在已經忘了具體的過程。。。哈!Sorry,這篇文章我會盡力展示框架的結構和使用方式,幫助你更好理解Ptr的優勢,思路是最重要的,程式碼並不重要!
開始幹活啦
gif中我展示了上拉載入,下拉重新整理,標準,全部功能,空View五種情況。這裡只是為了展示,其實我們在使用過程中,基本不會用到各種情況之間的互相切換。專案中目前只定義了兩種樣式的頭部,第一種就是類似gif中MaterialDesign的樣式,第二種就是傳統樣式類似PullToRefreshListView中的,這裡沒有展示。
一、使用指南
1.佈局使用
<com.leinyo.superptrwithrv.widget.ptr.PullToRefreshView
android:id="@+id/pull_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:ptr_refresh_mode="none"
app:ptr_scrollable="true"
/>複製程式碼
此處不廢話,講解屬性含義:
<declare-styleable name="PullToRefreshView">
<attr name="ptr_refresh_mode" format="enum">
<enum name="none" value="0"/>
<enum name="pull_from_start" value="1"/>
<enum name="pull_from_end" value="2"/>
<enum name="both" value="3"/>
</attr>
<attr name="ptr_check_login" format="boolean"/>
<attr name="ptr_header_mode" format="enum">
<enum name="material" value="0"/>
<enum name="normal" value="1"/>
</attr>
<attr name="ptr_padding_left" format="integer"/>
<attr name="ptr_padding_right" format="integer"/>
<attr name="ptr_scrollable" format="boolean"/>
</declare-styleable>複製程式碼
- ptr_refresh_mode 重新整理方式
none 標準的RecycleView
pull_from_start 支援下拉重新整理
pull_from_end 支援上拉載入
both 上拉載入下拉重新整理 - ptr_check_login 是否檢查登入狀態 只有登入(這部分程式碼自己實現)才會觸發下拉重新整理功能
- ptr_header_mode 下拉重新整理頭樣式
material gif中演示的樣式
normal 標準模式 - ptr_padding_left ptr_padding_right 設定RecycleView的padding
- ptr_scrollable 是否顯示滾動條
2.重新整理動作對應回撥
下拉重新整理對應回撥介面:
public interface OnPullRefreshListener {
void onPullRefresh();
}複製程式碼
onPullRefresh()方法會在重新整理動畫達到臨界值以後回撥。
取消下拉動畫方法:
public void onPullRefreshComplete() {
mPtrFrameLayout.refreshComplete();
}複製程式碼
上拉載入對應回撥介面:
public interface OnLoadMoreListener {
void onLoadMoreRefresh();
}複製程式碼
取消上拉動畫方法:
public void onLoadMoreComplete(boolean hasMore) {
mLoadMoreRecyclerViewContainer.loadMoreFinish(hasMore);
}複製程式碼
boolean值 代表是否還可以繼續上拉 false 不會再回撥onLoadMoreRefresh()!!!
both對應回撥介面:
public interface OnRefreshListener {
void onPullRefresh();
void onLoadMoreRefresh();
}複製程式碼
取消全部動畫方法:
public void onLoadComplete(boolean hasMore) {
mLoadMoreRecyclerViewContainer.loadMoreFinish(hasMore);
if (mCurrentRefreshMode == REFRESH_FROM_START) {
if (isRefreshing()) {
mPtrFrameLayout.refreshComplete();
}
}
}複製程式碼
3.新增EmptyView
如果我們的返回的資料是空的,需要顯示一個空頁面。我們不需要控制兩個View的show與gone,ListView能做的我們同樣能到。mPullView.addEmptyView(mEmpty);
注意:addEmptyView方法不能在顯示之前設定,否則會先顯示空View。為啥,後面再說。
4.新增頭HeadView
同樣ListView能做的我們也可以。mPullView.addHeaderView(mHeadView);
二、架構層次
這一部分一定是要上圖的,說多少都是無意的。
這部分你最好對Ptr有了解
- PtrFrameLayout
最外層ViewGroup,只負責下拉重新整理,首先接觸到觸控事件,符合下拉邏輯,則顯示頭佈局,否則向下分發事件 - LoadMoreRecyclerViewContainer
第二層ViewGroup,只負責上拉載入,監聽上拉事件,符合邏輯,通知RecyclerView繪製Foot佈局。 - RecyclerView
最後一層,我只負責顯示佈局,包括Head,EmptyView,Foot和正常的資料佈局。
怎麼樣?一張圖是不是已經足夠清楚了?看到現在你是否已經感嘆作者的設計能力了?再看看上面說到作者在issues裡已經回覆的:下拉重新整理和載入更多,不是同一個層級的功能。載入更多不應該由 UltraPTR 去實現,而應該由 Content自己去實現。
每一層級,只對應自己的業務邏輯,並不關心其他人在幹什麼,這就叫單一職責,解耦合。
本來覺得這裡應該是講得最多的,但是寫到這裡,發現實在沒啥可說的了,如果你真的看過Ptr的原始碼,相信到這裡你也已經沒有疑惑了,而且有種神清氣爽的感覺,其實就是這麼簡單,程式碼設計很重要,這也是為啥有的程式碼讓人看得很爽,有的程式碼讓人看了想吐
三、顯示佈局
我們上面說過真正負責顯示佈局的是RecyclerView了,怎麼顯示?當然是交給Adapter就可以了,多佈局顯示使用RecyclerView.Adapter的getItemViewType(int position)。需要記住的是:
我們抽出基類BaseRefreshAdapter,裡面對多佈局顯示有一些基礎的操作,需要子類繼承我們抽出基類BaseRefreshAdapter,並實現onCreateHolder(ViewGroup parent, int viewType)(他等同於onCreateViewHolder(ViewGroup parent, int viewType))和onBindHolder(VH holder, int position)(等同於onBindViewHolder(RecyclerView.ViewHolder holder, int position)),在子類中你只需要關心你自己的Item佈局就可以了,其他交給BaseRefreshAdapter中的邏輯就可以了。
這裡面有泛型定義,看看原始碼很好理解。
這就不難理解為啥上面說過addEmptyView方法不能在顯示佈局之前設定了,因為如果你初始化就執行addEmptyView方法,那麼當adapter初始化時,就會執行onCreateViewHolder()一系列方法,這樣馬上就會顯示EmptyView了,等你的真正需要的資料返回才會重新整理資料佈局。
最後也還是要有些廢話的
本人並未側重講解怎麼封裝,怎麼修改原始碼適配RecyclerView,也並未拆原始碼講解Ptr原理,也希望大家能理解,時間有點緊,而且輪子很多,不想再造了。
我希望如果你看到最後,這篇文章會對你有所幫助,學習人家的設計模式的同時,你也得到了一個支援多種功能,基於RecyclerView的再封裝Ptr框架(感覺好繞口啊),至於為何叫SuperPtr,純碎是為了好玩。。。
如果您在使用過程中,有疑問和改進意見歡迎聯絡我。
SupterPtr 對您的支援我深表感謝,喜歡請您star哦~~