属性动画(Property Animation)
宏观定义:指在一定的时间内让某个属性的值不断的发生变化,变化可以是匀速或者变速。
参考来源:https://www.jianshu.com/p/a480ca619dd9
视图动画的文章:https://mp.csdn.net/postedit/84896989
动画的分类如下:
概述
- 视图动画的缺陷:
- 对象的局限性:仅限于View
- 只改变了View的视觉效果,而没有改变View的属性
- 动画效果单一
- 属性动画的特点:
- 作用对象:任意对象,甚至没对象也可以
- 作用方式:改变对象的属性
- 动画效果:按需自定义,不再局限于上述4种基本变换
-
继承关系
继承关系
Java类名 | XML关键字 | 说明 |
---|---|---|
ValueAnimator | <animator> 放置在res/animator/目录下 | 在一个特定的时间里执行一个动画 |
TimeAnimator | 无 | 时序监听回调工具 |
ObjectAnimator | <objectAnimator> 放置在res/animator/目录下 | 一个对象的一个属性动画 |
AnimatorSet | <set> 放置在res/animator/目录下 | 动画集合 |
工作原理
指定时间内,修改属性(对象中对应的字段)的值,以此实现该对象在属性上的动画效果。
为了更好的理解,我们举一个栗子:
图1.linear animation
图1中我们搞出了一个假象的对象,动画作用于这个对象(实际可以说是对象的x属性,也即对象的水平位置),动画持续40ms,移动距离40px。每10ms(默认刷新速率),对象水平移动10px。40ms后,动画结束,物体停止在x=40处。这是一个典型的设置了linearInterpolator(匀速插值器)的动画。
为了更好的了解属性动画的工作原理,下面我们来看一看属性动画的组件是怎么计算上面例子的动画的。
图2.动画计算过程
其逻辑可以总结如下:
- TimeInterpolator实现类:插值器-描述动画的变化速率
- TypeEvaluator实现类:估值器-描述 属性值 变化的具体数值
- 为 ValueAnimator 设置动画的时长,以及对应属性的始 & 末值
- 设置属性在 始 & 末值 间的变化逻辑
- 根据2中的逻辑更新当前值
- 获取3中更新的值 ,修改目标属性值
- 刷新视图。
- 重复4-5,直到 属性值 == 末值
下面给出动画工作的关键类
Java类 | 说明 |
---|---|
ValueAnimator | 动画执行类;核心 |
ObjectAnimator | 动画执行类 |
TimeInterpolator | 时间插值(插值器接口),控制动画变化率 |
TypeEvaluator | 类型估值(估值器接口),设置属性值计算方式,根据属性的 始 & 末值 和 插值 一起计算出当前时间的属性值 |
AnimatorSet | 动画集 |
AnimatorInflater | 加载属性动画的XML文件 |
一些额外的类
Java类 | 说明 |
---|---|
LayoutTransition | 布局动画,为布局的容器设置动画 |
ViewPropertyAnimator | 为View的动画操作提供一种更加便捷的用法 |
PropertyValuesHolder | 保存动画过程中所需要操作的属性和对应的值 |
Keyframe | 控制每个时间段执行的动画距离 |
AnimationListener AnimationUpdateListener AnimatorListenerAdapter | 动画事件的监听 |
具体使用
ValueAnimator
- 属性动画的最核心的类
- 原理:控制值的变化,之后手动赋值给对象的属性,从而实现动画
对于控制的值的不同,Android 提供给我们三种构造方法来实例ValueAnimator对象
- ValueAnimator.ofInt(int... values) -- 整型数值
- ValueAnimator.ofFloat(float... values) -- 浮点型数值
- ValueAnimator.ofObject(TypeEvaluator evaluator, Object... values) -- 自定义对象类型
下面我们一一介绍
ValueAnimator.ofInt()
- 作用:将初始值 以整型数值的形式 过渡到结束值
- 估值器:内置
IntEvaluator
估值器 - 具体使用:
操作 值 的方式分为 XML 方式/ Java 代码方式
方式1: Java 方式
推荐 Java 方式,因为某些时候我们需要动态获取属性的起始值,显然XML方式是不支持动态获取的。
//设置动画 始 & 末值
//ofInt()两个作用:
//1. 获取实例
//2. 在传入参数之间平滑过渡
//如下则0平滑过渡到3
ValueAnimator animator = ValueAnimator.ofInt(0,3);
//如下传入多个参数,效果则为0->5,5->3,3->10
//ValueAnimator animator = ValueAnimator.ofInt(0,5,3,10);
//设置动画的基础属性
animator.setDuration(5000);//播放时长
animator.setStartDelay(300);//延迟播放
animator.setRepeatCount(0);//重放次数
animator.setRepeatMode(ValueAnimator.RESTART);
//重放模式
//ValueAnimator.START:正序
//ValueAnimator.REVERSE:倒序
//设置更新监听
//值 改变一次,该方法就执行一次
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//获取改变后的值
int currentValue = (int) animation.getAnimatedValue();
//输出改变后的值
Log.d("1111", "onAnimationUpdate: " + currentValue);
//改变后的值发赋值给对象的属性值
view.setproperty(currentValue);
//刷新视图
view.requestLayout();
}
});
//启动动画
animator.start();
以上就是一个标准的Java方式的模板
方式2: XML 方式
- 在路径
res/animator/
路径下常见 XML 文件,如set_animator.xml
- 在上述文件中设置动画参数
// ValueAnimator采用<animator> 标签
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:repeatCount="1"
android:repeatMode="reverse"/>
/>
- Java代码启动动画
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.set_animation);
// 载入XML动画
animator.setTarget(view);
// 设置动画对象
animator.start();
// 启动动画
ValueAnimator.ofFloat()
-
作用:将初始值 以浮点型数值的形式 过渡到结束值
-
估值器:内置
FloatEvaluator
估值器 - 具体使用:
和ValueAnimator.ofInt()及其类似,以下只说明不同之处,省略部分参考ofInt()
方式1:Java方式
ValueAnimator anim = ValueAnimator.ofFloat(0, 3);
//只是改了实例方法,除此之外完全一样
方式2:XML方式
只在设置动画 XML 文件中的属性时略有不同
// ValueAnimator 采用 <animator> 标签
// ObjectAnimator 采用 <objectAnimator> 标签
<animatorxmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:valueTo="200"
android:valueType="floatType"
android:propertyName="y"
android:repeatCount="1"
android:repeatMode="reverse"/>
ValueAnimator.ofObject()
- 作用:将初始值 以对象的形式 过渡到结束值
- 估值器:Android 不提供,需要自定义估值器
- 具体使用:
ValueAnimator.ofObject() 属于 ValueAnimator 的高级用法,我们前面提到的对任意对象进行动画操作,就是通过此方法实现的。
以下先放上示例模板:
// 创建初始动画的对象 & 结束动画的对象
Point point1 = new Point ();
Point point2 = new Point ();
// 创建动画对象 & 设置参数
ValueAnimator anim = ValueAnimator.ofObject(new myObjectEvaluator(), point1 , point2 );
// 参数说明
// 1. 自定义的估值器对象(TypeEvaluator 类型参数) - 下面会详细介绍
// 2. 初始动画的对象
// 3. 结束动画的对象
anim.setDuration(length);
anim.start();
上面示例中,我们看到有两个Point对象point1 , point2 。假设我们有一个自定义View,这个View中有一个Point对象用于管理坐标,然后我们在onDraw()方法中根据这个Point对象的坐标值进行绘制,也就是说,我们可以对Point对象进行动画操作,不停的根据Point的坐标刷新View重绘制,以此就可以实现 View 的动画了。
到这里都很好理解,不过我们还注意到传入了一个new myObjectEvaluator()
(Typ
eEvaluator
实现类) 参数,这是干什么的呢?不要急,下面我们就详细解答。
TypeEvaluator 估值器
其实我们已经不止一次的提到估值器的概念,如 ValueAnimator.ofInt()
方法中的 IntEvaluator
, ValueAnimator.ofFloat()
方法中的 FloatEvaluator
,这两种都是Android预置供我们使用的,二者通过计算告知动画系统如何从初始值过渡到结束值。
但是二者虽然好用,但也有其局限性:只能针对 Int / Float 类型数值操作。因此某些我们需要对任意对象进行动画操作的时候,二者显然不能满足我们的需求了,这时候我们需要自定义一个TypeEvaluator来告知系统如何进行过渡。
那么如何自定义呢?别急,我们可以先看一看系统提供的 IntEvaluator
是如何实现的:
/**
* This evaluator can be used to perform type interpolation between int values.
*/
public class IntEvaluator implements TypeEvaluator<Integer> {
/**
* This function returns the result of linearly interpolating the start and end values, with
* fraction representing the proportion between the start and end values. The
* calculation is a simple parametric calculation: result = x0 + t * (v1 - v0),
* where x0 is startValue, x1 is endValue, and t is fraction.
*
* @param fraction The fraction from the starting to the ending values
* @param startValue The start value; should be of type int or Integer
* @param endValue The end value; should be of type int or Integer
* @return A linear interpolation between the start and end values, given the fraction parameter.
*/
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
源码很简单,注释也相当详细,方便观看,我再次解释一下:IntEvaluator
实现了 TypeEvaluator
接口,然后重写了 evaluate()
方法。该方法的三个参数意义如下:
- fraction:表示动画完成度,据此计算当前动画的值
- startValue:动画初始值
- endValue:动画结束值
那么 evalute()
方法的返回值就不难理解了,简单的数学公式,返回当前动画的值。
是不是很简单?因此我们自定义 TypeEvaluator
时只需要实现 TypeEvaluator
接口,然后重写 evaluate()
方法,在此方法中处理好逻辑即可。
下面我们就动手写一个自定义 TypeEvaluator
自定义TypeEvaluator
我们还是以上面提过的Point对象管理View坐标的为例:
- 定义
Point
类
public class Point {
//记录坐标位置
private float x;
private float y;
//通过构造方法设置坐标,因此不需要额外的set方法
public Point(float x, float y) {
this.x = x;
this.y = y;
}
//get方法,获取当前坐标值
public float getX() {
return x;
}
public float getY() {
return y;
}
}
- 定义
PointEvaluator
//实现TypeEvaluator接口
public class PointEvaluator implements TypeEvaluator {
//重写evaluate()方法
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
//始末值强转为Point对象
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
//通过fraction计算当前动画的坐标值x,y
float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
//返回以上述x,y组装的新的Point对象
Point point = new Point(x,y);
return point;
}
}
Point
对象间的平滑过渡
// 创建初始动画的对象 & 结束动画的对象
Point point1 = new Point(0, 0);
Point point2 = new Point(500, 500);
// 创建动画对象 & 设置参数
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), point1 , point2 );
anim.setDuration(3000);
anim.start();
以上就是自定义TypeEvaluator的全部用法。
下面我们就可以尝试用上述知识练习如何对对象进行动画操作,从而实现自定义View的动画效果
自定义View的动画效果
- 新建MyAnimView继承View
public class MyAnimView extends View {
//常量
public static final float RADIUS = 50f;
//当前Point,记录当前动画的值(x,y坐标)
private Point curPoint;
//画笔
private Paint mPaint;
//Java代码实例化View时调用
public MyAnimView(Context context) {
super(context);
}
//XML文件实例时调用
public MyAnimView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//初始化画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
}
@Override
protected void onDraw(Canvas canvas) {
//第一次绘制时
if (curPoint == null){
//初始化坐标为(50f, 50f)
curPoint = new Point(RADIUS,RADIUS);
//画圆
drawCircle(canvas);
//开始动画
startAnimation();
}else {//非第一次绘制
drawCircle(canvas);
}
}
//在当前坐标处绘制一个半径为50f的圆
private void drawCircle(Canvas canvas) {
float x = curPoint.getX();
float y = curPoint.getY();
canvas.drawCircle(x,y,RADIUS,mPaint);
}
//开始动画
private void startAnimation() {
//设置 起始值&结束值
Point startPoint = new Point(RADIUS,RADIUS);//起始为左上角(50f,50f)
Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);//终点为右下角(屏幕宽度-50f,屏幕高度-50f)
final ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(),startPoint,endPoint);
//设置插值器
anim.setInterpolator(new BounceInterpolator());
//设置监听
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
//每当Point的值有改变的时候,都会调用onAnimationUpdate()方法
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//更新curPoint,即更新当前坐标
curPoint = (Point) animation.getAnimatedValue();
// 刷新,重现调用onDraw()方法
// 由于curPoint的值改变,那么绘制的位置也会改变,也就实现了一个从左上到右下的平移动画
invalidate();
}
});
anim.setDuration(5000);
anim.start();
}
}
- 布局文件中引入该自定义view
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.whdalive.learn_propertyanimation.Property.MyAnimView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
效果演示
上图中我们发现球并非匀速,而是有一个bounce的效果,这是因为我们为动画添加了一个插值器 anim.setInterpolator(new BounceInterpolator())
。
具体Interpolator的使用,我们在前一篇Android 视图动画(View Animation) 使用详解中已经详细讲解了,此处就不再赘述。
注意
ValueAnimator.ofObject()的本质还是操作值,只是将多个值封装到一个对象里,同时对该对象里的多个值一起操作而已
至此,ValueAnimator
的用法就介绍完了,下面我们介绍一个貌似更加常用的类 ObjectAnimator
ObjectAnimator
- 属性动画重要的类
- 原理:控制值的变化,之后自动赋给对象的属性,从而实现动画
- 与ValueAnimator对比
- ValueAnimator的子类
- ValueAnimator只是对值进行平滑的动画过渡;ObjectAnimator直接对任意对象的任意属性进行动画操作,如View的alpha属性
- ValueAnimator需要我们为对象属性手动赋值;ObjectAnimator会为对象属性自动赋值
具体使用
由于继承关系的存在,ObjectAnimator的用法和ValueAnimator及其类似:Java方式设置 / XML方式设置(推荐Java方式)
方式1:Java方式
ObjectAnimator animator = ObjectAnimator.ofFloat(Object object, String property, float ....values);
//ObjectAnimator animator = ObjectAnimator.ofInt(Object object, String property, int ....values);
//ObjectAnimator animator = ObjectAnimator.ofObject(Object object, String property, TypeEvaluator evaluator,Object....values);
// 以ofFloat为例 参数说明:
// Object object:需要操作的对象
// String property:需要操作的对象的属性
// float ....values:动画初始值 & 结束值(不固定长度)
// 若是两个参数a,b,则动画效果则是从属性的a值到b值
// 若是三个参数a,b,c,则则动画效果则是从属性的a值到b值再到c值
// 以此类推
// 至于如何从初始值 过渡到 结束值,同样是由估值器决定,此处ObjectAnimator.ofFloat()是有系统内置的浮点型估值器FloatEvaluator,同ValueAnimator讲解
//动画基本属性
anim.setDuration(500);
anim.setStartDelay(500);
anim.setRepeatCount(0);
anim.setRepeatMode(ValueAnimator.RESTART);
animator.start();
// 启动动画
方式2:XML方式设置
- 路径res/animator/下创建动画XML文件,如set_animation.xml
- 设置动画参数
ObjectAnimator 采用<objectAnimator > 标签
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="1" // 初始值
android:valueTo="0" // 结束值
android:valueType="floatType" // 变化值类型 :floatType & intType
android:propertyName="alpha" // 对象变化的属性名称
/>
- Java启动动画
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.view_animation);
// 载入XML动画
animator.setTarget(view);
// 设置动画对象
animator.start();
// 启动动画
以上为ObjectAnimator的标准使用模板,我们通过控制传入 ofFloat()的第二个参数preperty 或者 XML中的android:propertyName属性 来产生不同动画效果,以下为Android预置好的一些属性:即四种基本变换,透明度、平移、缩放、旋转
属性 | 作用 | 数值类型 |
---|---|---|
alpha | 透明度 | float |
translationX | X方向的位移 | float |
translationY | Y方向的位移 | float |
scaleX | X方向的缩放倍数 | float |
scaleY | Y方向的缩放倍数 | float |
rotation | 以屏幕方向为轴的旋转度数 | float |
rotationX | 以X轴为轴的旋转度数 | float |
rotationY | 以Y轴为轴的旋转度数 | float |
那么问题来了,除了以上的属性,我们还可以传入哪些属性值呢?
答案是 任意属性值
为了理解这个问题,我们需要先了解一下ObjectAnimator是如何自动为属性赋值的。
自动赋值
- 以alpha+textview为例,很显然textview是不具备alpha这一属性的,那么ObjectAnimator是如何操作的呢?
- 原理:实际上ObjectAnimator内部的工作机制是去寻找这个属性名对应的get和set方法,通过二者赋值。
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha",1f, 0f, 1f);
// TextView对象中并没有alpha这个属性值
// ObjectAnimator并不是直接对我们传入的属性名进行操作
// 而是根据传入的属性值"alpha" 去寻找对象对应属性名对应的get和set方法,从而通过set() & get()对属性进行赋值
// 因为TextView对象中有alpha属性所对应的get & set方法
// 所以传入的alpha属性是有效的
// 所以才能对alpha这个属性进行操作赋值
public void setRotation(float value);
public float getRotation();
// 实际上,这两个方法是由View对象提供的,其余平移旋转等属性同理
因此:
- ObjectAnimator 类针对的是任意对象 & 任意属性值,并不是单单针对于View对象
- 如果需要采用ObjectAnimator 类实现动画效果,那么需要操作的对象就必须有该属性的set()& get()
特别注意:如果想让对象的属性a的动画生效,属性a需要同时满足下面两个条件:
- 对象必须要提供属性a的set()方法
a. 如果没传递初始值,那么需要提供get()方法,因为系统要去拿属性a的初始值
b. 若该条件不满足,程序直接崩溃- 对象提供的 属性a的set()方法 对 属性a的改变 必须通过某种方法反映出来
a. 如带来ui上的变化
b. 若这条不满足,动画无效,但不会崩溃)
自定义对象实现动画效果
本质:
- 为对象设置需要操作属性的set() & get()方法
- 通过实现TypeEvaluator类从而定义属性变化的逻辑
下面我们通过实例来介绍如何通过自定义属性实现动画效果
-
实现效果(在前面运动小球的基础上控制颜色变化)
-
具体实现
- 为对象类属性color设置set() & get() 方法
public class MyAnimView extends View {
...
//将颜色设置为String类型,使用#RRGGBB格式表示颜色
private String color;
//get()方法
public String getColor() {
return color;
}
//set()方法
public void setColor(String color) {
mPaint.setColor(Color.parseColor(color));
this.color = color;
//改变画笔颜色后立即刷新视图,然后onDraw()方法就会调用
invalidate();
}
...
}
- 布局中加入自定义view
同前面ValueAnimator的自定义view - 根据需求实现TypeEvaluator接口
public class ColorEvaluator implements TypeEvaluator {
// 实现TypeEvaluator接口
private int mCurrentRed;
private int mCurrentGreen ;
private int mCurrentBlue ;
// 复写evaluate()
// 在evaluate()里写入对象动画过渡的逻辑:此处是写颜色过渡的逻辑
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
// 获取到颜色的初始值和结束值
String startColor = (String) startValue;
String endColor = (String) endValue;
// 通过字符串截取的方式将初始化颜色分为RGB三个部分,并将RGB的值转换成十进制数字
// 那么每个颜色的取值范围就是0-255
int startRed = Integer.parseInt(startColor.substring(1, 3), 16);
int startGreen = Integer.parseInt(startColor.substring(3, 5), 16);
int startBlue = Integer.parseInt(startColor.substring(5, 7), 16);
int endRed = Integer.parseInt(endColor.substring(1, 3), 16);
int endGreen = Integer.parseInt(endColor.substring(3, 5), 16);
int endBlue = Integer.parseInt(endColor.substring(5, 7), 16);
// 将初始化颜色的值定义为当前需要操作的颜色值
mCurrentRed = startRed;
mCurrentGreen = startGreen;
mCurrentBlue = startBlue;
// 计算初始颜色和结束颜色之间的差值
// 该差值决定着颜色变化的快慢:初始颜色值和结束颜色值很相近,那么颜色变化就会比较缓慢;否则,变化则很快
// 具体如何根据差值来决定颜色变化快慢的逻辑写在getCurrentColor()里.
int redDiff = Math.abs(startRed - endRed);
int greenDiff = Math.abs(startGreen - endGreen);
int blueDiff = Math.abs(startBlue - endBlue);
int colorDiff = redDiff + greenDiff + blueDiff;
if (mCurrentRed != endRed) {
mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0,
fraction);
// getCurrentColor()决定如何根据差值来决定颜色变化的快慢 ->>关注1
} else if (mCurrentGreen != endGreen) {
mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,
redDiff, fraction);
} else if (mCurrentBlue != endBlue) {
mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,
redDiff + greenDiff, fraction);
}
// 将计算出的当前颜色的值组装返回
String currentColor = "#" + getHexString(mCurrentRed)
+ getHexString(mCurrentGreen) + getHexString(mCurrentBlue);
// 由于我们计算出的颜色是十进制数字,所以需要转换成十六进制字符串:调用getHexString()->>关注2
// 最终将RGB颜色拼装起来,并作为最终的结果返回
return currentColor;
}
// 关注1:getCurrentColor()
// 具体是根据fraction值来计算当前的颜色。
private int getCurrentColor(int startColor, int endColor, int colorDiff,
int offset, float fraction) {
int currentColor;
if (startColor > endColor) {
currentColor = (int) (startColor - (fraction * colorDiff - offset));
if (currentColor < endColor) {
currentColor = endColor;
}
} else {
currentColor = (int) (startColor + (fraction * colorDiff - offset));
if (currentColor > endColor) {
currentColor = endColor;
}
}
return currentColor;
}
// 关注2:将10进制颜色值转换成16进制。
private String getHexString(int value) {
String hexString = Integer.toHexString(value);
if (hexString.length() == 1) {
hexString = "0" + hexString;
}
return hexString;
}
}
- 调用ObjectAnimator.ofObject()方法
//借助AnimatorSet,实现组合动画
public class MyAnimView extends View {
...
//开始动画
private void startAnimation() {
//设置 起始值&结束值
Point startPoint = new Point(RADIUS,RADIUS);//起始为左上角(50f,50f)
Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);//终点为右下角(屏幕宽度-50f,屏幕高度-50f)
final ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(),startPoint,endPoint);
//设置插值器
anim.setInterpolator(new BounceInterpolator());
//设置监听
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
//每当Point的值有改变的时候,都会调用onAnimationUpdate()方法
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//更新curPoint,即更新当前坐标
curPoint = (Point) animation.getAnimatedValue();
// 刷新,重现调用onDraw()方法
// 由于curPoint的值改变,那么绘制的位置也会改变,也就实现了一个从左上到右下的平移动画
invalidate();
}
});
// anim.setDuration(5000);
//anim.start();
//颜色过渡的代码逻辑放在startAnimation()方法中,本身就在MyAnimView中执行,因此传入 this 参数即可
ObjectAnimator anim2 = ObjectAnimator.ofObject(this,"color",new ColorEvaluator(),"#FF0000", "#0000FF");
//创建一个AnimatorSet,让两个动画同时播放,时长5s
AnimatorSet animationSet = new AnimatorSet();
animationSet.play(anim).with(anim2);
animationSet.setDuration(5000);
//启动动画
animationSet.start();
}
以上我们就实现了一个平移 & 颜色变化的动画。
至此关于ValueAnimator和ObjectAnimeato的使用就讲解完了,下面我们再讲讲一些额外的使用方法
1.AnimatorSet 组合动画
- 独立的动画能够实现的视觉效果毕竟是相当有限的,因此将多个动画组合到一起播放就显得尤为重要
- 使用:
AnimatorSet.play(Animator anim) :播放当前动画
AnimatorSet.after(long delay) :将现有动画延迟x毫秒后执行
AnimatorSet.with(Animator anim) :将现有动画和传入的动画同时执行
AnimatorSet.after(Animator anim) :将现有动画插入到传入的动画之后执行
AnimatorSet.before(Animator anim) : 将现有动画插入到传入的动画之前执行
- 实例
Java方式
ObjectAnimator a1 = ObjectAnimator.ofFloat(view, "alpha", 1.0f, 0f);
ObjectAnimator a2 = ObjectAnimator.ofFloat(view, "translationY", 0f, viewWidth);
......
AnimatorSet animSet = new AnimatorSet();
animSet.setDuration(5000);
animSet.setInterpolator(new LinearInterpolator());
//animSet.playTogether(a1, a2, ...); //两个动画同时执行
animSet.play(a1).after(a2); //先后执行
......//其他组合方式
animSet.start();
XML方式
<set
android:ordering=["together" | "sequentially"]>
<objectAnimator
android:propertyName="string"
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode=["repeat" | "reverse"]
android:valueType=["intType" | "floatType"]/>
<animator
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode=["repeat" | "reverse"]
android:valueType=["intType" | "floatType"]/>
<set>
...
</set>
</set>
2. 监听动画
- 监听到动画的各种事件,比如动画何时开始,何时结束,然后在开始或者结束的时候去执行一些逻辑处理
- Animator类提供addListener()方法,说明其子类都可以使用该方法(关于继承关系,前面我们提到过了)
- 使用方法
anim.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(Animation animation) {
//动画开始时执行
}
@Override
public void onAnimationRepeat(Animation animation) {
//动画重复时执行
}
@Override
public void onAnimationCancel()(Animation animation) {
//动画取消时执行
}
@Override
public void onAnimationEnd(Animation animation) {
//动画结束时执行
}
});
// 特别注意:每次监听必须4个方法都重写。
- 缺点:
很多时候我们并不想要监听那么多个事件,可能我只想要监听动画结束这一个事件,那么每次都要将四个接口全部实现一遍就显得非常繁琐。 - 如何解决
采用动画适配器(AnimatorListenerAdapter),解决 实现接口繁琐 的问题
anim.addListener(new AnimatorListenerAdapter() {
// 向addListener()方法中传入适配器对象AnimatorListenerAdapter()
// 由于AnimatorListenerAdapter中已经实现好每个接口
// 所以这里不实现全部方法也不会报错
@Override
public void onAnimationStart(Animator animation) {
// 如想只想监听动画开始时刻,就只需要单独重写该方法就可以
}
});
3. ViewPropertyAnimator用法
- Google为View的动画操作提供的一种便捷用法
- 具体使用:
// 使用解析
View.animate().xxx().xxx();
// ViewPropertyAnimator的功能建立在animate()上
// 调用animate()方法返回值是一个ViewPropertyAnimator对象,之后的调用的所有方法都是通过该实例完成
// 调用该实例的各种方法来实现动画效果
// ViewPropertyAnimator所有接口方法都使用连缀语法来设计,每个方法的返回值都是它自身的实例
// 因此调用完一个方法后可直接连缀调用另一方法,即可通过一行代码就完成所有动画效果
// 以下是例子
mButton = (Button) findViewById(R.id.Button);
// 创建动画作用对象:此处以Button为例
mButton.animate().alpha(0f);
// 单个动画设置:将按钮变成透明状态
mButton.animate().alpha(0f).setDuration(5000).setInterpolator(new BounceInterpolator());
// 单个动画效果设置 & 参数设置
mButton.animate().alpha(0f).x(500).y(500);
// 组合动画:将按钮变成透明状态再移动到(500,500)处
// 特别注意:
// 动画自动启动,无需调用start()方法.因为新的接口中使用了隐式启动动画的功能,只要我们将动画定义完成后,动画就会自动启动
// 该机制对于组合动画也同样有效,只要不断地连缀新的方法,那么动画就不会立刻执行,等到所有在ViewPropertyAnimator上设置的方法都执行完毕后,动画就会自动启动
// 如果不想使用这一默认机制,也可以显式地调用start()方法来启动动画
- 注意事项
- ViewPropertyAnimator实例通过View.animate()方法创建,之后的调用的所有方法,设置的所有属性都是通过这个实例完成的。
- ViewPropertyAnimator隐式启动动画,即当我们将动画定义完成之后,动画就会自动启动。当然,如果需要手动显式启动,我们仍然可以调用start()方法启动动画。
- ViewPropertyAnimator的连缀语法:每个方法返回值都是自身实例,因此方法的调用可以连缀调用,如上面示例代码中写的那样。
至此,关于属性动画能想到的就全都讲解完了,至于有所遗漏的,留作日后再补充吧~