Android开发Android开发经验谈程序员

让RecyclerView与众不同 - recyclerview

2019-05-15  本文已影响7人  Android架构

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移动架构总群】:加入群聊

资料大全
上一篇下一篇

猜你喜欢

热点阅读