属性动画是我们在进行Android开发中经常会遇到的内容。对很多人来说可能会存在“会用但是又用不好“的感觉。本文根据动画常见的使用场景,详细总结一下Android中各种属性动画的区别和特点,以及制作复杂动画所用到的关键帧、插值器、估值器的原理、使用场景和方法。
另外,说到动画优化,”硬件加速”是一个绕不开的话题。本文也将对硬件加速进行介绍,同时也会说到关于“离屏缓冲”相关的概念。帮助大家更好的理解、掌握和做出高效精致的动画。
一、属性动画
1、基本原理
属性动画最基本的原理,就是通过不断改变view的属性值,在每一次重绘时,用新的属性值来绘制view。连续的变化,产生动画的效果。
比如,view的x值从0 变到200,每过16ms,x值增加1。在这段时间,view从0逐步移动到200的位置。我们就看到了平移的动画效果。
Android提供多种动画类的工具。下面将逐一详细进行介绍。
2、ViewPropertyAnimator
ViewPropertyAnimator是对view自带的属性进行变化,优点是使用简单。可以通过view直接获取这种动画类型。该动画可以实现平移、旋转、缩放、渐变四种动画。
下面实现一个简单的平移效果:
他的api主要有,读者可以自行研究:
animator.translationX(500); // 沿水平方向平移
animator.translationY(500); // 沿垂直方向平移
animator.scaleX(2); // x方向缩放
animator.scaleY(2); // y方向缩放
animator.rotation(50); // 旋转
animator.rotationX(50); // 沿x轴翻转
animator.rotationY(50); // 沿y轴翻转
animator.alpha(0.3f); // 透明度变化
animator.setDuration(2000); // 动画持续时常
animator.setStartDelay(3000); // 动画开始前奏时间
这种属性动画的缺点是,只能对view提供的属性进行设置动画。如果是自定义的属性则无能为力。比如,自己绘制一个圆,要动态改变它的半径,实现动画效果。这种要用到我们接下来要讲的ObjectAnimator。
3、ObjectAnimator
这种属性动画,可以对自定义的属性执行动画效果。
自定义一个简单view,在view中只进行绘制圆的操作。
用ObjectAnimator对圆的半径做动画效果:
ObjectAnimator通过不断调用目标属性的“set“方法,来动态改变属性值。因此,我们自定义属性做动画一定要提供正确的set方法。否则它找不到,则动画失败。
另外,在set方法里需要调用”invalidate()“方法。invalidate表示”使失效“的意思。view的绘制是非常频繁的操作。为了性能考虑,android每次绘制信号到来(就是我们常说的VSync信号),并不会整个界面,只绘制改变了的区域。调用invalidate方法,该view区域被标记为脏区域,下次绘制信号到来,才会进行重绘。
如果开发中遇到动画无效果的问题,可以先检查下这两个问题。
除了基本类型的float、int类型的属性动画,ObjectAnimator还提供对象类型的属性动画。对对象类型进行属性变化。这个点在后面估值器的内容再详细讲解。
通过ObjectAnimator可以很方便的对自定义属性实现动画效果。它可以很方便的实现简单的单一动画。但是对于复杂的协同动画,比如先平移,再旋转,再缩放。再比如,平移同时进行旋转,最后再缩放等实现起来就比较繁琐。对于这种多个动画合成一个动画效果则可以使用AnimatorSet工具。
4、AnimatorSet
AnimatorSet是将多个动画进行组合,它提供顺序播放和同时播放两种协同方式。将多个简单动画进行统一管理,合成一个较复杂动画的效果。
用AnimatorSet实现一个先将圆平移,然后将圆的半径放大的效果。
AnimatorSet两个主要用到的API:
playSequentially(transXAnimator, radiusAnimator); // 按顺序执行动画
playTogether(transXAnimator, radiusAnimator); // 多个动画同时执行
5、PropertyValuesHolder
该动画工具与AnimatorSet类似,不过不同的是,AnimatorSet是协同多个动画,而PropertyValuesHolder是对一个动画的多个属性进行合成。
比如,同时操作”x”值属性和“radius“数显,实现一个,一边移动一边变大的效果。
6、KeyFrame
动画关键帧,通过在动画过程中插入关键帧,对动画过程进行更精细的控制。它控制的是动画完成度和属性值之间的关系。
它的API, “fraction”表示动画完成度,”value”表示此时对应属性值。当完成度为 x%是,属性值变化到Y。由此可以很精细的控制动画过程中的快慢节奏。
使用keyFrame实现一个先慢后快的动画效果。插入两个关键帧:Keyframe.ofFloat(0.7f, 30f);// 动画完成度为30%时,属性值变化到30。
Keyframe fast = Keyframe.ofFloat(1f, 200f); // 动画完成时,属性值变化到200。
7、Interpolator
Interpolator叫做“插值器“。它表达的是从时间完成度到动画完成度的隐射关系。即动画时间过去t,此时动画完成y。 定一个y = f(t)的函数。
Android自带提供了多种的插值器,其中有四种是最为常见的:
AccelerateDecelerateInterpolator // 加速减速插值器,使用该插值器可以实现让动画先快后慢的动画效果。使用场景一般是在界面内移动控件。将控件从一个位置移动到另一个位置。整个过程控件可见。
AccelerateInterpolator // 加速插值器,使用该插值器实现动画过程越来越快,最后突然停住的效果。经常用在做控件的出场动画。一个控件在移出屏幕时,越来越快,到离开屏幕后戛然而止。
DecelerateInterpolator // 减速插值器,可以实现动画过程越来越慢最后停在目标位置的动画。经常用在控件的入场动画。一个控件从外部进入屏幕,速度越来越慢,然后稳稳停在目标位置。
LinearInterpolator // 匀速插值器。 这种插值器使用场景较为少见。
插值器使用方法比较简单,直接给动画设置插值器即可。提供一个“将一个控件加速移除屏幕的动画“示例。用户也可以自定义自己的插值器,读者可自行尝试。
8、TypeEvaluator
TypeEvaluator称为“估值器“,是进行属性动画的关键工具。它的作用是实时精确计算属性值。属性动画过程中所使用的变化的属性值。就是使用它产生的。这里的对应关系是动画完成度和属性值。
看一个FloatEvaluator,它是进行float类型属性动画的估值器。可以看到它的实现很简单。就是用属性终止值减去起始值,乘以动画完成度。单纯的线性关系。
上面ObjectAnimator章节的最后提到对自定义的对象类型进行属性动画。这个时候,系统自带的估值器一般是不能直接使用的。需要我们自定义估值器。
如下面动画示例。将圆的中心点封装成Point对象。用该属性做一个改变圆位置的动效。
定义一个Point类,用Point对象表示圆心:
自定义一个对Point属性做动画的估值器,这里写个简单的,取起点和终点的线性关系。然后使用该估值器做属性动画。(Point对象可以做复用优化,避免重复创建对象,这里只演示动画,不纠结性能问题。)
9、Listeners
动画的监听器,主要用来对动画过程进行监听回调。有下面几个,很容易理解,读者可以自己看下。
public void onAnimationStart(Animator animation) {}
public void onAnimationEnd(Animator animation) { }
public void onAnimationCancel(Animator animation) {}
public void onAnimationRepeat(Animator animation) {}
10. 小结
以上就是关于属性动画的内容。基本我们所见到的复杂动画,都是通过组合使用这些工具做出来的。
二、硬件加速与离屏缓冲
硬件加速和离屏缓冲是和动画比较相关的话题。这里对这两个概念的基本原理和使用场景做一些介绍。
1、硬件加速
Android图形绘制分为软件绘制和硬件绘制。软件绘制是早期的Android唯一支持的绘制方式。
软件绘制:整个界面绘制的都是基于一个bitmap, 所有的view都网它上画,父view先画,子view后画。上下层融合,最终绘制一个bitmap。它是基于cpu进行绘制,在软件层面,在跟屏幕交互之前,把所有要绘制的像素都准备好。然后再绘制到屏幕上。
硬件绘制:使用gpu进行绘制,在绘制阶段cpu将绘制方法转换成一系列gpu指令(此时并不进行绘制运算,没有生成像素信息)。在屏幕显示的时候,GPU拿着cpu的指令,直接转换为像素应用到屏幕上,显示文字,图形等元素。与cpu不同,gpu没有对绘制进行融合的操作,它每次只绘制改变了的view。
硬件绘制就是指用专用硬件(gpu)进行绘制。gpu与cpu工作相分离,能够有效提高绘制效率,并且gpu被设计出来专用于图形绘制,比cpu要高效和专业的多。使用硬件绘制能够加快绘制效率,因此也称为“硬件加速“。
不过硬件绘制存在兼容性问题。因为gpu被设计出来时就已经指定了可以支持哪些图形。有些上层的绘制api可能没有被包含进去,则无法进行硬件绘制。这种情况,系统会自动转为用软件绘制。
通过AndroidManifest.xml的application(对整个应用)或activity(对该activity)标签里通过“android:hardwareAccelerated="true"”开启硬件加速。
2、离屏缓冲
离屏缓冲是指从内存里单独分出来一个区域,向这个区域填写绘制内容,完成后,贴到view上面显示。实际应该叫做“离屏缓存”。
之前通过saveLayer的方式开启离屏缓冲。
通过一个示例简单看saveLayer使用方式:创建一个bounds大小的离屏缓冲,然后绘制,绘制完之后将其释放。
后来google建议使用hareware_layer来替代saveLayoeur的方式。
google意思大概就是,每次拉出离屏缓冲,用完再丢弃,比较浪费性能(离屏缓冲是相对开销比较大的一种机制)。通过hareware_layer可以为view设置一个离屏缓冲,不用每次用都建立临时离屏缓冲。
hareware_layer是通过setLayerType() API来设置。他的参数用三种类型:LAYER_TYPE_HARDWARE、LAYER_TYPE_SOFTWARE、LAYER_TYPE_NONE。
// 开启view离屏缓冲,用硬件绘制
setLayerType(LAYER_TYPE_HARDWARE, null);
// 开启view离屏缓冲,用软件绘制;
setLayerType(LAYER_TYPE_SOFTWARE, null);
// 关闭view离屏缓冲
setLayerType(LAYER_TYPE_NONE, null);
这种开启view级别离屏缓冲主要用两个使用的场景:
1、整体开启硬件绘制时,对该view采用软件绘制
前面说过开启硬件绘制是在AndroidManifest文件里在application或者activity标签中开启。这种开启是一种“全局生效”。如果此时开启view级别以软件方式绘制的离屏缓冲,则可以将该view绘制改为软件绘制。
如果整体没有开启硬件绘制,设置硬件绘制的离屏缓冲是无效的,会自动滑落到用软件方式绘制。
网上有一种说法setLayerType(LAYER_TYPE_HARDWARE, null)是开启view级别硬件加速,是绝对错误的。
2、开启硬件离屏缓冲优化view自带属性动画
这是官方给出的使用场景:在view自带属性动画时,开启硬件离屏缓冲,使渲染更快。
它的主要原理是,这些view自带的属性,被android做了优化,这些属性的改变,不会导致gpu操作的改变。这些改变引起屏幕的变化,gpu是采用它独特的方式,在向屏幕渲染时进行整体调整,不用进行重绘。因此,性能会更高。
使用方式是,动画对象调用withLayer()方法即可: