【问题标题】:Android RecyclerView ItemTouchHelper revert swipe and restore view holderAndroid RecyclerView ItemTouchHelper 恢复滑动和恢复视图持有者
【发布时间】:2015-10-25 13:24:55
【问题描述】:

有没有办法恢复滑动操作并将视图持有者恢复到其初始位置滑动完成并且在ItemTouchHelper.Callback 实例上调用onSwiped?我让RecyclerViewItemTouchHelperItemTouchHelper.Callback 实例完美地协同工作,我只需要恢复滑动操作并且在某些情况下删除滑动的项目。

【问题讨论】:

  • 你有答案吗?
  • @maveroid 是的,请看下面。

标签: android swipe android-recyclerview


【解决方案1】:

经过一些随机戳,我找到了解决方案。在您的适配器上调用 notifyItemChanged。这将使滑出的视图动画回到它的原始位置。

【讨论】:

  • 在大多数情况下都有效,但有时这不起作用。
  • 它有效,但我有一个问题。当我调用 notifyItemChanged 时,它会在视图中产生“闪光”。有一种方法可以跳过这个“闪光”@DariusL?
  • 这会为我播放淡入淡出动画而不是平移动画。
  • 这可能是一个很晚的回复。但我已经尝试了将近 2 天的反弹动画。这是迄今为止我找到的最佳解决方案。
  • 我真的不喜欢这个解决方案,因为它不会顺利返回到它的位置,而是猛烈地......
【解决方案2】:

您应该覆盖ItemTouchHelper.Callback 中的onSwiped 方法并刷新该特定项目。

 @Override
 public void onSwiped(RecyclerView.ViewHolder viewHolder,
     int direction) {
     adapter.notifyItemChanged(viewHolder.getAdapterPosition());
 }

【讨论】:

  • 我在压制之前要求确认,可以取消。在搜索并尝试了上述解决方案之后,它并不是很满意,因此我尝试了您的解决方案。然后我看到我在我的代码中完全按照你所说的做了一个参数化回调......问题是我没有在“false”上调用回调方法来激活这部分代码。因此,你救了我。
  • 我不得不从 Fragment 方法运行上面的@jimmy0251 的刷新建议,因为我的 onSwiped() 改变了背景颜色。像魅力一样工作!
【解决方案3】:

Google 的 ItemTouchHelper 实现假定每个刷出的项目最终都会从回收站视图中删除,而在某些应用程序中可能并非如此。

RecoverAnimationItemTouchHelper 中的一个嵌套类,用于管理滑动/拖动项目的触摸动画。虽然名字暗示它只恢复项目的位置,但它实际上是唯一用于恢复(取消滑动/拖动)替换(滑动时移出)的类或在拖动时替换)项目。奇怪的命名。

RecoverAnimation 中有一个名为 mIsPendingCleanup 的布尔属性,ItemTouchHelper 使用它来确定项目是否正在等待删除。所以ItemTouchHelper,在给item附加了RecoverAnimation之后,在成功刷出后设置这个属性,只要设置了这个属性,动画就不会从recover动画列表中移除。问题是,mIsPendingCleanup 将始终设置为刷出的项目,导致项目的 RecoverAnimation 永远不会从动画列表中删除。因此,即使您在成功滑动后恢复项目的位置,它也会在您触摸它时立即发送回滑出位置 - 因为 RecoverAnimation 会导致动画从最新的滑出位置开始。

不幸的是,对此的解决方案是将ItemTouchHelper 类源代码复制到与支持库中相同的包中,并从RecoverAnimation 类中删除mIsPendingCleanup 属性。我不确定这是否被 Google 接受,并且我还没有将更新发布到 Play Store 以查看它是否会导致拒绝,但是您可以从支持库 v22.2.1 中找到具有上述内容的类源代码在https://gist.github.com/kukabi/f46e1c0503d2806acbe2 提到了修复。

【讨论】:

  • 能否请您提供有关此解决方案的一些详细信息。尚无法获得
  • 这个假设在今天仍然有效?
  • " ...假设每个被刷出的物品最终都会从回收站视图中删除.." 也许是 100 年前))
  • @ekashking 不完全是。如果您检查答案的日期,大约是 6 年前。
【解决方案4】:

针对此问题的解决方法 是通过调用ItemTouchHelper::attachToRecyclerView(RecyclerView) 两次重新附加ItemTouchHelper,然后调用私有方法ItemTouchHelper::destroyCallbacks()destroyCallbacks() 删除项目装饰和所有侦听器,但也清除所有 RecoverAnimations。

请注意,我们需要先调用itemTouchHelper.attachToRecyclerView(null),以欺骗ItemTouchHelper,使其认为第二次调用itemTouchHelper.attachToRecyclerView(recyclerView) 是一个新的回收器视图。

更多详情请查看ItemTouchHelperhere的源代码。

解决方法示例:

RecyclerView recyclerView = findViewById(R.id.recycler_view);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);

...
// Workaround to reset swiped out views
itemTouchHelper.attachToRecyclerView(null);
itemTouchHelper.attachToRecyclerView(recyclerView);

将其视为一种肮脏的解决方法,因为此方法使用内部的、未记录的 ItemTouchHelper 实现细节。

更新

来自ItemTouchHelper::attachToRecyclerView(RecyclerView)documentation

如果 TouchHelper 已经附加到 RecyclerView,它将首先与前一个分离。您可以使用 null 调用此方法以将其与当前的 RecyclerView 分离。

在参数文档中:

要添加此帮助程序的 RecyclerView 实例,如果要从当前 RecyclerView 中删除 ItemTouchHelper,则为 null。

所以至少它是部分记录的。

【讨论】:

  • 如果我没有结婚,我会嫁给你。谢谢,这是唯一对我有用的解决方案。
  • 您的解决方案使行取消滑动,没有任何动画。根据您的代码,它更容易并且避免了 notifyItemChanged 附带的一些样板代码,但它在视觉上并不吸引人。
  • 我同意 RJFares。它有效,在某些情况下,有一个解决方法总比什么都没有好。但是,jimmy0251 解决方案就像一个魅力,并且带有动画
  • 谢谢。我将 DiffUtil 用于我的回收器视图(以及一些 LiveData/MVVM 分层),并且 notifyItemChanged 或 notifyDataSetChanged 不起作用,尽管它在更简单的设置中确实可以正常工作。这当然可以解决问题,谢谢!
【解决方案5】:

使用最新的 androidX 软件包我仍然有这个问题,所以我需要稍微调整 @jimmy0251 解决方案以正确重置项目(他的解决方案仅适用于第一次滑动)。

 override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
                clipAdapter.notifyItemChanged(viewHolder.adapterPosition)
                itemTouchHelper.startSwipe(viewHolder)
            }

请注意,startSwipe() 会正确重置项目的恢复动画。

【讨论】:

  • working 答案是在问题发布 5 年后给出的。逆天! ?
  • 不工作,视图已恢复,但是当我尝试在另一个元素中滑动时,开始在重置的元素中滑动。
【解决方案6】:

在使用LiveDataListAdapter 提供列表的情况下,调用notifyItemChanged 不起作用。但是,我发现了一个笨拙的解决方法,其中涉及将 ItemTouchHelper 重新附加到 onSwiped 回调中的回收器视图中

val recyclerView = someRecyclerViewInYourCode

var itemTouchHelper: ItemTouchHelper? = null

val itemTouchCallback = object : ItemTouchHelper.Callback {
    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction:Int) {
        itemTouchHelper?.attachToRecyclerView(null)
        itemTouchHelper?.attachToRecyclerView(recyclerView)
    }
}

itemTouchHelper = ItemTouchHelper(itemTouchCallback)

itemTouchHelper.attachToRecyclerView(recyclerView)

【讨论】:

  • 调用notifyItemChanged 确实适用于列表适配器。您可能正在寻找此代码:fun refreshListItem(item: ListItem) { val position = adapter.currentList.indexOf(item) adapter.notifyItemChanged(position) }
【解决方案7】:

onSwiped 从不调用,总是还原

override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float {
    return 1f
}
override fun getSwipeEscapeVelocity(defaultValue: Float): Float {
    return Float.MAX_VALUE
}

【讨论】:

  • 只有一个问题,onSwiped 从未真正被调用过。我们确实想在发生部分滑动时注册一个事件。
【解决方案8】:

在您的适配器上调用 notifyDataSetChanged 以使向后滑动保持一致

【讨论】:

    【解决方案9】:

    @Павел Карпычев 解决方案实际上几乎是正确的

    notifyItemChanged 的问题在于它执行了额外的动画,并且可能与来自onDraw 的装饰重叠,所以只做一个干净的幻灯片,这就是你可以做的:

    public class SimpleSwipeCallback extends ItemTouchHelper.SimpleCallback {
    
        boolean swipeOutEnabled = true;
        int swipeDir = 0;
    
        public SimpleSwipeCallback() {
            super(0, ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT);
        }
    
        @Override
        public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
            return false;
        }
    
        @Override
        public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int swipeDir) {
            //Do action
        }
    
        @Override
        public void onChildDraw(Canvas c, RecyclerView recyclerView,
                                RecyclerView.ViewHolder viewHolder,
                                float dx, float dy, int actionState, boolean isCurrentlyActive) {
    
                //check if it should swipe out
                boolean shouldSwipeOut = //TODO;
                if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && (!shouldSwipeOut) {
                    swipeOutEnabled = false;
    
                    //Limit swipe
                    int maxMovement = recyclerView.getWidth() / 3;
    
                    //swipe right : left
                    float sign = dx > 0 ? 1 : -1;
    
                    float limitMovement = Math.min(maxMovement, sign * dx); // Only move to maxMovement
    
                    float displacementPercentage = limitMovement / maxMovement;
    
                    //limited threshold
                    boolean swipeThreshold = displacementPercentage == 1;
    
                    // Move slower when getting near the middle
                    dx = sign * maxMovement * (float) Math.sin((Math.PI / 2) * displacementPercentage);
    
                    if (isCurrentlyActive) {
                        int dir = dx > 0 ? ItemTouchHelper.RIGHT : ItemTouchHelper.LEFT;
                        swipeDir = swipeThreshold ? dir : 0;
                    }
                } else {
                    swipeOutEnabled = true;
                }
    
             //do decoration
    
            super.onChildDraw(c, recyclerView, viewHolder, dx, dy, actionState, isCurrentlyActive);
        }
    
        @Override
        public float getSwipeEscapeVelocity(float defaultValue) {
            return swipeOutEnabled ? defaultValue : Float.MAX_VALUE;
        }
    
        @Override
        public float getSwipeVelocityThreshold(float defaultValue) {
            return swipeOutEnabled ? defaultValue : 0;
        }
    
        @Override
        public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) {
            return swipeOutEnabled ? 0.6f : 1.0f;
        }
    
        @Override
        public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
            super.clearView(recyclerView, viewHolder);
    
            if (swipeDir != 0) {
                onSwiped(viewHolder, swipeDir);
                swipeDir = 0;
            }
        }
    }
    

    请注意,这会启用正常滑动(“swipeOut”)或有限滑动,具体取决于shouldSwipeOut

    【讨论】:

    • shouldSwipeOut ????你还没有在任何地方声明它......
    • 取决于您的项目,如果您不希望它被刷出,则将其设置为 false,或者您执行一些逻辑(通过 recyclerView 您可以获得适配器并通过 viewHolder 位置)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-28
    • 2017-02-07
    • 2019-09-27
    • 2016-11-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多