4.Android RecyclerView完美支持下拉刷新和上拉加载(美团奔跑的小人)

从月薪3000到年薪60万。从专科生到深圳一线大厂。关注我就能达到大师级水平,这话我终于敢说了, 年薪60万不是梦!

请问:怎么可以把代码格式调整?我贴出来换格式了。你们直接去Github下载工程!

今天开始讲RecycleView的系列教程。分割线,分组,局部刷新,动态添加,缓存原理,抖音效果,瀑布流。嵌套,动画等等





框架重写需要考虑的问题

1.是否支持下拉

2.是否支持上拉

3.自定义Header

4.自定义Footer

5.自定义动画

原理介绍:

使用RecyclerView.OnScrollListener滚动的监听器来监听RecyclerView是否滑动到了底部,这样我们就可以执行添加底部动画的操作。

边界条件

根据原理我们能够知道,做一个下拉刷新的动画加载,我们需要做的有两个动作:

1.如何判断RecyclerView已经滑动到底部。

2.如何添加RecyclerView的底部动画。

重要的就是RecyclerView滚动监听

 RecyclerView.OnScrollListener 

mRecyclerView.canScrollVertically(1) 是否拉到底部

4.一些属性的介绍

setWaveHeight、setHeaderHeight、setBottomHeight、setOverScrollHeight

setWaveHeight 设置头部可拉伸的最大高度。

setHeaderHeight 头部固定高度(在此高度上显示刷新状态)

setBottomHeight 底部高度

setOverScrollHeight 设置最大的越界高度

setEnableRefresh、setEnableLoadmore

灵活的设置是否禁用上下拉。

setHeaderView(IHeaderView headerView)、setBottomView(IBottomView bottomView)

设置头部/底部个性化刷新效果,头部需要实现IHeaderView,底部需要实现IBottomView。

setEnableOverScroll

是否允许越界回弹。

总结步骤:

1.完成头部添加和脚部添加

2.下拉刷新实现

3.同理,完成上拉加载

下拉刷新具体实现:

需要用的接口:

1.自动下拉

2.可以停止下拉

3.可以动态设置下拉的ui

4.下拉完成回调

下拉核心东西

1.处理拦截事件:因为拖动和点击事件有冲突


/****

    * 条目的点击事件和滑动事件

    * @param ev

    * @return

    */

@Override

publicbooleandispatchTouchEvent(MotionEventev) {

switch(ev.getAction()) {

caseMotionEvent.ACTION_DOWN:

// 记录手指按下的位置 ,之所以写在dispatchTouchEvent那是因为如果我们处理了条目点击事件,

// 那么就不会进入onTouchEvent里面,所以只能在这里获取

mFingerDownY=(int)ev.getRawY();

break;

caseMotionEvent.ACTION_UP:

if(mCurrentDrag) {//拖动没有超过一定距离就要还原

restoreRefreshView();

                }

break;

        }

returnsuper.dispatchTouchEvent(ev);

    }

2.处理滑动事件:


@Override

publicbooleanonTouchEvent(MotionEvente) {

switch(e.getAction()) {

caseMotionEvent.ACTION_MOVE:

// 如果是在最顶部才处理,否则不需要处理

if(canScrollUp()||mCurrentRefreshStatus==REFRESH_STATUS_REFRESHING) {

// 如果没有到达最顶端,也就是说还可以向上滚动就什么都不处理

returnsuper.onTouchEvent(e);

                }

// 解决下拉刷新自动滚动问题

if(mCurrentDrag) {

scrollToPosition(0);

                }

// 获取手指触摸拖拽的距离

intdistanceY=(int) ((e.getRawY()-mFingerDownY)*mDragIndex);

// 如果是已经到达头部,并且不断的向下拉,那么不断的改变refreshView的marginTop的值

if(distanceY>0) {

intmarginTop=distanceY-mRefreshViewHeight;

setRefreshViewMarginTop(marginTop);

updateRefreshStatus(marginTop);

mCurrentDrag=true;

returnfalse;

                }

break;

        }

returnsuper.onTouchEvent(e);

    }

需要判断是否到了顶部


/**

    * @return Whether it is possible for the child view of this layout to

    * scroll up. Override this if the child view is a custom view.

    * 判断是不是滚动到了最顶部,这个是从SwipeRefreshLayout里面copy过来的源代码

    */

publicbooleancanScrollUp() {

if(android.os.Build.VERSION.SDK_INT<14) {

returnViewCompat.canScrollVertically(this,-1)||this.getScrollY()>0;

}else{

returnViewCompat.canScrollVertically(this,-1);

        }

    }

滑动过程中要不断修改recycleView的参数值


/**

    * 设置刷新View的marginTop

    */

publicvoidsetRefreshViewMarginTop(intmarginTop) {

MarginLayoutParamsparams=(MarginLayoutParams)mRefreshView.getLayoutParams();

if(marginTop<-mRefreshViewHeight+1) {

marginTop=-mRefreshViewHeight+1;

        }

params.topMargin=marginTop;

mRefreshView.setLayoutParams(params);

    }

重写位置方法


@Override

protectedvoidonLayout(booleanchanged,intl,intt,intr,intb) {

super.onLayout(changed,l,t,r,b);

if(changed) {

if(mRefreshView!=null&&mRefreshViewHeight<=0) {

// 获取头部刷新View的高度

mRefreshViewHeight=mRefreshView.getMeasuredHeight();

if(mRefreshViewHeight>0) {

// 隐藏头部刷新的View  marginTop  多留出1px防止无法判断是不是滚动到头部问题

setRefreshViewMarginTop(-mRefreshViewHeight+1);

                }

            }

        }

    }

1.定义接口:下拉,停止下拉,下拉回调,下拉的view

* Description: 下拉刷新的辅助类为了匹配所有效果

*/

public abstract class RefreshViewCreator {

/**

    * 获取下拉刷新的View

*

    * @param context 上下文

    * @param parent  RecyclerView

*/

    public abstract ViewgetRefreshView(Context context, ViewGroup parent);

    /**

    * 正在下拉

    * @param currentDragHeight  当前拖动的高度

    * @param refreshViewHeight  总的刷新高度

    * @param currentRefreshStatus 当前状态

    */

    public abstract void onPull(int currentDragHeight, int refreshViewHeight, int currentRefreshStatus);

    /**

    * 正在刷新中

    */

    public abstract void onRefreshing();

    /**

    * 停止刷新

    */

    public abstract void onStopRefresh();

}

2.核心东西。事件处理

/**

* Description: 下拉刷新的RecyclerView

*/

public class RefreshRecyclerViewextends WrapRecyclerView {

// 下拉刷新的辅助类

    private RefreshViewCreatormRefreshCreator;

    // 下拉刷新头部的高度

    private int mRefreshViewHeight =0;

    // 下拉刷新的头部View

    private ViewmRefreshView;

    // 手指按下的Y位置

    private int mFingerDownY;

    // 手指拖拽的阻力指数

    private float mDragIndex =0.35f;

    // 当前是否正在拖动

    private boolean mCurrentDrag =false;

    // 当前的状态

    private int mCurrentRefreshStatus;

    // 默认状态

    public int REFRESH_STATUS_NORMAL =0x0011;

    // 下拉刷新状态

    public int REFRESH_STATUS_PULL_DOWN_REFRESH =0x0022;

    // 松开刷新状态

    public int REFRESH_STATUS_LOOSEN_REFRESHING =0x0033;

    // 正在刷新状态

    public int REFRESH_STATUS_REFRESHING =0x0033;

    public RefreshRecyclerView(Context context) {

super(context);

    }

public RefreshRecyclerView(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

    }

public RefreshRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

    }

// 先处理下拉刷新,同时考虑刷新列表的不同风格样式,确保这个项目还是下一个项目都能用

    // 所以我们不能直接添加View,需要利用辅助类

    public void addRefreshViewCreator(RefreshViewCreator refreshCreator) {

this.mRefreshCreator = refreshCreator;

        addRefreshView();

    }

@Override

    public void setAdapter(Adapter adapter) {

super.setAdapter(adapter);

        addRefreshView();

    }

/****

    * 条目的点击事件和滑动事件

    * @param ev

    * @return

    */

    @Override

    public boolean dispatchTouchEvent(MotionEvent ev) {

switch (ev.getAction()) {

case MotionEvent.ACTION_DOWN:

// 记录手指按下的位置 ,之所以写在dispatchTouchEvent那是因为如果我们处理了条目点击事件,

                // 那么就不会进入onTouchEvent里面,所以只能在这里获取

                mFingerDownY = (int) ev.getRawY();

break;

            case MotionEvent.ACTION_UP:

if (mCurrentDrag) {//拖动没有超过一定距离就要还原

                    restoreRefreshView();

                }

break;

        }

return super.dispatchTouchEvent(ev);

    }

/**

    * 重置当前刷新状态状态

    */

    private void restoreRefreshView() {

int currentTopMargin = ((MarginLayoutParams)mRefreshView.getLayoutParams()).topMargin;

        int finalTopMargin = -mRefreshViewHeight +1;

        if (mCurrentRefreshStatus ==REFRESH_STATUS_LOOSEN_REFRESHING) {

finalTopMargin =0;

            mCurrentRefreshStatus =REFRESH_STATUS_REFRESHING;

            if (mRefreshCreator !=null) {

mRefreshCreator.onRefreshing();

            }

if (mListener !=null) {

mListener.onRefresh();

            }

}

int distance = currentTopMargin - finalTopMargin;

        // 回弹到指定位置

        ValueAnimator animator = ObjectAnimator.ofFloat(currentTopMargin, finalTopMargin).setDuration(distance);

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

            public void onAnimationUpdate(ValueAnimator animation) {

float currentTopMargin = (float) animation.getAnimatedValue();

                setRefreshViewMarginTop((int) currentTopMargin);

            }

});

        animator.start();

        mCurrentDrag =false;

    }

@Override

    public boolean onTouchEvent(MotionEvent e) {

switch (e.getAction()) {

case MotionEvent.ACTION_MOVE:

// 如果是在最顶部才处理,否则不需要处理

                if (canScrollUp() ||mCurrentRefreshStatus ==REFRESH_STATUS_REFRESHING) {

// 如果没有到达最顶端,也就是说还可以向上滚动就什么都不处理

                    return super.onTouchEvent(e);

                }

// 解决下拉刷新自动滚动问题

                if (mCurrentDrag) {

scrollToPosition(0);

                }

// 获取手指触摸拖拽的距离

                int distanceY = (int) ((e.getRawY() -mFingerDownY) *mDragIndex);

                // 如果是已经到达头部,并且不断的向下拉,那么不断的改变refreshView的marginTop的值

                if (distanceY >0) {

int marginTop = distanceY -mRefreshViewHeight;

                    setRefreshViewMarginTop(marginTop);

                    updateRefreshStatus(marginTop);

                    mCurrentDrag =true;

return false;

                }

break;

        }

return super.onTouchEvent(e);

    }

/**

    * 更新刷新的状态

    */

    private void updateRefreshStatus(int marginTop) {

if (marginTop <= -mRefreshViewHeight) {

mCurrentRefreshStatus =REFRESH_STATUS_NORMAL;

        }else if (marginTop <0) {

mCurrentRefreshStatus =REFRESH_STATUS_PULL_DOWN_REFRESH;

        }else {

mCurrentRefreshStatus =REFRESH_STATUS_LOOSEN_REFRESHING;

        }

if (mRefreshCreator !=null) {

mRefreshCreator.onPull(marginTop, mRefreshViewHeight, mCurrentRefreshStatus);

        }

}

/**

    * 添加头部的刷新View

*/

    private void addRefreshView() {

RecyclerView.Adapter adapter = getAdapter();

        if (adapter !=null &&mRefreshCreator !=null) {

// 添加头部的刷新View

            View refreshView =mRefreshCreator.getRefreshView(getContext(), this);

            if (refreshView !=null) {

addHeaderView(refreshView);

                this.mRefreshView = refreshView;

            }

}

}

@Override

    protected void onLayout(boolean changed, int l, int t, int r, int b) {

super.onLayout(changed, l, t, r, b);

        if (changed) {

if (mRefreshView !=null &&mRefreshViewHeight <=0) {

// 获取头部刷新View的高度

                mRefreshViewHeight =mRefreshView.getMeasuredHeight();

                if (mRefreshViewHeight >0) {

// 隐藏头部刷新的View  marginTop  多留出1px防止无法判断是不是滚动到头部问题

                    setRefreshViewMarginTop(-mRefreshViewHeight +1);

                }

}

}

}

/**

    * 设置刷新View的marginTop

*/

    public void setRefreshViewMarginTop(int marginTop) {

MarginLayoutParams params = (MarginLayoutParams)mRefreshView.getLayoutParams();

        if (marginTop < -mRefreshViewHeight +1) {

marginTop = -mRefreshViewHeight +1;

        }

params.topMargin = marginTop;

        mRefreshView.setLayoutParams(params);

    }

/**

    * @return Whether it is possible for the child view of this layout to

* scroll up. Override this if the child view is a custom view.

    * 判断是不是滚动到了最顶部,这个是从SwipeRefreshLayout里面copy过来的源代码

    */

    public boolean canScrollUp() {

if (android.os.Build.VERSION.SDK_INT <14) {

return ViewCompat.canScrollVertically(this, -1) ||this.getScrollY() >0;

        }else {

return ViewCompat.canScrollVertically(this, -1);

        }

}

/**

    * 停止刷新

    */

    public void onStopRefresh() {

mCurrentRefreshStatus =REFRESH_STATUS_NORMAL;

        restoreRefreshView();

        if (mRefreshCreator !=null) {

mRefreshCreator.onStopRefresh();

        }

}

// 处理刷新回调监听

    private OnRefreshListenermListener;

    public void setOnRefreshListener(OnRefreshListener listener) {

this.mListener = listener;

    }

public interface OnRefreshListener {

void onRefresh();

    }

}

3.调用


publicclassPullActivityextendsAppCompatActivity{

privateRefreshRecyclerViewmRecycleView;

privateLinearLayoutManagermLinearLayoutManager;//布局管理器

privateListmList;

@Override

protectedvoidonCreate(BundlesavedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_pull);

mList=newArrayList();

mRecycleView=findViewById(R.id.rv_list);

//初始化数据

initData(mList);

//创建布局管理器,垂直设置LinearLayoutManager.VERTICAL,水平设置LinearLayoutManager.HORIZONTAL

mLinearLayoutManager=newLinearLayoutManager(this,LinearLayoutManager.VERTICAL,false);

//创建适配器,将数据传递给适配器

//设置布局管理器

mRecycleView.setLayoutManager(mLinearLayoutManager);

MyRecycleViewAdapteradapter=newMyRecycleViewAdapter(mList);

mRecycleView.setAdapter(newWrapRecyclerAdapter(adapter));

defaultRefreshCreator=newDefaultRefreshCreator();

mRecycleView.addRefreshViewCreator(defaultRefreshCreator);

mRecycleView.setOnRefreshListener(newRefreshRecyclerView.OnRefreshListener() {

@Override

publicvoidonRefresh() {

Log.d("PullActivity","onRefresh");

            }

        });

handler.sendEmptyMessageDelayed(0,5000);

    }

Handlerhandler=newHandler() {

@Override

publicvoidhandleMessage(@NonNullMessagemsg) {

super.handleMessage(msg);

mRecycleView.onStopRefresh();//停止刷新

        }

    };

DefaultRefreshCreatordefaultRefreshCreator;

publicvoidinitData(Listlist) {

for(inti=1;i<=80;i++) {

list.add("第"+i+"条数据");

        }

    }

}



问题:

给你一个普通的页面,你自己弄一个下拉刷新?怎么处理?

RecycleView是不是要搭配adapter一起?

不需要,recycleView和第三方的adataper是分开的,2个独立的控件

不能滑动都问题

支持下拉刷新和上拉加载的RefreshLayout,自带越界回弹效果,支持RecyclerView,AbsListView,ScrollView,WebView

Google官方推出了SwipeRefreshLayout和RecyclerView的共同使用,,为我们提供了更加便捷的列表下拉刷新功能,但是,并没有给我们提供上拉加载功能

基于第三方:    SmartRefreshLayout

基于第三方控件TwinklingRefreshLayout

第一种方式:嵌套

使用finishRefreshing()方法结束刷新,finishLoadmore()方法结束加载更多。此处OnRefreshListener还有其它方法,可以选择需要的来重写。

如果你想进入到界面的时候主动调用下刷新,可以调用startRefresh()/startLoadmore()方法。(会自动执行RefreshListenerAdapter监听事件中的onRefresh()/onLoadMore())

第二种方式:

方便实现个性化的 Header 和 Footer


demo地址:https://github.com/pengcaihua123456/shennandadao

你可能感兴趣的:(4.Android RecyclerView完美支持下拉刷新和上拉加载(美团奔跑的小人))