Android开发中我们经常使用动画来实现复杂的动态效果,本文通过记录和梳理动画相关使用和原理,以巩固相关知识点。
动画分类
Android中动画分为三类,View动画、帧动画和属性动画,从实现原理上来说View动画和帧动画属于同一类。
View动画
View动画的抽象类是Animation,其有四个基础实现类,分别对应平移(TranslateAnimation)、旋转(RotateAnimation)、缩放(ScaleAnimation)、透明度(AlphaAnimation).
使用
把一个view的透明度在400ms内从0变化到1的动画如下:
java
AlphaAnimation alphaAnimation = new AlphaAnimation(0.0f, 1.0f);
alphaAnimation.setDuration(400);
view.startAnimation(alphaAnimation);
原理
view动画的原理我们主要跟踪View使用过程来分析,以前文透明度动画的使用为例,其最后调用了view的startAnimation方法,参数为alphaAnimation
java
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
public void setAnimation(Animation animation) {
mCurrentAnimation = animation;
///****省略部分代码
}
上述代码主要做了三件事:
- 调用animation的setStartTime方法,该方法实际上只是设置了Animation对象的成员变量状态
java
public void setStartTime(long startTimeMillis) {
mStartTime = startTimeMillis;
mStarted = mEnded = false;
mCycleFlip = false;
mRepeated = 0;
mMore = true;
}
- 将动画赋值给view的mCurrentAnimation
- 调用invalidate请求重绘
可见View的startAnimation方法并没有直接开启动画,那么动画从哪里开始呢?我们查找下View的mCurrentAnimation的使用的地方,在View的draw方法里。
java
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
Transformation transformToApply = null;
boolean concatMatrix = false;
final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
//获取设置的动画对象
final Animation a = getAnimation();
if (a != null) {
//处理动画的公共逻辑
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
//获取动画对应的转换对象,
transformToApply = parent.getChildTransformation();
}
if (transformToApply != null) {
if (concatMatrix) {
if (drawingWithRenderNode) {
//通过动画转换器获取View渲染相关Matrix,通过Matrix来修改View的效果
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
canvas.translate(-transX, -transY);
//通过动画转换器获取View渲染相关Matrix,通过Matrix来修改View的效果
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
float transformAlpha = transformToApply.getAlpha();
if (transformAlpha < 1) {
alpha *= transformAlpha;
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
}
}
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
Animation a, boolean scalingRequired) {
Transformation invalidationTransform;
final int flags = parent.mGroupFlags;
final boolean initialized = a.isInitialized();
if (!initialized) {
//初始化动画
a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
//注入动画涉及到的Handler**重要
if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
//回调动画开始的接口
onAnimationStart();
}
final Transformation t = parent.getChildTransformation();
//根据时间获取当前转换相关状态,这个方法内部会根据时间计算对应的动画值,并设置给Transformation,这个Transformation的相关属性会作用于View。
boolean more = a.getTransformation(drawingTime, t, 1f);
if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
if (parent.mInvalidationTransformation == null) {
parent.mInvalidationTransformation = new Transformation();
}
invalidationTransform = parent.mInvalidationTransformation;
a.getTransformation(drawingTime, invalidationTransform, 1f);
} else {
invalidationTransform = t;
}
if (more) {
//**** 计算需要绘制的脏区域,并发起重绘请求,注意这里本来就是在View的draw方法中调用,这里再发起重绘以驱动View的动画继续向后执行
parent.invalidate(left, top, left + (int) (region.width() + .5f),
top + (int) (region.height() + .5f));
}
return more;
}
注意事项
- View动画无法改变View的自身位置,只是改变了View的内容的位置,所以移动动画后的View所在的位置无法响应点击时间,因为此时View的位置还在原来
- 动画不需要的时候记得要取消
属性动画
使用
把一个view的透明度在400ms内从0变化到1的属性动画实现如下:
java
ObjectAnimator.ofFloat(mRlApplyAll, "alpha", 0f, 1f).start();
原理
属性动画的原理我们主要跟踪使用过程来分析,以前文透明度动画的使用为例
java
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
//创建ObjectAnimator对象并设置值
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
}
public void start() {
//如果这个动画还在执行中,则先取消
AnimationHandler.getInstance().autoCancelBasedOn(this);
//调用父类的start方法,父类的start会调用其重载的start(boolean)方法
super.start();
}
java
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
//必须在有looper的线程
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
//初始化变量
mReversing = playBackwards;
mSelfPulse = !mSuppressSelfPulseRequested;
mStarted = true;
mPaused = false;
mRunning = false;
mAnimationEndRequested = false;
mLastFrameTime = -1;
mFirstFrameTime = -1;
mStartTime = -1;
//注意这个方法,很重要
addAnimationCallback(0);
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
//调用startAnimation方法,该方法只是初始化了动画,并回调了onAnimationStart方法
startAnimation();
}
}
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
//将当前Animation注册到AnimationHandler中,
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
private void startAnimation() {
mAnimationEndRequested = false;
initAnimation();
mRunning = true;
if (mSeekFraction >= 0) {
mOverallFraction = mSeekFraction;
} else {
mOverallFraction = 0f;
}
if (mListeners != null) {
notifyStartListeners();
}
}
java
#AnimationHandler.java
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
//调用provider的postFrameCallback方法将成员变量mFrameCallback注册,此处的provider实为MyFrameCallbackProvider
getProvider().postFrameCallback(mFrameCallback);
}
if (!mAnimationCallbacks.contains(callback)) {
//将动画callback注册到mAnimationCallbacks中,此处的callback就是当前Animation对象
mAnimationCallbacks.add(callback);
}
}
private AnimationFrameCallbackProvider getProvider() {
if (mProvider == null) {
mProvider = new MyFrameCallbackProvider();
}
return mProvider;
}
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
//这里可以看到,实际上是将成员变量注册到了Choreographer中,这个Choreographer翻译过来是编舞者,主要负责协调VSYNC信号来开始绘制相关流程,其请求&接受垂直同信号,回调到主线程,开启performTraversal()。
//postFrameCallback方法会做两件事:1.将当前callback注册到集合中,2.请求垂直同步信号,当收到垂直同步信号后,会开始处理集合中的callback,也就是会回调到mFrameCallback中的doFrame()
mChoreographer.postFrameCallback(callback);
}
//***省略
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
//计算当前时间动画状态
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
//继续将会调注册给Choreographer,接受下一个时间点到会调,这样动画就跑起来了
getProvider().postFrameCallback(this);
}
}
};
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)) {
//回调到callback的doAnimationFrame方法,这个callback就是之前注册Animator实例,在doAnimationFrame方法中会去根据插值器和估值器计算当前时间点到值,并设置给对应view,设置的方式是通过反射调用set相关方法
callback.doAnimationFrame(frameTime);
}
}
cleanUpList();
}
}
注意事项
- 注意内存泄漏
- start方法必须要在有looper的动画调用