是否有一种方法可以在完成滑动并在ItemTouchHelper.Callback
实例上调用onSwiped
后,撤消滑动操作并将视图持有者恢复到其初始位置?我已经把RecyclerView
、ItemTouchHelper
和ItemTouchHelper.Callback
实例完美地结合起来了,我只需要恢复滑动操作,在某些情况下不要删除已滑动的项目。
是否有一种方法可以在完成滑动并在ItemTouchHelper.Callback
实例上调用onSwiped
后,撤消滑动操作并将视图持有者恢复到其初始位置?我已经把RecyclerView
、ItemTouchHelper
和ItemTouchHelper.Callback
实例完美地结合起来了,我只需要恢复滑动操作,在某些情况下不要删除已滑动的项目。
notifyItemChanged
方法。这将使滑出视图以动画方式返回其原始位置。你应该重写 ItemTouchHelper.Callback
中的 onSwiped
方法,并刷新特定的项。
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder,
int direction) {
adapter.notifyItemChanged(viewHolder.getAdapterPosition());
}
谷歌的ItemTouchHelper
实现假设每个被滑出去的项最终都将从回收视图中删除,但在某些应用程序中可能并非如此。
RecoverAnimation
是ItemTouchHelper
中的一个嵌套类,它管理被滑动/拖动项的触摸动画。尽管名称暗示它仅恢复项目的位置,但实际上它是唯一用于恢复(取消滑动/拖动)和替换(在滑动时移出或在拖动时替换)项目的类。命名有点奇怪。
RecoverAnimation
中有一个名为mIsPendingCleanup
的布尔属性,ItemTouchHelper
使用它来确定该项是否等待删除。因此,在将RecoverAnimation
附加到该项后,ItemTouchHelper
在成功滑出后设置此属性,并且只要设置了此属性,该动画就不会从恢复动画列表中删除。问题在于,对于已滑出的项,mIsPendingCleanup
始终设置,导致该项的RecoverAnimation
永远不会从动画列表中删除。因此,即使您在成功滑动后恢复了该项的位置,只要触摸该项,它就会立即返回到已滑出的位置-因为RecoverAnimation
将导致动画从最新的已滑出位置开始。
ItemTouchHelper
类的源代码复制到与支持库相同的包中,并从RecoverAnimation
类中删除mIsPendingCleanup
属性。我不确定Google是否接受这个解决方案,我还没有将更新发布到Play商店,以查看是否会被拒绝,但你可以在https://gist.github.com/kukabi/f46e1c0503d2806acbe2上找到具有上述修复的支持库v22.2.1的类源代码。对于这个问题的一个笨拙的解决办法是通过两次调用ItemTouchHelper::attachToRecyclerView(RecyclerView)
重新附加ItemTouchHelper,然后调用私有方法ItemTouchHelper::destroyCallbacks()
。destroyCallbacks()
会删除项目装饰和所有侦听器,还会清除所有RecoverAnimations。
请注意,我们需要先调用itemTouchHelper.attachToRecyclerView(null)
来欺骗ItemTouchHelper
,让它认为第二次调用itemTouchHelper.attachToRecyclerView(recyclerView)
是一个新的recycler view。
进一步了解详情,请查看ItemTouchHelper
源代码这里。
解决办法示例:
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)
的文档中:
如果TouchHelper已附加到RecyclerView,则它将首先从以前的RecyclerView中分离。您可以将此方法与null一起调用,以将其从当前RecyclerView中分离。
并在参数文档中:
要向其添加此帮助程序的RecyclerView实例,或者如果要从当前RecyclerView中删除ItemTouchHelper,则为null。
所以至少部分已经被记录下来了。
如果使用LiveData
为ListAdapter
提供列表,调用notifyItemChanged
不起作用。但是我发现一个丑陋的解决方法,涉及在onSwiped
回调中重新附加ItemTouchHelper
到recycler视图中
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) }
- Carson Holzheimer使用最新的AndroidX包仍然存在这个问题,所以我需要稍微调整@jimmy0251的解决方案来正确重置项目(他的解决方案只适用于第一次滑动)。
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
clipAdapter.notifyItemChanged(viewHolder.adapterPosition)
itemTouchHelper.startSwipe(viewHolder)
}
startSwipe()
会正确地重置该项的恢复动画。onSwiped从不调用,总是回滚
override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float {
return 1f
}
override fun getSwipeEscapeVelocity(defaultValue: Float): Float {
return Float.MAX_VALUE
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val allowDelete = false // or show a dialog and ask for confirmation or whatever logic you need
if (allowDelete) {
adapter.remove(viewHolder.bindingAdapterPosition)
} else {
// start the inverse animation and reset the internal swipe state AFTERWARDS
viewHolder.itemView
.animate()
.translationX(0f)
.withEndAction {
itemTouchHelper.attachToRecyclerView(null)
itemTouchHelper.attachToRecyclerView(recyclerView)
}
.start()
}
}
ItemTouchHelper
成员都具有私有包访问修饰符,我们不想复制一个2000行的类来更改一行代码,因此让我们将我们的软件包指向androidx.recyclerview.widget
。mCallback.onSwiped
)时,我们可以恢复已滑动视图的初始状态。mCallback.onSwiped
仅从postDispatchSwipe
方法调用,因此在此之后,我们注入了我们的视图恢复(recoverOnSwiped
),它会清除已滑动视图上的任何滑动效果和动画。@file:Suppress("PackageDirectoryMismatch")
package androidx.recyclerview.widget
import android.annotation.SuppressLint
/**
* [ItemTouchHelper] with recover viewHolder's itemView from clean up
*/
class RecoveredItemTouchHelper(callback: Callback, private val withRecover: Boolean = true) : ItemTouchHelper(callback) {
private fun recoverOnSwiped(viewHolder: RecyclerView.ViewHolder) {
// clear any swipe effects from [viewHolder]
endRecoverAnimation(viewHolder, false)
if (mPendingCleanup.remove(viewHolder.itemView)) {
mCallback.clearView(mRecyclerView, viewHolder)
}
if (mOverdrawChild == viewHolder.itemView) {
mOverdrawChild = null
mOverdrawChildPosition = -1
}
viewHolder.itemView.requestLayout()
}
@Suppress("DEPRECATED_IDENTITY_EQUALS")
@SuppressLint("VisibleForTests")
internal override fun postDispatchSwipe(anim: RecoverAnimation, swipeDir: Int) {
// wait until animations are complete.
mRecyclerView.post(object : Runnable {
override fun run() {
if (mRecyclerView != null && mRecyclerView.isAttachedToWindow
&& !anim.mOverridden
&& (anim.mViewHolder.absoluteAdapterPosition !== RecyclerView.NO_POSITION)
) {
val animator = mRecyclerView.itemAnimator
// if animator is running or we have other active recover animations, we try
// not to call onSwiped because DefaultItemAnimator is not good at merging
// animations. Instead, we wait and batch.
if ((animator == null || !animator.isRunning(null))
&& !hasRunningRecoverAnim()
) {
mCallback.onSwiped(anim.mViewHolder, swipeDir)
if (withRecover) {
// recover swiped
recoverOnSwiped(anim.mViewHolder)
}
} else {
mRecyclerView.post(this)
}
}
}
})
}
}
recoverOnSwiped
中尝试使用 viewHolder
并应用任何你想要的动画。mCallback.clearView(mRecyclerView, viewHolder)
只是恢复了高度并将 translationX 和 translationY 重置为 0 -- 参见来源 - khoben@Павел Карпычев 的解决方案实际上几乎是正确的
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;
}
}
}
shouldSwipeOut
。shouldSwipeOut
你没有在任何地方声明... - ekashking