[0 to 0.5]从零开始学习Android动画知识(中)
属性动画(Property Animation)
属性动画(Property Animation)是在 Android 3.0(
API 11
)后才提供的一种全新动画模式
为什么要提供属性动画
API 11
之前的动画为视图动画(View Animation),仅仅作用于视图对象View,未作用于属性,且其动画效果过于单一。为了弥补视图动画的不足,实现补间动画无法实现的功能,安卓团队开发了具备更多功能和高拓展性的属性动画。
属性动画系统是一个强健的框架,用于为几乎任何内容添加动画效果。
属性动画的工作逻辑
属性动画是通过在指定时长内将对象的某一属性动态地由一个值变化到另一个值来实现的
值得一提,这里的步骤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
效果图(摘自网络)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wwv9ocK4-1649676105200)(C:\Users\Mark\Desktop\IP.png)]
AccelerateInterpolator源码 y : B ( x ) = x 2 f y : B(x)=x^{2f} y:B(x)=x2f
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或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 ) 2 P 0 + 2 x ( 1 − x ) P 1 + x 2 P 2 , x ∈ [ 0 , 1 ] y : B(x) = (1-x)^2P_0+2x(1-x)P_1+x^2P_2 , x\in[0,1] y:B(x)=(1−x)2P0+2x(1−x)P1+x2P2,x∈[0,1]
三阶 y : B ( x ) = ( 1 − x ) 3 P 0 + 3 x ( 1 − x ) 2 P 1 + 3 x 2 ( 1 − x ) P 2 + x 3 P 3 , x ∈ [ 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] y:B(x)=(1−x)3P0+3x(1−x)2P1+3x2(1−x)P2+x3P3,x∈[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)
,
i
n
p
u
t
∈
[
0
,
1
]
input\in[0,1]
input∈[0,1],所以我们只需要重写这一个方法即可
public class MyInterpolator implements TimeInterpolator {
@Override
public float getInterpolation(float v) {
//f(v) = ....
return f(v);
}
}
估值器(Evaluator)
前面我们提到Evaluator和Interpolator共同控制动画的属性变化,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
- 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提供了更简洁的链式调用来设置多个属性动画,这些动画是同时进行的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HdOamdRx-1649676105204)(C:\Users\Mark\Desktop\1.png)]
并且,每个属性都提供两种类型方法设置,代表直接设置属性和属性变化量
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 |
优点 | 简单易用 | 泛用性广 |
缺点 | 效果固定,可操作性低 | 难复用,复杂动画代码量大(封装解决) |
推荐文章: