溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android如何實現RecyclerView下拉刷新效果

發(fā)布時間:2021-04-16 11:32:08 來源:億速云 閱讀:186 作者:小新 欄目:移動開發(fā)

這篇文章主要介紹Android如何實現RecyclerView下拉刷新效果,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!

具體內容如下

思路

  • RealPullRefreshView繼承了一個LinearLayout

  • 里面放置了一個刷新頭布局,將其margin_top設置為負的刷新頭的高度的

  • 再添加一個RecyclerView

  • 觸摸事件分發(fā)機制,當在特定條件下讓RealPullRefreshView攔截觸摸事件,否則的話,不攔截,讓RecyclerView自己去處理觸摸事件

  • 在手指下拉時,定義好不同的狀態(tài)STATE,在不同狀態(tài)下,處理不同的顯示,這里講不同狀態(tài)下的刷新頭如何顯示,抽象為一個接口,用戶可以實現這個接口,自定義刷新頭的布局和動畫

  • 加載更多的功能是利用RecyclerView的多type布局實現的

  • 難點在于觸摸事件的攔截,和認真處理各種滑動的問題

使用

xml

 <com.example.apple.quickdemo.realview.view.RealPullRefreshView
    android:id="@+id/real_pull_refresh_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    app:refresh_header_view="@layout/headerview"/>

這是headerview

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
       android:layout_height="50dp"
       android:background="#ff0"
       android:orientation="horizontal"
       android:gravity="center">


  <TextView
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:background="@color/colorAccent"
    android:visibility="visible"
    android:id="@+id/tv"
    android:gravity="center"
    android:text="下拉刷新"
    android:textSize="21sp"/>
  <ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"

    android:src="@mipmap/ic_launcher"
    android:id="@+id/iv"
    />

</LinearLayout>

代碼

mRealPullRefreshView.setLayoutManager(mLayoutManager);
    mRealPullRefreshView.setAdapter(mMyAdapte);
    //用戶可以自定義自己的刷新頭布局和動畫
   //mRealPullRefreshView.setOnPullShowViewListener(new GifOnPullShowViewListerner(mRealPullRefreshView));

    mRealPullRefreshView.setOnPullListener(new RealPullRefreshView.OnPullListener() {
      @Override
      public void onRefresh() {

        mHandler.postDelayed(new Runnable() {
          @Override
          public void run() {
            mBodies.add(0, new Body("新數據"+i++,100));
            mRealPullRefreshView.refreshFinish();
          }
        }, 3000);

      }

      @Override
      public void onLoadMore() {

        final List<Body> more=new ArrayList<Body>();
        mHandler.postDelayed(new Runnable() {
          @Override
          public void run() {
            for (int i = 0; i < 3; i++) {
              more.add(new Body("more+++"+i,100));

            }


            mBodies.addAll(more);
            mRealPullRefreshView.loadMreFinish();
          }
        }, 1500);

      }
    });

自定義刷新頭布局和動畫

package com.example.apple.quickdemo.realview.show;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import com.example.apple.quickdemo.R;
import com.example.apple.quickdemo.realview.view.RealPullRefreshView;

/**
 * Created by apple on 2017/7/9.
 */

public class ImplOnPullShowViewListener implements RealPullRefreshView.OnPullShowViewListener {

  private TextView mTv;
  private ImageView mIv;
  private ObjectAnimator mAni;
  View mHeaderView;


  public ImplOnPullShowViewListener(RealPullRefreshView realPullRefreshView) {
    mHeaderView = realPullRefreshView.getRefreshHeaderView();
    mTv = (TextView) mHeaderView.findViewById(R.id.tv);
    mIv = (ImageView) mHeaderView.findViewById(R.id.iv);
    mAni = ObjectAnimator.ofFloat(mIv, "rotation", -15, 15).setDuration(300);
    mAni.setRepeatCount(ValueAnimator.INFINITE);
    mAni.setRepeatMode(ValueAnimator.REVERSE);
  }

  @Override
  public void onPullDownRefreshState(int scrollY, int headviewHeight,int deltaY) {
    mTv.setText("下拉刷新");
    float f = -((float) scrollY / (float) headviewHeight);
    Log.e("tag", f+ "");
    Log.e("tag", -scrollY + "scrollY");


    mIv.setScaleX(f);
    mIv.setScaleY(f);
  }

  @Override
  public void onReleaseRefreshState(int scrollY, int deltaY) {
    mTv.setText("松手刷新");

  }

  @Override
  public void onRefreshingState() {
    mTv.setText("正在刷新");
    mIv.setScaleX(1.0f);
    mIv.setScaleY(1.0f);
    mAni.start();

  }

  @Override
  public void onDefaultState() {
    if (mAni.isRunning()){
      mAni.end();
      mIv.setRotation(0);
    }
  }

}

源碼

package com.example.apple.quickdemo.realview.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.Nullable;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Scroller;
import android.widget.Toast;

import com.example.apple.quickdemo.R;
import com.example.apple.quickdemo.realview.show.ImplOnPullShowViewListener;

import static android.content.ContentValues.TAG;

/**
 * Created by apple on 2017/7/7.
 * 下拉刷新
 */

public class RealPullRefreshView extends LinearLayout {


  private int mTouchSlop;
  //  分別記錄上次滑動的坐標
  private int mLastX = 0;
  private int mLastY = 0;

  //  分別記錄上次滑動的坐標(onInterceptTouchEnvent)
  private int mLastXIntercept = 0;
  private int mLastYIntercept = 0;

  private Scroller mScroller;
  private VelocityTracker mVelocityTracker;


  private RecyclerView.Adapter mAdapter;

  public RecyclerView getRecyclerView() {
    return mRecyclerView;
  }

  private RecyclerView mRecyclerView;


  private int DEFAULT = 0;
  private final int PULL_DOWN_REFRESH = 1;
  private final int RELEASE_REFRESH = 2;
  private final int REFRESHING = 3;
  private final int LOAD_MORE = 4;
  private int STATE = DEFAULT;


  private int rfreshHeaderWidth;
  private int refreshHeadviewHeight;


  private OnPullListener mOnPullListener;
  private View mRefreshHeaderView;

  private RecyclerView.LayoutManager mLayoutManager;


  int refreshHeadviewId;

  public void setLayoutManager(RecyclerView.LayoutManager manager) {
    this.mLayoutManager = manager;
    mRecyclerView.setLayoutManager(mLayoutManager);

  }

  public void setAdapter(RecyclerView.Adapter adapter) {
    this.mAdapter = adapter;
    mRecyclerView.setAdapter(mAdapter);

  }

  public View getRefreshHeaderView() {
    return mRefreshHeaderView;
  }

  public void setOnPullShowViewListener(OnPullShowViewListener onPullShowViewListener) {
    mOnPullShowViewListener = onPullShowViewListener;
  }

  private OnPullShowViewListener mOnPullShowViewListener;

  public void setOnPullListener(OnPullListener onPullListener) {
    mOnPullListener = onPullListener;
  }

  public RealPullRefreshView(Context context) {
    super(context);

    initView(context);

  }

  public RealPullRefreshView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);

    initAttrs(context, attrs);
//   ★★★★★一個坑initAttrs方法里的typedArray去獲取屬性時,第一次獲取的屬性全是0,他會馬上重走一次構造方法,再次獲取一次,才能獲得正確的值
//   如果第一次獲取的值為0,則不去initView
    if (refreshHeadviewId != 0) {
      initView(context);
    }


  }


  public RealPullRefreshView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    initAttrs(context, attrs);
    if (refreshHeadviewId != 0) {
      initView(context);
    }
  }

  private void initAttrs(Context context, AttributeSet attrs) {

    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RealPullRefreshView);

    try {

      refreshHeadviewId = typedArray.getResourceId(R.styleable.RealPullRefreshView_refresh_header_view, 0);


    } finally {
      typedArray.recycle();

    }


  }


  private void initView(Context context) {

    mScroller = new Scroller(getContext());
    mVelocityTracker = VelocityTracker.obtain();


//    添加headerview

//    ★ ★ ★ ★ ★ 注意不要用這個方法inflate布局,會導致layout的所有屬性失效,height、width、margin
//    原因見 http://blog.csdn.net/zhaokaiqiang1992/article/details/36006467
//    ★ ★ ★ ★ ★ mRefreshHeaderView = mInflater.inflate(R.layout.headerview, null);

    mRefreshHeaderView = LayoutInflater.from(context).inflate(refreshHeadviewId, this, false);
    addView(mRefreshHeaderView);
//    }

//    以下代碼主要是為了設置頭布局的marginTop值為-headerviewHeight
//    注意必須等到一小會才會得到正確的頭布局寬高
    postDelayed(new Runnable() {
      @Override
      public void run() {
        Log.e("q11", refreshHeadviewHeight + "qqqqqqqqqq   " + mRefreshHeaderView.getHeight());
        rfreshHeaderWidth = mRefreshHeaderView.getWidth();
        refreshHeadviewHeight = mRefreshHeaderView.getHeight();

        MarginLayoutParams lp = new LinearLayout.LayoutParams(rfreshHeaderWidth, refreshHeadviewHeight);
        lp.setMargins(0, -refreshHeadviewHeight, 0, 0);
        mRefreshHeaderView.setLayoutParams(lp);

      }
    }, 100);


//   添加RecyclerView
    mRecyclerView = new RecyclerView(context);
    addView(mRecyclerView, LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);


//   這里我提供了一個默認的顯示效果,如果用戶不使用mRealPullRefreshView.setOnPullShowViewListener的話,會默認使用這個
//   用戶可以實現OnPullShowViewListener接口,去實現自己想要的顯示效果
    mOnPullShowViewListener = new ImplOnPullShowViewListener(this);


    setLoadMore();


  }

  private void setLoadMore() {
    // 當目前的可見條目是所有數據的最后一個時,開始加載新的數據

    mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

      @Override
      public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        int lastCompletelyVisibleItemPosition = -1;
        if (mLayoutManager instanceof LinearLayoutManager) {
          LinearLayoutManager manager = (LinearLayoutManager) mLayoutManager;
          lastCompletelyVisibleItemPosition = manager.findLastVisibleItemPosition();

        } else if (mLayoutManager instanceof GridLayoutManager) {
          GridLayoutManager manager = (GridLayoutManager) mLayoutManager;
          lastCompletelyVisibleItemPosition = manager.findLastVisibleItemPosition();

        } else if (mLayoutManager instanceof StaggeredGridLayoutManager) {
          StaggeredGridLayoutManager manager = (StaggeredGridLayoutManager) mLayoutManager;
          lastCompletelyVisibleItemPosition = manager.findLastVisibleItemPositions(new int[manager.getSpanCount()])[0];
        }
        if (lastCompletelyVisibleItemPosition + 1 == mAdapter.getItemCount()) {
          if (mOnPullListener != null && STATE == DEFAULT) {
            STATE = LOAD_MORE;
            mOnPullListener.onLoadMore();
          }
        }


      }
    });
  }


  @Override
  public boolean onInterceptTouchEvent(MotionEvent event) {
    boolean intercepted = false;
    int x = (int) event.getX();
    int y = (int) event.getY();

    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN: {
        intercepted = false;
        /*if (STATE!=DEFAULT||STATE!=REFRESHING){
        if (!mScroller.isFinished()) {
          mScroller.abortAnimation();
        }}*/
        break;
      }
      case MotionEvent.ACTION_MOVE: {
        int deltaX = x - mLastXIntercept;
        int deltaY = y - mLastYIntercept;

        int firstCompletelyVisibleItemPosition = -1;
        if (mLayoutManager instanceof LinearLayoutManager) {
          LinearLayoutManager manager = (LinearLayoutManager) mLayoutManager;
          firstCompletelyVisibleItemPosition = manager.findFirstCompletelyVisibleItemPosition();

        } else if (mLayoutManager instanceof GridLayoutManager) {
          GridLayoutManager manager = (GridLayoutManager) mLayoutManager;
          firstCompletelyVisibleItemPosition = manager.findFirstCompletelyVisibleItemPosition();

        } else if (mLayoutManager instanceof StaggeredGridLayoutManager) {
          StaggeredGridLayoutManager manager = (StaggeredGridLayoutManager) mLayoutManager;
          firstCompletelyVisibleItemPosition = manager.findFirstCompletelyVisibleItemPositions(new int[manager.getSpanCount()])[0];
        }


        //        ******************這里說明什么規(guī)則下,攔截,其余代碼不要動了,其余代碼指的是處理滑動沖突的代碼***************


        if (firstCompletelyVisibleItemPosition == 0 && deltaY > 0 && Math.abs(deltaY) > Math.abs(deltaX)) {//拉倒最頂部,繼續(xù)往下拉,將拉出頭布局,要父布局攔截
          intercepted = true;
        } else if (getScrollY() < 0) {//表示頭布局已經向下拉出來,頭布局已經顯示了,要父布局攔截
          intercepted = true;

        } else if (deltaY < 0) {
          intercepted = false;//不要父布局攔截了

        } else {
          intercepted = false;//不要父布局攔截了
        }
        //        ******************什么規(guī)則下,攔截***************

        break;
      }
      case MotionEvent.ACTION_UP: {
        intercepted = false;
        break;
      }
      default:
        break;
    }

    Log.d(TAG, "intercepted=" + intercepted);
    mLastX = x;
    mLastY = y;
    mLastXIntercept = x;
    mLastYIntercept = y;

    return intercepted;
  }


  /**
   * 下面不同布局,不同的滑動需求
   *
   * @param event
   * @return
   */
  @Override
  public boolean onTouchEvent(MotionEvent event) {
    mVelocityTracker.addMovement(event);
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN: {
        if (!mScroller.isFinished()) {
          mScroller.abortAnimation();
        }
        break;
      }
      case MotionEvent.ACTION_MOVE: {
        int deltaX = x - mLastX;
        int deltaY = y - mLastY;
        if (getScrollY() > 0) {
          //防止在正在刷新狀態(tài)下,上拉出空白
        } else if (getScrollY() <= 0 && getScrollY() > -refreshHeadviewHeight * 5) {
//          最多下拉到頭布局高度5倍的距離
          scrollBy(0, -deltaY / 2);
        }

        if (getScrollY() > -refreshHeadviewHeight && STATE != REFRESHING) {//頭布局顯示不全時,為下拉刷新PULL_DOWN_REFRESH狀態(tài)
          STATE = PULL_DOWN_REFRESH;

          if (mOnPullShowViewListener != null) {
            mOnPullShowViewListener.onPullDownRefreshState(getScrollY(), refreshHeadviewHeight, deltaY);
          }

        }
        if (getScrollY() < -refreshHeadviewHeight && STATE != REFRESHING) {//頭布局完全顯示時,為釋放刷新RELEASE_REFRESH狀態(tài)
          STATE = RELEASE_REFRESH;
          if (mOnPullShowViewListener != null) {
            mOnPullShowViewListener.onReleaseRefreshState(getScrollY(), deltaY);
          }

        }

        break;
      }
      case MotionEvent.ACTION_UP: {
        final int scrollY = getScrollY();
        //松手時,根據所處的狀態(tài),讓布局滑動到不同的地方,做不同的操作
        switch (STATE) {

          case PULL_DOWN_REFRESH:
            STATE = DEFAULT;
            //頭布局沒有完全顯示,完全隱藏頭布局
            smoothScrollBy(0, -scrollY);
            break;
          case RELEASE_REFRESH:
            STATE = REFRESHING;
            smoothScrollBy(0, -refreshHeadviewHeight - scrollY);


            if (mOnPullShowViewListener != null) {
              mOnPullShowViewListener.onRefreshingState();
            }


            if (mOnPullListener != null) {
              mOnPullListener.onRefresh();
            }
            break;
          case REFRESHING:
            if (getScrollY() < -refreshHeadviewHeight) {
              smoothScrollBy(0, -refreshHeadviewHeight - scrollY);
            } else {
              smoothScrollBy(0, -scrollY);
            }
            break;
        }

        mVelocityTracker.clear();
        break;
      }
      default:
        break;
    }

    mLastX = x;
    mLastY = y;
    return true;
  }

  /**
   * 當用戶使用完下拉刷新回調時,需要調用此方法,將頭不去隱藏,將STATE恢復
   */
  public void refreshFinish() {
    smoothScrollBy(0, 0 - getScrollY());
    getRecyclerView().getAdapter().notifyDataSetChanged();
    STATE = DEFAULT;
    if (mOnPullShowViewListener != null) {
      mOnPullShowViewListener.onDefaultState();
    }

    Toast.makeText(getContext(), "刷新成功!", Toast.LENGTH_SHORT).show();
  }

  /**
   * 當用戶使用完加載更多后回調時,需要調用此方法,將STATE恢復
   */
  public void loadMreFinish() {
    getRecyclerView().getAdapter().notifyDataSetChanged();
    STATE = DEFAULT;

    Toast.makeText(getContext(), "加載成功了!", Toast.LENGTH_SHORT).show();
  }

  /**
   * 在500毫秒內平滑地滾動多少像素點
   *
   * @param dx
   * @param dy
   */
  private void smoothScrollBy(int dx, int dy) {
    mScroller.startScroll(0, getScrollY(), 0, dy, 500);
    invalidate();
  }

  @Override
  public void computeScroll() {
    if (mScroller.computeScrollOffset()) {
      scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
      postInvalidate();
    }
  }

  /**
   * 釋放資源
   */
  @Override
  protected void onDetachedFromWindow() {
    mVelocityTracker.recycle();
    super.onDetachedFromWindow();
  }


//  ***************


//  *****************

  /**
   * 回調接口
   */
  public interface OnPullListener {
    /**
     * 當下拉刷新正在刷新時,這時候可以去請求數據,記得最后調用refreshFinish()復位
     */
    void onRefresh();

    /**
     * 當加載更多時
     */
    void onLoadMore();
  }


  /**
   * 回調接口,可以通過下面的回調,自定義各種狀態(tài)下的顯示效果
   * 可以根據下拉距離scrollY設計動畫效果
   */
  public interface OnPullShowViewListener {

    /**
     * 當處于下拉刷新時,頭布局顯示效果
     *
     * @param scrollY    下拉的距離
     * @param headviewHeight 頭布局高度
     * @param deltaY     moveY-lastMoveY,正值為向下拉
     */
    void onPullDownRefreshState(int scrollY, int headviewHeight, int deltaY);

    /**
     * 當處于松手刷新時,頭布局顯示效果
     *
     * @param scrollY 下拉的距離
     * @param deltaY moveY-lastMoveY,正值為向下拉
     */
    void onReleaseRefreshState(int scrollY, int deltaY);

    /**
     * 正在刷新時,頁面的顯示效果
     */
    void onRefreshingState();

    /**
     * 默認狀態(tài)時,頁面顯示效果,主要是為了復位各種狀態(tài)
     */
    void onDefaultState();


  }
}

以上是“Android如何實現RecyclerView下拉刷新效果”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業(yè)資訊頻道!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI