Android动画全解析(四)

来源:互联网 发布:管家婆打印软件 编辑:程序博客网 时间:2024/05/18 03:52

     上一节课,我们用一个将Animation动画综合在一起的例子分析了一下Animation动画框架的实现,这节课也是我们Android动画全解析的最后一课了,讲完这节课,动画分析的章节就完全结束了,下面我们还要继续研究View的实现,我们会以ListView、ViewPager等系统组件为原型,展开我们View全解析的课程。

     好了,这节课我们使用的例子是从github上找到的一个例子,写的也是非常好,从这个例子的效果就很明显可以看出Animator框架是比Animation框架更细致的,Animator能实现对动画细节更好的控制,甚至有些绚丽的动画使用Animator能够实现,而使用Animation还无法实现。那么我们就来看一下Animator到底是如何实现一个动画的,这节课我们使用的Demo是,源码下载地址:

     animator动画material-ripple

     我们先来看一下这个动画的效果,点击Overlay with hover按钮,会产生一个类似涟漪的效果,放开之后,涟漪不断扩大,最后消失,下面的Ripples static init按钮的实现原理是一样的,我们就不重复了,只分析一下Overlay with hover的涟漪效果的实现过程就可以了。这个动画的触发是通过onTouch事件来触发的,当我们按下按钮时,会调用MaterialRippleLayout类的onTouchEvent方法,我们来看一下它的实现:

      这里需要说一下,MaterialRippleLayout类是别人封装好的一个view,是继承FrameLayout,Overlay with hover文字是一个Button,是添加到MaterialRippleLayout当中的,这里有父布局,所以重写了一下onInterceptTouchEvent方法,将事件截获下来,因为后边的动画效果就是针对MaterialRippleLayout产生的,如果不截获触摸事件,onInterceptTouchEvent方法默认是返回false的,那么默认就分发到Button上去了,那么onTouchEvent事件无法执行,动画效果也就产生不了了,关于onInterceptTouchEvent方法,可以参考上一篇博客:

     Android-onInterceptTouchEvent()和onTouchEvent()总结

     在onTouchEvent方法中,我们的触摸会产生三种效果:开始按下MotionEvent.ACTION_DOWN、移动手指或者不动MotionEvent.ACTION_MOVE、松开手指MotionEvent.ACTION_UP,一定要搞清楚,MotionEvent.ACTION_DOWN和MotionEvent.ACTION_UP只会执行一次,哪怕你手指一直按着,DOWN事件也只产生一次。那么在当前动画中,分别使用PressedEvent、PerformClickEvent来产生按下的触摸动画和Click点一直的动画,接收到ACTION_DOWN事件,就构造一个PressedEvent对象,然后直接调用它的run方法,run方法当中是调用startHover()为生产动画的,startHover()方法的实现如下:

     我们的重点目标来了,先调用ObjectAnimator.ofFloat构建好hoverAnimator对象,然后给它设置插值器,最后就调用start开启动画了。那么我们一步步来看一下执行过程。

     1、ObjectAnimator.ofFloat(this, radiusProperty, rippleDiameter, radius).setDuration(HOVER_DURATION)

     先来看一下传入的几个参数,第一个this就代表当前的MaterialRippleLayout了,radiusProperty是作者自己定义的一个Property<MaterialRippleLayout, Float>对象,rippleDiameter和radius是两个整数。我们具体来看一下radiusProperty,它是一个Property对象,Property的类代码在frameworks/base/core/java/android/util包下面:

     接下来,我们看一下ObjectAnimator.ofFloat方法到底作了些什么,这个方法的逻辑也比较复杂,我们调用这句代码后,系统层为我们作了大量的工作,来解析我们的动画属性,这样才能保证后期动画的正常执行。
     首先创建一个ObjectAnimator对象,把我们传进来的前两个参数保存起来,因为是才构建对象,所以成员变量mValues、mProperty都为空,所以setProperty方法也就是简单的将我们传进来的参数property赋值给成员变量mProperty。接下来我们看一下anim.setFloatValues(values)的执行逻辑:
     mValues因为未赋值,所以为空,而mProperty是在上一步赋值的,所以不为空,那么就继续调用setValues(PropertyValuesHolder.ofFloat(mProperty, values))处理。这句代码我们分两步分析,先看一下PropertyValuesHolder.ofFloat(mProperty, values),它是将我们传入的可变参数values解析出来并返回一个PropertyValuesHolder对象,ofFloat方法直接new构造一个FloatPropertyValuesHolder对象,构造FloatPropertyValuesHolder时先调用父类的构造函数,然后调用setFloatValues(values)保存值,setFloatValues方法中还是先调父类方法,我们来看一下父类的setFloatValues方法当中调用的mKeyframes = KeyframeSet.ofFloat(values)这句代码的逻辑,因为我们传进来的可变参数就是在这里解析完成的。
     我们传进来的可变参数长度为2,所以这里构建一个长度为2的FloatKeyframe keyframes[]数组,从这个方法的逻辑可以看出来,如果我们传入的可变参数的长度为1,那么系统还是会构建一个长度为2的数组,第一个元素保存的值为0,第二个元素才保存我们传入的值。构建好FloatKeyframe数组之后,就用它来创建一个FloatKeyframeSet,在FloatKeyframeSet的构造方法当中就是简单的调用了KeyframeSet的构造方法,KeyframeSet的构造方法的实现如下:
     在这里呢,根据我们传入的Keyframe... keyframes来初始化成员变量,将我们的第一帧和最后一帧保存下来,最后一句用来保存插值器,但是当因为前面的过程我们还未调协插值器,所以这里获取到的还是空的。到这里呢,在ObjectAnimator类的setFloatValues方法中的PropertyValuesHolder.ofFloat(mProperty, values)这句才执行完成。
     我们分析的过程当中,时刻都要回头看一下,看看我们当前到哪一步了,否则,很快就会导致大脑堆栈崩溃!我们来回顾一下这个方法都干了什么,它是使用我们传入的values可变参数创建了一个FloatPropertyValuesHolder对象,赋值了FloatProperty mFloatProperty、Keyframes.FloatKeyframes mFloatKeyframes成员变量,在mFloatKeyframes成员变量当中共有两帧,每一帧实际是一个FloatKeyframe对象,它有两个成员变量:mFraction、mValue,其中mFraction是从父类Keyframe继承下来的,是一个整数,表示第几帧,mValue指当前帧对应的值,最终创建好的结果保存在FloatPropertyValuesHolder类的成员变量mFloatKeyframes当中。
     好了,我们继续看一下,setValues(PropertyValuesHolder.ofFloat(mProperty, values))的实现,这个方法是调用父类ValueAnimator的:
     这个方法就是初始化成员变量mValues和mValuesMap,我们传进来的可变参数PropertyValuesHolder... values的长度是1,因为两个float值是通过keyframe保存在一个FloatPropertyValuesHolder对象的mFloatKeyframes当中了。好了,到这呢,相当于我们在应用层调用的ObjectAnimator.ofFloat(this, radiusProperty, rippleDiameter, radius)这句代码才执行完。
     我们接着来看一下setDuration(HOVER_DURATION),它是直接调用父类ValueAnimator的setDuration方法来处理的,setDuration方法当中就是给mUnscaledDuration赋值,然后调用一下updateScaledDuration(),这个方法是干什么的呢?就是在手机的开发者选项当中有三个窗口动画缩放、过渡动画缩放、动画程序时长缩放的开关,可以动态调整动画的执行时长,比如我们传入的动画时长为2秒,设置的sDurationScale为5x,那么相当于原来2秒的动画就会放大为10秒,这样方便我们调试,当然一般在我们的应用中,sDurationScale是取默认值1x的。
     2、hoverAnimator.setInterpolator(new LinearInterpolator())
     这一步的执行应该比较简单,就是给我们的动画设置插值器。从前几节课当中,可以了解到,插值器是动画执行必须的,应用层如果不设置系统也会默认给我们一个new AccelerateDecelerateInterpolator();而如果我们调用setInterpolator时传入的参数为空,那么系统就会默认给我们设置一个LinearInterpolator。
     3、hoverAnimator.start()
     这一句就是启动我们的动画了。我们来看一下ObjectAnimator类的start方法:
     sAnimationHandler是父类定义的成员变量,也是线程单例的。这个方法就是将AnimationHandler对象中的mAnimations、mPendingAnimations、mDelayedAnims中所有与当前ObjectAnimator目标相同,并且属性相同getPropertyName()的所有元素全部调用anim.cancel()取消掉。判断是否相同是通过hasSameTargetAndProperties(anim)方法来完成的。取消完成后,调用父类的start方法。我们来看一下ValueAnimator类的start方法:
     这个方法将当前的Animator对象添加到mPendingAnimations等待队列中,然后判断是否需要延迟,如果不需要则调用notifyStartListeners()通知所有的监听器,动画马上开始了,最后就调用animationHandler.start()开始处理动画。AnimationHandler类的start方法是直接调用scheduleAnimation()来进行处理的。在scheduleAnimation()方法中就是将成员变量mAnimate添加到Choreographer的动画队列CALLBACK_ANIMATION当中,这个细节可以参考:Android Choreographer源码分析。那么当Vsync信号到来后,就会处理队列中的callback,也就是回调mAnimate的run方法,在mAnimate的run方法当中又会调用doAnimationFrame来处理,我们来看一下doAnimationFrame方法的实现:
     先来看第一个while,将等待队列mPendingAnimations中的动画取出来,判断是否需要延迟,如果需要延迟,则将它加入到mDelayedAnims队列中;如果不需要延迟,则直接调用anim.startAnimation(this)处理,startAnimation方法中先调用initAnimation()将动画对象初始化,完成后将它加入到AnimationHandler的mAnimations队列当中。接下来再看第一个for循环,它是通过调用anim.delayedAnimationFrame(frameTime)来判断当前动画延迟时间是否到了,如果延迟时间到了,则将它添加到就绪的队列mReadyAnims中。接下来再看看第二个for循环,它是调用anim.startAnimation(this)将准备就绪的队列mReadyAnims中的动画元素取出来进行处理,初始化完成将它添加到mAnimations队列中。再来看最后一个for循环,将所有mAnimations队列中的元素复制到mTmpAnimations暂存队列中,然后逐个取出来,调用anim.doAnimationFrame(frameTime)真正的开始执行动画。然后再向mChoreographer的Choreographer.CALLBACK_COMMIT队列中添加一个mCommit,来更新每个Animator的mStartTime,最后判断一下mAnimations队列或者mDelayedAnims任何一个不为空,则继续调用scheduleAnimation()向mChoreographer的动画队列中添加callback。这样随着Vsync信号在Choreographer中的接收,动画的数据也就一帧一帧的绘制出来了。我们还要分析一下真正执行动画的doAnimationFrame方法,注意这里是指ValueAnimator类的doAnimationFrame方法,而不是AnimationHandler类的方法。它的实现如下:
     这个方法就是根据当前动画的不同状态更新mStartTime、mPauseTime的值,然后调用animationFrame(currentTime)继续处理,animationFrame方法的实现如下:

     注意这里的两个case之间没有break,相当于两个case逻辑是相同的。从第一句fraction的计算方式中可以看出来,它和我们之前分析的Animation动画中的归一化的思想是相同的,当前时间减去起始时间除以动画总时长,也就是当前执行到的时间百分比,计算好fraction后,最后调用animateValue(fraction)进一步处理。注意,因为我们的动画对象是ObjectAnimator,而ObjectAnimator是有重写animateValue方法的,所以这里会回调ObjectAnimator的方法,它当中先回调父类,然后再处理自己的逻辑。我们来看一下父类ValueAnimator类的animateValue方法:

     这里就会以当前动画的百分比为参数,调用插值器的getInterpolation(fraction)去取回当前的加速度,然后更新每一帧的mAnimatedValue值,最后调用所有mUpdateListeners监听器通知动画更新。mValues[i].calculateValue(fraction)这句代码最终就和我们开始设置的两个值关联上了。calculateValue方法中就是通过调用mKeyframes.getValue(fraction)来进行计算的,这里的mKeyframes就是最开始构建好的FloatKeyframeSet了,它的getValue方法是调用getFloatValue来处理的:

     这个方法的逻辑分为四种场景来处理,第一种是常用的,也就是一般的可变参数长度为2,表示一个开始,一个结束;第二种情况就是当前是动画开始时,fraction为0;第三种场景就是动画结,fraction为1;第四种场景就是可变参数长度大于2,而且当前既不是开始,也不是结束。其他三种情况我们就不分析了,读者可以自己研究一下,我们就来看一下当前长度为2的情况。我们没有给成员变量mEvaluator赋值,因此它是空的,那么计算结果就是return firstValue + fraction * deltaValue,从这里可以看到,如果deltaValue为正值,则返回值越来越大,也就是我们画的圆越来越大;如果它为负,那么圆就会越来越小。计算完成,然后更新mAnimatedValue的值。我们再次回到ObjectAnimator类的animateValue方法当中,父类的逻辑执行完了,继续处理自己的逻辑,调用mValues[i].setAnimatedValue(target)进行处理,mValues[i]取出来的是我们开始构造ObjectAnimator对象时设置好的PropertyValuesHolder对象,我们来看一下PropertyValuesHolder类的setAnimatedValue方法:

     这里的参数target就是我们一开始时在应用层传入的第一个参数this,也就是指MaterialRippleLayout对象了。mProperty也是应用层传入的radiusProperty对象,因为我们有重写它的set方法,所以这里就回到我们应用层的set方法当中了,而调用回来的值value是通过getAnimatedValue()获取的,也就是我们上一步calculateValue计算完成赋值给成员变量mAnimatedValue的值。mProperty是一个Property对象,它的默认的set方法是直接抛出了个异常,所以如果我们要自己定义动画的Property对象,就必须重写set方法,否则执行到这里就会直接崩溃。因为我们没有设置mSetter,所以这里为空,这个方法也就执行完了。

     那么一个完整的帧动画的逻辑我们就分析完了,但是到这里大家可以看到,所有的逻辑都只是在改变Animator的属性,界面元素View的属性没有变化的话,根本是没有动画效果的,那为什么我们能看到圆形不断变大呢?答案就在我们重写的set方法当中了,set方法当中调用invalidate(),不断的使我们的界面失效,View系统在绘制的过程中就会回调onDraw,在这里我们调用canvas.drawCircle(currentCoords.x, currentCoords.y, radius, paint)不断的用设置好的radius画圆,最终的效果也就出来了。那么系统回调我们时有两处:一处是set方法;一处是onAnimationUpdate动画更新,这两处只要我们使界面失效都可以达到我们的目的。

     好了,那么这节课也就讲完了,demo中其他的动画效果我们就不分析了,有兴趣的卓友可以自己分析一下,我们来总结一下:

     1:从整个过程当中可以感觉到Animator框架比Animation更复杂,它的功能也更强大,它的动画主体传入的一个Object,那么就是说任何对象都可以使用动画。

     2:我们的Demo中使用ofFloat方法构建ObjectAnimator对象时只传入了一个radiusProperty属性,其实我们还可以定义更多的属性去实现更复杂的动画。

     3:Animator动画框架实现的全部是数据属性的改变,而在执行不断的绘制,需要应用层自己发起,入口有两处,一处是在set方法回调时,一处是在onAnimationUpdate回调时。

     4:Animator动画的数据组成中,每一个属性Property维度都对应有一个PropertyValuesHolder对象,多个PropertyValuesHolder是通过成员变量mValues数组组织在一起的,mProperty就是它的核心成员变量;每一个PropertyValuesHolder在被系统回调时,通过calculateValue方法不断的计算当前的动画属性值,然后更新成员变量mAnimatedValue,把数据准备好,应用层在绘制时就可以获取到当前动画的对应属性值。

0 0
原创粉丝点击