StaggeredGridLayoutManager结合DefaultItemAnimator移动动画异常问题
我之前也遇到过一模一样的情况——用StaggeredGridLayoutManager配合DefaultItemAnimator时,中间项交换动画正常,但首行Item交换会触发一堆不必要的动画。这本质是StaggeredGrid的布局特性和DefaultItemAnimator的默认逻辑不匹配导致的,下面给你几个可行的解决方案:
一、先确认数据更新的姿势是否正确
首先要确保你在交换Item数据时,用的是精确的通知方法,绝对不能用全量刷新:
// 错误示范:全量刷新会触发所有Item的动画,直接导致异常 adapter.notifyDataSetChanged(); // 正确姿势:交换数据后,精准通知位置变动 Collections.swap(dataList, 0, 1); // 注意:这里只需要调用一次notifyItemMoved,参数是交换前的位置和交换后的位置 adapter.notifyItemMoved(0, 1); // 额外通知另一个位置的Item更新状态,避免布局异常 adapter.notifyItemChanged(1);
全量刷新会让DefaultItemAnimator误以为所有Item都需要重新布局,这是引发异常动画的常见诱因,第一步一定要把这个姿势校正。
二、自定义ItemAnimator过滤目标动画
StaggeredGridLayoutManager在处理首行Item交换时,会因为列高重新计算,导致其他Item的位置被标记为“变动”,DefaultItemAnimator会对这些Item也触发动画。我们可以自定义Animator,只让目标的两个Item执行动画:
public class TargetedItemAnimator extends DefaultItemAnimator { private final Set<Integer> targetPositions = new HashSet<>(); public void setTargetAnimationPositions(int pos1, int pos2) { targetPositions.clear(); targetPositions.add(pos1); targetPositions.add(pos2); } @Override public boolean animateMove(@NonNull RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { // 只允许目标位置的Item执行移动动画 if (targetPositions.contains(holder.getAdapterPosition())) { return super.animateMove(holder, fromX, fromY, toX, toY); } else { // 跳过非目标Item的动画,直接标记完成 dispatchMoveFinished(holder); return false; } } @Override public void endAnimation(@NonNull RecyclerView.ViewHolder holder) { super.endAnimation(holder); targetPositions.remove(holder.getAdapterPosition()); } @Override public void endAnimations() { super.endAnimations(); targetPositions.clear(); } }
使用时,在交换Item前先设置目标位置:
// 交换数据 Collections.swap(dataList, 0, 1); // 设置要执行动画的两个位置 ((TargetedItemAnimator) recyclerView.getItemAnimator()).setTargetAnimationPositions(0, 1); // 通知适配器 adapter.notifyItemMoved(0, 1); adapter.notifyItemChanged(1);
三、调整StaggeredGrid的间隙策略
StaggeredGridLayoutManager的默认间隙处理(GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS)可能会导致布局重排时影响其他Item,我们可以尝试关闭自动间隙处理:
staggeredGridLayoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
这个设置可以减少因为间隙调整引发的额外布局变动,从而降低不必要的动画触发概率。
四、关闭Change动画减少干扰
DefaultItemAnimator默认开启了Change动画,这可能和Move动画冲突,尤其是在首项交换时。可以尝试关闭Change动画,只保留Move动画:
DefaultItemAnimator animator = new DefaultItemAnimator(); animator.setSupportsChangeAnimations(false); recyclerView.setItemAnimator(animator);
以上几种方法可以组合使用,其中自定义ItemAnimator的方案最能精准解决你的问题,确保只有交换的两个Item执行动画。
内容的提问来源于stack exchange,提问作者Cheok Yan Cheng

