【问题标题】:How to animate RecyclerView items when they appear如何在 RecyclerView 项目出现时为其设置动画
【发布时间】:2014-12-30 17:48:44
【问题描述】:

当 RecyclerView Items 出现时,我如何对其进行动画处理?

默认项目动画器仅在设置回收器数据后添加或删除数据时才动画。我是新开发的应用程序,不知道从哪里开始。

任何想法如何实现这一目标?

【问题讨论】:

    标签: android android-layout android-recyclerview


    【解决方案1】:

    编辑:

    根据the ItemAnimator documentation:

    这个类定义了当对适配器进行更改时在项目上发生的动画。

    因此,除非您将项目一一添加到您的 RecyclerView 并在每次迭代时刷新视图,否则我不认为 ItemAnimator 是您需要的解决方案。

    当RecyclerView 项目出现时,您可以使用 CustomAdapter 为它们设置动画:

    public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder>
    {
        private Context context;
    
        // The items to display in your RecyclerView
        private ArrayList<String> items;
        // Allows to remember the last item shown on screen
        private int lastPosition = -1;
    
        public static class ViewHolder extends RecyclerView.ViewHolder
        {
            TextView text;
            // You need to retrieve the container (ie the root ViewGroup from your custom_item_layout)
            // It's the view that will be animated
            FrameLayout container;
    
            public ViewHolder(View itemView)
            {
                super(itemView);
                container = (FrameLayout) itemView.findViewById(R.id.item_layout_container);
                text = (TextView) itemView.findViewById(R.id.item_layout_text);
            }
        }
    
        public CustomAdapter(ArrayList<String> items, Context context)
        {
            this.items = items;
            this.context = context;
        }
    
        @Override
        public CustomAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
        {
            View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.custom_item_layout, parent, false);
            return new ViewHolder(v);
        }
    
        @Override
        public void onBindViewHolder(ViewHolder holder, int position)
        {
            holder.text.setText(items.get(position));
    
            // Here you apply the animation when the view is bound
            setAnimation(holder.itemView, position);
        }
    
        /**
         * Here is the key method to apply the animation
         */
        private void setAnimation(View viewToAnimate, int position)
        {
            // If the bound view wasn't previously displayed on screen, it's animated
            if (position > lastPosition)
            {
                Animation animation = AnimationUtils.loadAnimation(context, android.R.anim.slide_in_left);
                viewToAnimate.startAnimation(animation);
                lastPosition = position;
            }
        }
    }
    

    您的 custom_item_layout 看起来像这样:

    <FrameLayout
        android:id="@+id/item_layout_container"
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <TextView
            android:id="@+id/item_layout_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceListItemSmall"
            android:gravity="center_vertical"
            android:minHeight="?android:attr/listPreferredItemHeightSmall"/>
    
    </FrameLayout>
    

    有关 CustomAdapters 和RecyclerView 的更多信息,请参阅此training on the official documentation。

    快速滚动问题

    使用此方法可能会导致快速滚动出现问题。在动画发生时可以重用视图。为了避免这种情况,建议在分离时清除动画。

        @Override
        public void onViewDetachedFromWindow(final RecyclerView.ViewHolder holder)
        {
            ((CustomViewHolder)holder).clearAnimation();
        }
    

    在 CustomViewHolder 上:

        public void clearAnimation()
        {
            mRootLayout.clearAnimation();
        }
    

    旧答案:

    看看Gabriele Mariotti's repo,我很确定你会找到你需要的。他为 RecyclerView 提供了简单的 ItemAnimator,例如 SlideInItemAnimator 或 SlideScaleItemAnimator。

    【讨论】:

    • 我见过,但那是在项目出现后添加和删除。我需要在它们出现之前开始动画。无论如何,谢谢马修。
    • 据我所知,你需要使用CustomAdapter。
    • 我想知道您是否体验过并可能解决了 RecyclerView 中这些动画的“卡住”效果?我对 ListView 使用了类似的代码,并且无论我滚动多快,我的项目都可以毫无问题地进行动画处理,但是使用 RecyclerView,如果我快速滚动,某些项目有时会卡在其他项目之上的屏幕上,并且它们不会完全隐藏- 就像动画在完成之前停止了一样。我实际上试图注释掉填充字段的代码部分(试图加快 onBindViewHolder 方法的执行)所以我只留下了动画代码
    • @MathieuMaree 感谢这个惊人的动画。它看起来很适合慢速滚动,但在快速滚动recyclerview 项目重叠。你找到这个问题了吗?有什么建议可以解决这个问题。
    • @GiruBhai 覆盖 onViewDetachedFromWindow 并在视图上调用 clearAnimation。问题是当 RecyclerView 试图重用视图时有动画在运行。
    【解决方案2】:

    仅使用 XML 变得简单

    Visit Gist Link

    res/anim/layout_animation.xml

    <?xml version="1.0" encoding="utf-8"?>
        <layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
            android:animation="@anim/item_animation_fall_down"
            android:animationOrder="normal"
            android:delay="15%" />
    

    res/anim/item_animation_fall_down.xml

    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="500">
    
        <translate
            android:fromYDelta="-20%"
            android:toYDelta="0"
            android:interpolator="@android:anim/decelerate_interpolator"
            />
    
        <alpha
            android:fromAlpha="0"
            android:toAlpha="1"
            android:interpolator="@android:anim/decelerate_interpolator"
            />
    
        <scale
            android:fromXScale="105%"
            android:fromYScale="105%"
            android:toXScale="100%"
            android:toYScale="100%"
            android:pivotX="50%"
            android:pivotY="50%"
            android:interpolator="@android:anim/decelerate_interpolator"
            />
    
    </set>
    

    在布局和recyclerview中使用,例如:

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layoutAnimation="@anim/layout_animation"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
    

    【讨论】:

    • @ArnoldBrown 通过更改动画文件。参考:stackoverflow.com/questions/5151591/…
    • 这是迄今为止最实用的答案。
    • 如何在每次打开列表时进行这项工作,因为现在它只是第一次这样做。
    • 数据集更改后必须调用recyclerView.scheduleLayoutAnimation(),否则动画将不起作用。
    • 当物品被回收并重新出现时,这项工作是否应该有效?我尝试了这个解决方案,当您第一次看到布局时,它非常适合初始动画。滚动后,项目返回查看时没有动画。
    【解决方案3】:

    当Recyclerview 项目首次出现时,我动画淡入了它们,如下面的代码所示。也许这对某人有用。

    private final static int FADE_DURATION = 1000; //FADE_DURATION in milliseconds
    
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
    
        holder.getTextView().setText("some text");
    
        // Set the view to fade in
        setFadeAnimation(holder.itemView);            
    }
    
    private void setFadeAnimation(View view) {
        AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f);
        anim.setDuration(FADE_DURATION);
        view.startAnimation(anim);
    }
    

    您还可以将setFadeAnimation() 替换为以下setScaleAnimation() 以通过从一个点缩放项目的外观来为项目的外观设置动画:

    private void setScaleAnimation(View view) {
        ScaleAnimation anim = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        anim.setDuration(FADE_DURATION);
        view.startAnimation(anim);
    }
    

    上面的代码有一些缺陷,当你滚动RecyclerView 时,项目总是褪色或缩放。如果您希望您可以添加代码以仅在首次创建包含 RecyclerView 的片段或活动时允许动画发生(例如,获取创建时的系统时间并仅允许在第一个 FADE_DURATION 毫秒内播放动画)。

    【讨论】:

    • 我为您的答案做了一些小修改,以使动画仅在向下滚动时起作用,请查看my answer
    • 这会破坏快速上下滚动时的布局(覆盖的列表项,某些项目的文本颜色错误)
    • 这不是为 recyclerview 项目设置动画的正确或推荐方式。您必须使用 ItemAnimator 类
    【解决方案4】:

    我从pbm's answer 用很少的modification 创建了动画,以使动画只运行一次

    换句话说Animation appear with you scroll down only

    private int lastPosition = -1;
    
    private void setAnimation(View viewToAnimate, int position) {
        // If the bound view wasn't previously displayed on screen, it's animated
        if (position > lastPosition) {
            ScaleAnimation anim = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            anim.setDuration(new Random().nextInt(501));//to make duration random number between [0,501)
            viewToAnimate.startAnimation(anim);
            lastPosition = position;
        }
    }
    

    并在onBindViewHolder调用函数

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
    
    holder.getTextView().setText("some text");
    
    // call Animation function
    setAnimation(holder.itemView, position);            
    }
    

    【讨论】:

    • 这里的lastPosition是什么?是arrayList.size() - 1
    • lastPosition 表示渲染视图的数量,所以它的开头值-1,每次渲染新视图时,我们都会启动动画并增加位置
    【解决方案5】:

    您可以像这样将android:layoutAnimation="@anim/rv_item_animation" 属性添加到RecyclerView:

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"                                        
        android:layoutAnimation="@anim/layout_animation_fall_down"
        />
    

    感谢这里的精彩文章: https://proandroiddev.com/enter-animation-using-recyclerview-and-layoutanimation-part-1-list-75a874a5d213

    【讨论】:

    • 这在 recyclerview 首次加载时对我有用,但在添加新项目时无效。 (虽然我猜这是个很好的问题)
    • 这是完美的答案
    【解决方案6】:

    一个好的起点是: https://github.com/wasabeef/recyclerview-animators/blob/master/animators/src/main/java/jp/wasabeef/recyclerview/adapters/AnimationAdapter.java

    你甚至不需要完整的库,那个类就足够了。 然后,如果你只是实现你的 Adapter 类给一个像这样的动画师:

    @Override
    protected Animator[] getAnimators(View view) {
        return new Animator[]{
                ObjectAnimator.ofFloat(view, "translationY", view.getMeasuredHeight(), 0)
        };
    }
    
    @Override
    public long getItemId(final int position) {
        return getWrappedAdapter().getItemId(position);
    }
    

    您会看到项目在滚动时从底部出现,这也避免了快速滚动的问题。

    【讨论】:

      【解决方案7】:

      在 recyclerview 中绑定到适配器中的项目可能不是最好的主意,因为这可能会导致 recyclerview 中的项目以不同的速度进行动画处理。在我的情况下,recyclerview 末尾的项目比顶部的项目更快地动画到它们的位置,因为顶部的项目有更远的移动,所以它看起来很不整洁。

      我用来将每个项目动画化到 recyclerview 中的原始代码可以在这里找到:

      http://frogermcs.github.io/Instagram-with-Material-Design-concept-is-getting-real/

      但我会复制并粘贴代码以防链接中断。

      第 1 步:在您的 onCreate 方法中设置此项,以确保动画只运行一次:

      if (savedInstanceState == null) {
          pendingIntroAnimation = true;
      }
      

      第 2 步:您需要将此代码放入要启动动画的方法中:

      if (pendingIntroAnimation) {
          pendingIntroAnimation = false;
          startIntroAnimation();
      }
      

      在链接中,作者正在动画工具栏图标,所以他把它放在这个方法中:

      @Override
      public boolean onCreateOptionsMenu(Menu menu) {
          getMenuInflater().inflate(R.menu.menu_main, menu);
          inboxMenuItem = menu.findItem(R.id.action_inbox);
          inboxMenuItem.setActionView(R.layout.menu_item_view);
          if (pendingIntroAnimation) {
              pendingIntroAnimation = false;
              startIntroAnimation();
          }
          return true;
      }
      

      第 3 步: 现在编写 startIntroAnimation() 的逻辑:

      private static final int ANIM_DURATION_TOOLBAR = 300;
      
      private void startIntroAnimation() {
          btnCreate.setTranslationY(2 * getResources().getDimensionPixelOffset(R.dimen.btn_fab_size));
      
          int actionbarSize = Utils.dpToPx(56);
          toolbar.setTranslationY(-actionbarSize);
          ivLogo.setTranslationY(-actionbarSize);
          inboxMenuItem.getActionView().setTranslationY(-actionbarSize);
      
          toolbar.animate()
                  .translationY(0)
                  .setDuration(ANIM_DURATION_TOOLBAR)
                  .setStartDelay(300);
          ivLogo.animate()
                  .translationY(0)
                  .setDuration(ANIM_DURATION_TOOLBAR)
                  .setStartDelay(400);
          inboxMenuItem.getActionView().animate()
                  .translationY(0)
                  .setDuration(ANIM_DURATION_TOOLBAR)
                  .setStartDelay(500)
                  .setListener(new AnimatorListenerAdapter() {
                      @Override
                      public void onAnimationEnd(Animator animation) {
                          startContentAnimation();
                      }
                  })
                  .start();
      }
      

      我的首选方案:

      我宁愿为整个 recyclerview 设置动画,而不是 recyclerview 中的项目。

      第 1 步和第 2 步保持不变。

      在第 3 步中,一旦您的 API 调用返回您的数据,我就会开始动画。

      private void startIntroAnimation() {
          recyclerview.setTranslationY(latestPostRecyclerview.getHeight());
          recyclerview.setAlpha(0f);
          recyclerview.animate()
                  .translationY(0)
                  .setDuration(400)
                  .alpha(1f)
                  .setInterpolator(new AccelerateDecelerateInterpolator())
                  .start();
      }
      

      这将为您的整个回收视图设置动画,使其从屏幕底部飞入。

      【讨论】:

      • 你说的是动画整个recyclerview
      • 是的 - 我是。您应该阅读第一段,了解为什么我认为动画整个 recyclerview 比每个项目更好。您可以尝试为每个项目设置动画,但效果不佳。
      • 什么是latestPostRecyclerview?
      • 您是否阅读了我回答的第一段,其中我已经说明了为什么动画项目视图不是一个好主意的原因?另外我建议尝试接受的解决方案,然后您会意识到问题所在,回来尝试这个解决方案。
      【解决方案8】:

      将此方法创建到您的 recyclerview 适配器

      private void setZoomInAnimation(View view) {
              Animation zoomIn = AnimationUtils.loadAnimation(context, R.anim.zoomin);// animation file 
              view.startAnimation(zoomIn);
          }
      

      最后在onBindViewHolder

      中添加这行代码

      setZoomInAnimation(holder.itemView);

      【讨论】:

        【解决方案9】:

        2019 年, 我建议将所有项目动画放入 ItemAnimator。

        让我们从在回收器视图中声明动画师开始:

        with(view.recycler_view) {
        adapter = Adapter()
        itemAnimator = CustomAnimator()
        }
        

        然后声明自定义动画师,

        class CustomAnimator() : DefaultItemAnimator() {
        
             override fun animateAppearance(
               holder: RecyclerView.ViewHolder,
               preInfo: ItemHolderInfo?,
               postInfo: ItemHolderInfo): Boolean{} // declare  what happens when a item appears on the recycler view
        
             override fun animatePersistence(
               holder: RecyclerView.ViewHolder,
               preInfo: ItemHolderInfo,
               postInfo: ItemHolderInfo): Boolean {} // declare animation for items that persist in a recycler view even when the items change
        
        }
        

        与上述类似,消失animateDisappearance,添加animateAdd,更改animateChange和移动animateMove。

        重要的一点是调用其中正确的动画调度程序。

        【讨论】:

        • 您能否提供一个使用此覆盖函数的自定义外观动画示例?我找不到示例,我不确定是否只需要在函数中指定动画。
        • gist.github.com/tadfisher/120d03f8380bfa8a16bf我在网上通过快速搜索找到了这个,这让我了解了重载的工作原理
        【解决方案10】:

        只需像下面那样扩展您的适配器

        public class RankingAdapter extends AnimatedRecyclerView<RankingAdapter.ViewHolder> 
        

        并将超级方法添加到 onBindViewHolder

        @Override
            public void onBindViewHolder(ViewHolder holder, final int position) {
                super.onBindViewHolder(holder, position);
        

        这是创建动画适配器的自动化方式,例如“Basheer AL-MOMANI”

        import android.support.v7.widget.RecyclerView;
        import android.view.View;
        import android.view.ViewGroup;
        import android.view.animation.Animation;
        import android.view.animation.ScaleAnimation;
        
        import java.util.Random;
        
        /**
         * Created by eliaszkubala on 24.02.2017.
         */
        public class AnimatedRecyclerView<T extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<T> {
        
        
            @Override
            public T onCreateViewHolder(ViewGroup parent, int viewType) {
                return null;
            }
        
            @Override
            public void onBindViewHolder(T holder, int position) {
                setAnimation(holder.itemView, position);
            }
        
            @Override
            public int getItemCount() {
                return 0;
            }
        
            protected int mLastPosition = -1;
        
            protected void setAnimation(View viewToAnimate, int position) {
                if (position > mLastPosition) {
                    ScaleAnimation anim = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
                    anim.setDuration(new Random().nextInt(501));//to make duration random number between [0,501)
                    viewToAnimate.startAnimation(anim);
                    mLastPosition = position;
                }
            }
        
        }
        

        【讨论】:

          【解决方案11】:

          我认为,最好这样使用它:(在 RecyclerView adapter 中只覆盖一个方法)

          override fun onViewAttachedToWindow(holder: ViewHolder) {
              super.onViewAttachedToWindow(holder)
          
              setBindAnimation(holder)
          }
          

          如果您想要 RV 中的每个附加动画。

          【讨论】:

            猜你喜欢
            • 2016-11-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-10-12
            相关资源
            最近更新 更多