属性动画的本质:
改变某一个View某一个时间点的属性值,比如一个View在0.1秒的时候在100px的位置,0.2秒的时候到了200px的位置,这就会让我们感觉到一个动画的效果,实际上就时每隔一段时间调用view.setX()方法。
看一下系统属性动画的简单用法1
2
3
4
5Button button = findViewById(R.id.button);
ObjectAnimator animator = ObjectAnimator.ofFloat(button,"translationX",0,200);
animator.setDuration(2000);
animator.start();
animator.setInterpolator(new LinearInterpolator());
通过上面的方法,就会让一个button移动起来。下面我们自己来实现一个这个效果。
动画是需要时间来完成的,不同的时间节点,控件的状态也不一样。所以需要把一个动画分解成一个一个的关键帧。
看下面的流程图
此图是我自己的理解,如果问题欢迎指正。
当我们开始调用一个动画的时候,比如ObjectAnimator.ofFloat(button,"translationX",0,200);
- 首先会把我们传入的View保存起来
- 然后会初始化一个Holder对象,这个Holder对象是用来干啥的呢?我们传入了一个translationX值,Holder对象中提供一个方法,把translationX拼接成一个set方法,setTranslationX,然后通过反射找到View中的此方法。当数值计算出来之后,执行获取的这个方法。
- KeyframeSet是一个关键帧的集合,最后我们传入0-200就是关键帧
- 插值器,用来计算某一时间中动画长度播放的百分比
- 估值器,根据插值器计算的百分比,来计算某个时间,动画所要更新的值。然后交给Holder执行动画
- 属性动画会监听系统发出的VSYNC信号,每收到一次信号就执行一次。
什么是FPS?FPS代表每秒的帧数,当FPS>=60的时候,我们的肉眼就不会感觉到卡顿了,FPS=60是个什么概念呢,1000/60≈16.6也就是说,想要不卡顿,我们需要在16毫秒以内绘制一帧出来。
什么是VSYNC?VSYNC是垂直同步的缩写,每16毫秒发送一个VSYNC信号,系统拿到这个信号之后开始刷新屏幕。
OK上面的概念大体了解了开干吧。
先来个简单的实体类Keyframe,这里以FloatKeyframe为例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class MyFloatKeyframe {
float mFraction;
Class mValueType;
float mValue;
public MyFloatKeyframe(float fraction, float value) {
mFraction = fraction;
mValueType = float.class;
mValue = value;
}
public float getValue() {
return mValue;
}
public void setValue(float value) {
mValue = value;
}
public float getFraction() {
return mFraction;
}
}
它就是个实体类,只要包含三个对象,当前执行的百分比,关键帧中的值的类型和动画在mFraction时刻的值
下面在来个对象,关键帧的集合
1 | public class MyKeyframeSet { |
这里面有两个比较关键的方法
- ofFloat:这是个静态的方法,用于构造帧集合对象,根据我们传入的关键帧的个数,来组件一个关键帧的数组,然后创建出关键帧帧集合对象并传入创建的关键帧数组。
- getValue:根据当前百分比调用估值器获取响应的值,这里的估值器直接使用系统的估值器里面实现很简单源码如下
1 | public class FloatEvaluator implements TypeEvaluator<Number> { |
我们知道估值器返回的是当前View需要移动的距离,上面的估值器就是返回开始的值加上(还剩的值乘以需要执行的百分比)就是当前View需要执行的数值。
OK,下面来看我们的入口类
1 | public class MyObjectAnimator implements VSYNCManager.AnimatorFrameCallBack{ |
它实现了一个VSYNCManager的对调对象,用来在回调方法doAnimator中执行动画
构造方法中将传入的View保存起来,View是个比较重量级的对象,放到WeakReference中方便回收
然后初始化了Holder对象,开始的时候我们知道Hodler对象是用来找到我们传入View的相关属性的set方法的。如下:
1 | public class MyFloatPropertyValuesHolder { |
Holder类中有两个方法:
- setupSetter:就是通过我们传入的字符串,拼接成一个set方法,然后通过反射找到这个方法,保存到成员变量中。
- setAnimatedValue:找到当前动画需要设置的值,然后调用目标View的相关set方法。
最后是VSYNC信号,由于我们无法拿到系统的VSYNC信号,这里通过一个线程来模拟发从信号。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41public class VSYNCManager {
AnimatorFrameCallBack mAnimatorFrameCallBack;
/**
* 可能会有多个动画同事使用,所以弄个集合
*/
private List<AnimatorFrameCallBack> list = new ArrayList<>();
private VSYNCManager(){
new Thread(mRunnable).start();
}
public static VSYNCManager getInstance(){
return new VSYNCManager();
}
public void add(AnimatorFrameCallBack callBack){
list.add(callBack);
}
Runnable mRunnable = new Runnable() {
@Override
public void run() {
while (true){
try {
Thread.sleep(16);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (AnimatorFrameCallBack callback : list) {
callback.doAnimator(System.currentTimeMillis());
}
}
}
};
interface AnimatorFrameCallBack{
void doAnimator(long currentTime);
}
}
很简单,开启一个线程,每睡16毫秒执行一个回调方法。因为可能不止一个View在监听这个信号,所以这里使用一个集合来保存回调对象,发送信号的时候循环遍历执行回调。
类中有个回调接口供我们的入口类MyObjectAnimator实现,发送信号的时候,执行回调方法doAnimator。
下面在看一下MyObjectAnimator中的回调方法doAnimator1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 public void doAnimator(long currentTime) {
float total= mDuration / 16;
//执行的百分比
float fraction = (index++)/total;
//通过插值器,改变百分比的值
if(interpolator != null){
interpolator.getInterpolation(fraction);
}
//循环播放
if(index>=total){
index = 0;
}
mPropertyValuesHolder.setAnimatedValue(target.get(),fraction);
}
//系统的匀速插值器
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public LinearInterpolator() {
}
public LinearInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return input;
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createLinearInterpolator();
}
}
- 我们传入的时间除以16,就是总共需要执行的次数
- 使用index来表示我们当前已经走了的次数,它跟总次数相除就是当前执行的百分比
- 通过插值器,改变百分比的值,如果是匀速的,当前的插值器返回的就是当前的值不变,从上面系统的匀速插值器LinearInterpolator中的getInterpolation方法也可以看到,我们传入啥就返回啥。如果不是匀速的,返回的百分比的值也就不一样,后面通过这个百分比算出来的需要执行的值也就不一样,我们看到的View动画执行速度也就不是匀速了。
- 最后调用mPropertyValuesHolder.setAnimatedValue方法传入百分比来执行动画。
OK,简易的动画到这里就写完了,怎么用呢,来到Activity中1
2
3
4MyObjectAnimator animator = MyObjectAnimator.ofFloat(button, "translationX", 0, 200);
animator.setInterpolator(new LinearInterpolator());
animator.setDuration(2000);
animator.start();
执行效果: