欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android RecyclerView的Item自定义动画及DefaultItemAnimator源码分析

程序员文章站 2024-03-05 12:58:54
这是关于recyclerview的第二篇,说的是如何自定义item动画,但是请注意,本文不包含动画的具体实现方法,只是告诉大家如何去自定义动画,如何去参考源代码。 ...

这是关于recyclerview的第二篇,说的是如何自定义item动画,但是请注意,本文不包含动画的具体实现方法,只是告诉大家如何去自定义动画,如何去参考源代码。 

我们知道,recyclerview默认会使用defaultitemanimator,所以如果我们需要自定义动画,那么应该好好的读读这个类的源代码,这样不仅仅是学习怎么自定义,还要学习android的设计模式。 

先弄明白一件事,defaultitemanimator继承自simpleitemanimator,simpleitemanimator继承自recyclerview.itemanimator,所以如果需要自定义动画,最简单的方法是继承simpleitemanimator。其次,动画的类型有四种,分别是add、remove、move以及change,这里我们只列举remove,举一反三。 

我们先看simpleitemanimator中的源码,在simpleitemanimator中有几个重要的方法: 

@override
 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);
  }
 }

解析:这个函数是重写recyclerview.itemanimator的,接口中参数分别是viewholder、prelayoutinfo以及postlayoutinfo,第一个参数是指item的viewholder,可以通过这个对象的itemview来获取它的view,第二个参数是指item删除前的位置信息,第三个是指新的位置信息。再接下来会判断viewholder是否已经被移除以及位置是否发生变化,然后在调用animateremove这个抽象方法,如果我们要自定义动画,就需要去实现它(回调思想)。

 public final void dispatchremovestarting(viewholder item) {
   onremovestarting(item);
 } 
public void onremovestarting(viewholder item) {
 } 

解析:dispatchremovestaring个是一个final方法,也就是不能被重写,如果我们需要处理一些在remove开始的时候的逻辑,我们就需要在animateremove方法中调用这个方法,这个方法会执行一个onremovestaring方法,这个方法就允许我们重写,所以逻辑应该写在onremovestaring中,当我们调用dispatchremovestaring的时候,onremovestaring就会被执行。 

这里只说了两个,但是,加上其他动作的就不只是两个啦。。。
所以,当我们继承了simpleitemanimator的时候,需要实现里面的一些方法,一般有如下这些: 
 ① animateremove(add、move和change):这些方法会在动画发生的时候回调,一般会在这个方法中用列表记录每个item的动画以及属性
 ② endanimation、endanimations:分别是在一个item或是多个item需要立即停止的时候回调
 ③ isrunning:如果需要顺畅滑动的时候,必须要重写这个方法,很多时候比如在网络加载的时候滑动卡顿就是这个方法逻辑不对
 ④ run'pendinganimations:这是最重要的一个方法。因为animatedisappearence等方法返回的是animateremove等方法返回的值,而这个方法则是根据这些值来确定是否有准备好的动画需要播放,如果有,就会回调这个方法。在这个方法我们需要处理每一个动作(remove、add、move以及change)的动画
 

所以,我们的一般步骤就是:
 ①创建一个simpleitemanimator的子类
 ②创建每个动作的动作列表
 ③重写animateremove等方法,当界面中有动作发生,这些函数会被回调,这里进行记录并返回true使得run'pendinganimations开始执行
 ④重写run'pendinganimations,当③的方法返回true的时候,就认为需要执行动画,我们需要把动画执行的逻辑写在这里面
 ⑤重写isrunning,提供动画播放状态,一般是返回动作列表是否为空
 ⑥如果有需要,重写endanimation、endanimations、onremovefinish等方法
具体的步骤有了,但是我们还不清楚该怎么构建它,不用着急,为了方便我们,谷歌其实已经提供了defaultitemanimator,我们可以参考一些它的源码,没有人讲的比源码有道理,我们需要的是有足够的耐心!
defaultitemanimator中定义了一些arraylist来存放动作的信息,如下:

 private arraylist<viewholder> mpendingremovals = new arraylist<>();
private arraylist<viewholder> mremoveanimations = new arraylist<>();


@override
 public boolean animateremove(final viewholder holder) {
  resetanimation(holder);
  mpendingremovals.add(holder);
  return true;
 }

解析:可以看到animatorremove方法直接是把viewholder加入列表中,然后返回true 

@override
 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
  ......
  // next, change stuff, to run in parallel with move animations
  ......
  // next, add stuff
  ......
 }

解析:根据上面可以知道,runpendinganimations会执行,可看到,在这个方法中遍历了动作列表,并让每个item都执行了animatorremoveimpl方法,其他动作的方法暂时先省略,有兴趣的可以自行阅读。 

private void animateremoveimpl(final viewholder holder) {
  final view view = holder.itemview;
  final viewpropertyanimatorcompat animation = viewcompat.animate(view);
  mremoveanimations.add(holder);
  animation.setduration(getremoveduration())
    .alpha(0).setlistener(new vpalisteneradapter() {
   @override
   public void onanimationstart(view view) {
    dispatchremovestarting(holder);
   }

   @override
   public void onanimationend(view view) {
    animation.setlistener(null);
    viewcompat.setalpha(view, 1);
    dispatchremovefinished(holder);
    mremoveanimations.remove(holder);
    dispatchfinishedwhendone();
   }
  }).start();
 }

解析:可以看到animatorremoveimpl方法中实现了整个动画的具体逻辑,具体怎么做不在本文范围中,在我们执行了动画之后,也就是在动画的listener中的onanimatorend中调用了dispatchremovefinish,还记得这个方法吗,它会执行onremovefinish方法,onremovefinish方法是可以供给我们重写的。然后把item移除动作列表。 

@override
 public boolean isrunning() {
  return (!mpendingadditions.isempty() ||
    !mpendingchanges.isempty() ||
    !mpendingmoves.isempty() ||
    !mpendingremovals.isempty() ||
    !mmoveanimations.isempty() ||
    !mremoveanimations.isempty() ||
    !maddanimations.isempty() ||
    !mchangeanimations.isempty() ||
    !mmoveslist.isempty() ||
    !madditionslist.isempty() ||
    !mchangeslist.isempty());
 }

解析:isrunning方法其实就是根据动作列表是否为空来返回结果
还有其他一些函数可以自己阅读源代码。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。