Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ItemTouchHelper with RecyclerView in NestedScrollView: Drag scrolling not work

I have implemented ItemTouchHelper like descriped in this articel: https://medium.com/@ipaulpro/drag-and-swipe-with-recyclerview-b9456d2b1aaf#.k7xm7amxi

All works fine if the RecyclerView is a child of the CoordinatorLayout.

But if the RecyclerView is a child of NestedScrollView in CoordinatorLayout, the drag scrolling not working anymore. Draging an item and move it to the top or bottom of the screen, the RecyclerView not scrolling like it do if its not a child of NestedScrollView.

Any ideas?

like image 1000
Scrounger Avatar asked Sep 27 '16 21:09

Scrounger


2 Answers

You have to disable the nestedScrolling for the recyclerView:

recyclerView.setIsNestedScrollingEnabled(false);
like image 176
Ourabi Avatar answered Oct 24 '22 03:10

Ourabi


I have run into this same problem and I spent nearly a whole day to solve it.

Precondition:

First of all, my xml layout looks like this:

<CoordinatorLayout>
    <com.google.android.material.appbar.AppBarLayout
        ...
    </com.google.android.material.appbar.AppBarLayout>
    <NestedScrollView>
        <RecyclerView/>
    </NestedScrollView>
</CoordinatorLayout>

And to make the scrolling behavior normal, I also let the nestedScrolling for the RecyclerView disabled by: RecyclerView.setIsNestedScrollingEnabled(false);

Reason:

But with ItemTouchHelper I still cannot make the Recyclerview auto scroll as expected when I drag the item in it. The reason why IT CANNOT SCROLL is in the method scrollIfNecessary() of ItemTouchHelper:

boolean scrollIfNecessary() {
    RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
    if (mTmpRect == null) {
        mTmpRect = new Rect();
    }
    int scrollY = 0;
    lm.calculateItemDecorationsForChild(mSelected.itemView, mTmpRect);
    if (lm.canScrollVertically()) {
        int curY = (int) (mSelectedStartY + mDy);
        final int topDiff = curY - mTmpRect.top - mRecyclerView.getPaddingTop();
        if (mDy < 0 && topDiff < 0) {
            scrollY = topDiff;
        } else if (mDy > 0) {
            final int bottomDiff = curY + mSelected.itemView.getHeight() + mTmpRect.bottom
                    - (mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom());
            if (bottomDiff > 0) {
                scrollY = bottomDiff;
            }
        }
    }
    if (scrollY != 0) {
        scrollY = mCallback.interpolateOutOfBoundsScroll(mRecyclerView,
                mSelected.itemView.getHeight(), scrollY,
                mRecyclerView.getHeight(), scrollDuration);
    }
    if (scrollY != 0) {
        mRecyclerView.scrollBy(scrollX, scrollY);
        return true;
    }
    return false;
}
  • Reason 1: when nestedScrolling for the RecyclerView is set to false, actually the effective scrolling object is the NestedScrollView, which is the parent of RecyclerView. So RecyclerView.scrollBy(x, y) here does not work at all!
  • Reason 2: mRecyclerView.getHeight() is much bigger than NestedScrollView.getHeight(). So when I drag the item in RecyclerView to bottom, the result of scrollIfNecessary() is also false.
  • Reason 3: mSelectedStartY does not seem like the expected value when in our case. Because we need to calculate the scrollY of NestedScrollView in our case.

Therefore, we need to override this method to fullfill our expectation. Here comes the solution:

Solution:

Step 1:

In order to override this scrollIfNecessary()(This method is not public), you need to new a class under a package named the same as ItemTouchHelper's. Like this: Example codes

Step 2:

Besides overriding scrollIfNecessary(), we also need to override select() in order to get the value of mSelectedStartY and the scrollY of NestedScrollView when starting draging.

public override fun select(selected: RecyclerView.ViewHolder?, actionState: Int) {
    super.select(selected, actionState)
    if (selected != null) {
        mSelectedStartY = selected.itemView.top
        mSelectedStartScrollY = (mRecyclerView.parent as NestedScrollView).scrollY.toFloat()
    }
}

Notice: mSelectedStartY and mSelectedStartScrollY are both very important for scrolling the NestedScrollView up or down.

Step 3:

Now we can override scrollIfNecessary(), and you need to pay attention to the comments below:

public override fun scrollIfNecessary(): Boolean {
    ...
    val lm = mRecyclerView.layoutManager
    if (mTmpRect == null) {
        mTmpRect = Rect()
    }
    var scrollY = 0
    val currentScrollY = (mRecyclerView.parent as NestedScrollView).scrollY
    
    // We need to use the height of NestedScrollView, not RecyclerView's!
    val actualShowingHeight = (mRecyclerView.parent as NestedScrollView).height

    lm!!.calculateItemDecorationsForChild(mSelected.itemView, mTmpRect!!)
    if (lm.canScrollVertically()) {
        // The true current Y of the item in NestedScrollView, not in RecyclerView!
        val curY = (mSelectedStartY + mDy - currentScrollY).toInt()

        // The true mDy should plus the initial scrollY and minus current scrollY of NestedScrollView
        val checkDy = (mDy + mSelectedStartScrollY - currentScrollY).toInt()
        
        val topDiff = curY - mTmpRect!!.top - mRecyclerView.paddingTop
        if (checkDy < 0 && topDiff < 0) {// User is draging the item out of the top edge.
            scrollY = topDiff
        } else if (checkDy > 0) { // User is draging the item out of the bottom edge.
            val bottomDiff = (curY + mSelected.itemView.height + mTmpRect!!.bottom
                    - (actualShowingHeight - mRecyclerView.paddingBottom))
            if (bottomDiff > 0) {
                scrollY = bottomDiff
            }
        }
    }
    if (scrollY != 0) {
        scrollY = mCallback.interpolateOutOfBoundsScroll(
            mRecyclerView,
            mSelected.itemView.height, scrollY, actualShowingHeight, scrollDuration
        )
    }
    if (scrollY != 0) {
        ...
        // The scrolling behavior should be assigned to NestedScrollView!
        (mRecyclerView.parent as NestedScrollView).scrollBy(0, scrollY)
        return true
    }
    ...
    return false
}

Result:

I can just show you my work through the Gif below:

Result

like image 32
Vensent Wang Avatar answered Oct 24 '22 02:10

Vensent Wang