滚动时使RecyclerView动画化

25
有没有办法在滚动RecyclerView时对其元素进行动画处理?
我看了一下DefaultItemAnimator和RecyclerView.ItemAnimator,但是这些动画似乎只有在数据集更改时才会被调用,请纠正我如果我错了的话。
我对RecyclerView.ItemAnimator.animateMove()有点困惑,它什么时候被调用?我在那个类中放了一些断点,但是没有一个断点停止我的应用程序。
不过回到我的问题,如何对RecyclerView进行动画处理?我希望一些元素有另一个不透明度,这取决于某些自定义规则。
我做了一些更多的研究,似乎移动动画正是我要寻找的。这些方法从dispatchLayout()中调用。以下是该方法的文档:
  

包装layoutChildren(),处理由布局引起的更改的动画。    动画工作的基础是有五种不同类型的项在播放:
   PERSISTENT:项目在布局之前和之后都可见
   REMOVED:项目在布局之前可见,被应用程序删除
   ADDED:项目在布局之前不存在,被应用程序添加
   DISAPPEARING:项目存在于数据集中之前/之后,但在布局过程中从    可见到不可见(它们由于其他更改的副作用而移出了    屏幕)
   APPEARING:项目存在于数据集中之前/之后,但在布局过程中从    不可见到可见(它们由于其他更改的副作用而移上去    屏幕)
   整体方法找出哪些项目在布局之前/之后存在,    为每个项目推断五种状态之一。然后动画    相应地设置:
   PERSISTENT视图被移动({@link ItemAnimator#animateMove(ViewHolder, int, int, int, int)})    移除的视图被删除({@link ItemAnimator#animateRemove(ViewHolder)})
   添加的视图已添加({@link ItemAnimator#animateAdd(ViewHolder)})
   DISAPPEARING视图被移到屏幕外
   APPEARING视图被移入屏幕

到目前为止,我正在寻找PERSISTENT、DISAPPEARING和APPEARING,但是这些方法从未被调用,因为有这样一行:
boolean animateChangesSimple = mItemAnimator != null && mItemsAddedOrRemoved
            && !mItemsChanged;

mItemsAddedOrRemoved 一直是 false,所以这些回调函数都不会被触发。有什么办法可以正确设置该标志?


你可以使用API11中引入的ObjectAnimator来为任何对象添加动画效果。 - raj
好的,但我怎么知道何时需要开始动画? - rekire
3个回答

10

我最终使用了一个OnScrollListener并在自定义的animate()方法中对其进行动画处理。在我的情况下,该代码只需要2毫秒,因此对于60fps来说没有问题。

recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(int newState) {
        if(newState == RecyclerView.SCROLL_STATE_IDLE) {
            // special handler to avoid displaying half elements
            scrollToNext();
        }
        animate();
    }

    @Override
    public void onScrolled(int dx, int dy) {
        animate();
    }
});

2
你能加入更多的代码吗,例如像scrollToNext这样的功能? - MaTriXy
这超出了我的问题范围。你应该提出一个独立的问题来询问那个。记得在实现过程中展示你当前的进展 :) - rekire
@rekire,你能帮我回答这个问题吗:https://stackoverflow.com/questions/45858059/recyclerview-settle-item-with-decelerate-interpolator-once-scroll-finishes - Sandip Fichadiya

6

我是这样做的。可能有所帮助。我不知道这是否是做到它的最佳方式,但对我来说可以正常工作。

更新: 为了修复快速滚动行为,请重写适配器的onViewDetachedFromWindow方法,并在动画视图上调用clearAnimation(在此情况下,holder.itemView.clearAnimation())。

up_from_bottom.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
 android:shareInterpolator="@android:anim/decelerate_interpolator">
<translate
    android:fromXDelta="0%" android:toXDelta="0%"
    android:fromYDelta="100%" android:toYDelta="0%"
    android:duration="400" />
</set>

down_from_top.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
 android:shareInterpolator="@android:anim/decelerate_interpolator">
<translate
    android:fromXDelta="0%" android:toXDelta="0%"
    android:fromYDelta="-100%" android:toYDelta="0%"
    android:duration="400" />
</set>

将以下代码放置在recyclerViewonBindViewHolder方法中。创建一个名为lastPosition的字段并将其初始化为-1。
Animation animation = AnimationUtils.loadAnimation(context,
            (position > lastPosition) ? R.anim.up_from_bottom
                    : R.anim.down_from_top);
    holder.itemView.startAnimation(animation);
    lastPosition = position;

当notifydatasetchange时,动画再次出现。有什么办法可以修复吗? - Aung Thiha

0
一个好的解决方案是在适配器的onBindViewHolder方法中对holder进行动画处理。这段代码摘自Material Test项目Slidenerd。
@Override 
public void onBindViewHolder(ViewHolderBoxOffice holder, int position) {
    Movie currentMovie = mListMovies.get(position);
    //one or more fields of the Movie object may be null since they are fetched from the web 
    holder.movieTitle.setText(currentMovie.getTitle());


    //retrieved date may be null 
    Date movieReleaseDate = currentMovie.getReleaseDateTheater();
    if (movieReleaseDate != null) {
        String formattedDate = mFormatter.format(movieReleaseDate);
        holder.movieReleaseDate.setText(formattedDate);
    } else { 
        holder.movieReleaseDate.setText(Constants.NA);
    } 


    int audienceScore = currentMovie.getAudienceScore();
    if (audienceScore == -1) {
        holder.movieAudienceScore.setRating(0.0F);
        holder.movieAudienceScore.setAlpha(0.5F);
    } else { 
        holder.movieAudienceScore.setRating(audienceScore / 20.0F);
        holder.movieAudienceScore.setAlpha(1.0F);
    } 


    if (position > mPreviousPosition) {
        AnimationUtils.animateSunblind(holder, true);         
    } else { 
        AnimationUtils.animateSunblind(holder, false);

    } 
    mPreviousPosition = position;

这里有一个链接


这个解决方案只适用于某个库,而不是所有库。 - Mostafa Rostami

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