文章目录
属性动画
Android中的动画分为三种:
- View Animation 视图动画
- Tween Animation 补间动画
- Property Animator 属性动画
属性动画也有两种:
- ValueAnimator 单一的属性动画(包括ObjectAnimator、TimeAnimator)
- AnimatorSet 多个属性动画组合
属性动画的使用
属性动画可以用xml或者代码来表达,这里会选择代码的形式,比较容易理解。
属性动画的使用也不难,例如我们做一个位移属性的动画
// 设置位移动画
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationY", 0, 1000);
//设置持续时间
animator.setDuration(1500);
//设置线性时间插值器
animator.setInterpolator(new LinearInterpolator());
// 开始
animator.start();
动画监听
动画支持添加多个监听器
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) {
// 动画重复
}
});
插值器(Interpolator)
-
定义:
利用时间流逝来换算当前的进度的百分比
-
种类:
- LinearInterpolator(线性插值器):匀速动画
- AccelerateDecelerateInterpolator(加速减速插值器):动画开始和结束慢,中间快。
- DecelerateInterpolator(减速插值器):动画逐渐变慢
-
使用:
animator.setInterpolator(new LinearInterpolator());
估值器(TypeEvaluator)
-
定义:
利用进度的百分比来换算当前属性值是多少
-
种类:
- IntEvaluator:针对整型属性
- FloatEvaluator:针对浮点型属性
- ArgbEvaluator:针对Color属性
-
作用
结合插值器获取当前的进度比,加上估值器计算出属性值,设置给作用的对象,这样表现出随着时间流逝,对象的属性值会发生不断的变化。这就是属性动画的基本原理。
同样也可以自定义Interpolator和TypeEvaluator来自定义自己想要的动画效果。
-
使用
1.设置TypeEvaluator
animator.setEvaluator(TypeEvaluator value)
2.构造函数
ObjectAnimator animator = Animator.ofObject(TypeEvaluator实例, 起始值, 结束值)
默认设置为FloatEvaluator
ObjectAnimator animator = ofFloat(Object target, String propertyName, float... values)
属性动画的两个条件
-
对象的类要提供set方法来修改属性值,因为属性动画就是利用反射修改对应的属性,如果对象的属性值没有初始化值,还需要提供get方法来获取属性值
-
修改对象属性值的set方法,UI界面应该有所变化,否则也会看不出动画效果
属性动画原理解析
ObjectAnimator初始化
从ObjectAnimator的ofFloat开始,我们看下ObjectAnimator的初始化
// ObjectAnimator.java
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
}
在ObjectAnimator中,有几个重要的变量
mTarget 表示作用的对象
mPropertyName 表示动画的属性名
mProperty 就是存储着属性类型和名字,跟反射有关
// ObjectAnimator
private WeakReference<Object> mTarget;
private String mPropertyName;
private Property mProperty;
private boolean mAutoCancel = false;
private ObjectAnimator(Object target, String propertyName) {
setTarget(target);
setPropertyName(propertyName);
}
这里都是设置动画的初始值
// ObjectAnimator.java
public void setFloatValues(float... values) {
if (mValues == null || mValues.length == 0) {
// No values yet - this animator is being constructed piecemeal. Init the values with
// whatever the current propertyName is
if (mProperty != null) {
setValues(PropertyValuesHolder.ofFloat(mProperty, values));
} else {
setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
}
} else {
super.setFloatValues(values);
}
}
// ValueAnimator.java
public void setFloatValues(float... values) {
if (values == null || values.length == 0) {
return;
}
if (mValues == null || mValues.length == 0) {
setValues(PropertyValuesHolder.ofFloat("", values));
} else {
PropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setFloatValues(values);
}
// New property/values/target should cause re-initialization prior to starting
mInitialized = false;
}
ObjectAnimator start()启动
属性动画启动
//ObjectAnimator
public void start() {
// 取消当前的属性动画
AnimationHandler.getInstance().autoCancelBasedOn(this);
super.start();
}
直接看ObjectAnimator的父类ValueAnimator
// ValueAnimator
@Override
public void start() {
start(false);
}
// ValueAnimator
private void start(boolean playBackwards) {
addAnimationCallback(0); // 1. 添加Choreographer动画绘制的回调
startAnimation(); // 2. 对外Animatior监听的起始回调
// 3. 计算、设置初始值
if (mSeekFraction == -1) {
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
1. addAnimationCallback()
这部分跟Choreographer有关系,作用是向Choreographer注册一次动画绘制的回调。
// ValueAnimator
private void addAnimationCallback(long delay) {
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
// AnimationHandler.java
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
// 当前动画为空,若非空则说明已经注册过了
// 底层是注册一次Vsync信号
getProvider().postFrameCallback(mFrameCallback);
}
// 添加监听回调
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
// 添加延迟动画监听回调
if (delay > 0) {
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
}
}
getAnimationHandler()获取的是AnimationHandler。
getProvider().postFrameCallback(mFrameCallback);
目的是AnimationHandler是向Choreographer注册一次Vsync信号,因为ValueAnimator实现了AnimationFrameCallback,接收信号之后AnimationHandler会回调ValueAnimator的doAnimationFrame()执行一次绘制。
// AnimationHandler.java
interface AnimationFrameCallback {
boolean doAnimationFrame(long frameTime);
void commitAnimationFrame(long frameTime);
}
2. startAnimation()
开始动画执行,对所有的属性值进行初始化,并且通知外部的动画监听动画已经开始
// ValueAnimator
private void startAnimation() {
mAnimationEndRequested = false;
initAnimation();
mRunning = true;
if (mListeners != null) {
// 通知外部动画监听器
notifyStartListeners();
}
}
//ValueAnimator
void initAnimation() {
if (!mInitialized) {
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
// 初始化默认值
mValues[i].init();
}
mInitialized = true;
}
}
3.setCurrentPlayTime/setCurrentFraction
根据是当前动画时长是从0开始,还是从中间开始来计算帧值(属性值)
//ValueAnimator
public void setCurrentPlayTime(long playTime) {
float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;
setCurrentFraction(fraction);
}
//ValueAnimator
public void setCurrentFraction(float fraction) {
initAnimation();
fraction = clampFraction(fraction);
mStartTimeCommitted = true; // do not allow start time to be compensated for jank
if (isPulsingInternal()) {
long seekTime = (long) (getScaledDuration() * fraction);
long currentTime = AnimationUtils.currentAnimationTimeMillis();
// Only modify the start time when the animation is running. Seek fraction will ensure
// non-running animations skip to the correct start time.
mStartTime = currentTime - seekTime;
} else {
// If the animation loop hasn't started, or during start delay, the startTime will be
// adjusted once the delay has passed based on seek fraction.
mSeekFraction = fraction;
}
mOverallFraction = fraction;
final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
animateValue(currentIterationFraction);
}
以下就是利用插值器和估值器,结合时长来计算出属性值,并设置给对应的对象属性。
//ObjectAnimator
@Override
void animateValue(float fraction) {
final Object target = getTarget();
if (mTarget != null && target == null) {
// We lost the target reference, cancel and clean up. Note: we allow null target if the
/// target has never been set.
cancel();
return;
}
super.animateValue(fraction);
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setAnimatedValue(target);
}
}
//ValueAnimator
void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].calculateValue(fraction);
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
计算属性值
//PropertyValuesHolder
void calculateValue(float fraction) {
Object value = mKeyframes.getValue(fraction);
mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
}
将计算结果赋值给对象
//PropertyValuesHolder
void setAnimatedValue(Object target) {
if (mProperty != null) {
mProperty.set(target, getAnimatedValue());
}
if (mSetter != null) {
try {
mTmpValueArray[0] = getAnimatedValue();
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
以上只是做了一些属性值的计算和赋值动作,并没有真正绘制起来
AnimationHandler
// AnimationHandler.java
private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime =
new ArrayMap<>();
private final ArrayList<AnimationFrameCallback> mAnimationCallbacks =
new ArrayList<>();
private final ArrayList<AnimationFrameCallback> mCommitCallbacks =
new ArrayList<>();
mFrameCallback实现了Choreographer.FrameCallback的接口,是动画绘制的关键:
- doAnimationFrame() 绘制动画
- 当前动画还有继续的时候(mAnimationCallbacks.size() > 0),继续往递归添加回调,这样就可以再次计算属性值、再次绘制,动画的效果就连续起来了
那么,mFrameCallback是什么时候执行的呢?
这跟Choreographer和Vsync信号有关,参考《Choreographer》
总之先知道Choreographer总会回调调用doFrame()
// AnimationHandler.java
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};
// AnimatorHandler.java
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
@Override
public void postCommitCallback(Runnable runnable) {
mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null);
}
}
AnimationHanlder的doAnimationFrame也是调用了ValueAnimator的doAnimationFrame()
// AnimationHandler.java
private void doAnimationFrame(long frameTime) {
long currentTime = SystemClock.uptimeMillis();
final int size = mAnimationCallbacks.size();
for (int i = 0; i < size; i++) {
final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
if (callback == null) {
continue;
}
if (isCallbackDue(callback, currentTime)) {
// 调用ValueAnimator的doAnimationFrame
callback.doAnimationFrame(frameTime);
if (mCommitCallbacks.contains(callback)) {
getProvider().postCommitCallback(new Runnable() {
@Override
public void run() {
// 修正动画
commitAnimationFrame(callback, getProvider().getFrameTime());
}
});
}
}
}
cleanUpList();
}
ValueAnimator实现了AnimationFrameCallback
// AnimationHandler.java
interface AnimationFrameCallback {
boolean doAnimationFrame(long frameTime);
void commitAnimationFrame(long frameTime);
}
看ValueAnimator的doAnimationFrame做了哪些动作
//ValueAnimator.java
public final boolean doAnimationFrame(long frameTime) {
if (mStartTime < 0) {
// First frame. If there is start delay, start delay count down will happen *after* this
// frame.
mStartTime = mReversing
? frameTime
: frameTime + (long) (mStartDelay * resolveDurationScale());
}
// Handle pause/resume
if (mPaused) {
mPauseTime = frameTime;
removeAnimationCallback();
return false;
} else if (mResumed) {
mResumed = false;
if (mPauseTime > 0) {
// Offset by the duration that the animation was paused
mStartTime += (frameTime - mPauseTime);
}
}
if (!mRunning) {
if (mStartTime > frameTime && mSeekFraction == -1) {
return false;
} else {
mRunning = true;
startAnimation();
}
}
if (mLastFrameTime < 0) {
if (mSeekFraction >= 0) {
long seekTime = (long) (getScaledDuration() * mSeekFraction);
mStartTime = frameTime - seekTime;
mSeekFraction = -1;
}
mStartTimeCommitted = false; // allow start time to be compensated for jank
}
mLastFrameTime = frameTime;
final long currentTime = Math.max(frameTime, mStartTime);
boolean finished = animateBasedOnTime(currentTime);
if (finished) {
endAnimation();
}
return finished;
}
- 控制动画的停止和暂停
- 控制动画的开始和结束
- animateBasedOnTime()会去根据时长重新赋值
boolean animateBasedOnTime(long currentTime) {
...
animateValue(currentIterationFraction);
return done;
}
animateBaseOnTime()调用animateValue,重新计算赋值属性值。
但是这里会引发一个疑问:
从上到下,都只是说明了动画一直在随着时间流逝不停地修改属性值,并没有将属性值绘制到界面UI上
如果你了解了Choreographer,你就会知道ViewRootImpl跟Choreographer息息相关的。
Choreographer
我们重点看下Choreographer怎么处理postFrameCallback()
// Choreographer
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true); //
mHandler.sendMessageAtTime(msg, dueTime);
}
Choreographer主要处理四种类型的动作,根据四种动作会有四个数组mCallbackQueues,对应存储着各自的回调
// 输入
public static final int CALLBACK_INPUT = 0;
// 动画
public static final int CALLBACK_ANIMATION = 1;
// 遍历,执行measure、layout、draw
public static final int CALLBACK_TRAVERSAL = 2;
// 遍历完成的提交操作,用来修正动画启动时间
public static final int CALLBACK_COMMIT = 3;
继续看Choreographer在运行在当前主线程的loop中,主线程的loop优先处理同步消息。
case MSG_DO_SCHEDULE_CALLBACK:
doScheduleCallback(msg.arg1);
break;
doScheduleCallback内部也会发送一个MSG_DO_FRAME消息
case MSG_DO_FRAME:
doFrame(System.nanoTime(), 0);
break;
void doFrame(long frameTimeNanos, int frame) {
mFrameInfo.markInputHandlingStart();
// 处理输入事件
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
// 处理动画事件(修改属性值)
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
// 触发ViewRootImpl绘制
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
// 触发动画修正
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
}
最后CALLBACK_TRAVERSAL,会引发出ViewRootImpl重新发起一次绘制流程。
可以参考《Choreographer》
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/app/6248.html