美文网首页Android学习之鸿蒙&Android
4.Android RecyclerView完美支持下拉刷

4.Android RecyclerView完美支持下拉刷

作者: 鹏城十八少 | 来源:发表于2021-06-29 07:59 被阅读0次

    从月薪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完美支持下拉刷

        本文链接:https://www.haomeiwen.com/subject/hcdhultx.html