版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
一、传统的2D动画 — 补间动画+帧动画
在 API3.0 之前,动画使用分为两种:补间动画+帧动画。
补间动画 Tween Animation
补间动画支持平移、旋转、缩放和透明度。与帧动画相比,动画效果已经较为流畅。但是补间动画最大的缺陷是,他只是改变了 View 的显示效果,并不是真正的去改变 View 的位置。举个简单的例子,有个在左边的 Button 按钮,使用补间动画让其平移到右边显示,这时候会发现,点击显示在右边的 Button 是不能触发点击事件的,只有点击原先左边的位置才可以触发点击事件。
帧动画 Frame Animation
帧动画就是轮播一组图片,类似 git 图片的效果。虽然说实现起来简单,但是效果单一,而且需要很多图片,占用资源较大。
帧动画是逐帧进行播放,而补间动画是给出关键两帧,在这两个关键帧之间补充上渐变的效果来实现动画(属性动画也是)。
二、属性动画 Property Animation
属性动画是在 API 3.0以后才出现的,不向下兼容(现在应该基本没有没有 3.0 以下的吧),效果很强大。属性动画是真实修改 View 的实际属性。不过相比补间动画,属性动画的效率会稍微差点。
1.属性动画
先来看一个效果。
下面看下这个实现的代码。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
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"
tools:context="com.xiaoyue.animator.MainActivity">
<ImageView
android:id="@+id/duo"
android:onClick="startAnimator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/duo"/>
</android.support.constraint.ConstraintLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = (ImageView) findViewById(R.id.duo);
}
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i ++) {
imageView.setTranslationX(3*i);
try {
thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
public void startAnimator(View view){
thread.start();
}
}
这边是起一个线程,每隔 30ms 让 imageView 进行平移 3,循环 100次,从而达到动画的效果。属性动画的实现也类似这样,取两个关键帧的时候的属性,在中间过程中均匀变化起属性值,由底层调起,不断进行绘制,达到动画效果,
注:这边没有具体去处理细节,不能多次点击。
2.ObjectAnimator
对上面代码修改了 startAnimator 方法。
public void startAnimator(View view){
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageView, "translationX", 0f, 200f);
objectAnimator.setDuration(500);
objectAnimator.start();
}
ofFloat(Object target, String propertyName, float… values)
target : 执行动画的对象
propertyName : 要修改的属性
values : 关键帧的值,即属性变化范围
propertyName 会在 PropertyValuesHolder 中通过反射的机制获取到执行动画对象的 set 方法,然后底层定时调用这个 set 方法。只要是 target 身上有的 set 方法的属性,都可以在这边进行设置。
单独使用 ObjectAnimator 这样的话,只能一次对一个属性进行操作,比如进行平移,进行缩放,却不能同时进行平移和缩放操作。如果说要使用 ObjectAnimator 同时进行多个动画的执行,这时候要设置动画监听,同步操作其他的属性。
3.属性动画的监听
同时放大 X 方向和 Y 方向。
public void startAnimator(View view){
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageView, "scaleX", 0f, 1f);
objectAnimator.setDuration(500);
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float f = (float) animation.getAnimatedValue();
imageView.setScaleY(f);
}
});
objectAnimator.start();
}
只修改 startAnimator 方法,在控制 scaleX 的时候,通过监听同时控制 setScaleY 的值。系统会不停的调用监听的方法,getAnimatedValue() 是获取当前属性的值。
这边要强调的是:
ObjectAnimator.ofFloat(imageView, “scaleX”, 0f, 1f);
ObjectAnimator.ofFloat(imageView, “ScaleX”, 0f, 1f);
属性的首写字母大小写是不敏感的,这个在后面源码分析中可以看到原因。但是非首写字母大小写是敏感的。
属性即使反射不到方法,也是不会出错的。下面这段代码效果与上面一样。(Android Studio 检测可能会报错,还是可以打包的。)
public void startAnimator(View view){
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageView, "hehe", 0f, 200f);
objectAnimator.setDuration(500);
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float f = (float) animation.getAnimatedValue();
imageView.setScaleX(f/200);
imageView.setScaleY(f/200);
}
});
objectAnimator.start();
}
这边是通过 getAnimatedValue() 获取当前设置属性的值,从而计算其他要设置属性的值。其实更多时候是希望直接获取到当前时间的百分比,在 API 12+有提供对应的方法:
getAnimatedFraction(); //返回动画执行的百分比
属性动画的其他监听
objectAnimator.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
//监听动画开始
}
@Override
public void onAnimationRepeat(Animator animation) {
//监听动画重复
}
@Override
public void onAnimationEnd(Animator animation) {
//监听动画结束
}
@Override
public void onAnimationCancel(Animator animation) {
//监听动画取消
}
});
如果不需要这么多方法,也可以使用 AnimatorListenerAdapter,AnimatorListenerAdapter 是一个实现 AnimatorListener 的抽象类,这样就可以只写自己需要的方法。
objectAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
}
});
4.ValueAnimator
ObjectAnimator 是继承与 ValueAnimator,我们实现多动画时候,也可以直接使用 ValueAnimator。
public void startAnimator(View view){
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f);
valueAnimator.setDuration(500);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float f = (float) animation.getAnimatedValue();
imageView.setScaleX(f);
imageView.setScaleY(f);
}
});
valueAnimator.start();
}
可以通过初始值的设置,做成一个类似点击效果的动画。
如果只需要监听值变化就用 ValueAnimator,需要监听到属性的变换则使用 ObjectAnimator。
5.PropertyValuesHolder
也是使用 PropertyValuesHolder 创建多个属性动画,然后传递给 ObjectAnimator。
public void startAnimator(View view){
PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX", 0f, 1f);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY", 0f, 1f);
ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(imageView,holder1, holder2);
objectAnimator.setDuration(500);
objectAnimator.start();
}
6.AnimatorSet
使用动画集合:
public void startAnimator(View view){
ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(imageView,"scaleX", 0f, 1f);
ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(imageView,"scaleY", 0f, 1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(objectAnimator1, objectAnimator2);
animatorSet.setDuration(500);
animatorSet.start();
}
playTogether(Animator… items) 可以是多个动画同时执行,AnimatorSet 还提供了一个方法 playSequentially(Animator… items) 让动画按顺序依次执行。
public void startAnimator(View view){
ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(imageView,"scaleX", 0f, 1f);
ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(imageView,"scaleY", 0f, 1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playSequentially(objectAnimator1, objectAnimator2);
animatorSet.setDuration(500);
animatorSet.start();
}
AnimatorSet 也允许根据自己的需求自由组合动画执行情况。下面三个方法可以混和使用。
//objectAnimator1 与 objectAnimator2 一起执行
animatorSet.play(objectAnimator1).with(objectAnimator2);
//animator1 执行完后执行 animator2
animatorSet.play(objectAnimator1).after(objectAnimator2);
//animator2 执行完后执行 animator1
animatorSet.play(objectAnimator1).before(objectAnimator2);
三、估值器
先来看一个抛物线的效果:
看一下用上面方法实现的代码:
public void startAnimator(View view){
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f);
valueAnimator.setDuration(500);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float f = (float) animation.getAnimatedValue();
//为了方便看,这边扩大100倍。速度为 5。
imageView.setX(100f*(f*5));
//加速度 y=vt=1/2*g*t*t
imageView.setY(100f*0.5f*0.98f*(f*5)*(f*5));
}
});
valueAnimator.start();
}
在来看一下估值器的使用代码:
public void startAnimator(View view){
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setDuration(500);
valueAnimator.setObjectValues(new PointF(0, 0));
//放在 evaluate 方法里面的话,系统会不断的创建回收,频率较高,会造成内存抖动
final PointF pointF = new PointF();
valueAnimator.setEvaluator(new TypeEvaluator<PointF>() {
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
pointF.x = 100f*(fraction*5);
//加速度 y=vt=1/2*g*t*t
pointF.y = 100f*0.5f*0.98f*(fraction*5)*(fraction*5);
return pointF;
}
});
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF f = (PointF) animation.getAnimatedValue();
imageView.setX(f.x);
imageView.setY(f.y);
}
});
valueAnimator.start();
}
在这个简单的例子里,使用估值器的时候,代码看起来边复杂了。这边只是希望通过对比来学习估值器,当各个变化关系复杂时候,用估值器可以是代码看起来简介。
两个实现都是通过监听某一个值的变化,从而对对应的属性做出变化。ValueAnimator 是可以监听的 Object 变化的,上一段代码是简单的监听一个 float 值,且 ValueAnimator 自身有提供对应的 ofFloat 方法,本质还是调用 ValueAnimator.setObjectValues 方法。
自定义一个估值器,可以在估值器中添加自己的算法,在执行过程中对 Object 的属性进行变化,然后在监听中获取到对应的 Object,然后用对应的值去修改控件的属性。
//设置一个估值器
valueAnimator.setEvaluator(new TypeEvaluator<PointF>() {
@Override
public PointF evaluate(float fraction, PointF startValue,
PointF endValue) {
// 估值计算方法---可以在执行的过程当中干预改变属性的值---做效果:用自己的算法来控制
//不断地去计算修改坐标
pointF.x = 100f*(fraction*5);
//加速度 y=vt=1/2*g*t*t
// pointF.y = 0.5f*9.8f*(fraction*5)*(fraction*5);
pointF.y = 10f*0.5f*9.8f*(fraction*5)*(fraction*5);
return pointF;
}
});
安卓自带实现了一些估值器,也可以直接。
四、插值器
先简单对比一下估计值器和插值器的区别:
估值器:对各个属性计算的算法,具体变化数值
插值器:本质上是一个时间的函数,设置值的变化规律。
插值器默认变化是线性的,值的改变是随时间变换均匀变化的。设置插值器后,则可根据自己的需求让值随时间变换而变化。插值器的实现可以通过改变算法(即估值器)来实现。
AccelerateInterpolator 加速插值器
public void startAnimator(View view){
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageView, "translationY", 0f,500f);
objectAnimator.setDuration(800);
objectAnimator.setInterpolator(new AccelerateInterpolator(1));
objectAnimator.start();
}
看一下 AccelerateInterpolator 与时间的关系:可以看见,变化是越来越快。
AccelerateDecelerateInterpolator 加速减速插值器
public void startAnimator(View view){
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageView, "translationY", 0f,500f);
objectAnimator.setDuration(800);
objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
objectAnimator.start();
}
AccelerateDecelerateInterpolator 是变换速度先快后慢。
这些插值器都是安卓自带的,下面在提供一些安卓自带的插值器函数,基本上是够用,不够的也可以自己实现。
AnticipateInterpolator 回荡秋千插值器
BounceInterpolator 弹跳插值器
CycleInterpolator 正弦周期变化插值器
DecelerateInterpolator 减速插值器
AnticipateOvershootInterpolator
OvershootInterpolator