簡單點,基於Ultra-Pull-To-Refresh的下拉重新整理上拉載入再封裝

Leinyo發表於2016-12-21

開始前總是要有廢話的

很早之前,上拉載入下拉重新整理這種互動方式一經推出,就火炸了。如果你在兩三年前就接觸過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的優勢,思路是最重要的,程式碼並不重要!

開始幹活啦

簡單點,基於Ultra-Pull-To-Refresh的下拉重新整理上拉載入再封裝

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);

二、架構層次

這一部分一定是要上圖的,說多少都是無意的。

簡單點,基於Ultra-Pull-To-Refresh的下拉重新整理上拉載入再封裝

這部分你最好對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哦~~

相關文章