SwipeRefreshLayout詳解和自定義上拉加載更多

個(gè)人主頁(yè)
演示Demo下載


本文重點(diǎn)介紹了SwipeRefreshLayout的使用和自定View繼承SwipeRefreshLayout添加上拉加載更多的功能。

  • 介紹之前,先來(lái)看一下SwipeRefreshLayout實(shí)現(xiàn)的下拉刷新效果圖。從圖中可以看到,下拉到了一定的高度才會(huì)進(jìn)行刷新,高度不夠就會(huì)回收上去,正在刷新過(guò)程中,繼續(xù)下拉沒(méi)反應(yīng),說(shuō)明刷新時(shí)屏蔽掉了下拉事件。


    上拉刷新效果圖

一、SwipeRefreshLayout簡(jiǎn)單介紹

  • 先看以下官方文檔,已有了很詳細(xì)的描述了。


    官方文檔說(shuō)明
  • 這里我再大概解釋一下:

    • 在豎直滑動(dòng)時(shí)想要刷新頁(yè)面可以用SwipeRefreshLayout來(lái)實(shí)現(xiàn)。它通過(guò)設(shè)置OnRefreshListener來(lái)監(jiān)聽(tīng)界面的滑動(dòng)從而實(shí)現(xiàn)刷新。也可以通過(guò)一些方法來(lái)設(shè)置SwipeRefreshLayout是否可以刷新。如:setRefreshing(true),展開(kāi)刷新動(dòng)畫(huà)。
      setRefreshing(false),取消刷新動(dòng)畫(huà)。setEnable(true)下拉刷新將不可用。

    • 使用這個(gè)布局要想達(dá)到刷新的目的,需要在這個(gè)布局里包裹可以滑動(dòng)的子控件,如ListView等,并且只能有一個(gè)子控件。

  • 介紹總結(jié):使用SwipeRefreshLayout可以實(shí)現(xiàn)下拉刷新,前提是布局里需要包裹一個(gè)可以滑動(dòng)的子控件,然后在代碼里設(shè)置OnRefreshListener設(shè)置監(jiān)聽(tīng),最后在監(jiān)聽(tīng)里設(shè)置刷新時(shí)的數(shù)據(jù)獲取就可以了。由于是新出來(lái)的東西,所以要想使用,先把support library的版本升級(jí)到19.1或更新。

二、SwipeRefreshLayout主要方法介紹

翻看官方的文檔,可以看到方法有很多,這里只介紹五個(gè)經(jīng)常用到的方法。

  • isRefreshing()

    • 判斷當(dāng)前的狀態(tài)是否是刷新?tīng)顟B(tài)。
  • setColorSchemeResources(int... colorResIds)

    • 設(shè)置下拉進(jìn)度條的顏色主題,參數(shù)為可變參數(shù),并且是資源id,可以設(shè)置多種不同的顏色,每轉(zhuǎn)一圈就顯示一種顏色。
  • setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener listener)

    • 設(shè)置監(jiān)聽(tīng),需要重寫(xiě)onRefresh()方法,頂部下拉時(shí)會(huì)調(diào)用這個(gè)方法,在里面實(shí)現(xiàn)請(qǐng)求數(shù)據(jù)的邏輯,設(shè)置下拉進(jìn)度條消失等等。
  • setProgressBackgroundColorSchemeResource(int colorRes)

    • 設(shè)置下拉進(jìn)度條的背景顏色,默認(rèn)白色。
  • setRefreshing(boolean refreshing)

    • 設(shè)置刷新?tīng)顟B(tài),true表示正在刷新,false表示取消刷新。

三、SwipeRefreshLayout的基本使用

  • 介紹了SwipeRefreshLayout,主要的方法也講了,接下來(lái)就是實(shí)戰(zhàn),其實(shí)使用起來(lái)非常的簡(jiǎn)單。

3.1 設(shè)置布局

  • 官方文檔已經(jīng)說(shuō)明,SwipeRefreshLayout只能有一個(gè)孩子,當(dāng)然我們不般也不會(huì)往里面放其他的布局。我們只需要在容器里包裹一個(gè)ListView就好了。

              <ListView
                  android:id="@+id/lv"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"/>
      
          </android.support.v4.widget.SwipeRefreshLayout>
      -->
    

3.2 在代碼中使用

  • 在該布局文件對(duì)應(yīng)的Activity或其他類(lèi)中獲取布局id,先設(shè)置ListView顯示的適配器,然后再設(shè)置SwipeRefreshLayout。
    // 不能在onCreate中設(shè)置,這個(gè)表示當(dāng)前是刷新?tīng)顟B(tài),如果一進(jìn)來(lái)就是刷新?tīng)顟B(tài),SwipeRefreshLayout會(huì)屏蔽掉下拉事件
    //swipeRefreshLayout.setRefreshing(true);

      // 設(shè)置顏色屬性的時(shí)候一定要注意是引用了資源文件還是直接設(shè)置16進(jìn)制的顏色,因?yàn)槎际莍nt值容易搞混
      // 設(shè)置下拉進(jìn)度的背景顏色,默認(rèn)就是白色的
      swipeRefreshView.setProgressBackgroundColorSchemeResource(android.R.color.white);
      // 設(shè)置下拉進(jìn)度的主題顏色
      swipeRefreshView.setColorSchemeResources(R.color.colorAccent, R.color.colorPrimary, R.color.colorPrimaryDark);
    
      // 下拉時(shí)觸發(fā)SwipeRefreshLayout的下拉動(dòng)畫(huà),動(dòng)畫(huà)完畢之后就會(huì)回調(diào)這個(gè)方法
      swipeRefreshView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
          @Override
          public void onRefresh() {
    
              // 開(kāi)始刷新,設(shè)置當(dāng)前為刷新?tīng)顟B(tài)
              //swipeRefreshLayout.setRefreshing(true);
    
              // 這里是主線程
              // 一些比較耗時(shí)的操作,比如聯(lián)網(wǎng)獲取數(shù)據(jù),需要放到子線程去執(zhí)行
              // TODO 獲取數(shù)據(jù)
              final Random random = new Random();
              new Handler().postDelayed(new Runnable() {
                  @Override
                  public void run() {
                      mList.add(0, "我是天才" + random.nextInt(100) + "號(hào)");
                      mAdapter.notifyDataSetChanged();
    
                      Toast.makeText(MainActivity.this, "刷新了一條數(shù)據(jù)", Toast.LENGTH_SHORT).show();
    
                      // 加載完數(shù)據(jù)設(shè)置為不刷新?tīng)顟B(tài),將下拉進(jìn)度收起來(lái)
                      swipeRefreshView.setRefreshing(false);
                  }
              }, 1200);
    
              // System.out.println(Thread.currentThread().getName());
    
              // 這個(gè)不能寫(xiě)在外邊,不然會(huì)直接收起來(lái)
              //swipeRefreshLayout.setRefreshing(false);
          }
      });
    
  • 經(jīng)過(guò)以上兩步簡(jiǎn)單的設(shè)置就能使用SwipeRefreshLayout了。

四、自定義View繼承SwipeRefreshLayout,添加上拉加載更多功能

由于谷歌并沒(méi)有提供上拉加載更多的布局,所以我們只能自己去定義布局實(shí)現(xiàn)這個(gè)功能。

這里通過(guò)自定義View繼承SwipeRefreshLayout容器,然后添加上拉加載更多的功能。

  • 先來(lái)看一下上拉加載更多的效果圖


    上拉加載更多效果圖

4.1 定義View繼承SwipeRefreshLayout,添加上拉加載功能

代碼中的注釋比較詳細(xì),這里就不一一解釋了,說(shuō)一下大概的實(shí)現(xiàn)思路,主要分為四步。

4.1.1 獲取子控件ListView

  • 在布局使用中,這里和SwipeRefreshLayout一樣,ListView是SwipeRefreshView的子控件,所以需要在onLayout()方法中獲取子控件ListView。
    // 獲取ListView,設(shè)置ListView的布局位置
    if (mListView == null) {
    // 判斷容器有多少個(gè)孩子
    if (getChildCount() > 0) {
    // 判斷第一個(gè)孩子是不是ListView
    if (getChildAt(0) instanceof ListView) {
    // 創(chuàng)建ListView對(duì)象
    mListView = (ListView) getChildAt(0);

                  // 設(shè)置ListView的滑動(dòng)監(jiān)聽(tīng)
                  setListViewOnScroll();
              }
          }
      }
    

4.1.2 對(duì)ListView設(shè)置滑動(dòng)監(jiān)聽(tīng)

  • 監(jiān)聽(tīng)ListView的滑動(dòng)事件,當(dāng)滑動(dòng)到底部,并且當(dāng)前可見(jiàn)頁(yè)的最后一個(gè)條目等于adapter的getCount數(shù)目-1,就滿足加載數(shù)據(jù)的條件。
    /**
    * 設(shè)置ListView的滑動(dòng)監(jiān)聽(tīng)
    */
    private void setListViewOnScroll() {

          mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
              @Override
              public void onScrollStateChanged(AbsListView view, int scrollState) {
                  // 移動(dòng)過(guò)程中判斷時(shí)候能下拉加載更多
                  if (canLoadMore()) {
                      // 加載數(shù)據(jù)
                      loadData();
                  }
              }
    
              @Override
              public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    
              }
          });
      }
    

4.1.3 處理SwipeRefreshView容器的分發(fā)事件

  • 由于ListView是SwipeRefreshView的子控件,所以這里要進(jìn)行事件的分發(fā)處理,判斷用戶的滑動(dòng)距離是否滿足條件。
    /**
    * 在分發(fā)事件的時(shí)候處理子控件的觸摸事件
    *
    * @param ev
    * @return
    */
    private float mDownY, mUpY;
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

          switch (ev.getAction()) {
              case MotionEvent.ACTION_DOWN:
                  // 移動(dòng)的起點(diǎn)
                  mDownY = ev.getY();
                  break;
              case MotionEvent.ACTION_MOVE:
                  // 移動(dòng)過(guò)程中判斷時(shí)候能下拉加載更多
                  if (canLoadMore()) {
                      // 加載數(shù)據(jù)
                      loadData();
                  }
    
                  break;
              case MotionEvent.ACTION_UP:
                  // 移動(dòng)的終點(diǎn)
                  mUpY = getY();
                  break;
          }
          return super.dispatchTouchEvent(ev);
      }
    

4.1.4 判斷條件,滿足就用回調(diào)去加載數(shù)據(jù)

  • 當(dāng)滿足了需要判斷的所有的條件之后,就可以去調(diào)用加載數(shù)據(jù)的方法,這里提供一個(gè)設(shè)置上拉布局顯示和隱藏的方法,通過(guò)傳入當(dāng)前的狀態(tài),是true就顯示加載,是false就隱藏。
    /**
    * 判斷是否滿足加載更多條件
    *
    * @return
    */
    private boolean canLoadMore() {
    // 1. 是上拉狀態(tài)
    boolean condition1 = (mDownY - mUpY) >= mScaledTouchSlop;
    if (condition1) {
    System.out.println("是上拉狀態(tài)");
    }

          // 2. 當(dāng)前頁(yè)面可見(jiàn)的item是最后一個(gè)條目
          boolean condition2 = false;
          if (mListView != null && mListView.getAdapter() != null) {
              condition2 = mListView.getLastVisiblePosition() == (mListView.getAdapter().getCount() - 1);
          }
    
          if (condition2) {
              System.out.println("是最后一個(gè)條目");
          }
          // 3. 正在加載狀態(tài)
          boolean condition3 = !isLoading;
          if (condition3) {
              System.out.println("不是正在加載狀態(tài)");
          }
          return condition1 && condition2 && condition3;
      }
    
      /**
       * 處理加載數(shù)據(jù)的邏輯
       */
      private void loadData() {
          System.out.println("加載數(shù)據(jù)...");
          if (mOnLoadListener != null) {
              // 設(shè)置加載狀態(tài),讓布局顯示出來(lái)
              setLoading(true);
              mOnLoadListener.onLoad();
          }
    
      }
    
      /**
       * 設(shè)置加載狀態(tài),是否加載傳入boolean值進(jìn)行判斷
       *
       * @param loading
       */
      public void setLoading(boolean loading) {
          // 修改當(dāng)前的狀態(tài)
          isLoading = loading;
          if (isLoading) {
              // 顯示布局
              mListView.addFooterView(mFooterView);
          } else {
              // 隱藏布局
              mListView.removeFooterView(mFooterView);
    
              // 重置滑動(dòng)的坐標(biāo)
              mDownY = 0;
              mUpY = 0;
          }
      }
    

4.2 使用自定義View

4.2.1. 書(shū)寫(xiě)布局

  • 因?yàn)槭抢^承自SwipeRefreshLayout,所以SwipeRefreshView也只能有一個(gè)孩子

      <!--自定義View實(shí)現(xiàn)SwipeRefreshLayout,添加上拉加載更多的功能-->
      <com.pinger.swiperefreshdemo.view.SwipeRefreshView
          android:id="@+id/srl"
          android:layout_width="match_parent"
          android:layout_height="match_parent">
    
          <ListView
              android:id="@+id/lv"
              android:layout_width="match_parent"
              android:layout_height="match_parent"/>
    
      </com.pinger.swiperefreshdemo.view.SwipeRefreshView>
    

4.2.2. 在代碼中使用

  • 在代碼中使用更加的簡(jiǎn)單,只需要設(shè)置監(jiān)聽(tīng)重寫(xiě)onLoad()方法,在里面加載數(shù)據(jù),加載完數(shù)據(jù)然后設(shè)置為不加載狀態(tài)就可以了。

      // 設(shè)置下拉加載更多
      swipeRefreshView.setOnLoadListener(new SwipeRefreshView.OnLoadListener() {
          @Override
          public void onLoad() {
              new Handler().postDelayed(new Runnable() {
                  @Override
                  public void run() {
    
                      // 添加數(shù)據(jù)
                      for (int i = 30; i < 35; i++) {
                          mList.add("我是天才" + i+ "號(hào)");
                          // 這里要放在里面刷新,放在外面會(huì)導(dǎo)致刷新的進(jìn)度條卡住
                          mAdapter.notifyDataSetChanged();
                      }
    
                      Toast.makeText(MainActivity.this, "加載了" + 5 + "條數(shù)據(jù)", Toast.LENGTH_SHORT).show();
    
                      // 加載完數(shù)據(jù)設(shè)置為不加載狀態(tài),將加載進(jìn)度收起來(lái)
                      swipeRefreshView.setLoading(false);
                  }
              }, 1200);
          }
      });
    

個(gè)人主頁(yè)
演示Demo下載

以上純屬于個(gè)人平時(shí)工作和學(xué)習(xí)的一些總結(jié)分享,如果有什么錯(cuò)誤歡迎隨時(shí)指出,大家可以討論一起進(jìn)步。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容