属性动画
上一节我们看过了Animation视图动画的原理浅析,了解到Animation的原理是通过ViewRootImpl来监听下一个屏幕刷新信号,然后DecorView对View树重新绘制并且顺带将动画绘制出来。
关于属性动画有两个常用类ValueAnimator和它的子类ObjectAnimator,还有view.animate()会得到一个ViewPropertyAnimator对象,需要自己重写其updateListener做动画效果。
基本使用
ValueAnimator本身是没有动画效果的,需要用户自定义listener,获取到计算好的animatedValue进行赋值给view。
val animator1 = ValueAnimator.ofFloat(0f, 1.5f)
animator1.duration = 1000
animator1.repeatCount = 3
animator1.repeatMode = ValueAnimator.REVERSE
animator1.addUpdateListener {
val data = it.animatedValue as Float
testAnimal.scaleX = data
testAnimal.scaleY = data
}
ObjectAnimator继承于ValueAnimator,提供了对改变View属性的动画支持。官网解释为需要适合的get/set属性方法去设置这个值,也就是这个属性在View类是存在的,如果不存在也可以自己去写属性的get/set方法。需要注意的是ObjectAnimator也有ofFloat、ofInt、ofArgb等方法,需要确定属性的返回值是否一致,而且属性名要骆驼命名法。
// translationY、scaleX、scaleY、alpha等是View类的基本属性
val animator1 = ObjectAnimator.ofFloat(testAnimal, "translationY", 0f, -120f)
val animator2 = ObjectAnimator.ofFloat(testAnimal, "scaleX", 0f, -120f)
class View {
@ViewDebug.ExportedProperty(category = "drawing")
public float getTranslationY() {
return mRenderNode.getTranslationY();
}
public void setTranslationY(float translationY) {
//...
}
//scaleX同上
}
进阶用法
(1)自定义属性动画
View中width和height是没有set方法的,但是动画需要改变View的宽度(scale是拉伸View),这时候需要包装属性的set方法,或者使用ValueAnimator重写动画的updateListener。
// 第一种 包装属性
val animator1 = ObjectAnimator.ofInt(ViewWrapper(btn_start2), "width", 120, 420)
animator1.duration = 1000
private inner class ViewWrapper(val target: View) {
fun getWidth(): Int {
return target.layoutParams.width
}
fun setWidth(width: Int) {
target.layoutParams.width = width
target.requestLayout()
}
}
// 第二种 ValueAnimator重写动画
// 可以使用估值器计算值的百分比,在之后的动画原理会讲到TypeEvaluator
val animator1 = ValueAnimator.ofInt(120, 420)
animator1.duration = 1000
animator1.addUpdateListener {
val data = it.animatedValue as Int
testAnimal.layoutParams.width = data
testAnimal.requestLayout()
}
(2)AnimatorSet组合动画
Animator同样会有组合动画,并且可对多个View进行动画,设置动画的执行顺序,更能操控动画的执行过程。
// Node包装了动画的节点,对动画进行分类成父动画、子动画、兄弟动画
animatorSet = AnimatorSet()
val animator1 = ObjectAnimator.ofInt(ViewWrapper(view2), "width", 120, 420)
val animator3 = ValueAnimator.ofFloat(0f, 1.5f)
animator3.addUpdateListener {
val data = it.animatedValue as Float
view1.scaleX = data
view1.scaleY = data
}
animatorSet.duration = 1000
animatorSet.play(animator1).after(animator3)
/** class AnimatorSet */
// 同时执行的动画
public Builder with(Animator anim) {
Node node = getNodeForAnimation(anim);
mCurrentNode.addSibling(node);
return this;
}
// 之前执行的动画
public Builder before(Animator anim) {
Node node = getNodeForAnimation(anim);
mCurrentNode.addChild(node);
return this;
}
// 之后执行的动画
public Builder after(Animator anim) {
Node node = getNodeForAnimation(anim);
mCurrentNode.addParent(node);
return this;
}
Animator动画原理解析
了解到ObjectAnimator继承于ValueAnimator,我们直接从ValueAnimator看。
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) { // 保证线程与Looper关联
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mReversing = playBackwards; //playBackwards传入参数为true或false,表示是否动画反向播放
mSelfPulse = !mSuppressSelfPulseRequested;
if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
if (mRepeatCount == INFINITE) {
float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
mSeekFraction = 1 - fraction;
} else {
mSeekFraction = 1 + mRepeatCount - mSeekFraction;
}
}
mStarted = true;
mPaused = false;
mRunning = false;
mAnimationEndRequested = false;
mLastFrameTime = -1;
mFirstFrameTime = -1;
mStartTime = -1;
// 添加Animation回调
addAnimationCallback(0);
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
startAnimation(); // 做一些动画的初始化工作
if (mSeekFraction == -1) {
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
// 注意这里传入的是this,意味着会在ValueAnimator中回调
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
AnimationHandler用于确保动画使用计算后的同一值,保证动画的同步执行
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
//记住这里哈 等下还要回来的
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};
/** 注册callBack以获取下一帧延时回调
*/
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
// 当前的mAnimationCallbacks为空的话(第一次执行动画)
if (mAnimationCallbacks.size() == 0) {
getProvider().postFrameCallback(mFrameCallback);
}
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
if (delay > 0) {
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
}
}
private AnimationFrameCallbackProvider getProvider() {
if (mProvider == null) {
mProvider = new MyFrameCallbackProvider();
}
return mProvider;
}
/**
* 从这里可以看到Choreographer类,这个类我们在Animation和View的绘制过程中都可以看到。
* 这个类做了什么?
* Choregrapher类主要来控制同步处理输入、动画、绘制三个UI操作,也就是当屏幕的每一次刷新信号到来时要去处理某
* 些事
* mFrameInfo.markInputHandlingStart();
* doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
* mFrameInfo.markAnimationsStart();
* doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
* mFrameInfo.markPerformTraversalsStart();
* doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
* // 遍历上述事件完成,提交操作,用于修正动画的启动时间
* doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
*/
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
// 发送祯回调通知,以在下一祯时回调
mChoreographer.postFrameCallback(callback);
}
// ...
}
/** 回到上面说的地方,下一帧通知过来了,正式去处理动画内容
*/
private void doAnimationFrame(long frameTime) {
long currentTime = SystemClock.uptimeMillis();
final int size = mAnimationCallbacks.size();
for (int i = 0; i < size; i++) {
// 这里就是上面所说ValueAnimator添加callback,传入this
final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
if (callback == null) {
continue;
}
if (isCallbackDue(callback, currentTime)) { // 判断动画是否过期
callback.doAnimationFrame(frameTime); // 调用ValueAnimator的doAnimationFrame回调
if (mCommitCallbacks.contains(callback)) {
getProvider().postCommitCallback(new Runnable() {
@Override
public void run() {
commitAnimationFrame(callback, getProvider().getFrameTime());
}
});
}
}
}
cleanUpList(); // 动画执行话,清除callBack列表
}
ValueAnimator类中真正执行动画的过程,与Animation一致,都是通过估值器、插值器等计算出百分比值等,在动画的Update中去绘制。
public final boolean doAnimationFrame(long frameTime) {
// ..省略代码,概括为处理第一帧的时间
//动画中第一帧时间可能会早于当前时间,这里主要修正当前时间,避免以负值作为帧的显示
final long currentTime = Math.max(frameTime, mStartTime);
boolean finished = animateBasedOnTime(currentTime); // 计算,显示下一帧动画
if (finished) {
endAnimation();
}
return finished;
}
/** 为动画处理单个帧操作,并且返回值标识动画是否结束
*/
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
//省略代码 计算动画进度,保证动画进度处于0~1之间,如果有设置repeatCount属性,则这个值是会超过1的,所以需要经过计算转换为0~1
animateValue(currentIterationFraction); // 对应Animation的applyTransformation()方法
}
return done;
}
/** 清除Animation的回调,回复lastFramTime、mStarted、mRunning等属性值
*/
private void endAnimation() {
if (mAnimationEndRequested) {
return;
}
removeAnimationCallback();
// 省略大法
}
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); // 设置View属性的地方
}
if (mUpdateListeners != null) { // 如果Animator有添加updateListener方法,则操作地方在这里
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
关于mValues[i].calculateValue(fraction)的代码,等有时间再去深究其中代码,这里已交代完了Animator的执行流程,下图是Animator的调用流程图(手动盗图)
属性动画的优点
- 动画的执行整体可控制,可以添加listener进行控制动画过程
- 动画不限于自有属性,可自定义属性进行动画实现
- 可以改变View的属性,不仅限于只绘制其位置