之前由于业务需求,要在列表的Item中做一个点赞的效果,并且自己做了个动画效果,完了点赞的数目也要跟着改变,于是操作完RecyclerView做了一个notifyItemChanged()的操作,功能都顺利实现,美中不足的是当前Item闪了一下,QA甚至为此提了Bug,一开始以为是图片加载库的问题,之后随着图片加载库从ImageLoader换成Picaso,又换成Glide,这个Bug一直如影随形。后来才发现“闪一下”原来是RecyclerView的默认动画,我的代码里有这样一句 mRecyclerView.setItemAnimator(new DefaultItemAnimator());
原来是这句代码搞的鬼,于是注掉再跑,但并没什么卵用。于是又改成mRecyclerView.setItemAnimator(null);
仍然没什么卵用。看来加不加这句,RecyclerView都默认执行了这个动画,看来还有点小麻烦。
先来了解下这个RecyclerView的动画吧。
RecyclerView.ItemAnimator
ItemAnimator能够帮助Item实现独立的动画。
ItemAnimator触发于以下三种事件:
某条数据被插入到数据集合中
从数据集合中移除某条数据
更改数据集合中的某条数据
在Android中默认实现了一个DefaultItemAnimator
,我们可以通过以下代码为Item增加动画效果:
recyclerView.setItemAnimator(new DefaultItemAnimator());
在之前的版本中,当数据集合发生改变时,我们通过调用notifyDataSetChanged()
来刷新列表,因为这样做会触发列表的重绘,所以并不会出现任何动画效果,但现在我的需求是只改变了当前一个Item的状态,因此需要调用一些以notifyItem*()
作为前缀的特殊方法,比如:
向指定位置插入Item
public final void notifyItemInserted(int position)
移除指定位置Item
public final void notifyItemRemoved(int position)
更新指定位置Item
public final void notifyItemChanged(int position)
但是现在的问题就是,调用notifyItem*()方法会触发RecyclerView的默认动画,而这个动画我并不想要,但是似乎并没有合适的办法来屏蔽这个动画,QA甚至认为这是个Bug,需要修复。与同行们交流了下,也有人遇到同样的问题,解决办法居然是调用notifyDataSetChanged()方法来刷新数据,这样就不会有闪一下的动画了。但是这样不就失去使用RecyclerView的优势和意义了吗?
最后我是通过重写RecyclerView的动画来解决这一“Bug”。
public class NoAlphaItemAnimator extends RecyclerView.ItemAnimator {
}
将DefaultItemAnimator类里的代码全部copy到自己写的动画类中,然后做一些修改。
首先找到private void animateChangeImpl(final ChangeInfo changeInfo) {}
方法。
找到方法里这两句代码:
1:oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() { ... }
2:newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).alpha(1).setListener(new VpaListenerAdapter() { ... }
替换成:
1:oldViewAnim.setListener(new VpaListenerAdapter() { ... }
2newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).setListener(new VpaListenerAdapter() { ... }
也就是说分别去掉上述代码中的alpha(0)
和alpha(1)
,然后保存。这个时候基本就打工告成了。最后在自己的RecyclerView中进行如下调用mRecyclerView.setItemAnimator(new NoAlphaItemAnimator());
,再跑起来,bug完美解决。
关于这个问题,不知道大家有没有其他更好的办法解决,如果有可以告诉我,欢迎讨论交流。
上面的方案还是太重了,果然有同学有更好的方案:
((SimpleItemAnimator)recyclerView.getItemAnimator()).setSupportsChangeAnimations(false);
加上这一句就可以了,设置为false,动画就不显示了。感谢@七方 同学。
还有另外一种方案:
recyclerView.getItemAnimator().setChangeDuration(0);
通过设置动画执行时间为0来解决问题,感谢@越风 同学。
网友评论
mRecyclerView.setItemAnimator(null); 即可解决问题...
Support 包 24.XXX
mRecyclerView.setItemAnimator(null); 即可解决....
Support 包版本 24.XXXX.
animator.setChangeDuration(0);
animator.setMoveDuration(0);
animator.setRemoveDuration(0);
animator.setAddDuration(0);
设置为0
/**
* 替换指定索引的数据条目
*
* @param location
* @param newModel
*/
public void setItem(int location, M newModel) {
mData.set(location, newModel);
//局部刷新
notifyItemChanged(location, "22"); 这样也能解决
}
compile 'com.android.support:recyclerview-v7:23.2.1'
这个版本及以上的版本中recycleview 中新增
/**
* Notify any registered observers that the item at <code>position</code> has changed with an
* optional payload object.
*
* <p>This is an item change event, not a structural change event. It indicates that any
* reflection of the data at <code>position</code> is out of date and should be updated.
* The item at <code>position</code> retains the same identity.
* </p>
*
* <p>
* Client can optionally pass a payload for partial change. These payloads will be merged
* and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the
* item is already represented by a ViewHolder and it will be rebound to the same
* ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing
* payloads on that item and prevent future payload until
* {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume
* that the payload will always be passed to onBindViewHolder(), e.g. when the view is not
* attached, the payload will be simply dropped.
*
* @param position Position of the item that has changed
* @param payload Optional parameter, use null to identify a "full" update
*
* @see #notifyItemRangeChanged(int, int)
*/
public final void notifyItemChanged(int position, Object payload) {
mObservable.notifyItemRangeChanged(position, 1, payload);
}
方法 ,楼主可以试试。
mRecyclerViewgetItemAnimator().setChangeDuration(0); 把动画持续时间设置为0
@Override
public void onBindViewHolder(DynamicViewHolder holder, int position, List<Object> payloads) {
if (payloads == null || payloads.isEmpty()) {
onBindViewHolder(holder, position);
} else {
//change item
}
}
不要重新加载图片就OK了
adapter中重写 onBindViewHolder(VH holder, int position, List<Object> payloads),应该是你姿势不对
ItemAnimator animator = new DefaultItemAnimator();
animator.setChangeDuration(0);
recyclerView.setItemAnimator(animator);
回复速度很快啊,我是这么用的:
Glide.with(context)
.load(product.thumbUrl)
.centerCrop()
.placeholder(R.drawable.ic_product_default)
.crossFade()
.into(viewHolder.productImage);