Android 动画 攻略

325 阅读44分钟

攻略大全

1. 粘贴攻略

本质上,一切运动的画面皆可称为动画效果。

1.1 逐帧动画(Frame Animation)

将动画拆分为帧的形式,且定义每一帧等于每一张图片,最后按序播放一组预先定义好的图片。

  • 优点:使用简单、方便
  • 缺点:容易引起 OOM,因为会使用大量 & 尺寸较大的图片资源

1.1.1 应用场景

较为复杂的个性化动画效果。

使用时一定要避免使用尺寸较大的图片,否则会引起OOM

1.2 补间动画(Tweened Animation)

通过确定开始的视图样式 & 结束的视图样式、中间动画变化过程由系统补全来确定一个动画

  1. 结束的视图样式:平移、缩放、旋转 & 透明度样式
  2. 即补间动画的动画效果就是:平移、缩放、旋转 & 透明度动画

image.png

  • 优:使用简单、方便 = 已封装好基础动画效果
  • 缺:仅控制整体实体效果,无法控制属性

1.2.1 应用场景

a. 标准的动画效果

  • 补间动画常用于视图View的一些标准动画效果:平移、旋转、缩放 & 透明度;
  • 除了常规的动画使用,补间动画还有一些特殊的应用场景。

b. 特殊的应用场景

  • Activity 的切换效果(淡入淡出、左右滑动等)
  • Fragement 的切换效果(淡入淡出、左右滑动等)
  • 视图组(ViewGroup)中子元素的出场效果(淡入淡出、左右滑动等)

1.3 属性动画

属性动画(Property Animation)是在 Android 3.0API 11)后才提供的一种全新动画模式。

image.png

image.png

image.png

1.3.1 应用场景

属性相关、更加复杂的动画效果。

如改变对象的颜色属性动画效果

1.3.2 动画对比

image.png

1.3.4 使用问题 & 建议

image.png

1.4 插值器 & 估值器

image.png

1.4.1 插值器

  • 定义:Android实现动画效果中的一个辅助接口
  • 作用:设置 属性值 从初始值过渡到结束值 的变化规律
  1. 如匀速、加速 & 减速 等等
  2. 即确定了 动画效果变化的模式,如匀速变化、加速变化 等等

1.4.2 估值器

  • 定义:一个接口
  • 作用:设置 属性值 从初始值过渡到结束值 的变化具体数值
  1. 插值器(Interpolator)决定 值 的变化规律(匀速、加速blabla),即决定的是变化趋势;而接下来的具体变化数值则交给估值器
  2. 属性动画特有的属性

协助插值器 实现非线性运动的动画效果

非线性运动:动画改变的速率不是一成不变的,如加速 & 减速运动都属于非线性运动

1.5 Gif动画

采用LZW压缩算法进行编码,是一种无损的基于索引色的图片格式。由于采用了无损压缩,相比古老的bmp格式,尺寸较小,而且支持透明(注意:它不是 alpha 通道透明度,不能支持半透明效果)和动画。缺点是由于gif只存储8位索引(也就是最多能表达2^8=256种颜色),色彩复杂、细节丰富的图片不适合保存为gif格式。色彩简单的logo、icon、线框图适合采用gif格式。

1.6 Webp动画

WebP图片是一种新的图像格式,由Google开发。与png、jpg相比,相同的视觉体验下,WebP图像的尺寸缩小了大约30%。另外,WebP图像格式还支持有损压缩、无损压缩、透明和动画。

1.7 SVGA动画

SVG是一种用XML定义的语言,用来描述二维矢量及矢量/栅格图形。

SVG提供了3种类型的图形对象:矢量图形(vectorgraphicshape例如:由直线和曲线组成的路径)、图像(image)、文本(text)。

图形对象还可进行分组、添加样式、变换、组合等操作,特征集包括嵌套变换(nestedtransformations)、剪切路径(clippingpaths)、alpha蒙板(alphamasks)、滤镜效果(filtereffects)、模板对象(templateobjects)和其它扩展(extensibility)。

SVG图形是可交互的和动态的,可以在SVG文件中嵌入动画元素或通过脚本来定义动画。

它提供了目前网络流行的PNGJPEG格式无法具备的优势:可以任意放大图形显示,但绝不会以牺牲图像质量为代价;可在SVG图像中保留可编辑和可搜寻的状态;平均来讲,SVG文件比JPEGPNG格式的文件要小很多,因而下载也很快。

  • SVG 指可伸缩矢量图形 (Scalable Vector Graphics)

  • SVG 用来定义用于网络的基于矢量的图形

  • SVG 使用 XML 格式定义图形

  • SVG 图像在放大或改变尺寸的情况下其图形质量不会有所损失

  • SVG 是万维网联盟的标准

  • SVG 与诸如 DOM 和 XSL 之类的 W3C 标准是一个整体

SVGA,就是Scalable Vector Graphics Animetion,即可伸缩矢量图形动画,就是一种2d位图动画的储存格式。以很小的容量播放一个带通道的效果,很好的替代png序列(文件容量大)、gif文件格式(不支持alpha透明通道进而实现半透明效果、色域低)。

绘制矢量图形也蛮占性能,目前更多使用提前做好的位图导入AE制作动画。

带来的直观好处:一个10几MB大的序列帧效果,用svga格式可以只要几百K甚至几十K。设计师所制作的效果,就是用户在版本中的完整效果,保证效果也让内存不会爆掉。

SVGA 是一种跨平台的开源动画格式,同时兼容 iOS / Android / Web。SVGA 除了使用简单,性能卓越,同时让动画开发分工明确,各自专注各自的领域,大大减少动画交互的沟通成本,提升开发效率。动画设计师专注动画设计,通过工具输出svga动画文件,提供给开发工程师在集成 svga player 之后直接使用。

1.8 Lottie动画

Lottie 是 Airbnb 开源的一套跨平台的完整的动画效果解决方案,设计师可以使用 Adobe After Effects 设计出漂亮的动画之后,使用 Lottic 提供的 Bodymovin 插件将设计好的动画导出成 JSON 格式,就可以直接运用在 iOSAndroidWebReact Native之上,无需其他额外操作。

2. 造火箭攻略

2.1 直播App中Android酷炫礼物动画实现方案(上篇)

2.2 直播App中Android酷炫礼物动画实现方案(下篇)

3. 拧螺丝攻略

3.1 逐帧动画

步骤1:将动画资源(即每张图片资源)放到 drawable文件夹里

  • 技巧:找到自己需要的gif动画,用gif分解软件(如 GifSplitter)将 gif 分解成一张张图片即可。

步骤2:设置、启动动画

  • 分两种方式:xml跟java。

  • 方式1:xml

     // 1. 在 res/drawable的文件夹里创建动画效果.xml文件 - knight_attack.xml
    <?xml version="1.0" encoding="utf-8"?>
     <animation-list
         xmlns:android="http://schemas.android.com/apk/res/android"
         // 设置是否只播放一次,默认为false
         android:oneshot="true">

         // item = 动画图片资源;duration = 设置一帧持续时间(ms)
         <item android:drawable="@drawable/a0" android:duration="100"/>
         <item android:drawable="@drawable/a1" android:duration="100"/>
         <item android:drawable="@drawable/a2" android:duration="100"/>
         <item android:drawable="@drawable/a3" android:duration="100"/>
         <item android:drawable="@drawable/a4" android:duration="100"/>
         <item android:drawable="@drawable/a5" android:duration="100"/>
         <item android:drawable="@drawable/a6" android:duration="100"/>
         <item android:drawable="@drawable/a7" android:duration="100"/>
         <item android:drawable="@drawable/a8" android:duration="100"/>
         <item android:drawable="@drawable/a9" android:duration="100"/>
         <item android:drawable="@drawable/a10" android:duration="100"/>
         <item android:drawable="@drawable/a11" android:duration="100"/>
         <item android:drawable="@drawable/a12" android:duration="100"/>
         <item android:drawable="@drawable/a13" android:duration="100"/>
         <item android:drawable="@drawable/a14" android:duration="100"/>
         <item android:drawable="@drawable/a15" android:duration="100"/>
         <item android:drawable="@drawable/a16" android:duration="100"/>
         <item android:drawable="@drawable/a17" android:duration="100"/>
         <item android:drawable="@drawable/a18" android:duration="100"/>
         <item android:drawable="@drawable/a19" android:duration="100"/>
         <item android:drawable="@drawable/a20" android:duration="100"/>
         <item android:drawable="@drawable/a21" android:duration="100"/>
         <item android:drawable="@drawable/a22" android:duration="100"/>
         <item android:drawable="@drawable/a23" android:duration="100"/>
         <item android:drawable="@drawable/a24" android:duration="100"/>
         <item android:drawable="@drawable/a25" android:duration="100"/>
     </animation-list>

     // 2. 载入 & 启动动画
     public class FrameActivity extends AppCompatActivity {
         private Button btn_startFrame,btn_stopFrame;
         private ImageView iv;
         private AnimationDrawable animationDrawable;

         iv = (ImageView) findViewById(R.id.iv);
         btn_startFrame = (Button) findViewById(R.id.btn_startFrame);
         btn_stopFrame = (Button) findViewById(R.id.btn_stopFrame);

         // 载入动画
         btn_startFrame.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {

                 // 1. 设置动画
                 // 2. 获取动画对象
                 animationDrawable = (AnimationDrawable) iv.getDrawable();
                 // 3. 启动动画
                 animationDrawable.start();

             }
         });

         // 停止动画
         btn_stopFrame.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {

                 // 1. 设置动画
                 // 2. 获取动画对象
                 animationDrawable = (AnimationDrawable) iv.getDrawable();
                 // 3. 暂停动画
                 animationDrawable.stop();
             }
         });

     }
 }
  • 方式2:java
     // 直接从drawable文件夹获取动画资源(图片)
     animationDrawable = new AnimationDrawable();
     for (int i = 0; i <= 25; i++) {
     int id = getResources().getIdentifier("a" + i, "drawable", getPackageName());
     Drawable drawable = getResources().getDrawable(id);
     animationDrawable.addFrame(drawable, 100);
     }

     // 载入动画
     btn_startFrame.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {

             // 1. 获取资源对象
             iv.setImageDrawable(animationDrawable);
             // 2. 停止动画
             // 特别注意:在动画start()之前要先stop(),不然在第一次动画之后会停在最后一帧,这样动画就只会触发一次
             animationDrawable.stop();
             // 3. 启动动画
             animationDrawable.start();
         }
     });

     // 停止动画
     btn_stopFrame.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
             // 1. 获取资源对象
             iv.setImageDrawable(animationDrawable);
             // 2. 停止动画
             animationDrawable.stop();
         }
     });
 }

3.2 补间动画

  • 补间动画的使用方式分为两种:在XML 代码 / Java 代码里设置
  1. 前者优点:动画描述的可读性更好
  2. 后者优点:动画效果可动态创建

3.2.1 平移动画(Translate)

/*
 * 设置方式1:xml
 */
 // 步骤1:在 res/anim的文件夹里创建动画效果.xml文件
 // 此处路径为res/anim/view_animation.xml

 // 步骤2:根据不同动画效果的语法设置不同动画参数-view_animation.xml
   <?xml version="1.0" encoding="utf-8"?>
   
   <translate xmlns:android="http://schemas.android.com/apk/res/android"
      // 采用<translate /> 标签表示平移动画
      // 以下参数是4种动画效果的公共属性,即都有的属性
      android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果
      android:startOffset ="1000" // 动画延迟开始时间(ms)
      android:fillBefore = “true” // 动画播放完后,视图是否会停留在动画开始的状态,默认为true
      android:fillAfter = “false” // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
      android:fillEnabled= “true” // 是否应用fillBefore值,对fillAfter值无影响,默认为true
      android:repeatMode= “restart” // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart|
      android:repeatCount = “0” // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
      android:interpolator = @[package:]anim/interpolator_resource // 插值器,即影响动画的播放速度,下面会详细讲
      
      // 以下参数是平移动画特有的属性
      android:fromXDelta="0" // 视图在水平方向x 移动的起始值
      android:toXDelta="500" // 视图在水平方向x 移动的结束值
      android:fromYDelta="0" // 视图在竖直方向y 移动的起始值
      android:toYDelta="500" // 视图在竖直方向y 移动的结束值
      /> 

  // 步骤3:在Java代码中创建Animation对象并播放动画
  // 1. 创建需要设置动画的 视图View
  Button mButton = (Button) findViewById(R.id.Button); 
  // 2. 创建动画对象并传入设置的动画效果xml文件
  Animation translateAnimation = AnimationUtils.loadAnimation(this, R.anim.view_animation);
  // 3. 播放动画
  mButton.startAnimation(translateAnimation);

/*
 * 设置方式2:Java
 */
 // 步骤1:创建需要设置动画的视图View
 Button mButton = (Button) findViewById(R.id.Button);
 // 步骤2:创建平移动画的对象
 // 平移动画对应的Animation子类为TranslateAnimation
 Animation translateAnimation = new TranslateAnimation(05000500);
 // 参数说明
 // fromXDelta :视图在水平方向x 移动的起始值
 // toXDelta :视图在水平方向x 移动的结束值
 // fromYDelta :视图在竖直方向y 移动的起始值
 // toYDelta:视图在竖直方向y 移动的结束值

 // 步骤3:属性设置:方法名是在其属性前加“set”,如设置时长setDuration()
 translateAnimation.setDuration(3000);

 // 步骤4:播放动画
 mButton.startAnimation(translateAnimation);

3.2.2 缩放动画(Scale)

缩放动画有一个“缩放中心”的概念,说明如下:

image.png

/*
 * 设置方式1:xml
 */
 // 步骤1:在 res/anim的文件夹里创建动画效果.xml文件
 // 此处路径为res/anim/view_animation.xml

 // 步骤2:根据不同动画效果的语法设置不同动画参数-view_animation.xml
    <?xml version="1.0" encoding="utf-8"?>
    // 采用<scale/> 标签表示是缩放动画
    <scale xmlns:android="http://schemas.android.com/apk/res/android"

    // 以下参数是4种动画效果的公共属性,即都有的属性
    android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果
    android:startOffset ="1000" // 动画延迟开始时间(ms)
    android:fillBefore = “true// 动画播放完后,视图是否会停留在动画开始的状态,默认为true
    android:fillAfter = “false// 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
    android:fillEnabled= “true// 是否应用fillBefore值,对fillAfter值无影响,默认为true
    android:repeatMode= “restart” // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart|
    android:repeatCount = “0// 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
    android:interpolator = @[package:]anim/interpolator_resource // 插值器,即影响动画的播放速度,下面会详细讲
    
    // 以下参数是缩放动画特有的属性
    android:fromXScale="0.0" 
    // 动画在水平方向X的起始缩放倍数
    // 0.0表示收缩到没有;1.0表示正常无伸缩
    // 值小于1.0表示收缩;值大于1.0表示放大

    android:toXScale="2"  //动画在水平方向X的结束缩放倍数

    android:fromYScale="0.0" //动画开始前在竖直方向Y的起始缩放倍数
    android:toYScale="2" //动画在竖直方向Y的结束缩放倍数

    android:pivotX="50%" // 缩放轴点的x坐标
    android:pivotY="50%" // 缩放轴点的y坐标
    // 轴点 = 视图缩放的中心点

    // pivotX pivotY,可取值为数字,百分比,或者百分比p
    // 设置为数字时(如50),轴点为View的左上角的原点在x方向和y方向加上50px的点。在Java代码里面设置这个参数的对应参数是Animation.ABSOLUTE。
    // 设置为百分比时(如50%),轴点为View的左上角的原点在x方向加上自身宽度50%和y方向自身高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_SELF。
    // 设置为百分比p时(如50%p),轴点为View的左上角的原点在x方向加上父控件宽度50%和y方向父控件高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_PARENT

    // 两个50%表示动画从自身中间开始,具体如下图
    /> 
  
// 步骤3:在Java代码中创建Animation对象并播放动画
  // 1. 创建需要设置动画的 视图View
  Button mButton = (Button) findViewById(R.id.Button); 
  // 2. 创建动画对象并传入设置的动画效果xml文件
  Animation scaleAnimation = AnimationUtils.loadAnimation(this, R.anim.view_animation);
  // 3. 播放动画
  mButton.startAnimation(scaleAnimation);

/*
 * 设置方式2:Java
 */

 // 步骤1:创建 需要设置动画的 视图View
 Button mButton = (Button) findViewById(R.id.Button);

 // 步骤2:创建缩放动画的对象 & 设置动画效果
 // 缩放动画对应的Animation子类为RotateAnimation
 Animation scaleAnimation = new ScaleAnimation(0,2,0,2,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
 // 参数说明:
 // 1. fromX :动画在水平方向X的结束缩放倍数
 // 2. toX :动画在水平方向X的结束缩放倍数
 // 3. fromY :动画开始前在竖直方向Y的起始缩放倍数
 // 4. toY:动画在竖直方向Y的结束缩放倍数
 // 5. pivotXType:缩放轴点的x坐标的模式
 // 6. pivotXValue:缩放轴点x坐标的相对值
 // 7. pivotYType:缩放轴点的y坐标的模式
 // 8. pivotYValue:缩放轴点y坐标的相对值
 // pivotXType = Animation.ABSOLUTE:缩放轴点的x坐标 =  View左上角的原点 在x方向 加上 pivotXValue数值的点(y方向同理)
 // pivotXType = Animation.RELATIVE_TO_SELF:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 自身宽度乘上pivotXValue数值的值(y方向同理)
 // pivotXType = Animation.RELATIVE_TO_PARENT:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 父控件宽度乘上pivotXValue数值的值 (y方向同理)
  
  // 步骤3:属性设置:方法名是在其属性前加“set”,如设置时长setDuration() 
  scaleAnimation.setDuration(3000);

  // 步骤4:播放动画
  mButton.startAnimation(scaleAnimation);

3.2.3 旋转动画(Rotate)

类似于缩放动画的“缩放中心”,旋转动画也有一个“旋转轴点”的概念:

image.png

/*
 * 设置方式1:xml
 */
 // 步骤1:在 res/anim的文件夹里创建动画效果.xml文件
 // 此处路径为res/anim/view_animation.xml

 // 步骤2:根据不同动画效果的语法设置不同动画参数-view_animation.xml
 // 采用<rotate/> 标签表示是旋转动画
    <?xml version="1.0" encoding="utf-8"?>
    <rotate xmlns:android="http://schemas.android.com/apk/res/android"

    // 以下参数是4种动画效果的公共属性,即都有的属性
    android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果
    android:startOffset ="1000" // 动画延迟开始时间(ms)
    android:fillBefore = “true” // 动画播放完后,视图是否会停留在动画开始的状态,默认为true
    android:fillAfter = “false” // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
    android:fillEnabled= “true” // 是否应用fillBefore值,对fillAfter值无影响,默认为true
    android:repeatMode= “restart” // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart|
    android:repeatCount = “0” // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
    android:interpolator = @[package:]anim/interpolator_resource // 插值器,即影响动画的播放速度,下面会详细讲
    
    // 以下参数是旋转动画特有的属性
    android:duration="1000"
    android:fromDegrees="0" // 动画开始时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
    android:toDegrees="270" // 动画结束时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
    android:pivotX="50%" // 旋转轴点的x坐标
    android:pivotY="0" // 旋转轴点的y坐标
    // 轴点 = 视图缩放的中心点

    // pivotX pivotY,可取值为数字,百分比,或者百分比p
    // 设置为数字时(如50),轴点为View的左上角的原点在x方向和y方向加上50px的点。在Java代码里面设置这个参数的对应参数是Animation.ABSOLUTE。
    // 设置为百分比时(如50%),轴点为View的左上角的原点在x方向加上自身宽度50%和y方向自身高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_SELF。
    // 设置为百分比p时(如50%p),轴点为View的左上角的原点在x方向加上父控件宽度50%和y方向父控件高度50%的点。在Java代码里面设置这个参数的对应参数是Animation.RELATIVE_TO_PARENT
    // 两个50%表示动画从自身中间开始,具体如下图

    /> 
  
 // 步骤3:在Java代码中创建Animation对象并播放动画
  // 1. 创建需要设置动画的 视图View
  Button mButton = (Button) findViewById(R.id.Button); 
  // 2. 创建动画对象并传入设置的动画效果xml文件
  Animation rotateAnimation = AnimationUtils.loadAnimation(this, R.anim.view_animation);
  // 3. 播放动画
  mButton.startAnimation(rotateAnimation);

/*
 * 设置方式2:Java
 */
 // 步骤1:创建需要设置动画的视图View
 Button mButton = (Button) findViewById(R.id.Button);

 // 步骤2:创建旋转动画的对象 & 设置动画效果
 // 旋转动画对应的Animation子类为RotateAnimation
 Animation rotateAnimation = new RotateAnimation(0,270,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
 // 参数说明:
 // 1. fromX :动画在水平方向X的结束缩放倍数
 // 2. toX :动画在水平方向X的结束缩放倍数
 // 3. fromY :动画开始前在竖直方向Y的起始缩放倍数
 // 4. toY:动画在竖直方向Y的结束缩放倍数
 // 5. pivotXType:缩放轴点的x坐标的模式
 // 6. pivotXValue:缩放轴点x坐标的相对值
 // 7. pivotYType:缩放轴点的y坐标的模式
 // 8. pivotYValue:缩放轴点y坐标的相对值
 // pivotXType = Animation.ABSOLUTE:缩放轴点的x坐标 =  View左上角的原点 在x方向 加上 pivotXValue数值的点(y方向同理)
 // pivotXType = Animation.RELATIVE_TO_SELF:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 自身宽度乘上pivotXValue数值的值(y方向同理)
 // pivotXType = Animation.RELATIVE_TO_PARENT:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 父控件宽度乘上pivotXValue数值的值 (y方向同理)
  
  // 步骤3:属性设置:方法名是在其属性前加“set”,如设置时长setDuration() 
  rotateAnimation.setDuration(3000);

  // 步骤4:播放动画
  mButton.startAnimation(rotateAnimation);

3.2.4 透明动画(Alpha)

/*
 * 设置方式1:xml
 */
   // 步骤1:在 res/anim的文件夹里创建动画效果.xml文件
   // 此处路径为res/anim/view_animation.xml

   // 步骤2:根据不同动画效果的语法设置不同动画参数-view_animation.xml
   // 采用<alpha/> 标签表示是透明度动画
   <?xml version="1.0" encoding="utf-8"?>
   <alpha xmlns:android="http://schemas.android.com/apk/res/android"

    // 以下参数是4种动画效果的公共属性,即都有的属性
    android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果
    android:startOffset ="1000" // 动画延迟开始时间(ms)
    android:fillBefore = “true” // 动画播放完后,视图是否会停留在动画开始的状态,默认为true
    android:fillAfter = “false” // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
    android:fillEnabled= “true” // 是否应用fillBefore值,对fillAfter值无影响,默认为true
    android:repeatMode= “restart” // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart|
    android:repeatCount = “0” // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
    android:interpolator = @[package:]anim/interpolator_resource // 插值器,即影响动画的播放速度,下面会详细讲
    
    // 以下参数是透明度动画特有的属性
    android:fromAlpha="1.0" // 动画开始时视图的透明度(取值范围: -1 ~ 1)
    android:toAlpha="0.0"// 动画结束时视图的透明度(取值范围: -1 ~ 1)

    /> 
  
 // 步骤3:在Java代码中创建Animation对象并播放动画
  // 1. 创建需要设置动画的 视图View
  Button mButton = (Button) findViewById(R.id.Button); 
  // 2. 创建动画对象并传入设置的动画效果xml文件
  Animation alphaAnimation = AnimationUtils.loadAnimation(this, R.anim.view_animation);
  // 3. 播放动画
  mButton.startAnimation(alphaAnimation);

/*
 * 设置方式2:Java
 */
   // 步骤1:创建需要设置动画的视图View
   Button mButton = (Button) findViewById(R.id.Button);

   // 步骤2:创建透明度动画的对象 & 设置动画效果
   // 透明度动画对应的Animation子类为AlphaAnimation
   Animation alphaAnimation = new AlphaAnimation(1,0);
   // 参数说明:
   // 1. fromX :动画在水平方向X的结束缩放倍数
   // 2. toX :动画在水平方向X的结束缩放倍数
   // 3. fromY :动画开始前在竖直方向Y的起始缩放倍数
   // 4. toY:动画在竖直方向Y的结束缩放倍数
   // 5. pivotXType:缩放轴点的x坐标的模式
   // 6. pivotXValue:缩放轴点x坐标的相对值
   // 7. pivotYType:缩放轴点的y坐标的模式
   // 8. pivotYValue:缩放轴点y坐标的相对值
   // pivotXType = Animation.ABSOLUTE:缩放轴点的x坐标 =  View左上角的原点 在x方向 加上 pivotXValue数值的点(y方向同理)
   // pivotXType = Animation.RELATIVE_TO_SELF:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 自身宽度乘上pivotXValue数值的值(y方向同理)
   // pivotXType = Animation.RELATIVE_TO_PARENT:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 父控件宽度乘上pivotXValue数值的值 (y方向同理)
    
    // 步骤3:属性设置:方法名是在其属性前加“set”,如设置时长setDuration() 
    AlphaAnimation.setDuration(3000);

    // 步骤4:播放动画
    mButton.startAnimation(AlphaAnimation);

3.2.5 Activity 的切换效果

即 Activity 启动 / 退出时的动画效果,主要包括淡入淡出、左滑右滑等。

3.2.5.1 系统预设

本身系统已经封装好了淡入淡出、左滑右滑的效果。

// 系统已经封装好的动画效果
  // 淡入淡出:android.R.anim.fade_in、android.R.anim.fade_out
  // 由左向右滑入:android.R.anim.slide_in_left、android.R.anim.slide_out_right

// 核心方法:overridePendingTransition(int enterAnim, int exitAnim)
  // 调用时机:Activity的onCreate() 或 finish()
  // 参数说明
  // 对于在onCreate()设置:
     // enterAnim:进入该Activity时的动画效果资源ID
     // exitAnim:进入该Activity时上一个Activity离开时的动画效果资源ID

  // 对于在finish()设置:
     // enterAnim:进入其他Activity时 进入Activity的动画效果资源ID
     // exitAnim:进入其他Activity时 该Activity离开时的动画效果资源ID

// 具体使用
  // 方式1:在onCreate()设置
  @Override
      public void onCreate(Bundle savedInstanceState) {
          overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
          super.onCreate(savedInstanceState);
      }

  // 方式2:在finish()设置
   @Override
      public void finish() {
          super.finish();
          overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
      }

这里需要特别注意的是:如果进入退出页面:一个需要动画、另外一个不需要动画,但也必须设置时间相同的、没有任何变化的动画,否则会出现黑屏。

3.2.5.2 自定义切换效果

3.2.6 Fragment动画切换效果

类似于Activity,Fragment的动画切换效果同样存在两种方式:使用系统预设和自定义切换动画效果。

// 方式1:系统预设
  // 通过setTransition(int transit)进行设置
  // transit参数说明
  // 1. FragmentTransaction.TRANSIT_NONE:无动画
  // 2. FragmentTransaction.TRANSIT_FRAGMENT_OPEN:标准的打开动画效果
  // 3. FragmentTransaction.TRANSIT_FRAGMENT_CLOSE:标准的关闭动画效果
  // 标准动画设置好后,在Fragment添加和移除的时候都会有。

  // 具体使用
  FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
  fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);


// 方式2:自定义动画效果
  // 通过FragmentTransavtion.setCustomAnimations()设置
  // 此处的自定义动画效果同Activity,此处不再过多描述

  // 具体使用
  FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
  fragmentTransaction.setCustomAnimations(R.anim.in_from_right,R.anim.out_to_left);

3.2.7 视图组(ViewGroup)中子元素的出场效果

视图组(ViewGroup)中子元素可以具备出场时的补间动画效果。常用需求场景:为ListView的 item 设置出场动画。

// 步骤1:设置子元素的出场动画
// res/anim/view_animation.xml

  <?xml version="1.0" encoding="utf-8"?>
  // 此处采用了组合动画
  <set xmlns:android="http://schemas.android.com/apk/res/android" >
      android:duration="3000"

      <alpha
          android:duration="1500"
          android:fromAlpha="1.0"
          android:toAlpha="0.0" />

      <translate
          android:fromXDelta="500"
          android:toXDelta="0"
           />
  </set>

// 步骤2:设置 视图组(ViewGroup)的动画文件
// res/anim/anim_layout.xml
  <?xml version="1.0" encoding="utf-8"?>
  // 采用LayoutAnimation标签
  <layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
      android:delay="0.5"
      // 子元素开始动画的时间延迟
      // 如子元素入场动画的时间总长设置为300ms
      // 那么 delay = "0.5" 表示每个子元素都会延迟150ms才会播放动画效果
      // 第一个子元素延迟150ms播放入场效果;第二个延迟300ms,以此类推

      android:animationOrder="normal"
      // 表示子元素动画的顺序
      // 可设置属性为:
      // 1. normal :顺序显示,即排在前面的子元素先播放入场动画
      // 2. reverse:倒序显示,即排在后面的子元素先播放入场动画
      // 3. random:随机播放入场动画

      android:animation="@anim/view_animation"
      // 设置入场的具体动画效果
      // 将步骤1的子元素出场动画设置到这里
      />

// 步骤3:为视图组(ViewGroup)指定andorid:layoutAnimation属性
   // 指定的方式有两种: XML / Java代码
   // 方式1:XML
   <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#FFFFFF"
        android:orientation="vertical" >
        <ListView
            android:id="@+id/listView1"
            android:layoutAnimation="@anim/anim_layout"
            // 指定layoutAnimation属性用以指定子元素的入场动画
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </LinearLayout>

    // 方式2:Java
    // 注:不需额外设置res/ anim /anim_layout.xml该xml文件了
    ListView lv = (ListView) findViewById(R.id.listView1);

    // 加载子元素的出场动画
    Animation animation = AnimationUtils.loadAnimation(this,R.anim.anim_item);
    
    // 设置LayoutAnimation的属性
    LayoutAnimationController controller = new LayoutAnimationController(animation);
    controller.setDelay(0.5f);
    controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
    
    // 为ListView设置LayoutAnimation的属性
    lv.setLayoutAnimation(controller);

3.2.8 组合动画

// 方式:xml跟java代码设置

// 方式1:xml
 // 步骤1:在路径 res/anim 的文件夹里创建动画效果 .xml文件 - view_animation.xml
 // 步骤2:设置组合动画(同单个动画设置)
  <?xml version="1.0" encoding="utf-8"?>
    // 采用< Set/>标签
    <set xmlns:android="http://schemas.android.com/apk/res/android">

    // 组合动画同样具备公共属性
        android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果
        android:startOffset ="1000" // 动画延迟开始时间(ms)
        android:fillBefore = “true” // 动画播放完后,视图是否会停留在动画开始的状态,默认为true
        android:fillAfter = “false” // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
        android:fillEnabled= “true” // 是否应用fillBefore值,对fillAfter值无影响,默认为true
        android:repeatMode= “restart” // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart|
        android:repeatCount = “0” // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
        android:interpolator = @[package:]anim/interpolator_resource // 插值器,即影响动画的播放速度,下面会详细讲
        
    // 组合动画独特的属性
        android:shareinterpolator = “true”
        // 表示组合动画中的动画是否和集合共享同一个差值器
        // 如果集合不指定插值器,那么子动画需要单独设置

    // 组合动画播放时是全部动画同时开始
    // 如果想不同动画不同时间开始就要使用android:startOffset属性来延迟单个动画播放时间

    // 设置旋转动画,语法同单个动画
        <rotate
            android:duration="1000"
            android:fromDegrees="0"
            android:toDegrees="360"
            android:pivotX="50%"
            android:pivotY="50%"
            android:repeatMode="restart"
            android:repeatCount="infinite"
            />

    // 设置平移动画,语法同单个动画
        <translate
            android:duration="10000"
            android:startOffset = “1000”// 延迟该动画播放时间
            android:fromXDelta="-50%p"
            android:fromYDelta="0"
            android:toXDelta="50%p"
            android:toYDelta="0" />

    // 设置透明度动画,语法同单个动画
        <alpha
            android:startOffset="7000"
            android:duration="3000"
            android:fromAlpha="1.0"
            android:toAlpha="0.0" />


    // 设置缩放动画,语法同单个动画
        <scale
            android:startOffset="4000"
            android:duration="1000"
            android:fromXScale="1.0"
            android:fromYScale="1.0"
            android:pivotX="50%"
            android:pivotY="50%"
            android:toXScale="0.5"
            android:toYScale="0.5" />
    // 特别注意:
    // 1. 在组合动画里scale缩放动画设置的repeatCount(重复播放)和fillBefore(播放完后,视图是否会停留在动画开始的状态)是无效的。
    // 2. 所以如果需要重复播放或者回到原位的话需要在set标签里设置
    // 3. 但是由于此处rotate旋转动画里已设置repeatCount为infinite,所以动画不会结束,也就看不到重播和回复原位

    </set>

    // 步骤3:播放动画
        // 创建 需要设置动画的 视图View
        Button mButton = (Button) findViewById(R.id.Button);
        // 创建 动画对象 并传入设置的动画效果xml文件
        Animation setAnimation = AnimationUtils.loadAnimation(this, R.anim.view_animation);
        // 播放动画
        mButton.startAnimation(setAnimation);


// 方式2:java代码设置
    // 创建 需要设置动画的 视图View
    Button mButton = (Button) findViewById(R.id.Button);
    

    // 组合动画设置
    AnimationSet setAnimation = new AnimationSet(true);
    // 步骤1:创建组合动画对象(设置为true)

    // 步骤2:设置组合动画的属性
    // 特别说明以下情况
    // 因为在下面的旋转动画设置了无限循环(RepeatCount = INFINITE)
    // 所以动画不会结束,而是无限循环
    // 所以组合动画的下面两行设置是无效的
    setAnimation.setRepeatMode(Animation.RESTART);
    setAnimation.setRepeatCount(1);// 设置了循环一次,但无效

    // 步骤3:逐个创建子动画(方式同单个动画创建方式,此处不作过多描述)

    // 子动画1:旋转动画
    Animation rotate = new RotateAnimation(0,360,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
    rotate.setDuration(1000);
    rotate.setRepeatMode(Animation.RESTART);
    rotate.setRepeatCount(Animation.INFINITE);

    // 子动画2:平移动画
    Animation translate = new TranslateAnimation(TranslateAnimation.RELATIVE_TO_PARENT,-0.5f,
    TranslateAnimation.RELATIVE_TO_PARENT,0.5f,
    TranslateAnimation.RELATIVE_TO_SELF,0
    ,TranslateAnimation.RELATIVE_TO_SELF,0);
    translate.setDuration(10000);

    // 子动画3:透明度动画
    Animation alpha = new AlphaAnimation(1,0);
    alpha.setDuration(3000);
    alpha.setStartOffset(7000);

    // 子动画4:缩放动画
    Animation scale1 = new ScaleAnimation(1,0.5f,1,0.5f,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
    scale1.setDuration(1000);
    scale1.setStartOffset(4000);

    // 步骤4:将创建的子动画添加到组合动画里
    setAnimation.addAnimation(alpha);
    setAnimation.addAnimation(rotate);
    setAnimation.addAnimation(translate);
    setAnimation.addAnimation(scale1);

    // 步骤5:播放动画
    mButton.startAnimation(setAnimation);
        

3.2.9 监听动画

// 主要通过setAnimationListener()设置
Animation.setAnimationListener(new Animation.AnimationListener() {
    @Override
    public void onAnimationStart(Animation animation) {
        // 动画开始时回调
    }

    @Override
    public void onAnimationEnd(Animation animation) {
        // 动画结束时回调
    }

    @Override
    public void onAnimationRepeat(Animation animation) {
        //动画重复执行的时候回调
    }
});

3.3 属性动画

对于属性动画的使用,主要是:

  1. 两个使用方法类:ValueAnimator 类 & ObjectAnimator 类
  2. 两个辅助使用类:插值器 & 估值器

3.3.1 ValueAnimator类

  • 定义:属性动画机制中 最核心的一个类
  • 实现动画的原理:通过不断控制 值 的变化,再不断 手动 赋给对象的属性,从而实现动画效果。如图下:

image.png

从上面原理可以看出:ValueAnimator类中有3个重要方法:

  1. ValueAnimator.ofInt(int values)
  2. ValueAnimator.ofFloat(float values)
  3. ValueAnimator.ofObject(int values)

操作值的方式 分为 XML 设置 / Java 代码设置,具体如下:

/*
 * 设置方式1:xml
 */
// 步骤1:在路径 res/animator的文件夹里创建相应的动画 .xml文件 - set_animation.xml

// 步骤2:设置动画参数
// ValueAnimator采用<animator>  标签
<animator xmlns:android="http://schemas.android.com/apk/res/android"  
    android:valueFrom="0"   // 初始值
    android:valueTo="3"  // 结束值
    android:valueType="intType" // 变化值类型 :floatType & intType
    android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果
    android:startOffset ="1000" // 动画延迟开始时间(ms)
    android:fillBefore = “true// 动画播放完后,视图是否会停留在动画开始的状态,默认为true
    android:fillAfter = “false// 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
    android:fillEnabled= “true// 是否应用fillBefore值,对fillAfter值无影响,默认为true
    android:repeatMode= “restart” // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart
    android:repeatCount = “0// 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
    android:interpolator = @[package:]anim/interpolator_resource // 插值器,即影响动画的播放速度
/> 

// 步骤3:启动动画
    // 载入XML动画
    Animator animator = AnimatorInflater.loadAnimator(context, R.animator.set_animation);  
    // 设置动画对象
    animator.setTarget(view);  
    // 启动动画
    animator.start();  

/*
 * 设置方式2:Java
 */
// 步骤1:设置动画属性的初始值 & 结束值
    ValueAnimator anim = ValueAnimator.ofInt(0, 3);
    // ofInt()作用:
    // 1. 创建动画实例
    // 2. 将传入的多个Int参数进行平滑过渡:此处传入0和3,表示将值从0平滑过渡到3
    // 如果传入了3个Int参数 a,b,c ,则是先从a平滑过渡到b,再从b平滑过渡到C,以此类推
    // ValueAnimator.ofInt()内置了整型估值器,直接采用默认的.不需要设置,即默认设置了如何从初始值 过渡到 结束值

    // 关注1:ofInt()源码分析
    public static ValueAnimator ofInt(int... values) {
        // 允许传入一个或多个Int参数
        // 1. 输入一个的情况(如a):从0过渡到a;
        // 2. 输入多个的情况(如a,b,c):先从a平滑过渡到b,再从b平滑过渡到C

        ValueAnimator anim = new ValueAnimator();
        // 创建动画对象
        anim.setIntValues(values);
        // 将传入的值赋值给动画对象
        return anim;
    }

// 步骤2:设置动画的播放各种属性
    // 设置动画运行的时长
    anim.setDuration(500); 
    
    // 设置动画延迟播放时间
    anim.setStartDelay(500); 

    // 设置动画重复播放次数 = 重放次数+1
    // 动画播放次数 = infinite时,动画无限重复
    anim.setRepeatCount(0);
    
    // 设置重复播放动画模式
    anim.setRepeatMode(ValueAnimator.RESTART);
    // ValueAnimator.RESTART(默认):正序重放
    // ValueAnimator.REVERSE:倒序回放

// 步骤3:将改变的值手动赋值给对象的属性值:通过动画的更新监听器
// 设置值的更新监听器,即:值每次改变、变化一次,该方法就会被调用一次
    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // 获得改变后的值
            int currentValue = (Integer) animation.getAnimatedValue();

            // 将改变后的值赋给对象的属性值
            View.setproperty(currentValue);

            // 刷新视图,即重新绘制,从而实现动画效果
            View.requestLayout();
        }
    });

// 步骤4:启动动画
  anim.start();

3.3.2 ObjectAnimator类

直接对对象的属性值进行改变操作,从而实现动画效果

  1. 如直接改变 Viewalpha 属性 从而实现透明度的动画效果
  2. 继承自ValueAnimator类,即底层的动画实现机制是基于ValueAnimator
  • 本质原理: 通过不断控制 值 的变化,再不断 自动 赋给对象的属性,从而实现动画效果。如下图:

image.png

从上面的工作原理可以看出:ObjectAnimatorValueAnimator类的区别:

  • ValueAnimator 类是先改变值,然后 手动赋值 给对象的属性从而实现动画;是 间接 对对象属性进行操作;
  • ObjectAnimator 类是先改变值,然后 自动赋值 给对象的属性从而实现动画;是 直接 对对象属性进行操作

3.4 插值器

3.4.1 使用插值器

插值器在动画的使用有两种方式:在XML / Java代码中设置:

/*
 * 使用方式1:xml
 * 主要是设置插值器属性 android:interpolator
 */
 <?xml version="1.0" encoding="utf-8"?>
    <scale xmlns:android="http://schemas.android.com/apk/res/android"
        // 通过资源ID设置插值器 
        android:interpolator="@android:anim/overshoot_interpolator"
        android:duration="3000"
        android:fromXScale="0.0"
        android:fromYScale="0.0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="2"
        android:toYScale="2" />
 >

/*
 * 使用方式2:java
 */
// 步骤1:创建 需要设置动画的 视图View
Button mButton = (Button) findViewById(R.id.Button);
// 步骤2:创建透明度动画的对象 & 设置动画效果
Animation alphaAnimation = new AlphaAnimation(1,0);
alphaAnimation.setDuration(3000);
// 步骤3:创建对应的插值器类对象
Interpolator overshootInterpolator = new OvershootInterpolator();
// 步骤4:给动画设置插值器
alphaAnimation.setInterpolator(overshootInterpolator);
// 步骤5:播放动画
mButton.startAnimation(alphaAnimation);

3.4.2 系统内置插值器

Android内置了 9 种内置的插值器实现:

image.png

系统默认的插值器是AccelerateDecelerateInterpolator,即先加速后减速

3.4.3 自定义插值器

自定义插值器需要实现Interpolator或TimeInterpolator接口,并复写getInterpolation()方法

  1. 补间动画 实现 Interpolator接口;属性动画实现TimeInterpolator接口
  2. TimeInterpolator接口是属性动画中新增的,用于兼容Interpolator接口,这使得所有过去的Interpolator实现类都可以直接在属性动画使用

Interpolator接口和TimeInterpolator接口说明如下:

// Interpolator接口
public interface Interpolator {  

    // 内部只有一个方法:getInterpolation()
     float getInterpolation(float input) {  
        // 参数说明
        // input值值变化范围是0-1,且随着动画进度(0% - 100% )均匀变化
        // 即动画开始时,input值 = 0;动画结束时input = 1
        // 而中间的值则是随着动画的进度(0% - 100%)在0到1之间均匀增加
        
      ...// 插值器的计算逻辑

      return xxx;
      // 返回的值就是用于估值器继续计算的fraction值
    }  

// TimeInterpolator接口
// 同上
public interface TimeInterpolator {  
  
    float getInterpolation(float input){
         ...
    };  
}  

从上面可以看出,自定义插值器的关键在于:对input值根据动画的进度(0%-100%)通过逻辑计算从而计算出当前属性值改变的百分比。先来看两个已经实现好的系统内置差值器:

  • 匀速插值器:LinearInterpolator
  • 先加速再减速 插值器:AccelerateDecelerateInterpolator
/*
 * 匀速差值器:LinearInterpolator
 */
@HasNativeInterpolator  
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {  
   
    ... // 仅贴出关键代码

    public float getInterpolation(float input) {  
        return input;  
        // 没有对input值进行任何逻辑处理,直接返回
        // 即input值 = fraction值
        // 因为input值是匀速增加的,因此fraction值也是匀速增加的,
        // 所以动画的运动情况也是匀速的,所以是匀速插值器
    }  

/*
 * 先加速再减速 差值器:AccelerateDecelerateInterpolator
 */
@HasNativeInterpolator  
public class AccelerateDecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {  
      
    ... // 仅贴出关键代码
    
    public float getInterpolation(float input) {  
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
        // input的运算逻辑如下:
        // 使用了余弦函数,因input的取值范围是0到1,那么cos函数中的取值范围就是π到2π。
        // 而cos(π)的结果是-1,cos(2π)的结果是1
        // 所以该值除以2加上0.5后,getInterpolation()方法最终返回的结果值还是在0到1之间。
        // 只不过经过了余弦运算之后,最终的结果不再是匀速增加的了,而是经历了一个先加速后减速的过程
        // 所以最终,fraction值 = 运算后的值 = 先加速后减速
        // 所以该差值器是先加速再减速的
    }  
}

3.5 估值器

3.5.1 使用估值器

ObjectAnimator anim = ObjectAnimator.ofObject(myView2, "height", new Evaluator(),13);
// 系统内置的估值器有3个:
// IntEvaluator:以整型的形式从初始值 - 结束值 进行过渡
// FloatEvaluator:以浮点型的形式从初始值 - 结束值 进行过渡
// ArgbEvaluator:以Argb类型的形式从初始值 - 结束值 进行过渡

3.5.2 自定义估值器

根据 插值器计算出当前属性值改变的百分比 & 初始值 & 结束值 来计算 当前属性具体的数值

如:动画进行了50%(初始值=100,结束值=200 ),那么匀速插值器计算出了当前属性值改变的百分比是50%,那么估值器则负责计算当前属性值 = 100 + (200-100)x50% = 150.

自定义估值器需要实现 TypeEvaluator接口 & 复写evaluate()

// 步骤1:实现TypeEvaluator接口
    public class ObjectEvaluator implements TypeEvaluator{  

// 步骤2:复写evaluate()
// 作用:估值器的计算逻辑,即写入对象动画过渡的逻辑
    @Override  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        // 参数说明
        // fraction:表示动画完成度(根据它来计算当前动画的值),也是插值器getInterpolation()的返回值
        // startValue:动画的初始值
        // endValue:动画的结束值

        // 估值器的计算逻辑
        ... 
        
        // 返回对象动画过渡逻辑计算后的值
        // 即赋给动画属性的具体数值
        return value;  
    } 

// 特别注意
// 那么插值器的input值 和 估值器fraction有什么关系呢?
// 答:input的值决定了fraction的值:input值经过计算后传入到插值器的getInterpolation(),
// 然后通过实现getInterpolation()中的逻辑算法,
// 根据input值来计算出一个返回值,而这个返回值就是fraction了

属性动画中的ValueAnimator.ofInt() & ValueAnimator.ofFloat()都具备系统内置的估值器,即FloatEvaluator & IntEvaluator,即系统已经默认实现了 如何从初始值 过渡到 结束值 的逻辑

但对于ValueAnimator.ofObject(),从上面的工作原理可以看出并没有系统默认实现,因为对对象的动画操作复杂 & 多样,系统无法知道如何从初始对象过度到结束对象

因此,对于ValueAnimator.ofObject(),我们需自定义估值器(TypeEvaluator)来告知系统如何进行从 初始对象 过渡到 结束对象的逻辑。

3.6 SVGA动画

3.6.1 svg

可将svg图片导入studio中,经过IDE的转化后即可使用。

image.png

3.6.1.1 svg标签

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg  width="300" height="300" viewBox="0, 0, 100, 200"  xmlns="http://www.w3.org/2000/svg" version="1.1">
    <circle cx="100" cy="50" r="49" stroke="black"
    stroke-width="2" fill="red" />
</svg>

3.6.1.2 path标签

在 SVG 里,你可以把 path 看成是最基本的绘制元素,正因为它是最基本的,万变不离其宗,他能演化出各种复杂的绘制效果。所以 path 是最基本也是最复杂的绘制元素。

我们知道一个 path 标签,最重要的属性是 d 属性,它是一组指令和参数的集合。在 d 属性的值里,我们能看到一堆非常复杂的指令字符串。

<path d="
    M73.8616812,68.8664775
    L74.5015359,74.5939423
    L68.1746283,71.7969507
    C66.2299599,72.4159872 64.1377269,72.7711218 61.9444643,72.7711218
    C51.9719158,72.7711218 43.8883163,65.7823167 43.8883163,57.1611168
    C43.8883163,48.5399169 51.9719158,41.5511118 61.9444643,41.5511118
    C71.9164005,41.5511118 80,48.5399169 80,57.1611168
    C80,61.8286883 77.6181486,66.006419 73.8616812,68.8664775" id="Fill-1" fill="#FFFFFF"></path>
指令参数含义
Mx y将画笔移动到点(x,y)
Lx y画笔从当前的点绘制线段到点(x,y)
Hx画笔从当前的点绘制水平线段到点(x,y0),y0 表示绘制前画笔所在 y 轴坐标,也就是 y 轴不变
Vy画笔从当前的点绘制竖直线段到点(x0,y),x0 表示绘制前画笔所在 x 轴坐标,也就是 x 轴不变
Arx ry x-axis-rotation large-arc-flag sweep-flag x y画笔从当前的点绘制一段圆弧到点(x,y)
Cx1 y1, x2 y2, x y画笔从当前的点绘制一段三次贝塞尔曲线到点(x,y)
Sx2 y2, x y特殊版本的三次贝塞尔曲线(省略第一个控制点)
Qx1 y1, x y绘制二次贝塞尔曲线到点(x,y)
Tx y特殊版本的二次贝塞尔曲线(省略控制点)
Z无参数绘制闭合图形,如果 d 属性不指定Z命令,则绘制线段,而不是封闭图形

以上是 path 路径中的全部指令,相对来说比较好理解。每个指令都有对应的小写指令。例如M 10,10 有对应的 m 10,10 。大写代表绝对位置,所谓绝对位置即对 SVG 画布左上角原点的绝对。小写代表相对位置,所谓相对位置是以当前画笔所在位置进行定位

3.6.1.3 基本图形

图形标签模板含义
矩形< rect >< rect x="60" y="10" rx="10" ry="10" width="30" height="30"/>x:起点横坐标,y:起点纵坐标,rx:倒角x轴方向半径,ry:倒角x轴方向半径,width:宽度,height:高度
圆形< circle >< circle cx="100" cy="100" r="50" fill="#fff">cx:圆心横坐标,cy:圆心纵坐标,r:半径
椭圆< ellipse >< ellipse cx="75" cy="75" rx="20" ry="5"/>cx:椭圆心横坐标,cy:椭圆心纵坐标,rx:椭圆x轴方向半径,ry:椭圆y轴方向半径
直线< line >< line x1="10" x2="50" y1="110" y2="150"/>x:起点横坐标,y:起点纵坐标,rx:倒角x轴方向半径,ry:倒角x轴方向半径,width:宽度,height:高度x1,y1:起点,x2,y2:终点
折线< polyline >< polyline points="60 110, 65 120, 70 115, 75 130, 80 125, 85 140, 90 135, 95 150, 100 145"/>每两个点以空格配对为一个坐标点,逗号隔开形成坐标集合。连成折线。
多边形< polygon >< polygon points="50 160, 55 180, 70 180, 60 190, 65 205, 50 195, 35 205, 40 190, 30 180, 45 180"/>类似折线,不同的是,最后一个点会自动闭合第一个点,形成闭环。

3.6.1.4 symbol标签

它的作用说白话点就是相当于是一个元件,放在我们的工具箱里,就像下面这样:

<svg class="svg-sprite">[工具箱]
	<symbol id="icon-wave_add" viewBox="0 0 76 76"><path d="M38 0a4 4 0 014 4v30h30a4 4 0 110 8H41.999L42 72a4 4 0 11-8 0l-.001-30H4a4 4 0 110-8h30V4a4 4 0 014-4z" fill="currentColor" fill-rule="evenodd" opacity="none"></path></symbol>
	<symbol id="icon-time" viewBox="0 0 10 10"><path d="M5 0a5 5 0 110 10A5 5 0 015 0zm0 1.5a.5.5 0 00-.5.5v3.02l.008.088a.5.5 0 00.238.343L7.02 6.794l.082.039a.5.5 0 00.603-.215l.039-.082a.5.5 0 00-.216-.603L5.5 4.735V2l-.008-.09A.5.5 0 005 1.5z" fill="rgba(153,153,153,1)" fill-rule="evenodd" class=" "></path></symbol>
	<symbol id="icon-wave_delete" viewBox="0 0 40 40"><g fill="none" fill-rule="evenodd"><circle fill="#000" opacity="0.2" cx="20" cy="20" r="20"></circle><path stroke="#FFF" stroke-width="4" stroke-linecap="round" d="M13 13l14 14M27 13L13 27"></path></g></symbol>
</svg>

3.7 Lottie动画

3.7.1 Lottie依赖

Gradle 是唯一支持的构建配置,所以只需要在项目的 build.gradle 文件中添加依赖即可:

dependencies {
  implementation "com.airbnb.android:lottie:$lottieVersion"
}

3.7.2 Lottie 核心类

  • LottieAnimationView:继承自 AppCompatImageView,是加载 Lottie 动画的默认和最简单的方式。

  • LottieDrawable:具有大多数与 LottieAnimationView 相同的 API,因此可以在任何视图上使用它。

3.7.3 加载动画

Lottie 支持 Jellybean (API 16) 及以上版本。Lottie 动画支持从以下位置加载动画:

  • src/main/res/raw 中的 json 动画。
  • src/main/assets 中的 json 文件。
  • src/main/assets 中的 zip 文件。
  • json 或 zip 文件的 Url
  • json 字符串。源可以来自任何东西,包括自己的网络堆栈。
  • json 文件或 zip 文件的 InputStream

3.7.3.1 在 XML 中使用

最简单的使用方法是使用 LottieAnimationView。Lottie 支持加载来自 res/rawassets/ 的动画资源。建议使用 res/raw,因为可以对动画通过 R 文件使用静态引用,而不只是使用字符串名称。这也可以帮助构建静态分析,因为它可以跟踪动画的使用。

LottieAnimationView 的常用属性及其功能如下:

属性功能
lottie_fileName设置播放动画的 json 文件名称
lottie_rawRes设置播放动画的 json 文件资源
lottie_autoPlay设置动画是否自动播放(默认为false)
lottie_loop设置动画是否循环(默认为false)
lottie_repeatMode设置动画的重复模式(默认为restart)
lottie_repeatCount设置动画的重复次数(默认为-1)
lottie_cacheStrategy设置动画的缓存策略(默认为weak)
lottie_colorFilter设置动画的着色颜色(优先级最低)
lottie_scale设置动画的比例(默认为1f)
lottie_progress设置动画的播放进度
lottie_imageAssetsFolder设置动画依赖的图片资源文件地址

在 res/raw (lottie_rawRes) 或 assets/ (lottie_fileName) 中存放动画的 JSON 文件,然后就可以在 xml 中直接使用,如下:

<com.airbnb.lottie.LottieAnimationView
        android:id="@+id/animation_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"

        app:lottie_rawRes="@raw/hello_world"
        // or
        app:lottie_fileName="hello_world.json"

        app:lottie_loop="true"
        app:lottie_autoPlay="true" />

3.7.3.2 在代码中使用

LottieAnimationView 的常用方法及其功能如下:

方法功能
setAnimation(String)设置播放动画的 json 文件名称
setAnimation(String, CacheStrategy)设置播放动画的 json 文件资源和缓存策略
setAnimation(int)设置播放动画的 json 文件名称
setAnimation(int, CacheStrategy)设置播放动画的 json 文件资源和缓存策略
loop(boolean)设置动画是否循环(默认为false)
setRepeatMode(int)设置动画的重复模式(默认为restart)
setRepeatCount(int)设置动画的重复次数(默认为-1)
lottie_cacheStrategy设置动画的缓存策略(默认为weak)
lottie_colorFilter设置动画的着色颜色(优先级最低)
setScale(float)设置动画的比例(默认为1f)
setProgress(float)设置动画的播放进度
setImageAssetsFolder(String)设置动画依赖的图片资源文件地址
playAnimation()从头开始播放动画
pauseAnimation()暂停播放动画
resumeAnimation()继续从当前位置播放动画
cancelAnimation()取消播放动画

如果不想用 xml 实现,可以通过代码来实现,可以直接加载本地动画资源,也可以从网络请求加载动画。

  • 从 res/raw 或 assets/ 加载动画资源:
LottieAnimationView animationView = ...

animationView.setAnimation(R.raw.hello_world);
// or
animationView.setAnimation(R.raw.hello_world.json);

animationView.playAnimation();

该方法在后台加载文件并解析动画,并在完成后异步开始渲染。

  • 从网络请求加载动画: Lottie 的一个优点是可以从网络请求加载动画。所以,应该将网络请求的响应内容转换为字符串格式。Lottie 使用一个流化的 json 反序列化器来提高性能和内存使用率,所以不要将它转换成您自己的 JSONObject,这只会损害性能。
LottieAnimationView animationView = ...
// This allows lottie to use the streaming deserializer mentioned above.
JsonReader jsonReader = new JsonReader(new StringReader(json.toString()));
animationView.setAnimation(jsonReader);
animationView.playAnimation();

3.7.4 Lottie的缓存策略

你的应用程序中可能会有一些经常使用的动画,比如加载动画等等。为了避免每次加载文件和发序列化的开销,你可以在你的动画上设置一个缓存策略。上面所有的 setAnimation APIs都可以采用可选的第二个参数 CacheStrategy。在默认情况下,Lottie 将保存对动画的弱引用,这对于大多数情况来说应该足够了。但是,如果确定某个动画肯定会经常使用,那么请将其缓存策略更改为 CacheStrategy.Strong;或者如果确定某个动画很大而且不会经常使用,把缓存策略改成 CacheStrategy.None

CacheStrategy 可以是NoneWeakStrong 三种形式来让 LottieAnimationView 对加载和解析动画的使用强或弱引用的方式。弱或强表示缓存中组合的 GC 引用强度。

3.7.5 直接使用LottieDrawable

LottieAnimationView 是基于 LottieDrawable 的一个包装好的 ImageView 。LottieAnimationView 上的所有 API 都在 LottieDrawable 上进行镜像,因此可以创建自己的实例并在任何可以使用drawable的地方使用它,例如自定义 View 或菜单。

3.7.6 使用 LottieComposition 去预加载动画

动画的支持模型是 LottieComposition。在大多数情况下,在 LottieAnimationViewLottieDrawable 上调用 setAnimation(…) 便足够了。但是,如果要预加载动画以使其立即可用,则可以使用 LottieComposition.Factory API返回可以直接在 LottieAnimationViewLottieDrawable 设置的 LottieComposition 对象。同样,通常不需要自己添加管理 compositions 的开销。LottieAnimationView 中的默认缓存足以满足大多数用例的需要。

LottieAnimationView animationView = ...;
 ...
 Cancellable compositionLoader = LottieComposition.Factory.fromJsonString(getResources(), jsonString, (composition) -> {
     animationView.setComposition(composition);
     animationView.playAnimation();
 });

 // Cancel to stop asynchronous loading of composition
 // compositionCancellable.cancel();

3.7.7 动画监听

animationView.addAnimatorUpdateListener((animation) -> {
    // do something.
});
animationView.playAnimation();
...
if (animationView.isAnimating()) {
    // do something.
}
...
animationView.setProgress(0.5f);
...

在更新侦听器回调中:

  • animation.getAnimatedValue() 将返回动画的播放进度,而不考虑当前设置的最小/最大帧[0,1]。
  • animation.getAnimatedFraction() 将返回动画的播放进度,同时考虑设置的最小/最大帧[minFrame,maxFrame]。

3.7.8 控制 Lottie 动画执行的速度和时长

尽管 playAnimation() 对于绝大多数用例来说已足够,但可以在更新回调中调用 setProgress(...) 方法为自己的动画设置进度。

// Custom animation speed or duration.
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
animator.addUpdateListener(animation -> {
    animationView.setProgress(animation.getAnimatedValue());
});
animator.start();
...

3.7.9 循环播放

可以像使用 ValueAnimator 一样通过 setRepeatMode(...)setRepeatCount(...) 方法控制动画的循环播放,或者你直接在 xml 中使用 lottie_loop="true" 开启循环播放。

3.7.10 动画尺寸(px vs dp)

LottieAfter Effects 中的所有 px 值转换为设备上的 dps,以便在设备上以相同的大小呈现所有内容。这意味着它不是在 After Effects 中制作1920x1080的动画,而是在After Effects中更像411x731px,它大致对应于当今大多数手机的dp屏幕尺寸。

但是,如果您的动画不是完美尺寸,则有两种选择:

3.7.10.1 ImageView scaleType

LottieAnimationView 是一个包装好的 ImageView,它支持 centerCropcenterInside,所以可以像使用其他 image 一样使用这两个工具方法。

3.7.10.2 Lottie setScale(...)

LottieAnimationViewLottieDrawable 都有 setScale(float) API,可以使用它来手动放大或缩小动画。这很少有用,但可以在某些情况下使用。

如果动画执行缓慢,请务必查看有关性能的文档。但是,可以尝试使用 scaleType 缩放动画,这将减少 Lottie 每帧渲染的数量。如果你有大的 mask 和 mattes,这将特别有用。

3.7.11 播放动画片段

播放/循环动画的单个部分通常很方便。例如,动画的前半部分可能处于“开启”状态,下半部分处于“关闭”状态。LottieAnimationView 和 LottieDrawable 有用于控制当前段的 API:

setMinFrame(...)

setMaxFrame(...)

setMinProgress(...)

setMaxProgress(...)

setMinAndMaxFrame(...)

setMinAndMaxProgress(...)

循环API将遵循此处设置的最小/最大帧。

3.8 硬件加速

所谓硬件加速,指的是把某些计算工作交给专门的硬件来做,而不是和普通的计算工作一样交给 CPU 来处理。这样不仅减轻了 CPU 的压力,而且由于有了「专人」的处理,这份计算工作的速度也被加快了。这就是「硬件加速」。

而对于 Android 来说,硬件加速有它专属的意思:在 Android 里,硬件加速专指把 View 中绘制的计算工作交给 GPU 来处理。进一步地再明确一下,这个「绘制的计算工作」指的就是把绘制方法中的那些 Canvas.drawXXX() 变成实际的像素这件事。

3.8.1 原理

在硬件加速关闭的时候,Canvas 绘制的工作方式是:把要绘制的内容写进一个 Bitmap,然后在之后的渲染过程中,这个 Bitmap 的像素内容被直接用于渲染到屏幕。这种绘制方式的主要计算工作在于把绘制操作转换为像素的过程(例如由一句 Canvas.drawCircle() 来获得一个具体的圆的像素信息),这个过程的计算是由 CPU 来完成的。大致就像这样:

006tKfTcly1fjmybsw31fj30nb0iywfq.jpg

而在硬件加速开启时,Canvas 的工作方式改变了:它只是把绘制的内容转换为 GPU 的操作保存了下来,然后就把它交给 GPU,最终由 GPU 来完成实际的显示工作。大致是这样:

006tKfTcly1fjmygtwiutj30nb0o2ta8.jpg 如图,在硬件加速开启时,CPU 做的事只是把绘制工作转换成 GPU 的操作,这个工作量相对来说是非常小的。

3.8.2 怎么就「加速」了?

从上面的图中可以看出,硬件加速开启后,绘制的计算工作由 CPU 转交给了 GPU。不过这怎么就能起到「加速」作用,让绘制变快了呢?

硬件加速能够让绘制变快,主要有三个原因:

  1. 本来由 CPU 自己来做的事,分摊给了 GPU 一部分,自然可以提高效率;
  2. 相对于 CPU 来说,GPU 自身的设计本来就对于很多常见类型内容的计算(例如简单的圆形、简单的方形)具有优势;
  3. 由于绘制流程的不同,硬件加速在界面内容发生重绘的时候绘制流程可以得到优化,避免了一些重复操作,从而大幅提升绘制效率。

其中前两点可以总结为一句:用了 GPU,绘制就是快。原因很直观,不再多说。

关于第三点,它的原理我大致说一下:

前面说到,在硬件加速关闭时,绘制内容会被 CPU 转换成实际的像素,然后直接渲染到屏幕。具体来说,这个「实际的像素」,它是由 Bitmap 来承载的。在界面中的某个 View 由于内容发生改变而调用 invalidate() 方法时,如果没有开启硬件加速,那么为了正确计算 Bitmap 的像素,这个 View 的父 View、父 View 的父 View 乃至一直向上直到最顶级 View,以及所有和它相交的兄弟 View,都需要被调用 invalidate()来重绘。一个 View 的改变使得大半个界面甚至整个界面都重绘一遍,这个工作量是非常大的。

而在硬件加速开启时,前面说过,绘制的内容会被转换成 GPU 的操作保存下来(承载的形式称为 display list,对应的类也叫做 DisplayList),再转交给 GPU。由于所有的绘制内容都没有变成最终的像素,所以它们之间是相互独立的,那么在界面内容发生改变的时候,只要把发生了改变的 View 调用 invalidate() 方法以更新它所对应的 GPU 操作就好,至于它的父 View 和兄弟 View,只需要保持原样。那么这个工作量就很小了。

正是由于上面的原因,硬件加速不仅是由于 GPU 的引入而提高了绘制效率,还由于绘制机制的改变,而极大地提高了界面内容改变时的刷新效率。

所以把上面的三条压缩总结一下,硬件加速更快的原因有两条:

  1. 用了 GPU,绘制变快了;
  2. 绘制机制的改变,导致界面内容改变时的刷新效率极大提高。

3.8.3 限制

如果仅仅是这样,硬件加速只有好处没有缺陷,那大家都不必关心硬件加速了,这篇文章也不会出现:既然是好东西就用呗,关心那么多原理干吗?

可事实就是,硬件加速不只是好处,也有它的限制:受到 GPU 绘制方式的限制,Canvas 的有些方法在硬件加速开启时会失效或无法正常工作。比如,在硬件加速开启时, clipPath() 在 API 18 及以上的系统中才有效。具体的 API 限制和 API 版本的关系如下图:

006tKfTcly1fjn0huxdm5j30lr0q0n25.jpg

所以,如果你的自定义控件中有自定义绘制的内容,最好参照一下这份表格,确保你的绘制操作可以正确地在所有用户的手机里能够正常显示,而不是只在你的运行了最新版本 Android 系统的 Nexus 或 Pixel 里测试一遍没问题就发布了。小心被祭天。

不过有一点可以放心的是,所有的原生自带控件,都没有用到 API 版本不兼容的绘制操作,可以放心使用。所以你只要检查你写的自定义绘制就好。

3.8.4 View Layer

在之前几期的内容里我提到过几次,如果你的绘制操作不支持硬件加速,你需要手动关闭硬件加速来绘制界面,关闭的方式是通过这行代码:

view.setLayerType(LAYER_TYPE_SOFTWARE, null);

有不少人都有过疑问:什么是 layer type?如果这个方法是硬件加速的开关,那么它的参数为什么不是一个 LAYER_TYPE_SOFTWARE 来关闭硬件加速以及一个 LAYER_TYPE_HARDWARE 来打开硬件加速这么两个参数,而是三个参数,在 SOFTWARE 和 HARDWARE 之外还有一个 LAYER_TYPE_NONE?难道还能既不用软件绘制,也不用硬件绘制吗?

事实上,这个方法的本来作用并不是用来开关硬件加速的,只是当它的参数为 LAYER_TYPE_SOFTWARE 的时候,可以「顺便」把硬件加速关掉而已;并且除了这个方法之外,Android 并没有提供专门的 View 级别的硬件加速开关,所以它就「顺便」成了一个开关硬件加速的方法。

setLayerType() 这个方法,它的作用其实就是名字里的意思:设置 View Layer 的类型。所谓 View Layer,又称为离屏缓冲(Off-screen Buffer),它的作用是单独启用一块地方来绘制这个 View ,而不是使用软件绘制的 Bitmap 或者通过硬件加速的 GPU。这块「地方」可能是一块单独的 Bitmap,也可能是一块 OpenGL 的纹理(texture,OpenGL 的纹理可以简单理解为图像的意思),具体取决于硬件加速是否开启。采用什么来绘制 View 不是关键,关键在于当设置了 View Layer 的时候,它的绘制会被缓存下来,而且缓存的是最终的绘制结果,而不是像硬件加速那样只是把 GPU 的操作保存下来再交给 GPU 去计算。通过这样更进一步的缓存方式,View 的重绘效率进一步提高了:只要绘制的内容没有变,那么不论是 CPU 绘制还是 GPU 绘制,它们都不用重新计算,而只要只用之前缓存的绘制结果就可以了。

多说一句,其实这个离屏缓冲(Off-screen Buffer),更准确的说应该叫做离屏缓存(Off-screen Cache)会更合适一点。原因在上面这一段里已经说过了,因为它其实是缓存而不是缓冲。(这段话仅代表个人意见)

基于这样的原理,在进行移动、旋转等(无需调用 invalidate())的属性动画的时候开启 Hardware Layer 将会极大地提升动画的效率,因为在动画过程中 View 本身并没有发生改变,只是它的位置或角度改变了,而这种改变是可以由 GPU 通过简单计算就完成的,并不需要重绘整个 View。所以在这种动画的过程中开启 Hardware Layer,可以让本来就依靠硬件加速而变流畅了的动画变得更加流畅。实现方式大概是这样:

view.setLayerType(LAYER_TYPE_HARDWARE, null);

ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);


animator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        view.setLayerType(LAYER_TYPE_NONE, null);
    }
});

animator.start();

或者如果是使用 ViewPropertyAnimator,那么更简单:

view.animate()
    .rotationY(90)
    .withLayer(); // withLayer() 可以自动完成上面这段代码的复杂操作

不过一定要注意,只有你在对 translationX translationY rotation alpha 等无需调用 invalidate() 的属性做动画的时候,这种方法才适用,因为这种方法本身利用的就是当界面不发生时,缓存未更新所带来的时间的节省。所以简单地说——

这种方式不适用于基于自定义属性绘制的动画。一定记得这句话。

另外,除了用于关闭硬件加速和辅助属性动画这两项功能外,Layer 还可以用于给 View 增加一些绘制效果,例如设置一个 ColorMatrixColorFilter 来让 View 变成黑白的:

ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setSaturation(0);
Paint paint = new Paint();
paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
view.setLayerType(LAYER_TYPE_HARDWARE, paint);

另外,由于设置了 View Layer 后,View 在初次绘制时以及每次 invalidate() 后重绘时,需要进行两次的绘制工作(一次绘制到 Layer,一次从 Layer 绘制到显示屏),所以其实它的每次绘制的效率是被降低了的。所以一定要慎重使用 View Layer,在需要用到它的时候再去使用。

3.8.5 总结

硬件加速指的是使用 GPU 来完成绘制的计算工作,代替 CPU。它从工作分摊和绘制机制优化这两个角度提升了绘制的速度。

硬件加速可以使用 setLayerType() 来关闭硬件加速,但这个方法其实是用来设置 View Layer 的:

  1. 参数为 LAYER_TYPE_SOFTWARE 时,使用软件来绘制 View Layer,绘制到一个 Bitmap,并顺便关闭硬件加速;
  2. 参数为 LAYER_TYPE_HARDWARE 时,使用 GPU 来绘制 View Layer,绘制到一个 OpenGL texture(如果硬件加速关闭,那么行为和 VIEW_TYPE_SOFTWARE 一致);
  3. 参数为 LAYER_TYPE_NONE 时,关闭 View Layer。

View Layer 可以加速无 invalidate() 时的刷新效率,但对于需要调用 invalidate() 的刷新无法加速。

View Layer 绘制所消耗的实际时间是比不使用 View Layer 时要高的,所以要慎重使用。

4. 复制攻略

4.1 《Adnroid开发艺术探索》