美文网首页Android开发Android开发经验谈程序员
让RecyclerView与众不同 - recyclerview

让RecyclerView与众不同 - recyclerview

作者: Android架构 | 来源:发表于2019-05-15 20:37 被阅读7次

    RecyclerView已经普及使用,其各式各样的布局格式,以及众多的优越特性,使得RecyclerView具有很大的灵活性。其中之一便是ItemAnimator,通过自定义ItemAnimator可以实现各种各样的Item增加,删除,改变,移动等动画效果。这也是本篇文章的主要内容。

    一、recyclerview-animators

    本篇文章将围绕recyclerview-animators展开,先介绍其简单使用,然后介绍其实现原理。

    首先,介绍recyclerview-animators的效果图。GitHub地址:recyclerview-animators

    这个库主要实现两部分内容。

    一:自定义ItemAnimator,实现Item增加和删除的动画效果。

    二:对Adapter进行封装,在onBindViewHolder中,绑定过程中设置显示动画。

    效果图

    自定义ItemAnimator效果
    Adapter动画效果

    二、recyclerview-animators的使用

    ItemAnimator的使用

    1.引入依赖
     compile 'jp.wasabeef:recyclerview-animators:2.2.6'
    
    
    2.使用
    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
    recyclerView.setItemAnimator(new SlideInLeftAnimator());
    
    
    3.高级功能

    动画时长

    recyclerView.getItemAnimator().setAddDuration(1000);
    recyclerView.getItemAnimator().setRemoveDuration(1000);
    recyclerView.getItemAnimator().setMoveDuration(1000);
    recyclerView.getItemAnimator().setChangeDuration(1000);
    
    

    插值器

    SlideInLeftAnimator animator = new SlideInLeftAnimator();
    animator.setInterpolator(new OvershootInterpolator());
    recyclerView.setItemAnimator(animator);
    
    

    另外自定义动画实现

    static class MyViewHolder extends RecyclerView.ViewHolder implements AnimateViewHolder {
      public MyViewHolder(View itemView) {
        super(itemView);
      }
    
      @Override
      public void preAnimateRemoveImpl(RecyclerView.ViewHolder holder) {
    
      }
    
      @Override
      public void animateRemoveImpl(RecyclerView.ViewHolder holder, ViewPropertyAnimatorListener listener) {
        ViewCompat.animate(itemView)
              .translationY(-itemView.getHeight() * 0.3f)
              .alpha(0)
              .setDuration(300)
              .setListener(listener)
              .start();
      }
    
      @Override
      public void preAnimateAddImpl(RecyclerView.ViewHolder holder) {
        ViewCompat.setTranslationY(itemView, -itemView.getHeight() * 0.3f);
        ViewCompat.setAlpha(itemView, 0);
      }
    
      @Override
      public void animateAddImpl(RecyclerView.ViewHolder holder, ViewPropertyAnimatorListener listener) {
        ViewCompat.animate(itemView)
              .translationY(0)
              .alpha(1)
              .setDuration(300)
              .setListener(listener)
              .start();
      }
    }
    
    
    4.注意

    使用以下方式,才会触发动画效果,具体下面分析:

    notifyItemInserted(int)
    notifyItemRemoved(int)
    notifyItemRangeInserted(int, int)
    notifyItemRangeRemoved(int, int)
    
    

    例如:

    public void remove(int position) {
      mDataSet.remove(position);
      notifyItemRemoved(position);
    }
    
    public void add(String text, int position) {
      mDataSet.add(position, text);
      notifyItemInserted(position);
    }
    
    

    Adapter的使用

    1.使用
    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
    MyAdapter adapter = new MyAdapter();
    recyclerView.setAdapter(new AlphaInAnimationAdapter(adapter));
    
    
    2.高级功能

    动画时长

    MyAdapter adapter = new MyAdapter();
    AlphaInAnimationAdapter alphaAdapter = new AlphaInAnimationAdapter(adapter);
    alphaAdapter.setDuration(1000);
    recyclerView.setAdapter(alphaAdapter);
    
    

    插值器

    MyAdapter adapter = new MyAdapter();
    AlphaInAnimationAdapter alphaAdapter = new AlphaInAnimationAdapter(adapter);
    alphaAdapter.setInterpolator(new OvershootInterpolator());
    recyclerView.setAdapter(alphaAdapter);
    
    

    是否仅显示一次动画效果

    MyAdapter adapter = new MyAdapter();
    AlphaInAnimationAdapter alphaAdapter = new AlphaInAnimationAdapter(adapter);
    scaleAdapter.setFirstOnly(false);
    recyclerView.setAdapter(alphaAdapter);
    
    

    复合动画

    MyAdapter adapter = new MyAdapter();
    AlphaInAnimationAdapter alphaAdapter = new AlphaInAnimationAdapter(adapter);
    recyclerView.setAdapter(new ScaleInAnimationAdapter(alphaAdapter));
    
    

    三、自定义ItemAnimator实现原理

    上面我们分析了recyclerview-animators的实现,那么我们如何实现自己想要的更炫酷的动画方式呢,以及上述注意中为什么只能使用那几种方式才能实现动画效果呢?这一切都需要理解其原理才行,下面我们主要分析自定义动画的实现。

    1.类结构

    首先,所有的自定义ItemAnimator都是继承BaseItemAnimator实现的。

    public abstract class BaseItemAnimator extends SimpleItemAnimator 
    
    

    而SimpleItemAnimator 又是什么? 是RecyclerView中根据ItemAnimator进行的一些简单的封装。

    abstract public class SimpleItemAnimator extends RecyclerView.ItemAnimator
    
    

    RecyclerView有着默认的动画效果实现DefaultItemAnimator,也继承了SimpleItemAnimator ,后面具体比较分析。

    一切的源头都是ItemAnimator,所以先来了解下它的主要方法。

    2.ItemAnimator

    当RecyclerView中的item在屏幕上由可见变为不可见时调用此方法

     public abstract boolean animateDisappearance(@NonNull ViewHolder viewHolder,
                    @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo);
    
    

    当RecyclerView中的item显示到屏幕上时调用此方法

    public abstract boolean animateAppearance(@NonNull ViewHolder viewHolder,
                    @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
    
    

    当RecyclerView中的item状态发生改变时调用此方法(notifyItemChanged(position))

    public abstract boolean animateChange(@NonNull ViewHolder oldHolder,
                    @NonNull ViewHolder newHolder,
                    @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
    
    

    统筹RecyclerView中所有的动画,统一启动执行

    abstract public void runPendingAnimations();
    
    

    这几个方法是自定义ItemAnimator的关键,实现不同的动画效果。

    SimpleItemAnimator 在ItemAnimator的基础上,针对上述几个方法,进行了一定的封装,使得可以更专注于动画的实现。

    3.SimpleItemAnimator

    SimpleItemAnimator对这几个方法是如何封装的呢?
    以添加一个Item举例,最后调用的是animateAppearance方法,SimpleItemAnimator的animateAppearance方法代码如下:

    public boolean animateDisappearance(@NonNull ViewHolder viewHolder,
                @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
            int oldLeft = preLayoutInfo.left;
            int oldTop = preLayoutInfo.top;
            View disappearingItemView = viewHolder.itemView;
            int newLeft = postLayoutInfo == null ? disappearingItemView.getLeft() : postLayoutInfo.left;
            int newTop = postLayoutInfo == null ? disappearingItemView.getTop() : postLayoutInfo.top;
            if (!viewHolder.isRemoved() && (oldLeft != newLeft || oldTop != newTop)) {
                disappearingItemView.layout(newLeft, newTop,
                        newLeft + disappearingItemView.getWidth(),
                        newTop + disappearingItemView.getHeight());
                if (DEBUG) {
                    Log.d(TAG, "DISAPPEARING: " + viewHolder + " with view " + disappearingItemView);
                }
                return animateMove(viewHolder, oldLeft, oldTop, newLeft, newTop);
            } else {
                if (DEBUG) {
                    Log.d(TAG, "REMOVED: " + viewHolder + " with view " + disappearingItemView);
                }
                return animateRemove(viewHolder);
            }
        }
    
    

    其实封装的很简单,只是判断了一下布局前后两个ViewHolder的left/top坐标是否相等 如果不相等则调用animateMove,否则调用animateAdd(viewHolder)方法。

    其他方法类似,最后我们只需要实现animateRemove, animateAdd, animateMove, animateChange 这4个方法。

    下面我们先看看RecyclerView中DefaultItemAnimator的实现,再来分析BaseItemAnimator。

    4.DefaultItemAnimator

    首先是对animateRemove, animateAdd, animateMove, animateChange 这4个方法的实现。
    还是以animateAdd为例:

     public boolean animateAdd(final ViewHolder holder) {
            resetAnimation(holder);
            ViewCompat.setAlpha(holder.itemView, 0);
            mPendingAdditions.add(holder);
            return true;
        }
    
    

    首先调用resetAnimation(holder)停止此holder中itemView的动画效果,然后将holder.itemView的透明度设置为0不可见状态(也可以设置其他属性,相当于动画开始的初始效果)。
    将holder添加到mPendingAdditions集合中。

    注意:此集合一会儿会在runPendingAnimations方法中进行遍历执行动画。

    然后是对runPendingAnimations的实现。
    下面上源码,很长,不过别害怕,很简单。

    public void runPendingAnimations() {
            boolean removalsPending = !mPendingRemovals.isEmpty();
            boolean movesPending = !mPendingMoves.isEmpty();
            boolean changesPending = !mPendingChanges.isEmpty();
            boolean additionsPending = !mPendingAdditions.isEmpty();
            if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
                // nothing to animate
                return;
            }
            // First, remove stuff
            for (ViewHolder holder : mPendingRemovals) {
                animateRemoveImpl(holder);
            }
            mPendingRemovals.clear();
            // Next, move stuff
            if (movesPending) {
                final ArrayList<MoveInfo> moves = new ArrayList<>();
                moves.addAll(mPendingMoves);
                mMovesList.add(moves);
                mPendingMoves.clear();
                Runnable mover = new Runnable() {
                    @Override
                    public void run() {
                        for (MoveInfo moveInfo : moves) {
                            animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
                                    moveInfo.toX, moveInfo.toY);
                        }
                        moves.clear();
                        mMovesList.remove(moves);
                    }
                };
                if (removalsPending) {
                    View view = moves.get(0).holder.itemView;
                    ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
                } else {
                    mover.run();
                }
            }
            // Next, change stuff, to run in parallel with move animations
            if (changesPending) {
                final ArrayList<ChangeInfo> changes = new ArrayList<>();
                changes.addAll(mPendingChanges);
                mChangesList.add(changes);
                mPendingChanges.clear();
                Runnable changer = new Runnable() {
                    @Override
                    public void run() {
                        for (ChangeInfo change : changes) {
                            animateChangeImpl(change);
                        }
                        changes.clear();
                        mChangesList.remove(changes);
                    }
                };
                if (removalsPending) {
                    ViewHolder holder = changes.get(0).oldHolder;
                    ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
                } else {
                    changer.run();
                }
            }
            // Next, add stuff
            if (additionsPending) {
                final ArrayList<ViewHolder> additions = new ArrayList<>();
                additions.addAll(mPendingAdditions);
                mAdditionsList.add(additions);
                mPendingAdditions.clear();
                Runnable adder = new Runnable() {
                    @Override
                    public void run() {
                        for (ViewHolder holder : additions) {
                            animateAddImpl(holder);
                        }
                        additions.clear();
                        mAdditionsList.remove(additions);
                    }
                };
                if (removalsPending || movesPending || changesPending) {
                    long removeDuration = removalsPending ? getRemoveDuration() : 0;
                    long moveDuration = movesPending ? getMoveDuration() : 0;
                    long changeDuration = changesPending ? getChangeDuration() : 0;
                    long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
                    View view = additions.get(0).itemView;
                    ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
                } else {
                    adder.run();
                }
            }
        }
    
    

    remove最先执行,for循环遍历mPendingRemovals集合中所有的ViewHolder。remove完成后,再同时开始move和change动画,最后执行add动画。

    其中,需要注意几个重要的方法:
    animateRemoveImpl(holder);
    animateMoveImpl(moveInfo.holder,moveInfo.fromX, moveInfo.fromY,moveInfo.toX, moveInfo.toY);
    animateChangeImpl(change);
    animateAddImpl(holder);

    这几个方法执行具体的动画效果,具体来看源码。

    下面来分析下BaseItemAnimator的实现。

    5.BaseItemAnimator

    了解了DefaultItemAnimator,那么对BaseItemAnimator的实现就清楚了。

    首先BaseItemAnimator使用了DefaultItemAnimator中的runPendingAnimations()方法的实现。
    其次BaseItemAnimator使用了DefaultItemAnimator中的animateMove, animateChange,以及animateRemoveImpl,animateChangeImpl等方法的实现。

    所以BaseItemAnimator只留下了animateRemove, animateAdd方法,方便我们自定义实现。

    以animateAdd方法为例:

     @Override public boolean animateAdd(final ViewHolder holder) {
        endAnimation(holder);
        preAnimateAdd(holder);
        mPendingAdditions.add(holder);
        return true;
      }
    
    

    第一步:停止此holder中itemView的动画效果,与DefaultItemAnimator一致。

    第二步:初始化itemView的属性

    第三步:将holder添加到mPendingAdditions集合中。

    与DefaultItemAnimator的不同主要是第二步,详细看看。

    private void preAnimateAdd(final ViewHolder holder) {
        ViewHelper.clear(holder.itemView);
    
        if (holder instanceof AnimateViewHolder) {
          ((AnimateViewHolder) holder).preAnimateAddImpl(holder);
        } else {
          preAnimateAddImpl(holder);
        }
      }
    
    

    如果holder属于AnimateViewHolder类或子类,那么就调用其preAnimateAddImpl()方法,否则调用内部的preAnimateAddImpl()方法。

    protected void preAnimateAddImpl(final ViewHolder holder) {
      }
    
    

    该方法空实现,由子类负责实现,初始化定义各种动画初始属性。

    关于AnimateViewHolder类,前面说过,可以通过自定义实现该接口,实现各种动画效果来代替具体的ItemAnimator,其内部需要实现的方法,与继承BaseItemAnimator的子类需要实现的方法一样。

    除此之外,针对add,在runPendingAnimations()方法中,需要指定具体的动画效果。

    截取runPendingAnimations()方法中的add部分。

    if (additionsPending) {
          final ArrayList<ViewHolder> additions = new ArrayList<ViewHolder>();
          additions.addAll(mPendingAdditions);
          mAdditionsList.add(additions);
          mPendingAdditions.clear();
          Runnable adder = new Runnable() {
            public void run() {
              boolean removed = mAdditionsList.remove(additions);
              if (!removed) {
                // already canceled
                return;
              }
              for (ViewHolder holder : additions) {
                doAnimateAdd(holder);
              }
              additions.clear();
            }
          };
    
    

    其中重要的一行就是:doAnimateAdd(holder);

    private void doAnimateAdd(final ViewHolder holder) {
        if (holder instanceof AnimateViewHolder) {
          ((AnimateViewHolder) holder).animateAddImpl(holder, new DefaultAddVpaListener(holder));
        } else {
          animateAddImpl(holder);
        }
    
        mAddAnimations.add(holder);
      }
    
    

    和上面差不多,其中animateAddImpl()方法空实现,需要子类实现。

    那么子类需要实现四个方法,就能完成自定义动画了,是不是很简单?

    6.具体实例

    下面举一个实现具体动画的例子:

    public class FadeInAnimator extends BaseItemAnimator {
    
      public FadeInAnimator() {
      }
    
      public FadeInAnimator(Interpolator interpolator) {
        mInterpolator = interpolator;
      }
    
      @Override protected void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
        ViewCompat.animate(holder.itemView)
            .alpha(0)
            .setDuration(getRemoveDuration())
            .setInterpolator(mInterpolator)
            .setListener(new DefaultRemoveVpaListener(holder))
            .setStartDelay(getRemoveDelay(holder))
            .start();
      }
    
      @Override protected void preAnimateAddImpl(RecyclerView.ViewHolder holder) {
        ViewCompat.setAlpha(holder.itemView, 0);
      }
    
      @Override protected void animateAddImpl(final RecyclerView.ViewHolder holder) {
        ViewCompat.animate(holder.itemView)
            .alpha(1)
            .setDuration(getAddDuration())
            .setInterpolator(mInterpolator)
            .setListener(new DefaultAddVpaListener(holder))
            .setStartDelay(getAddDelay(holder))
            .start();
      }
    }
    
    

    很简单吧,这样我们就可以任意自定义各种增删item的效果了。

    四、Adapter动画效果实现原理

    主要是用了装饰者模式,对原来的adapter进行了一层封装,增加了额外的功能。

    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
    MyAdapter adapter = new MyAdapter();
    recyclerView.setAdapter(new AlphaInAnimationAdapter(adapter));
    
    

    把原始的adapter传入了具有动画效果的新的Adpter中,那么新的Adpter内部是怎么实现的?
    以AlphaInAnimationAdapter为例:

    public class AlphaInAnimationAdapter extends AnimationAdapter {
    
      private static final float DEFAULT_ALPHA_FROM = 0f;
      private final float mFrom;
    
      public AlphaInAnimationAdapter(RecyclerView.Adapter adapter) {
        this(adapter, DEFAULT_ALPHA_FROM);
      }
    
      public AlphaInAnimationAdapter(RecyclerView.Adapter adapter, float from) {
        super(adapter);
        mFrom = from;
      }
    
      @Override protected Animator[] getAnimators(View view) {
        return new Animator[] { ObjectAnimator.ofFloat(view, "alpha", mFrom, 1f) };
      }
    }
    
    

    可见其内部主要提供了getAnimators方法,定义动画效果,其他方法来自于AnimationAdapter。

    AnimationAdapter中主要是通过onBindViewHolder来增加动画效果

     @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        mAdapter.onBindViewHolder(holder, position);
    
        int adapterPosition = holder.getAdapterPosition();
        if (!isFirstOnly || adapterPosition > mLastPosition) {
          for (Animator anim : getAnimators(holder.itemView)) {
            anim.setDuration(mDuration).start();
            anim.setInterpolator(mInterpolator);
          }
          mLastPosition = adapterPosition;
        } else {
          ViewHelper.clear(holder.itemView);
        }
      }
    
    

    循环遍历每个item,增加动画效果。

    五、总结

    通过上面的分析,对recyclerview-animators库有了一个深刻的了解,基于此基础可以实现各种炫酷的动画效果。

    自己是从事了七年开发的Android工程师,不少人私下问我,2019年Android进阶该怎么学,方法有没有?

    没错,年初我花了一个多月的时间整理出来的学习资料,希望能帮助那些想进阶提升Android开发,却又不知道怎么进阶学习的朋友。【包括高级UI、性能优化、架构师课程、NDK、Kotlin、混合式开发(ReactNative+Weex)、Flutter等架构技术资料】,希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

    资料获取方式:加入Android架构交流QQ群聊:513088520 ,进群即领取资料!!!

    点击链接加入群聊【Android移动架构总群】:加入群聊

    资料大全

    相关文章

      网友评论

        本文标题:让RecyclerView与众不同 - recyclerview

        本文链接:https://www.haomeiwen.com/subject/yhomaqtx.html