自己动手写RecyclerView的下拉刷新1

简介: 自己动手写RecyclerView的下拉刷新

实现RecyclerView下拉刷新的功能,网上有很多文章,但大多文章都是将RecyclerView外面套了一层SwipRefreshLayout以此来达到下拉刷新的目的!个人觉得这种方式不太优雅,于是通过查找资料及阅读源码,找到了一个比较优雅的方式实现RecyclerView的下拉刷新,本文将带你以不一样的方式实现RecyclerView的下拉刷新。

从基础入手

  万丈高楼平地起,做什么事都不是一蹴而就的,再伟大的工程也是一点点完成的。这里就先从简单的入手,先为RecyclerView添加头部。

为RecyclerView添加头部View

  我们都知道实现RecyclerView的数据和视图的绑定是通过继承RecyclerView.Adapter类,同样,为RecyclerView添加头部也是需要继承RecyclerView.Adapter类,然后,在子类里面做文章。在添加头部之前有必要先了解一下RecyclerView.Adapter的几个常用的方法,分别为以下几个方法

onCreateViewHolder(ViewGroup parent, int viewType)
getItemViewType(int position)
onBindViewHolder(MyViewHolder holder, int position)
getItemCount()

使用过ListView的都知道,为了优化ListView的性能,我们在写Adapter时需要自己写一个ViewHolder类来重用ListView中的item视图,而在为RecyclerView实现Adapter时,强制我们要有一个ViewHolder。

onCreateViewHolder方法就是用来创建新View;

getItemViewType方法是可以根据不同的position可以返回不同的类型;

onBindViewHolder是将数据与视图绑定;

getItemCount方法就是获得需要显示数据的总数。

  了解了Adapter中的几个方法,下面就利用这几个方法为RecyclerView添加头部View,下面上代码

@Override
    public RefreshViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
      //这里的viewType即为getItemViewType返回的type
        if (viewType == TYPE_REFRESH_HEADER) {
            return new RefreshViewHolder(mInflater.inflate(R.layout.refresh_header_item, parent, false));
        }
        return new RefreshViewHolder(mInflater.inflate(R.layout.normal_item, parent, false));
    }
    @Override
    public void onBindViewHolder(RefreshViewHolder holder, int position) {
    }
    @Override
    public int getItemCount() {
        return mLists.size();
    }
    @Override
    public int getItemViewType(int position) {
      //当position位置为0时,返回为头部的类型
        if (position == 0) {
            return TYPE_REFRESH_HEADER;
        }
        return TYPE_NORMAL;
    }

可以看出,在onCreateViewHolder方法中,为不同的viewType设置了不同的类型,即为RecyclerView添加了头部,看下效果图

好了,现在已经为RecyclerView添加了头部,下一步就是将头部变成刷新的View。

添加下拉刷新的View

  现在将头部View修改一下,直接上代码

<?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="wrap_content"
    android:gravity="bottom">
    <RelativeLayout
        android:id="@+id/header_content"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:paddingTop="10dip">
        <TextView
            android:id="@+id/tvRefreshStatus"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="@string/pullRefresh"
            android:textColor="#B5B5B5" />
        <ImageView
            android:id="@+id/ivHeaderArrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginLeft="35dp"
            android:layout_marginRight="10dp"
            android:layout_toLeftOf="@id/tvRefreshStatus"
            android:src="@drawable/ic_pulltorefresh_arrow" />
    </RelativeLayout>
</LinearLayout>

好了,看下效果图

现在这个效果显然不是我们想要的,我们想要的效果是在正常状态下头部的下拉刷新不可见,当下拉到一定程度再显示。

  这里有两点需要注意:

  1. 在正常的状态下,下拉刷新的头部是不可见的;
  2. 当下拉到一定程度再将头部刷新显示出来。

现在,来实现正常状态下的效果,正常状态下不可见,这时可以将头部刷新的View高度设置为0,下面看下代码

public ArrowRefreshHeader(Context context) {
        this(context,null);
    }
    public ArrowRefreshHeader(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }
    public ArrowRefreshHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();//初始化视图
    }
    private void init() {
        LinearLayout.LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        layoutParams.setMargins(0, 0, 0, 0);
        this.setLayoutParams(layoutParams);
        this.setPadding(0, 0, 0, 0);
        //将refreshHeader高度设置为0
        mContentLayout = (LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.refresh_header_item, null);
        addView(mContentLayout, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0));
        //初始化控件
        mArrowImageView = findViewById(R.id.ivHeaderArrow);
        mStatusTextView =  findViewById(R.id.tvRefreshStatus);
        mProgressBar = findViewById(R.id.refreshProgress);
        //初始化动画
        mRotateUpAnim = new RotateAnimation(0.0f, -180.0f,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        mRotateUpAnim.setDuration(200);
        mRotateUpAnim.setFillAfter(true);
        mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        mRotateDownAnim.setDuration(200);
        mRotateDownAnim.setFillAfter(true);
        //将mContentLayout的LayoutParams高度和宽度设为自动包裹并重新测量
        measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        mMeasuredHeight = getMeasuredHeight();//获得测量后的高度
    }

这里使用了自定义View,写了一个ArrowRefreshHeader继承至LinearLayout,在构造方法中将头部刷新的View进行了初始化,这里这句代码是关键

mContentLayout = (LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.refresh_header_item, null);
        addView(mContentLayout, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0));

可以看到,这里将头部刷新的View的高度设置成了0,这样,就实现了在正常状态下,头部刷新不显示的效果。将RefreshHeaderAdapter的onCreateViewHolder方法修改一下,如下

 @Override
    public RefreshViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_REFRESH_HEADER) {
//            return new RefreshViewHolder(mInflater.inflate(R.layout.refresh_header_item, parent, false));
            return new RefreshViewHolder(new ArrowRefreshHeader(mContext));
        }
        return new RefreshViewHolder(mInflater.inflate(R.layout.normal_item, parent, false));
    }

再看下效果

成功实现在正常状态下不显示头部刷新的效果,下面继续实现第2步,当下拉到一定程度进行显示。既然这里有下拉,肯定涉及到了手势监听,自然是需要一个类继承自RecyclerView,然后重写onTouchEvent方法,下面看代码

@Override
    public boolean onTouchEvent(MotionEvent e) {
        if (mLastY == -1) {
            mLastY = e.getRawY();
        }
        switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastY = e.getRawY();
                sumOffSet = 0;
                break;
            case MotionEvent.ACTION_MOVE:
                float deltaY = (e.getRawY() - mLastY) / 2;//为了防止滑动幅度过大,将实际手指滑动的距离除以2
                mLastY = e.getRawY();
                sumOffSet += deltaY;//计算总的滑动的距离
                if (isOnTop() && !mRefreshing) {
                    mRefreshHeader.onMove(deltaY, sumOffSet);//接口回调,移动刷新的头部View
                    if (mRefreshHeader.getVisibleHeight() > 0) {
                        return false;
                    }
                }
                break;
                default:
                    mLastY = -1; // reset
                    if (isOnTop()&& !mRefreshing) {
                        if (mRefreshHeader.onRelease()) {
                            //手指松开
                        }
                    }
                    break;
        }
        return super.onTouchEvent(e);
    }

这部分代码就是获取手指滑动的距离,然后利用接口回调,将下拉的距离传递到自定义的View中,让头部刷新的View改变距离及改变状态。先看下定义的接口,接口中主要就是集中刷新的状态以及代表各个状态的变量,代码如下

public interface IRefreshHeader {
    int STATE_NORMAL = 0;//正常状态
    int STATE_RELEASE_TO_REFRESH = 1;//下拉的状态
    int STATE_REFRESHING = 2;//正在刷新的状态
    int STATE_DONE = 3;//刷新完成的状态
    void onReset();
    /**
     * 处于可以刷新的状态,已经过了指定距离
     */
    void onPrepare();
    /**
     * 正在刷新
     */
    void onRefreshing();
    /**
     * 下拉移动
     */
    void onMove(float offSet, float sumOffSet);
    /**
     * 下拉松开
     */
    boolean onRelease();
    /**
     * 下拉刷新完成
     */
    void refreshComplete();
    /**
     * 获取HeaderView
     */
    View getHeaderView();
    /**
     * 获取Header的显示高度
     */
    int getVisibleHeight();
}

我们的自定义View即ArrowRefreshHeader这个类实现了上面的接口,上面说道,通过接口回调的形式将移动的距离通过onMove方法回传给了ArrowRefreshHeader,现在看下ArrowRefreshHeader中onMove方法的具体实现,如下

if (getVisibleHeight() > 0 || offSet > 0) {
            setVisibleHeight((int) offSet + getVisibleHeight());
            if (mState <= STATE_RELEASE_TO_REFRESH) { // 未处于刷新状态,更新箭头
                if (getVisibleHeight() > mMeasuredHeight) {
                    onPrepare();
                } else {
                    onReset();
                }
            }
        }

这里将移动的距离通过setVisibleHeight方法进行显示,再看下这个方法的实现

public void setVisibleHeight(int height) {
        if (height < 0) height = 0;
        LayoutParams lp = (LayoutParams) mContentLayout.getLayoutParams();
        lp.height = height;
        mContentLayout.setLayoutParams(lp);
    }

这个方法的主要功能就是将距离设置给了LayoutParams中的height字段,然后再重新设置mContentLayout的布局属性。

  通过以上的方法,便可以实现当下拉到一定距离时显示头部刷新View的功能了,下面看下实现的效果

到了这一步已经可以实现下拉以及刷新的功能了,但是只会一直刷新,根本停不下来!这里是因为我们没有调用刷新完成的回调方法,下面开始实现刷新完成是的功能。


相关文章
|
6月前
|
Android开发 Kotlin
android开发,使用kotlin学习滚动控件RecyclerView
android开发,使用kotlin学习滚动控件RecyclerView
171 0
自己动手写RecyclerView的下拉刷新2
自己动手写RecyclerView的下拉刷新
自己动手写RecyclerView的上拉加载
自己动手写RecyclerView的上拉加载
|
JavaScript
每日一题:如何实现上拉加载,下拉刷新?
每日一题:如何实现上拉加载,下拉刷新?
281 0
每日一题:如何实现上拉加载,下拉刷新?
|
XML 程序员 开发工具
【Android开发】 ListView使用实战详解,你get到了嘛?
【Android开发】 ListView使用实战详解,你get到了嘛?
308 0
【Android开发】 ListView使用实战详解,你get到了嘛?
|
API
为RecyclerView添加下拉刷新功能
在之前的文章中,我们实现了带有header和footer功能的WrapRecyclerView。 现今App中列表的下拉刷新和上拉加载已经是一种习惯了,这两个操作也确实方便很多。 为RecyclerView添加这个功能可以通过多种方法,这里我选用了一种简单的做法。基于pulltorefresh这个库。
184 0
|
缓存 Android开发
RecyclerView高级进阶之优雅地解决瀑布流的两个神坑
RecyclerView高级进阶之优雅地解决瀑布流的两个神坑
RecyclerView高级进阶之优雅地解决瀑布流的两个神坑
老大爷都能看懂的RecyclerView动画原理
老大爷都能看懂的RecyclerView动画原理
老大爷都能看懂的RecyclerView动画原理
Flutter 21: 图解 ListView 下拉刷新与上拉加载 (三)【RefreshIndicator】
0 基础学习 Flutter,第二十一步:ListView 上拉加载更多与下拉刷新,解决方案三!
5228 0
|
Android开发
Flutter 18: 图解 ListView 下拉刷新与上拉加载 (二)【NotificationListener】
0 基础学习 Flutter,第十八步:ListView 上拉加载更多与下拉刷新,解决方案二!
5099 0