Android基础动画粗讲

为什么要学习动画

当然是因为COOL啊

用户对产品的体验是由多种要素构成的。

在APP开发过程中,影响产品最终形象的元素有很多,而动画效果一直是其中不可或缺的一部分。

好的动画效果不仅可以在单一界面中容纳更多信息传达并反馈更多的状态 (更炫酷);
还可以引导用户对产品的交互,促进用户对信息的理解,使用产品时更加流畅 (更简单)。

动画框架

Android的动画本来有两种:补间动画(Tween Animation)和逐帧动画(Frame Animation),并统称为视图动画(View Animation),在Android3.0(API 11)之后又加入了属性动画 (Property Animation)。

graph LR A[Animation]-->D[View Animation] A-->E[Property Animation] D-->C[Frame Animation] D-->B[Tween Animation]

视图动画(View Animation)

逐帧动画(Frame Animation)

简单来说,逐帧动画就是将一组预先准备好的图片通过控制依次显示并循环播放(参考幻灯片),从而造成一种动画效果。

通常逐帧动画有两种实现方式: XML 和 Java 。

用XML方法实现

首先,创建XML文件并加入每帧素材

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot= "false" >
<item android:drawable="@drawable/type1" android:duration="150" />
<item android:drawable="@drawable/type2" android:duration="150" />
    </animation-list>
  • android:oneshot 代表是否循环(true 为不循环,false 为循环)
  • drawable代表加入的图片素材,而duration代表该素材一帧的持续时长(ms)

然后,将上述XML文件(frame.xml)作为ImageView(image)的图像,并调用AnimationDrawable类获取该图像生成的实例

private ActivityMainBinding binding;
private AnimationDrawable frame;
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   binding = ActivityMainBinding.inflate(getLayoutInflater());
   setContentView(binding.getRoot());
   binding.image.setImageResource(getResources().getIdentifier("frame","drawable",getPackageName()));
   frame = (AnimationDrawable) binding.image.getDrawable();//获取实例
   binding.Start.setOnClickListener(view -> {frame.start();});
   binding.Stop.setOnClickListener(view -> {frame.stop();});
}

效果如下:

frame

用Java方法实现

Java代码实现逐帧动画和xml类似,相当于使用Java代码获取AnimationDrawable实例

private ActivityMainBinding binding;
private AnimationDrawable frame;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    binding = ActivityMainBinding.inflate(getLayoutInflater());
    setContentView(binding.getRoot());
    frame = new AnimationDrawable();
  	for (int i = 1 ,id;i <= 2; i ++ ) {
         id = getResources().getIdentifier("type" + i, "drawable", getPackageName());
         frame.addFrame(getResources().getDrawable(id),150);
    }//相当于创建frame.xml
   frame.setOneShot(false);//设置是否循环
   binding.image.setImageDrawable(frame);
   binding.Start.setOnClickListener(view -> {frame.start();});
   binding.Stop.setOnClickListener(view -> {frame.stop();});
}

逐帧动画的优缺点

优点:代码量小,使用较为方便、简单
缺点:动画的变化基于图片的切换,越是流畅的动画越需要更多的图片,从而占用了大量内存。所以,使用逐帧动画时一定要注意图片的大小和数目!

补间动画(Tween Animation)

补间动画是通过已确定的开始视图样式和结束视图样式,并由系统计算来补全其中间动画变化过程的动画

补间动画同样支持XML和Java两种方法,这里我推荐使用更易读、可切换、可重复使用的XML方法

Android支持的补间动画效果有以下4种:旋转(rotate)、位移(translate)、缩放(scale)和透明度(alpha)

动画名 对于Animation子类 res/anim/目录下xml标签 动画效果
缩放 ScaleAnimation <scale> 进行特定范围的缩放
透明度 AlphaAnimation <alpha> 改变透明度,实现隐现
位移 TranslateAnimation <translate> 进行位置的移动
旋转 RotateAnimation <rotate> 围绕特定的点进行旋转
复合 AnimationSet <set> 复合使用上述四种效果

上述动画的类均继承自Animation类,因此存在一些通用的动画属性和方法

xml属性 对应Java方法 效果
android:duration setDuration(long) 动画持续时间(ms)
android:fillAfter setFillAfter(boolean) 保持动画结束时的状态,默认ture
android:fillBefore setFillBefore(boolean) 还原到动画开始时的状态,默认为false
android:repeatCount setRepeatCount(int) 动画重复执行的次数
android:repeatMode setRepeatMode(int) 动画重复模式 ,restart从头开始,reverse倒叙回放
android:startOffset setStartOffset(long) 动画开始前延迟的时间(ms)
android:interpolator setInterpolator(Interpolator) 插值器,改变动画的不同阶段的执行速度

这里的插值器(interpolator)在属性动画中较为常用,我们将在后面细说。

透明度动画(Alpha)

xml方法代码

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:fromAlpha="1.0"
    android:toAlpha="0.0"
    />
 animation = AnimationUtils.loadAnimation(this,R.anim.alpha_anim);

对应Java方法代码

animation = new AlphaAnimation(1,0);
animation.setDuration(4000);
binding.tween.startAnimation(animation);

这里的AlphaAnimation(float,float)的两个值域为 [0,1] 的参数分别对应

XML中的android:fromAlpha(初始透明度) 和 android:toAlpha(终止透明度)

且值越接近0,透明度越高

效果如下:

[查看图片]

旋转动画(Rotate)

xml方法代码

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:toDegrees="180"
    android:pivotY="50%"
    android:pivotX="50%"
    android:duration="2000"
/>

对应Java方法代码

animation = new RotateAnimation(0,180,Animation.RELATIVE_TO_SELF,0.50f,Animation.RELATIVE_TO_SELF,0.50f);
//RotateAnimation(fromDegrees,toDegrees,pivotXType,pivotX,pivotYType,pivotY)
animation.setDuration(1000);

显然,RotataAnimation()的前两个参数分别对应 android:fromDegrees(起始角度) 和 android:toDegrees(最终角度)

而这里多出来的两个常数参数 pivotXTypepivotYType分别指定了如何去解释pivotX和pivotY

Animation.ABSOLUTE Animation.RELATIVE_TO_SELF Animation.RELATIVE_TO_PARENT
指定尺寸是一个绝对像素数量。使用之后pivotX 或pivotY 的值都是绝对像素数量(px)。 指定尺寸是一个浮点数,用这个浮点数来乘动画对象的宽或高。如使用之后动画最终的x轴 =(pivotX 的值) * (动画对象的宽),即百分数 指定尺寸是一个浮点数,用这个浮点数来乘动画父对象的宽或高。如使用之后动画最终的x轴 =(pivotX 的值) * (动画父对象的宽),即百分数

效果如下:

[查看图片]

缩放动画(Scale)

xml方法代码

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromXScale="0.0"
    android:fromYScale="0.0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toXScale="1.0"
    android:toYScale="1.0"
/>

对应Java方法代码

animation = new ScaleAnimation(0,1,0,1,Animation.RELATIVE_TO_SELF,0.50f,Animation.RELATIVE_TO_SELF,0.50f);
//ScaleAnimation(fromXScale,toXScale,fromYScale,toYScale,pivotXType,pivotX,pivotYType,pivotY)
animation.setDuration(1000);

这里的fromXScaletoXScale分别确定了开始和结尾的缩放比例,而pivotX确定了缩放的中心

效果如下:

[查看图片]

位移动画(Translate)

xml方法代码

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromXDelta="0"
    android:fromYDelta="0"
    android:toXDelta="0%"
    android:toYDelta="130%"
/>

对应Java方法代码

animation = new TranslateAnimation(Animation.RELATIVE_TO_SELF,0,1,0,1,0,1,1.30f);
//TranslateAnimation(fromXType,fromXDelta,toXType,toXDelta,fromYType,fromYDelta,toYType,toYDelta)
//这里为了控制代码长度,将Animation.RELATIVE_TO_SELF换成其对应值1
//还有一种常用的动画 TranslateAnimation(fromXDelta,toXDelta,fromYDelta,toYDelta)
//这时Delta代表的是坐标值
animation.setDuration(1000);

位移动画(Translate)的参数与旋转动画(Rotate)中的使用方法一致,不同的fromXType有不同的fromXDelta解释与之对应。

但要注意,Translate的百分数位置起始点是其左上角,即当fromDelta为0时,图像从左上角出发

效果如下:

[查看图片]

复合动画(Set)

顾名思义,Set动画即前几个动画的复合使用

xml方法代码

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/linear_interpolator"
    android:shareInterpolator="true"
    android:duration="2000"
    >
    <alpha
        android:fromAlpha="0.5"
        android:toAlpha="1.0"
        />
    <scale
        android:fromXScale="0.0"
        android:fromYScale="0.0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="1.0"
        android:toYScale="1.0"
        />
    <translate
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:toXDelta="0%"
        android:toYDelta="100%"
        />
    <rotate
        android:fromDegrees="0"
        android:toDegrees="360"
        android:pivotY="50%"
        android:pivotX="50%"
        android:repeatCount="100"
    />
</set>

对应Java方法代码

AnimationSet animationSet = new AnimationSet(true);
//AnimationSet(shareInterpolator)
animationSet.addAnimation(animation);
//只需将之前的其他动画的animation “加入” animationSet即可
binding.tween.startAnimation(animationSet);

AnimationSet(shareInterpolator)中的shareInterpolator是一个boolean类型的值

其为true时代表所有子动画共用一个Interpolator , linear_interpolator代表动画变化均匀

效果如下:

[查看图片]

Tween Animation的监听

动画监听器可以对动画的执行状态进行监听,并通过重写进行一系列操作

Animation.addListener(new AnimatorListener() {
          @Override
          public void onAnimationStart(Animation animation) {
              //监听动画开始
              //对应animation.start()
          }
      
           @Override
          public void onAnimationRepeat(Animation animation) {
              //监听动画重复
          }

         @Override
          public void onAnimationCancel()(Animation animation) {
              //监听动画被取消
              //对应animation.cancel()
          }
    
          @Override
          public void onAnimationEnd(Animation animation) {
              //监听动画执行结束
          }
      });

补间动画的优缺点

优点:使用简单,效果流畅,相比于逐帧动画来说不用准备大量素材

缺点:作用于视图对象View,未作用于属性。即所有View的移动、隐藏、旋转仅仅是看到的动画效果,实际View的位置/大小/比例并没有发生本质上的改变。

属性动画(Property Animation)

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

为什么要提供属性动画

API 11之前的动画为视图动画(View Animation),仅仅作用于视图对象View,未作用于属性,且其动画效果过于单一。

为了弥补视图动画的不足,实现补间动画无法实现的功能,安卓团队开发了具备更多功能和高拓展性的属性动画。

属性动画系统是一个强健的框架,用于为几乎任何内容添加动画效果。

属性动画的工作逻辑

属性动画是通过在指定时长内将对象的某一属性动态地由一个值变化到另一个值来实现的

graph TB A([开始])--步骤1-->B[设置动画的运行时长 & 动画效果对应的初始值 & 结束值]--步骤2--> C[设置属性值从初始值 过渡到 结束值的变化逻辑] --步骤3-->D[根据上述变化逻辑不断改变 当前值]--步骤4-->E[当前值 每改变一次,就赋予对象的属性值一次]--步骤5-->F[每次赋值调用invalidate函数刷新视图]--若当前值 == 结束值-->G([结束]) F--若当前值 != 结束值-->D

值得一提,这里的步骤2是通过插值器(Interpolator)估值器(Evaluator)共同完成

步骤4是通过ValueAnimator类ObjectAnimator类来实现的

插值器(Interpolator)

Interpolator通过控制动画过程的变化速率,从而实现动画的非线性运动效果。

这里提供一些内置的插值器

类/接口 XML资源ID(@android:anim/) 效果
AccelerateDecelerateInterpolator accelerate_decelerate_interpolator 变化率在开始和结束时缓慢但在中间会加快(默认插值器)
AccelerateInterpolator accelerate_interpolator 变化率在开始时较为缓慢,然后会加快
DecelerateInterpolator decelerate_interpolator 变化率开始很快,然后减速
AnticipateInterpolator anticipate_interpolator 先反向改变距离,然后再急速正向变化
OvershootInterpolator overshoot_interpolator 急速正向变化,再超出最终值,然后返回
AnticipateOvershootInterpolator anticipate_overshoot_interpolator 先反向变化,再急速正向变化,然后超过定位值,最后返回到最终值
BounceInterpolator bounce_interpolator 快结束时值会跳跃(不连续、不平滑变化)
CycleInterpolator cycle_interpolator 在指定数量的周期内重复,变化速率按正弦曲线改变
LinearInterpolator linear_interpolator 变化率恒定不变
PathInterpolator 自定义 贝赛尔[1]曲线速度
TimeInterpolator 自定义 接口,用于自定义 属性动画 的插值器

这里要注意的是,补间动画和属性动画都会用到Interpolator,但在自定义插值器时,补点动画对应的接口是Interpolator,而属性动画对应的接口是TimeInterpolator

效果图(摘自网络)

IP

AccelerateInterpolator源码 \(y : B(x)=x^{2f}\)

public float getInterpolation(float input) {
    if (mFactor == 1.0f) {
        return input * input;
    } else {
        return (float)Math.pow(input, mDoubleFactor);
    }
}
PathInterpolator

PathInterpolator 类是 Android 5.0 (API 21) 中引入的新插值器。它基于贝塞尔曲线[1:1]Path 对象。

由于本文以动画为主,所以Path类的使用可以移步至Android开发之Path详解,这里仅讲述贝塞尔曲线

我们将动画的初始状态到动画的结束状态定义为一条 (0,0) 到 (1,1) 的曲线 。其中 x轴代表时间,y轴代表对应的动画状态

An interpolator that can traverse a Path that extends from Point(0, 0) to (1, 1). The x coordinate along the Path is the input value and the output is the y coordinate of the line at that point.

简而言之,PathInterpolator的曲线是 动画状态对于时间的一个函数

This means that the Path must conform to a function y = f(x).

在PathInterpolator类中,给出了二阶贝塞尔曲线和三阶贝塞尔曲线。

二阶 \(y : B(x) = (1-x)^2P_0+2x(1-x)P_1+x^2P_2 , x\in[0,1]\)

三阶 \(y: B(x)=(1-x)^3P_0+3x(1-x)^2P_1+3x^2(1-x)P_2+x^3P_3 , x\in[0,1]\)

<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
    android:controlX1="0.4"
    android:controlY1="0.4"
    android:controlX2="0.8"
    android:controlY2="0.8"/>
<!--    只有一对坐标时便是二阶曲线 -->

对应Path方法

//创建一个任意Path的插值器
Path path = new Path();
path.moveTo(0,0);


//创建一个二阶贝塞尔曲线的插值器
//PathInterpolator(float controlX, float controlY)
path.quadTo(controlX1, controlY1, 1f, 1f);


//创建一个三阶贝塞尔曲线的插值器
//PathInterpolator(float controlX1, float controlY1, float controlX2, float controlY2)
path.cubicTo(controlX1, controlY1,controlX2, controlY2, 1f, 1f);

PathInterpolator(Path path)
//当其他Path路径满足一个 (0,0) 到 (1,1) 的函数时,也可使用。
自定义Interpolator

TimeInterpolator接口中只有一个方法public abstract float getInterpolation (float input)\(input\in[0,1]\),所以我们只需要重写这一个方法即可

public class MyInterpolator implements TimeInterpolator {
    @Override
    public float getInterpolation(float v) {
        //f(v) = ....
        return f(v);
    }
}

估值器(Evaluator)

前面我们提到EvaluatorInterpolator共同控制动画的属性变化,Interpolator决定属性值随时间变化的规律;而具体变化属性数值则交给Evaluator

Evaluator对应接口(TypeEvalutor)
public interface TypeEvaluator<T> {
    public T evaluate(float fraction, T startValue, T endValue);
}

让我们具体分析一下这个接口

  • TypeEvaluator是一个泛型接口,即可以对任意属性进行控制
  • evaluate() 泛型方法
  • franction 动画属性值的进度,对应Interpolator中的getInterpolator函数的返回值
  • startValue 动画的初始值
  • endValue 动画的结束值

同样,系统也内置了几个Evaluator类

IntEvaluator FloatEvaluator ArgbEvaluator
返回int类型的属性 返回Float类型属性 颜色类型估值器,返回16进制颜色值

ArgbEvaluator源码:

public class ArgbEvaluator implements TypeEvaluator {  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        int startInt = (Integer) startValue;  
        int startA = (startInt >> 24);  
        int startR = (startInt >> 16) & 0xff;  
        int startG = (startInt >> 8) & 0xff;  
        int startB = startInt & 0xff;  
  
        int endInt = (Integer) endValue;  
        int endA = (endInt >> 24);  
        int endR = (endInt >> 16) & 0xff;  
        int endG = (endInt >> 8) & 0xff;  
        int endB = endInt & 0xff;  
  
        return (int)((startA + (int)(fraction * (endA - startA))) << 24) |  
                (int)((startR + (int)(fraction * (endR - startR))) << 16) |  
                (int)((startG + (int)(fraction * (endG - startG))) << 8) |  
                (int)((startB + (int)(fraction * (endB - startB))));  
    }  
}  
自定义Evaluator(实现接口中的方法即可)
public class MyEvaluator implements TypeEvaluator {  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        object NowProperty = .....//获得当前属性
        return NowProperty;
    }  
}  
Evaluator的使用

Evaluator的使用与Interpolator相似,调用ValueAnimator.setEvaluator(Evaluator)方法即可,但是属性的改变还是由ValueAnimator类实现,我们将在下面介绍ValueAnimator

ValueAnimator类

This class provides a simple timing engine for running animations which calculate animated values and set them on target objects.There is a single timing pulse that all animations use. It runs in a custom handler to ensure that property changes happen on the UI thread.

简而言之,ValueAnimator便是根据时间变化从而改变属性值的那个类,但其不会直接实现动画效果,而是需要通过对动画的监听做一些操作,在监听中赋予动画对应的属性,

下面是属性动画的结构,视图动画的基类是Animation,而属性动画的基类是Animator

graph TB A[Animator]-->B[AnimatorSet] A-->C[ValueAnimator] C-->D[ObjectAnimator] C-->E[TimeAnimator]
  • ObjectAnimator:直接动画所给的对象,他会调用对象属性的get/set方法把属性的值设置给对象的属性,直接实现动画效果
  • TimeAnimator:这个也不直接实现动画效果,只是提供一个监听回调,返回动画执行的总时间,距离上次动画执行的时间等
  • AnimatorSet:可以指定一组动画的执行顺序,让它们可以一起执行,顺序执行,延迟执行
ValueAnimator对属性的修改

ValueAnimator类中提供了以下方法用于完成属性值的过渡

public static ValueAnimator ofInt(int... values) {} 
public static ValueAnimator ofArgb(int... values) {}
public static ValueAnimator ofFloat(float... values) {}
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {}
//我们只需设置对应属性值,动画的属性就能在设置的属性值间过渡
//e.g. ValueAnimator.ofInt(1,10,110,1) 动画值变会从1过渡到10,再到100,最后到1

由于ValueAnimator无法直接使用动画效果,我们必须在监听中完成修改

valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
   	 @Override
  	 public void onAnimationUpdate(ValueAnimator valueAnimator) {
  	}
});

//下面是Animator类的监听
animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {
                
            }

            @Override
            public void onAnimationEnd(Animator animator) {

            }

            @Override
            public void onAnimationCancel(Animator animator) {

            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
自定义属性动画的过渡变化
ValueAnimator.ofObject(new MyEvaluator,new Myclass(0),new Myclass(1));
//这里只需要传入自定义的类和其对应的自定义估值器,便可实现属性值的过渡

我们这里来实现图片的移动

valueAnimator = ValueAnimator.ofObject(new MyEvaluator(),new Point(0,0),new Point(screenWidth,screenHeight));
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setDuration(2000);
binding.Point1.setOnClickListener(view -> {valueAnimator.start();});
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
     @Override
   	   public void onAnimationUpdate(ValueAnimator valueAnimator) {
   	   Point point = (Point) valueAnimator.getAnimatedValue();
   	   binding.Point1.setX(point.X);
       binding.Point1.setY(point.Y);
   	 }
});
//顺便一提,ValueAnimator和TweenAnimation有许多方法都是相同的,这里便不再提及

效果如下:

[查看图片]

我们可以看到不仅视图移动了,其实整个控件都移动了

ObjectAnimator类

不同于ValueAnimator类,ObjectAnimator是可以直接对任意对象的任意属性进行动画操作的,比如说View的alpha属性

并且,作为ValueAnimator的子类,ObjectAnimator中也有ValueAnimator的方法

ObjectAnimation对属性的改变(以ofInt为例)
public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {}

这里面有三类参数

  • target : 对象,指定要改变谁的属性

  • propertyName : 属性名,指定要改变对象的什么属性,这个属性名要求在对象中必须有对应的public的get/set"PropertyName"的方法。如改变透明度就要求对象中必须有setAlpha方法才行,常用的有ScaleX/Y,TranlationX/Y,Rotation

  • values : 和ValueAnimator.ofInt的值定义一样

e.g.改变文字颜色

objectAnimator = ObjectAnimator.ofInt(binding.TouchText,"TextColor",0xffffffff,0xffff0000,0xffffffff);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.setDuration(2000);
objectAnimator.setRepeatCount(-1);
binding.TouchText.setOnClickListener(view -> {objectAnimator.start();});

效果如下:

[查看图片]

属性动画有些可以链式调用,所以上述代码也可以写成

ObjectAnimator.ofInt(binding.TouchText,"TextColor",0xffffffff,0xffff0000,0xffffffff).setDuration(2000).start();
//setInterpolator和setRepeatCount无法链式调用
自定义ObjectAnimator

我们之前提到过,propertyName必须要有对应的get/set方法,所以在自定义的View中实现set/get方法并且自定义对应属性的估值器,就可以实现自定义动画

public class MyView extends View {
 
	...
 
	private object MyProperty;
 
	public object getMyProperty() {
		return MyProperty;
	}

	public void setMyProperty(object MyProperty) {
		this.MyProperty = MyProperty;
		......//对属性赋值
		invalidate();//更新动画
	}
 
	...
 
}
objectAnimator = ObjectAnimator.ofObject(MyView,"MyProperty",value...);
objectAnimator.setInterpolator(...);
objectAnimator.setEvaluator(new MyEvaluator());
objectAnimator.start();

AnimatorSet混合动画

AnimatorSet可以按指定顺序播放一组Animator对象

向AnimatorSet添加动画有两种不同的方法:

  • 调用playTogether()或playSequentially()方法一次添加一组动画
  • 调用play(Animator)与Builder类一个一个添加动画
playTogether()/playSequentially()方法添加
objectAnimator1 = ObjectAnimator.ofInt(binding.TouchText,"TextColor",0xffffffff,0xffff0000,0xffffffff).setDuration(2000);
objectAnimator2 = ObjectAnimator.ofFloat(binding.TouchText,"TranslationY",1,2000,1).setDuration(9000);
objectAnimator3 = ObjectAnimator.ofFloat(binding.TouchText,"RotationX",1,2000,1).setDuration(2000);
animatorSet.playTogether(objectAnimator1,objectAnimator2);
animatorSet.playSequentially(objectAnimator3);
binding.TouchText.setOnClickListener(view -> {animatorSet.start();});

playTogether()中对不同动画对同一个属性进行修改时,由于动画同时发生,后面会覆盖前面的动画。

playSequentially()是一个动画执行完后执行下一个动画,但如果前一个动画是无限循环,下一个动画永远不会执行。

利用play(Animator)构建Builder对象
public AnimatorSet.Builder play(Animator anim){}
public class Builder {
    Builder() {}
    public AnimatorSet.Builder with(Animator anim) {}
    public AnimatorSet.Builder before(Animator anim) {}
    public AnimatorSet.Builder after(Animator anim) {}
    public AnimatorSet.Builder after(long delay) {}
}
Public Methods 效果
after(long) 设置创建此 Builder对象的 play(Animator)调用中提供的动画,以在给定的时间量过去时播放
after(Animator) 当在此方法调用中提供的动画结束时,创建此对象的 Builder对象的 play(Animator)调用中提供的动画开始时,设置给定动画以播放
before(Animator) 当创建此 Builder对象的 play(Animator)调用中提供的动画结束时,设置给定动画以播放
with(Animator) 设置给定动画以与创建此 Builder对象的 play(Animator)调用中提供的动画同时播放

上述代码也可以改为

animatorSet.play(objectAnimator1).with(objectAnimator2).before(objectAnimator3);
//所有的Builder内方法的顺序都是相对于 play(Animator)来说的

简洁的ViewPropertyAnimator

我们都知道,属性动画是一种不断地对值进行操作的动画

但在绝大多数情况下,我们主要都还是对View进行动画操作的

因此Google官方在Android 3.1系统中补充了ViewPropertyAnimator类

ViewPropertyAnimator是专门针对View对象动画而操作的类,通过View的animate()获取实例对象;

 /**
 * This method returns a ViewPropertyAnimator object, which can be used to animate
 * specific properties on this View.
 *
 * @return ViewPropertyAnimator The ViewPropertyAnimator associated with this View.
 */
public ViewPropertyAnimator animate() {
    if (mAnimator == null) {
        mAnimator = new ViewPropertyAnimator(this);
    }
    return mAnimator;
}

ViewPropertyAnimator提供了更简洁的链式调用来设置多个属性动画,这些动画是同时进行的。

1

并且,每个属性都提供两种类型方法设置,代表直接设置属性和属性变化量

 private void animateProperty(int constantName, float toValue) {
        float fromValue = getValue(constantName);
        float deltaValue = toValue - fromValue;
        animatePropertyBy(constantName, fromValue, deltaValue);
    }

 private void animatePropertyBy(int constantName, float byValue) {
        float fromValue = getValue(constantName);
        animatePropertyBy(constantName, fromValue, byValue);
    }

e.g.

objectAnimator = ObjectAnimator.ofFloat(binding.TouchText,"TranslationY",2000).setDuration(2000);
objectAnimator2 = ObjectAnimator.ofFloat(binding.TouchText,"RotationX",2000).setDuration(2000);
animatorSet.playTogether(objectAnimator,objectAnimator2);
animatorSet.start();

可以简化成:

binding.TouchText.animate().translationY(2000).rotationX(2000).setDuration(2000);

总结

名称 视图动画(ViewAnimation) 属性动画(PropertyAnimation)
Animation Animator
作用对象 View(视图) Property(属性)
拓展性 仅支持四类动画 可对任意值进行过渡变化
适合写法 XML Java
优点 简单易用 泛用性广
缺点 效果固定,可操作性低 难复用,复杂动画代码量大(封装解决)

推荐文章:

Interpolators详解

Android开发之Path详解

矢量动画(Scalable Vector Graphics)

不同于前面的为控件做动画效果的方法,矢量动画则是为图形做出动画效果

矢量图

矢量图不同于位图是用像素描述图像的,它是用数学曲线描述图形,一张图片就对应一系列的曲线组合。

所以,矢量图既减小了图像体积,又不会随图片放大而失真

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">
    <path
        android:pathData="M20.84,4.61a5.5,5.5 0,0 0,-7.78 0L12,5.67l-1.06,-1.06a5.5,5.5 0,0 0,-7.78 7.78l1.06,1.06L12,21.23l7.78,-7.78 1.06,-1.06a5.5,5.5 0,0 0,0 -7.78z"
        android:strokeLineCap="round"
        android:strokeColor="#ED4337"
        android:fillColor="#00000000"
        android:strokeWidth="2"
        android:strokeLineJoin="round"/>
</vector>

2

新人任务用过的矢量图,可以看到这里图片的绘制都是在pathData中,下面讲一下pathData的语法

命令 效果 语法
M 新建起点 Mx,y
L 连接直线 Lx,y
H 纵坐标不变,横向连线 Hx
V 横坐标不变,纵向连线 Vy
Q 二次贝塞尔曲线 Qx1,y1,x2,y2
C 三次贝塞尔曲线 Cx1,y1,x2,y2,x3,y3
Z 连接首尾,闭合曲线

看看就好,毕竟画图是Design的事情,我们只需通过Android Studio的Vector Asset Studio工具导入获取一张矢量图,但我们还是要对SVG的属性有一定的了解

名称 标签 效果
strokeAlpha path 定义路径边框的透明度
strokeWidth path 定义路径边框的粗细尺寸
strokeColor path 定义路径边框的颜色
fillColor path 定义填充路径的颜色
fillAlpha path 定义填充路径的颜色透明度
pathData path 定义路径形状,可用来做morphing动效
trimPathStart path 从路径起始位置截断路径的比率,取值范围从 0 到1
trimPathEnd path 从路径结束位置截断路径的比率,取值范围从 0 到1
trimPathOffset path 设置路径截取的范围,取值范围从0到1
rotation group 定义旋转角度
pivotX group 定义缩放和旋转时的 X 参考点
pivotY group 定义缩放和旋转时的 Y 参考点
scaleX group 定义 X 轴的缩放倍数
scaleY group 定义 Y 轴的缩放倍数
translateX group 定义 X 轴方向位移
translateY group 定义 Y 轴方向位移
将矢量图形动画化

由于本文讲述的是动画,那么如何用矢量图片做出动画效果便是重中之重,当然你可以用逐帧动画的方法,不过这里有更好的animated-vector

我们新建ImageView

<ImageView
    android:id="@+id/Point2"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:src="@drawable/vector_anim"
    />

再在drawable目录下新建vector_anim.xml

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/key" >
    <target
        android:name="rotationGroup"
        android:animation="@anim/rotation" />
<!--    旋转360度-->
    <target
        android:name="v"
        android:animation="@anim/path_morph" />
<!--    从一个path变换到另一个path-->
</animated-vector>

新建key.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:height="300dp"
    android:width="300dp"
    android:viewportHeight="70"
    android:viewportWidth="70" >
<!--        通过控制path和group的属性变化来实现动画效果-->
    <group
        android:name="rotationGroup"
        android:pivotX="35"
        android:pivotY="35"
        android:rotation="0.0" >
        <path
            android:name="v"
            android:fillColor="#023040"
            android:pathData="M10,10 L60,10 L60,20 L10,20 Z M10,30 L60,30
                L60,40 L10,40 Z M10,50 L60,50 L60,60 L10,60 Z" />
    </group>
</vector>

anim目录下新建rotation.xmlpath_morph.xml

<!-- rotation.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <objectAnimator
        android:duration="1500"
        android:propertyName="rotation"
        android:valueFrom="0"
        android:valueTo="360" />
</set>
<!-- path_morph.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:duration="1500"
        android:propertyName="pathData"
        android:valueFrom="M10,10 L60,10 L60,20 L10,20 Z M10,30 L60,30 L60,40
            L10,40 Z M10,50 L60,50 L60,60 L10,60 Z"
        android:valueTo="M5,35 L40,0 L47.072,7.072 L12.072,42.072 Z M10,30 L60,30 L60,40
            L10,40 Z M12.072,27.928 L47.072,62.928 L40,70 L5,35 Z"
        android:valueType="pathType" />
</set>

valueFromvalueTo分别指定了变换前的path和变换后的path,它要求前后两个path必须是同形path[2]

使用

binding.Point2.setOnClickListener(view -> {
        Drawable drawable = binding.Point2.getDrawable();
        ((Animatable) drawable).start();});
}

效果如下:

[查看图片]

矢量动画虽然可以做出比较炫酷的效果,但其代码量十分冗杂,理解也较为复杂,所以这里我不再讲解更让人难受的animated-selectortrimPath只贴出相关链接Android矢量动画实践

那有没有既炫酷又简单的动画呢?

Lottie

LottieGithub上一个的动画库,其框架可以通过解析json文件来实现动画效果,而无需开发写太多的代码去控制动画

Lottie支持API 16及以上的版本

lottie

我们可以在Free Lottie Animation Files, Tools & Plugins - LottieFiles中获取免费或收费的动画文件

Lottie的使用

首先,我们在build.gradle中添加依赖(截至现在最新版本的lottie是5.0.3)

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

接着添加对应控件,只需将json文件放在src/main/asset目录下

<com.airbnb.lottie.LottieAnimationView
       android:id="@+id/lottie"
       android:layout_width="match_parent"
       android:layout_height="350dp"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       app:lottie_fileName="bicycle.json"
       app:lottie_loop="true"

       />

或者Java调用

LottieAnimationView animationView = (LottieAnimationView) binding.lottie;
animationView.setAnimation("bicycle.json");//在assets目录下的动画json文件名。
animationView.loop(true);//设置动画循环播放
animationView.playAnimation();//播放动画

效果如下:

[查看图片]

属性 功能
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 设置动画依赖的图片资源文件地址

上面是lottieXML中一些属性(推荐使用XML,方便复用)

lottie的其他用法和前面视图动画相似,这里不再细讲,有兴趣的小伙伴可以自己在网上查阅

BYE~


  1. 根据给定的点坐标所绘制出的一条光滑曲线 ↩︎ ↩︎

  2. 拥有相同的命令数,并且对应位置的命令符相同的两个path ↩︎

posted @ 2022-08-31 11:14  MarkDespite  阅读(145)  评论(0)    收藏  举报