RecyclerView、条目滑动、java.lang.IllegalStateException

4
我的任务是在RecyclerView中实现水平滑动的项目。我实际上已经完成了这个任务,感谢ViewPager源代码和其他一些资源,但是有一个场景出现问题。
我的SwipeableRecyclerView(SRV)扩展了RecyclerView并实现了RecyclerView.OnItemTouchListener以执行魔术,它实际上起作用了。它还定义了一个自定义接口SwipeListener,其中定义了一些方法,最重要的是(对于这个问题)'onSwipe(View view, int position, boolean right)'。
我的活动向SRV提供了接口的实现,当检测到滑动并触发监听器方法时,它会执行以下操作:
  • 使用'view.animate().translationX(translationX)'将项目/滑动视图正确方向移出屏幕
  • 上面启动的动画具有侦听器,在onAnimationEnd中删除指定位置处的项目,并调用adapter.notifyItemRemoved(position)
  • 结果是视图消失到滑动的一侧,然后recycler使用其动画删除行-所有这些都非常好地工作
有时候当我很快地滑动时,就像第一个动画还没有完成时,我开始滑动另一行。有时会创建带有无视图的“洞”/幻影行;有时会出现以下异常:
java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling

而堆栈跟踪显示,adapter.notifyItemRemoved()是导致异常的方法。

对于异常问题,事件顺序如下(来自我的调试跟踪):

started  for position: 11
started  for position: 10
finished for position: 11
removing 11
removed  11
finished position: 10
removing 10

因此,两个完成动画都是一个接一个地启动的,当其中一个删除其项目并调用notifyItemRemoved时,另一个完成并调用删除该项。实际上,我不确定这可能是什么,因为所有这些都发生在主线程上,但我猜滚动也是在动画帧中完成的,并且由于第二次删除而需要新的计算。

所以,我陷入了困境,不知道该怎么做。实际上,我认为最简单的事情应该是在启动动画时禁用整个回收站视图的触摸,并在滚动完成后再次启用它们。然而,我无法禁用SRV-我尝试过setEnabled(false),clickable等,但没有任何作用。

有什么帮助吗?

2个回答

4
你可以检查 mRecyclerView.getItemAnimator().isRunning(),这比当前答案更简单。只要是 trueMotionEvent 就不应该触发任何删除操作。

我测试了一下,但它并没有起作用。我仍然可以滑动两个或更多视图,使它们的动画重叠,这导致了崩溃。虽然我的代码可能有其他问题,但我需要进行更多的测试。不过,谢谢你的帖子,我之前不知道你提到的方法及其带监听器的姐妹方法 - 真是太酷了。 - wujek
也许这对我有用。动画似乎是由adapter.notifyItemRemoved()触发的。 - natario
我明白为什么它对我不起作用:当我检测到解雇时,在我的解决方案中(代理标志),我立即禁用任何其他滑动并开始完成动画,其onAnimationEnd监听器方法会删除该项并调用notifyItemRemoved(),从而导致删除动画启动。您提出的解决方案引入了一个非常短的窗口(从我的滑动完成动画开始运行直到调用notifyItemRemoved()方法开始动画)。 (续在另一条评论中)。 - wujek
时间窗口非常短,但在第一个项的notifyItemRemoved()被调用之前,我仍然能够开始滑动第二个项,这会导致问题。在我的解决方案中,一旦检测到滑动手势,我禁用了滑动,这样用户就无法在动画运行时再次滑动。这非常棘手。 - wujek
1
我认为这两个条件都是必要的。只要正在进行先前的滑动手势,就不应该监听新的滑动手势,而且只要删除动画正在进行中(即ItemAnimator().isRunning() == true),就不应该监听新的滑动手势。 - natario
是的,但在我下面帖子中描述的情况下,只有在animator onRemoveFinished()方法中将swipable设置为true。对于我的用例来说,这已经足够了,因为我只进行了删除操作,但在一般情况下,你是正确的 - 我会合并这两种方法。谢谢。 - wujek

2

以下内容对我最终起到了作用:

  • add a swipingEnabled property to the SRV; when false, onInterceptTouchEvent just calls through to the parent implementation, effectively disabling item swiping
  • add this code in the method which is responsible for handling the swipe gesture (like removing the entry etc.):

    swipeableRecyclerView.setSwipingEnabled(false);
    
  • add this code near where the SRV is created:

    swipeableRecyclerView.setItemAnimator(new DefaultItemAnimator() {
    
        @Override
        public void onRemoveFinished(RecyclerView.ViewHolder item) {
            swipeableRecyclerView.setSwipingEnabled(true);
        }
    });
    

    This just extends the default item animator with an implementation of a method which is called when the remove animation is finished.

现在,我的滑动删除被“序列化”,不会出现异常。


2
我发现使用onMoveFinished更好,因为它考虑了移除项目后的重新洗牌。 - Ljdawson
这是否意味着,在 onRemoveFinished() 完成后,会执行一系列的移动操作?我假设(也许是错误的)移除包含了整个操作... - wujek
1
我测试过了,确实是这样的:在onRemoveFinished之后,会有一系列的onMoveFinished调用。谢谢你的提示。 - wujek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接