《Android内核剖析》读书笔记 第13章 View工作原理【动画概述】

从动画效果的影响范围角度看,View系统中的动画可以分为三类:

  1. 窗口动画:窗口对应的动画,其作用对象是Surface;窗口可以是Activity对应的窗口,也可以是对话框这样的子窗口,当然还可以是直接通过WindowManager.addView()添加的任意窗口;
  2. 布局动画:指ViewGroup容器对象包含的动画,该动画在ViewGroup对象中定义,但实际上影响的却是该容器内的子视图,其本质过程是根据布局动画为每个子视图设置不同的动画,从而使得整体上看起来像是作用于整个容器;
  3. 视图动画:作用于某个具体View对象的动画;

动画类型与设计思路

动画使用Animation类来表示,该类中会保存动画的起始时间,并在动画开始后,Animation会在不同的时间返回不同的动画参数,由Transformation类来保存这些动画参数信息(内含mMatrix、mAlpha两个变量),并将这些参数应用到对应的动画主体(对于窗口动画,主体就是Surface;对于布局动画和视图动画,主体都是View)中,然后invalidate相关的动画主体,在下次重绘时就使用新的参数从而显示新的画面,这样在整个动画的时间间隔内,就像我们看电影一样产生了连续的动画效果,但其本质仍然是由一个个静态的画面所组成,只是当每秒超过60帧人眼就基本上不会感觉到卡顿或不连续了;

View系统目前支持五种基本动画:平移translate、缩放scale、旋转rotate、扭曲skew、透明阿尔法alpha;

除了扭曲Skew之外,其余四种动画都可以在xml中用对应标签来申明,代码层面就是分别对应Animation的几个子类实现,比如TranslateAnimation/ScaleAnimation/RotateAnimation/AlphaAnimation;


动画的组合使用

在我们开发过程中可能经常会组合使用动画,比如先平移、再缩放;这就需要一个统一的模型来处理这5种基本动画,其实对于每种基本动画都对应一个基本的数学变换公式,而矩阵运算更是可以方便的把这几种变换的公式都统一起来,在进行具体变换时,只需要改变矩阵中的某行某列的值即可,android系统中采用3X3矩阵(原则上2X3的矩阵已能够满足这几种动画参数的保存,但无法对2个2X3的矩阵进行组合运算,所以用3X3矩阵)来保存这5种动画的相关参数,代码层面就是Matrix类,该类中封装了除alpha之外的其余4种动画参数、以及相关操作api;主要分为三类:

  • setXXX:该系列方法用来直接设置相关动画参数,调用该系列方法将直接清除之前设置过的相关参数;
    比如代码为m.preTranslate(..); m.setRotate(.); 这样set之前的preTranslate将会被清除,相当于该句代码不奇效;
  • preXXX:该系列方法用来设置先进行某个动画,每次pre方法调用都是基于前一次pre方法的;
    比如代码为m.preTranslate(..); m.preScale(.); 这样表示先执行缩放、再平移;
  • postXXX:该系列方法用来设置后进行某个动画,每次post方法调用同样是基于前一次post方法的,并且post方法永远都在pre方法之后,与代码顺序无关;
    比如代码为m.postTranslate(..); m.preScale(.); m.postRotate(.); 这样表示先缩放、再平移、最后旋转;
    再比如m.postTranslate(..); m.setSkew(.); m.postRotate(.); m.preScale(.); 这样表示先缩放、再扭曲、最后旋转,平移因为在set之前所以被清除;

要实现组合动画的效果,可以继承Animation实现自定义的类,核心是重载void applyTransformation(float interpolatedTime, Transformation t)方法;

注意:改变的动画参数实际上是保存在参数t中,t会在类初始化时自动new一个空的Transformation对象,之后调用t.getMatrix()获取矩阵,并进行相关设值;

在代码层面还需认识Interpolator类,他是一个插值器,他的作用是可以让开发者自定义动画随时间的变化曲线,比如常见的有做匀速运动、直线加速运动、曲线加速、先加速后减速等,其实这是一个数学模型问题,开发者可以实现任意的插值器;


视图动画的使用

  1. 可以在res/drawable目录中新建xml文件,和我们新建shape/selector效果图片集一样,内部采取animation_list标签来设定各个时刻需要显示的图片,其本质上是创建了一个AnimationDrawable,可以简单理解为具有动画效果的GIF图片,也有叫逐帧动画,使用Drawable的地方均可以拥有该动画效果,动画的开始与结束可调用AnimationDrawable.start()/stop()方法;
  2. 可以在res/anim目录下新建xml文件,内部采用animationSet/scale/rotate/translate/alpha标签来设置这几种简单的动画;
    之后利用AnimationUtil.loadAnimation(.)将其加载转换为Animation对象,并调用View.setAnimation(.);
  3. 实现自定义的Animation子类,直接调用View.setAnimation(.);完成动画设定,通过Animation.start()/cancel()控制动画开始和结束;
    视图动画在执行时,实际上改变的是View的位置,并没有改变View自身的属性,比如你对一个Button进行缩放操作,在动画执行过程中表面看Button尺寸发生了变化,但实际上Button的点击响应区域并没有变化;

布局动画的使用

  1. 可以在res/anim目录下新建xml文件,内部采用layoutAnimation标签来定义布局动画,代码层面就对应于 LayoutAnimationController类,内部再通过android:animation标签引用具体的视图动画;之后在layout.xml中对需要使用动画的布局容器(比如ListView/LinearLayout)通过标签layoutAnimation实现对刚才定义的布局动画的引用;布局容器初始化时会自动解析相关动画设置,并在执行dispatchDraw()方法中将布局动画转换为对应子视图的视图动画,也通过ViewGroup.startLayoutAnimation()启动动画;
    疑问:布局动画可以手工停止吗?有兴趣的同学自己去找答案;
  2. 实现自定义 LayoutAnimationController子类,关键的是要重载 long getDelayForView(View view)方法;用来根据子视图在父视图中的index返回具体的delay毫秒数;可直接调用ViewGroup.setLayoutAnimation(.);完成动画设定,通过LayoutAnimationController.start()启动动画;

窗口动画的使用

窗口动画的具体实现是在WindowManagerService.applyAnimation()方法中,这里将主要介绍Activity对应窗口动画的使用,即Activity间切换时的动画;

  1. 与视图动画一样在res/anim目录下新建xml动画文件分别用于窗口进入、关闭时的效果,之后在执行Activity.startActivity(Intent intent);之后调用Activity.overridePendingTransition(..)完成对老窗口关闭、新窗口进入动画效果的设定;
  2. 通过 ActivityOptions.makeXXX(…)系列构造方法返回具体实例,并通过 ActivityOptions.toBundle()将其转换为Bundle对象,最后调用Activity.startActivity(Intent intent, Bundle bundle);启动Activity;
    注意:和上一种方式的区别是启动时多了一个Bundle参数,该参数里面就存放了相关动画参数;
    ActivityOptions.makeXXX()系列方法目前提供了三种类型:
    1. makeCustomAnimation:加载自定义动画,和第一种加载方式一致;
    2. makeScaleUpAnimation:加载从指定点然后缩放至最终显示窗口的动画;
    3. makeThumbnailScaleXXXAnimation:加载从显示窗口缩小到无、或从无到指定窗口的动画;

属性动画的出现

属性动画是在3.0中出现的,相关的类位于android.animation里面,而老式动画(以上提到的几种都是)支持则位于android.view.animation里面;

与视图动画比较而言,属性动画更具灵活性,他不仅可以用于视图View,也可以用于任何对象;动画期间他改变的是View的具体属性,同样是Button按钮缩放操作,动画期间该Button的具体大小和位置就已经发生了变化,有效点击响应区域也已随属性变化而变化;常见使用方式如下:

  1. 最常见的方式是使用 ObjectAnimator.ofXXX(Object target, String propertyName, …)方法获得Animator实例,并调用start()方法启动动画;
    其中target为待执行动画的主体,比如某个Button对象,propertyName为待改变的属性名,比如alpha;若要对多个属性进行变化,则可以使用 PropertyValuesHolder来保存各个属性的相关变化信息;
    注意:要使用ObjectAnimator必须要求待改变的属性具有set/get方法;
  2. 使用ValueAnimator.ofXXX(..)方法获得Animator实例,并调用 addUpdateListener(AnimatorUpdateListener listener)完成属性变化Listener的注册,其中参数listener为AnimatorUpdateListener接口的实现类;之后调用start()方法启动动画;
    该方式具有非常好的扩展性和通用性,动画的执行和过程中要做的具体属性值改变完全解耦了,开发者可以在AnimatorUpdateListener接口实现里做任何想做的事情;
  3. 可以在res/anim目录下新建xml文件,内部采用set/animator/objectAnimator等标签来定义动画信息,代码层面就对应于AnimatorSet/ValueAnimator/ObjectAnimator,之后再通过AnimatorInflater.loadAnimator(..)来完成对xml描述的加载进而获得Animator对象,通过animation.setTarget(.)设定目标主体,并用start()启动动画; 
  4. 初始化LayoutTransition,并通过LayoutTransition.setAnimator(int transitionType, Animator animator)完成对具体场景下动画的指定,之后调用ViewGroup.setLayoutTransition(.)完成对布局容器添加动画支持,从而完成当ViewGroup中子视图可视状态发生变化时的动画应用 
     transitionType的具体场景包括:
    • APPEARING:当一个元素变为Visible时对其应用的动画
    • DISAPPEARING:当一个元素变为InVisible时对其应用的动画
    • CHANGE_APPEARING:当一个元素变为Visible时,因系统要重新布局有一些元素需要移动,这些要移动的元素应用的动画
    • CHANGE_DISAPPEARING:当一个元素变为Gone时,因系统要重新布局有一些元素需要移动,这些要移动的元素应用的动画

你可能感兴趣的:(Android)