recyclerview嵌套recyclerview刷新_Android 支持刷新、加载更多、带反弹效果的RecyclerView…

  • Post author:
  • Post category:其他

点击上方“Android技术杂货铺”,选择“标星”

干货文章,第一时间送达!

3090f52b03970b593b4c8ed4438f2946.png

开篇

当前市面上很多支持刷新、加载更多RecyclerView开源库,为何我这里还要自己再写一个?因为市面上的这些支持刷新加载更多的RecyclerView开源库实现方式基本上都是:在Adapter的外层在包裹一层Adapter,这种实现方式主要有以下两个不方便。

1、在用户添加ItemDecoration的时候,会影响到刷新头部和加载更多底部的样式。

2、在用户更新列表某条记录时,不方便找到该记录对应的position。例如notifyItemInserted(int position)等。

效果截屏

ec5df624f20472d48205b0927ca35348.png

gradle引用

    implementation 'jsc.kit.adapter:adapter-component:_latestVersion'
属性

PullToRefreshRecyclerView

8f04991ab0eb7de7903e9c1e501976d4.png

简析源码

public class PullToRefreshRecyclerView extends ViewGroup {}

1、初始化布局

private void initView(Context context) {
        inflate(context, R.layout.recycler_pull_to_refresh_recycler_view, this);
        recyclerView = findViewById(R.id.recycler_view);

        final ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
        mMinimumVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
        mMaximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
        scaledTouchSlop = viewConfiguration.getScaledTouchSlop();
    }

    private void initAttrs(Context context, AttributeSet attrs, int defStyleAttr) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PullToRefreshRecyclerView, defStyleAttr, 0);
        int headerLayoutId = a.getResourceId(R.styleable.PullToRefreshRecyclerView_prvHeaderLayout, -1);
        int footerLayoutId = a.getResourceId(R.styleable.PullToRefreshRecyclerView_prvFooterLayout, -1);

        //refresh text
        pullDownToRefreshText = a.hasValue(R.styleable.PullToRefreshRecyclerView_prvPullDownToRefreshText) ?
                a.getString(R.styleable.PullToRefreshRecyclerView_prvPullDownToRefreshText) :
                getResources().getString(R.string.recycler_default_pull_down_to_refresh);
        releaseToRefreshText = a.hasValue(R.styleable.PullToRefreshRecyclerView_prvReleaseToRefreshText) ?
                a.getString(R.styleable.PullToRefreshRecyclerView_prvReleaseToRefreshText) :
                getResources().getString(R.string.recycler_default_release_to_refresh);
        refreshingText = a.hasValue(R.styleable.PullToRefreshRecyclerView_prvRefreshingText) ?
                a.getString(R.styleable.PullToRefreshRecyclerView_prvRefreshingText) :
                getResources().getString(R.string.recycler_default_refreshing);
        refreshCompletedText = a.hasValue(R.styleable.PullToRefreshRecyclerView_prvRefreshCompletedText) ?
                a.getString(R.styleable.PullToRefreshRecyclerView_prvRefreshCompletedText) :
                getResources().getString(R.string.recycler_default_refresh_completed);

        //load more text
        pullUpToLoadMoreText = a.hasValue(R.styleable.PullToRefreshRecyclerView_prvPullUpToLoadMoreText) ?
                a.getString(R.styleable.PullToRefreshRecyclerView_prvPullUpToLoadMoreText) :
                getResources().getString(R.string.recycler_default_pull_up_to_load_more);
        releaseToLoadMoreText = a.hasValue(R.styleable.PullToRefreshRecyclerView_prvReleaseToLoadMoreText) ?
                a.getString(R.styleable.PullToRefreshRecyclerView_prvReleaseToLoadMoreText) :
                getResources().getString(R.string.recycler_default_release_to_load_more);
        loadingMoreText = a.hasValue(R.styleable.PullToRefreshRecyclerView_prvLoadingMoreText) ?
                a.getString(R.styleable.PullToRefreshRecyclerView_prvLoadingMoreText) :
                getResources().getString(R.string.recycler_default_loading_more);
        loadMoreCompletedText = a.hasValue(R.styleable.PullToRefreshRecyclerView_prvLoadMoreCompletedText) ?
                a.getString(R.styleable.PullToRefreshRecyclerView_prvLoadMoreCompletedText) :
                getResources().getString(R.string.recycler_default_load_more_completed);
        a.recycle();

        if (headerLayoutId == -1) {
            headerView = LayoutInflater.from(context).inflate(R.layout.recycler_default_header_view, this, false);
            setHeader(createDefaultHeader());
        } else {
            headerView = LayoutInflater.from(context).inflate(headerLayoutId, this, false);
        }
        if (footerLayoutId == -1) {
            footerView = LayoutInflater.from(context).inflate(R.layout.recycler_default_footer_view, this, false);
            setFooter(createDefaultFooter());
        } else {
            footerView = LayoutInflater.from(context).inflate(footerLayoutId, this, false);
        }
        addView(headerView, 0);
        addView(footerView);


        setHaveMore(false);
    }

2、测量

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        headerHeight = headerView.getMeasuredHeight();
        footerHeight = footerView.getMeasuredHeight();
    }

3、排版页面元素

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        headerView.layout(0, 0 - headerView.getMeasuredHeight(), getMeasuredWidth(), 0);
        recyclerView.layout(0, 0, getMeasuredWidth(), getMeasuredHeight());
        footerView.layout(0, getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight() + footerView.getMeasuredHeight());
    }

4、touch事件分发拦截。这里我们只拦截滑动事件,其他事件交由RecyclerView自己去处理。

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (getState() == REFRESH_COMPLETED
                || getState() == LOAD_MORE_COMPLETED)
            return super.onInterceptTouchEvent(ev);

        int action = ev.getActionMasked();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                stopReboundAnim();
                recyclerView.stopScroll();
                lastTouchY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float curTouchY = ev.getY();
                float dy = curTouchY - lastTouchY;
                dy = dy > 0 ? dy + 0.5f : dy - 0.5f;
                lastTouchY = curTouchY;
                //如果滑动距离小于scaledTouchSlop,则把事件交给子View消耗;
                //否则此事件交由自己的onTouchEvent(MotionEvent event)方法消耗。
                if (Math.abs((int) dy) >= scaledTouchSlop / 2)
                    return true;
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

5、处理拦截到的滑动事件。VelocityTracker跟踪滑动速度。

@Override
    public boolean onTouchEvent(MotionEvent ev) {
        enSureVelocityTrackerNonNull();
        int action = ev.getActionMasked();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                trackMotionEvent(ev);
                lastTouchY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                trackMotionEvent(ev);
                float curTouchY = ev.getY();
                float dy = curTouchY - lastTouchY;
                if (dy != 0) {
                    dy = dy > 0 ? dy + 0.5f : dy - 0.5f;
                    lastTouchY = curTouchY;
                    executeMove((int) -dy);
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                final VelocityTracker tracker = velocityTracker;
                tracker.computeCurrentVelocity(1000, mMaximumVelocity);
                int velocity = (int) tracker.getYVelocity();
                recycleVelocityTracker();
                executeUpOrCancelMotionEvent(velocity);
                break;
        }
        return true;
    }

6、执行滑动。

private void executeMove(int distance) {
        if (distance == 0)
            return;

        int scrollY = getScrollY();
        int scrolledY = 0;
        if (distance 0) {//向下滑动
            //如果正在加载更多,我们避免加载更多底部视图被滑动至不可见。
            if (!isLoadingMore() && scrollY > 0) {
                scrolledY = Math.max(0 - scrollY, distance);
                scrollBy(0, scrolledY);
                distance = distance - scrolledY;
            }

            //滑动列表。
            scrolledY = Math.max(0 - getRecyclerViewMaxCanPullDownDistance(), distance);
            if (scrolledY != 0)
                recyclerView.scrollBy(0, scrolledY);

            //如果正在加载更多且已滑动至列表顶部,不可再向下滑动。
            if (!isLoadingMore()) {
                distance = distance - scrolledY;
                distance = toScaledValue(distance);
                if (distance != 0)
                    scrollBy(0, distance);
            }
        } else {//向上滑动
            //如果正在刷新,我们避免刷新头部视图别滑动至不可见。
            if (!isRefreshing() && scrollY 0) {
                scrolledY = Math.min(Math.abs(scrollY), distance);
                scrollBy(0, scrolledY);
                distance = distance - scrolledY;
            }

            //滑动列表
            scrolledY = Math.min(getRecyclerViewMaxCanPullUpDistance(), distance);
            if (scrolledY != 0)
                recyclerView.scrollBy(0, scrolledY);

            //如果正在刷新且已滑动至列表底部,不可再向上滑动。
            if (!isRefreshing()) {
                distance = distance - scrolledY;
                distance = toScaledValue(distance);
                if (distance != 0)
                    scrollBy(0, distance);
            }
        }

        if (getScrollY() 0) {
            if (!isRefreshEnable() || isRefreshing()) {
                header.onScroll(getState(), isRefreshEnable(), isRefreshing(), getScrollY(), headerHeight, getRefreshThresholdValue());
                return;
            }

            // getRefreshThresholdValue()释放执行刷新阈值
            if (getScrollY()                 //release to refresh
                setState(RELEASE_TO_REFRESH);
            } else {
                //pull down to refresh
                setState(PULL_DOWN_TO_REFRESH);
            }
            header.onScroll(getState(), isRefreshEnable(), isRefreshing(), getScrollY(), headerHeight, getRefreshThresholdValue());
        } else if (getScrollY() > 0) {
            if (!isLoadMoreEnable() || isLoadingMore()) {
                footer.onScroll(getState(), isLoadMoreEnable(), isLoadingMore(), getScrollY(), footerHeight);
                return;
            }

            // getLoadMoreThresholdValue()释放执行加载更多阈值
            if (getScrollY() > getLoadMoreThresholdValue()) {
                //release to load more
                setState(RELEASE_TO_LOAD_MORE);
            } else {
                //pull up to load more
                setState(PULL_UP_TO_LOAD_MORE);
            }
            footer.onScroll(getState(), isLoadMoreEnable(), isLoadingMore(), getScrollY(), footerHeight);
        } else {
            header.onScroll(getState(), isRefreshEnable(), isRefreshing(), getScrollY(), headerHeight, getRefreshThresholdValue());
            footer.onScroll(getState(), isLoadMoreEnable(), isLoadingMore(), getScrollY(), footerHeight);
        }
    }

7、执行touch结束事件。

private void executeUpOrCancelMotionEvent(int velocity) {
        switch (getState()) {
            case REFRESHING:
                executeRebound(0 - headerHeight);
                recyclerView.fling(0, 0 - velocity);
                break;
            case LOADING_MORE:
                executeRebound(footerHeight);
                recyclerView.fling(0, 0 - velocity);
                break;
            case RELEASE_TO_REFRESH:
                executeRebound(0 - headerHeight);
                break;
            case RELEASE_TO_LOAD_MORE:
                executeRebound(isHaveMore() ? footerHeight : 0);
                break;
            default:
                executeRebound(0);
                recyclerView.fling(0, 0 - velocity);
                break;
        }
    }

    private void executeRebound(int destinationScrollY) {
        int scrollYDistance = destinationScrollY - getScrollY();
        int duration = Math.abs(scrollYDistance);
        duration = Math.max(200, duration);
        duration = Math.min(500, duration);
        if (animator == null) {
            animator = ObjectAnimator.ofPropertyValuesHolder(this, PropertyValuesHolder.ofInt(SCROLL_Y, getScrollY(), destinationScrollY));
            animator.setInterpolator(new AccelerateDecelerateInterpolator());
            animator.addListener(new SimpleAnimatorListener() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    switch (getState()) {
                        case RELEASE_TO_REFRESH:
                            if (!isRefreshing() && onRefreshListener != null) {
                                setState(REFRESHING);
                                currentPage = startPage;
                                onRefreshListener.onRefresh(getContext(), currentPage, pageSize);
                            }
                            break;
                        case RELEASE_TO_LOAD_MORE:
                            if (isHaveMore() && !isLoadingMore() && onRefreshListener != null) {
                                setState(LOADING_MORE);
                                currentPage++;
                                onRefreshListener.onLoadMore(getContext(), currentPage, pageSize);
                            }
                            break;
                        case REFRESH_COMPLETED:
                            setState(INIT);
                            lastRefreshTimeStamp = System.currentTimeMillis();
                            header.updateLastRefreshTime(lastRefreshTimeStamp);
                            break;
                        case LOAD_MORE_COMPLETED:
                            setState(INIT);
                            break;
                    }
                }
            });
        } else {
            animator.setIntValues(getScrollY(), destinationScrollY);
        }
        animator.setDuration(duration);
        animator.start();
    }
使用示例
  • 1、简单使用示例:

PullToRefreshRecyclerView pullToRefreshRecyclerView;

        //设置分页加载的起始页序号以及每页数据数量
        pullToRefreshRecyclerView.initializeParameters(1, 10);
        //关闭下拉刷新
//        pullToRefreshRecyclerView.setRefreshEnable(false);
        //关闭加载更多
//        pullToRefreshRecyclerView.setLoadMoreEnable(false);
        //设置下拉刷新和上拉加载更多监听
        pullToRefreshRecyclerView.setOnRefreshListener(new PullToRefreshRecyclerView.OnRefreshListener() {
            @Override
            public void onRefresh(@NonNull Context context, int currentPage, int pageSize) {
                index = -1;
                loadNetData();
            }

            @Override
            public void onLoadMore(@NonNull Context context, int currentPage, int pageSize) {
                loadNetData();
            }
        });
        RecyclerView recyclerView = pullToRefreshRecyclerView.getRecyclerView();
        recyclerView.setLayoutManager(new LinearLayoutManager(inflater.getContext()));
        recyclerView.addItemDecoration(new SpaceItemDecoration(
                CompatResourceUtils.getDimensionPixelSize(this, R.dimen.space_16),
                CompatResourceUtils.getDimensionPixelSize(this, R.dimen.space_2)
        ));


//模拟加载网络数据
    private int index = -1;
    private Random random = new Random();
    private void loadNetData(){
        pullToRefreshRecyclerView.postDelayed(new Runnable() {
            @Override
            public void run() {
                //刷新(或加载更多)完成
                pullToRefreshRecyclerView.completed();
                List items = new ArrayList<>();int count = 7 + random.nextInt(12);for (int i = 0; i                     index ++;
                    ClassItem item = new ClassItem();
                    item.setLabel("this is " + index);
                    items.add(item);
                }//判定是否是第一页数据if (pullToRefreshRecyclerView.isFirstPage()) {
                    adapter3.setData(items);
                } else {
                    adapter3.addData(items);
                }//设置是否还有下一页数据
                pullToRefreshRecyclerView.setHaveMore(items.size() >= pullToRefreshRecyclerView.getPageSize());
            }
        }, 50 + random.nextInt(2000));
    }
  • 2、自定义下拉刷新:

2.1、设置刷新(头部)

"http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:prvHeaderLayout="@layout/xxx"
    android:id="@+id/pull_to_refresh_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

2.2、设置刷新逻辑监听

public  void setHeader(@NonNull H header)

2.3、实现刷新逻辑

IHeader header =  new IHeader() {

            @Override
            public void initChildren(@NonNull View headerView) {
                //这里初始化下拉刷新view
                //也就是app:prvHeaderLayout="@layout/xxx"属性对应的布局文件
            }

            @Override
            public void updateLastRefreshTime(long lastRefreshTimeStamp) {
                //这里是上次刷新时间更新监听
            }

            @Override
            public void onUpdateState(int state, CharSequence txt) {
                //这里是监听下拉刷新的各种状态
                //监听到的状态有:PULL_DOWN_TO_REFRESH、RELEASE_TO_REFRESH、REFRESHING、REFRESH_COMPLETED
                switch (state) {
                    case PullToRefreshRecyclerView.REFRESHING:
                        //正在刷新,我们可以正在这里启动正在刷新的动画

                        break;
                    case PullToRefreshRecyclerView.REFRESH_COMPLETED:
                        //刷新完成,我们可以在这里关闭正在刷新的动画以及头部复位

                        break;
                    default:
                        break;
                }
            }

            @Override
            public void onScroll(int state, boolean refreshEnable, boolean isRefreshing, int scrollY, int headerHeight, int refreshThresholdValue) {
                //这里是监听下拉刷新动作
                //监听到的状态有:INIT、PULL_DOWN_TO_REFRESH、RELEASE_TO_REFRESH、REFRESHING、REFRESH_COMPLETED
            }
        };
  • 3、自定义上拉加载更多

3.1、设置加载更多(底部)

"http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:prvFooterLayout="@layout/xxx"
    android:id="@+id/pull_to_refresh_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

3.2、设置加载更多逻辑监听

public  void setHeader(@NonNull H header)

3.3、实现加载更多逻辑

IFooter footer = new IFooter() {

            @Override
            public void initChildren(@NonNull View footerView) {
                //这里初始化上拉加载更多view
                //也就是app:prvFooterLayout="@layout/xxx"属性对应的布局文件
            }

            @Override
            public void onUpdateState(@State int state, CharSequence txt) {
                //这里是监听上拉加载更多的各种状态
                //监听到的状态有:PULL_UP_TO_LOAD_MORE、RELEASE_TO_LOAD_MORE、LOADING_MORE、LOAD_MORE_COMPLETED
                switch (state) {
                    case PullToRefreshRecyclerView.LOADING_MORE:
                        //正在加载更多,我们可以正在这里启动正在加载更多的动画

                        break;
                    case PullToRefreshRecyclerView.LOAD_MORE_COMPLETED:
                        //加载更多完成,我们可以在这里关闭正在加载更多的动画以及底部复位

                        break;
                    default:
                        break;
                }
            }

            @Override
            public void onScroll(int state, boolean loadMoreEnable, boolean isLoadingMore, int scrollY, int footerHeight) {
                //这里是监听上拉加载更多动作
                //监听到的状态有:INIT、PULL_UP_TO_LOAD_MORE、RELEASE_TO_LOAD_MORE、LOADING_MORE、LOAD_MORE_COMPLETED
            }
        };

使用介绍就到这里。
从0撸出这个开源库不容易,希望童鞋们在GitHub: https://github.com/JustinRoom/SimpleAdapterDemo 上给一颗星星✨支持下。谢谢!如果在使用过程中不懂(或需要改进的地方),可以在评论里给我留言

作者:JustinRoom
链接:https://www.jianshu.com/p/31603f12445f

String类相关面试题很难?不要慌,问题不大

为什么程序员总是发现不了自己的Bug?

Android 显示view的粒子爆炸/绽放效果
73d6c0becd08876d4202532a15aa7942.png


版权声明:本文为weixin_39597399原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。