RecyclerView and SwipeRefreshLayout

后端 未结 12 1842
再見小時候
再見小時候 2020-12-22 15:46

I\'m using the new RecyclerView-Layout in a SwipeRefreshLayout and experienced a strange behaviour. When scrolling the list back to the top sometim

相关标签:
12条回答
  • 2020-12-22 16:13

    I run into the same problem. My solution is overriding onScrolled method of OnScrollListener.

    Workaround is here:

        recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
    
        }
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            int offset = dy - ydy;//to adjust scrolling sensitivity of calling OnRefreshListener
            ydy = dy;//updated old value
            boolean shouldRefresh = (linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0)
                        && (recyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) && offset > 30;
            if (shouldRefresh) {
                swipeRefreshLayout.setRefreshing(true);
            } else {
                swipeRefreshLayout.setRefreshing(false);
            }
        }
    });
    
    0 讨论(0)
  • 2020-12-22 16:13

    Single line solution.

    setOnScrollListener is deprecated.

    You can use setOnScrollChangeListener for same purspose like this :

    recylerView.setOnScrollChangeListener((view, i, i1, i2, i3) -> swipeToRefreshLayout.setEnabled(linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0));
    
    0 讨论(0)
  • 2020-12-22 16:23

    unfortunately, this is a known bug in LinearLayoutManager. It does not computeScrollOffset properly when the first item is visible. will be fixed when it is released.

    0 讨论(0)
  • 2020-12-22 16:26

    Before you use this solution: RecyclerView is not complete yet, TRY NOT TO USE IT IN PRODUCTION UNLESS YOU'RE LIKE ME!

    As for November 2014, there are still bugs in RecyclerView that would cause canScrollVertically to return false prematurely. This solution will resolve all scrolling problems.

    The drop in solution:

    public class FixedRecyclerView extends RecyclerView {
        public FixedRecyclerView(Context context) {
            super(context);
        }
    
        public FixedRecyclerView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public FixedRecyclerView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }
    
        @Override
        public boolean canScrollVertically(int direction) {
            // check if scrolling up
            if (direction < 1) {
                boolean original = super.canScrollVertically(direction);
                return !original && getChildAt(0) != null && getChildAt(0).getTop() < 0 || original;
            }
            return super.canScrollVertically(direction);
    
        }
    }
    

    You don't even need to replace RecyclerView in your code with FixedRecyclerView, replacing the XML tag would be sufficient! (The ensures that when RecyclerView is complete, the transition would be quick and simple)

    Explanation:

    Basically, canScrollVertically(boolean) returns false too early,so we check if the RecyclerView is scrolled all the way to the top of the first view (where the first child's top would be 0) and then return.

    EDIT: And if you don't want to extend RecyclerView for some reason, you can extend SwipeRefreshLayout and override the canChildScrollUp() method and put the checking logic in there.

    EDIT2: RecyclerView has been released and so far there's no need to use this fix.

    0 讨论(0)
  • 2020-12-22 16:29

    write the following code in addOnScrollListener of the RecyclerView

    Like this:

        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener(){
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                int topRowVerticalPosition =
                        (recyclerView == null || recyclerView.getChildCount() == 0) ? 0 : recyclerView.getChildAt(0).getTop();
                swipeRefreshLayout.setEnabled(topRowVerticalPosition >= 0);
    
            }
    
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }
        });
    
    0 讨论(0)
  • 2020-12-22 16:29

    I have experienced same issue. I solved it by adding scroll listener that will wait until expected first visible item is drawn on the RecyclerView. You can bind other scroll listeners too, along this one. Expected first visible value is added to use it as threshold position when the SwipeRefreshLayout should be enabled in cases where you use header view holders.

    public class SwipeRefreshLayoutToggleScrollListener extends RecyclerView.OnScrollListener {
            private List<RecyclerView.OnScrollListener> mScrollListeners = new ArrayList<RecyclerView.OnScrollListener>();
            private int mExpectedVisiblePosition = 0;
    
            public SwipeRefreshLayoutToggleScrollListener(SwipeRefreshLayout mSwipeLayout) {
                this.mSwipeLayout = mSwipeLayout;
            }
    
            private SwipeRefreshLayout mSwipeLayout;
            public void addScrollListener(RecyclerView.OnScrollListener listener){
                mScrollListeners.add(listener);
            }
            public boolean removeScrollListener(RecyclerView.OnScrollListener listener){
                return mScrollListeners.remove(listener);
            }
            public void setExpectedFirstVisiblePosition(int position){
                mExpectedVisiblePosition = position;
            }
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                notifyScrollStateChanged(recyclerView,newState);
                LinearLayoutManager llm = (LinearLayoutManager) recyclerView.getLayoutManager();
                int firstVisible = llm.findFirstCompletelyVisibleItemPosition();
                if(firstVisible != RecyclerView.NO_POSITION)
                    mSwipeLayout.setEnabled(firstVisible == mExpectedVisiblePosition);
    
            }
    
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                notifyOnScrolled(recyclerView, dx, dy);
            }
            private void notifyOnScrolled(RecyclerView recyclerView, int dx, int dy){
                for(RecyclerView.OnScrollListener listener : mScrollListeners){
                    listener.onScrolled(recyclerView, dx, dy);
                }
            }
            private void notifyScrollStateChanged(RecyclerView recyclerView, int newState){
                for(RecyclerView.OnScrollListener listener : mScrollListeners){
                    listener.onScrollStateChanged(recyclerView, newState);
                }
            }
        }
    

    Usage:

    SwipeRefreshLayoutToggleScrollListener listener = new SwipeRefreshLayoutToggleScrollListener(mSwiperRefreshLayout);
    listener.addScrollListener(this); //optional
    listener.addScrollListener(mScrollListener1); //optional
    mRecyclerView.setOnScrollLIstener(listener);
    
    0 讨论(0)
提交回复
热议问题