简体   繁体   English

"在 NestedScrollView 中带有 RecyclerView 的 ItemTouchHelper:拖动滚动不起作用"

[英]ItemTouchHelper with RecyclerView in NestedScrollView: Drag scrolling not work

您必须为recyclerView禁用nestedScrolling

recyclerView.setIsNestedScrollingEnabled(false);

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:首先,我的 xml 布局如下所示:

<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);为了使滚动行为正常,我还让RecyclerViewnestedScrolling被禁用: RecyclerView.setIsNestedScrollingEnabled(false);

Reason:原因:

But with ItemTouchHelper I still cannot make the Recyclerview auto scroll when I drag the item in it.但是使用ItemTouchHelper ,当我将项目拖入其中时,我仍然无法使Recyclerview自动滚动。 The reason why IT CANNOT SCROLL is in the method scrollIfNecessary() of ItemTouchHelper : IT CANNOT SCROLL的原因在于 ItemTouchHelper 的ItemTouchHelper scrollIfNecessary()方法:

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 .原因一:RecyclerViewnestedScrolling设置为 false 时,实际上有效的滚动对象是NestedScrollView ,它是RecyclerView的父对象。 So RecyclerView.scrollBy(x, y) here does not work at all!所以这里的RecyclerView.scrollBy(x, y)根本不起作用!
  • Reason 2: mRecyclerView.getHeight() is much bigger than NestedScrollView.getHeight() .原因 2: mRecyclerView.getHeight()NestedScrollView.getHeight()大得多 So when I drag the item in RecyclerView to bottom, the result of scrollIfNecessary() is also false.因此,当我将RecyclerView中的项目拖到底部时, scrollIfNecessary()的结果也是错误的。
  • Reason 3: mSelectedStartY does not seem like the expected value when in our case.原因 3:在我们的例子中, mSelectedStartY看起来不像预期的值。 Because we need to calculate the scrollY of NestedScrollView in our case.因为在我们的例子中我们需要计算scrollYNestedScrollView

Therefore, we need to override this method to fullfill our expectation.因此,我们需要重写这个方法来满足我们的期望。 Here comes the solution:解决方案来了:

Solution:解决方案:

Step 1:步骤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 .为了覆盖此scrollIfNecessary() (此方法不是public ),您需要在与ItemTouchHelper同名的包下新建一个类。 Like this:像这样: 示例代码

Step 2:第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.除了覆盖scrollIfNecessary()之外,我们还需要覆盖select()以便在开始拖动时获取scrollY mSelectedStartY NestedScrollView

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.注意: mSelectedStartYmSelectedStartScrollY对于NestedScrollView向上或向下滚动都非常重要。

Step 3:第 3 步:

Now we can override scrollIfNecessary() , and you need to pay attention to the comments below:现在我们可以覆盖scrollIfNecessary()了,你需要注意下面的注释:

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:我可以通过下面的 Gif 向您展示我的工作:

结果

This is the solution that works for me.这是对我有用的解决方案。

Create 2 custom classes创建 2 个自定义类

1> LockableScrollView 1> LockableScrollView

public class LockableScrollView extends NestedScrollView {公共类 LockableScrollView 扩展 NestedScrollView {

// true if we can scroll (not locked)
// false if we cannot scroll (locked)
private boolean mScrollable = true;

public LockableScrollView(@NonNull Context context) {
    super(context);
}

public LockableScrollView(@NonNull Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
}

public LockableScrollView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}


public void setScrollingEnabled(boolean enabled) {
    mScrollable = enabled;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    // Don't do anything with intercepted touch events if
    // we are not scrollable
    if (ev.getAction() == MotionEvent.ACTION_MOVE) {// if we can scroll pass the event to the superclass
        return mScrollable && super.onInterceptTouchEvent(ev);
    }
    return super.onInterceptTouchEvent(ev);

}

android:descendantFocusability="blocksDescendants" android:descendantFocusability="blocksDescendants"

add in NestedScrollView and add添加NestedScrollView并添加

android:focusableInTouchMode="true" android:focusableInTouchMode="true"

in child layout it look like below在子布局中它看起来像下面

   <androidx.core.widget.NestedScrollView 
        android:descendantFocusability="blocksDescendants"> 

    <androidx.constraintlayout.widget.ConstraintLayout
        android:focusableInTouchMode="true">
        </androidx.constraintlayout.widget.ConstraintLayout> 

</androidx.core.widget.NestedScrollView>

check this github repo https://github.com/khambhaytajaydip/Drag-Drop-recyclerview检查这个 github 仓库https://github.com/khambhaytajaydip/Drag-Drop-recyclerview

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM