Android RecyclerView的Item自定义动画及DefaultItemAnimator源码分析
这是关于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方法其实就是根据动作列表是否为空来返回结果
还有其他一些函数可以自己阅读源代码。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: Java中使用JDBC操作数据库简单实例
下一篇: Java中锁的实现和内存语义浅析