Android动画-Animation原理解析
一、概述
在android中动画分为3类,帧动画、补间动画、属性动画
今天要说的就是“补间动画”,补间动画的基类是Animation,具体的实现都在TranslateAnimation、ScaleAnimation、RotateAnimation、AlphaAnimation中实现的
这个动画的原理是将控件View在时间上连续的绘制,就形成了动画,但是这个动画有个2个主要缺点
1、控件的本身没有移动或者旋转,位置信息没有改变,只是在绘制View的时候进行了矩阵Matrix变换
,所以当你要点击控件的时候,必须要点击原来位置的,
2、这个方式实现动画扩展性不高,只能针对View的处理,
google意识到了这点,于是在android3.0推出了属性动画,具体可以在我的对应文章有所分析
今天通过分析Animation来学习借鉴下谷歌的思路,也是不错的
二、分析
首先我们来分析下这个类,这个是一个补间动画的基类,这个类是个抽象类
这个类主要作用:实现了补间动画的基本逻辑,已经提供了子类要实现的接口,形成了基本框架
我们来看下重要的api
//设置动画时间
animation.setDuration(2000);
//设置重复模式,这里有两种RESTART:这个是从头开始,Reverse:这个是反向回到起点
animation.setRepeatMode(Animation.REVERSE);
//这个是动画重复次数,如果设置-1就是无线次数,可以是Animation.INFINITE
animation.setRepeatCount(2);
//设置为true,动画结束时停留在动画的最后一帧
animation.setFillAfter(true);
//设置为false,动画结束时候停留在动画的第一帧
animation.setFillBefore(true);
//重置当前动画,主要是做了清空资源,初始化变量,当我们调用了cancel后,如果要重新开始
//就要先调用下reset哦
animation.reset();
//动画取消,这个取消不是暂停的意思,补间动画是没有暂停功能的,animation没有提供
//这个方法调用后动画会setFillAfter 或者setFillBefore 的设置回到起点还是终点帧
animation.cancel();
//开始动画
animation.start();
//设置一个handler,来处理里面的各种监听事件
animation.setListenerHandler(Handler)
//设置动画监听
animation.setAnimationListener();
//设置插值器,这个可以改变动画的运动节奏,
animation.setInterpolator(interpolator);
//设置动画开始的延迟时间,默认是0 意思 立即开始动画
animation.setStartOffset(2000);
//按照scale 对animation和startOffset 进行比例缩放或者扩大
animation.scaleCurrentDuration(scale);
以上就是Animation 经常用的api了
/**
* Helper for getTransformation. Subclasses should implement this to apply
* their transforms given an interpolation value. Implementations of this
* method should always replace the specified Transformation or document
* they are doing otherwise.
*
* @param interpolatedTime The value of the normalized time (0.0 to 1.0)
* after it has been run through the interpolation function.
* @param t The Transformation object to fill in with the current
* transforms.
*/
protected void applyTransformation(float interpolatedTime, Transformation t) {
}
这个是Animation中需要子类重写的方法,需要根据自己的需求实现对应逻辑的
那么我们就来看下TranslateAnimation如何实现的
主要下面这三个方法
//这个方法就是初始化起始点和重点,还有对应的类型,这个类型包含三种
//public static final int ABSOLUTE = 0; 绝对的类型,传入的是像素点
//public static final int RELATIVE_TO_SELF = 1; 这个是相对于自己的长度,toXValue=2,2倍的自己width长度
//public static final int RELATIVE_TO_PARENT = 2; 这个是相对于父视图的尺寸对比
//
public TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue,
int fromYType, float fromYValue, int toYType, float toYValue) {
mFromXValue = fromXValue;
mToXValue = toXValue;
mFromYValue = fromYValue;
mToYValue = toYValue;
mFromXType = fromXType;
mToXType = toXType;
mFromYType = fromYType;
mToYType = toYType;
}
/*
interpolatedTime:这个是经过插值器计算完了之后的返回值
Transformation:这个是一个转换器,是Animation和View的绘制的桥梁,Transformation里面有个Matrix变换
矩阵,Animation的子类对这个转换器操作后,View拿到后,取出matrix来进行实际的转换
*/
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float dx = mFromXDelta;
float dy = mFromYDelta;
if (mFromXDelta != mToXDelta) {
//根据插值器的值,在什么比例进度上 做不同的距离移动
dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
}
if (mFromYDelta != mToYDelta) {
dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
}
//将这个值设置到matrix
t.getMatrix().setTranslate(dx, dy);
}
//将不同的type类型 转换成像素
//比如类型是RELATIVE_TO_SELF,相对于自己,自己控件本身width=200 那么这个最终的值是:mFromXValue*自己控件本身width
/*
protected float resolveSize(int type, float value, int size, int parentSize) {
switch (type) {
case ABSOLUTE:
return value;
case RELATIVE_TO_SELF:
return size * value;
case RELATIVE_TO_PARENT:
return parentSize * value;
default:
return value;
}
}
*/
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
mFromXDelta = resolveSize(mFromXType, mFromXValue, width, parentWidth);
mToXDelta = resolveSize(mToXType, mToXValue, width, parentWidth);
mFromYDelta = resolveSize(mFromYType, mFromYValue, height, parentHeight);
mToYDelta = resolveSize(mToYType, mToYValue, height, parentHeight);
}
applyTransformation 这个方法是子类需要实现的方法,那问题来了,这个方法在什么地方调用的呢,很显然这个应该是父类Animation里调用的,我们能可以找到是在父类中的getTransformation中调用的
getTransformation方法是Animation的核心方法实现了插值器的标准值计算(0-1)还有对重复执行的控制逻辑
这个方法子类一般不用重写
public boolean getTransformation(long currentTime, Transformation outTransformation) {
if (mStartTime == -1) {
mStartTime = currentTime;
}
final long startOffset = getStartOffset();
final long duration = mDuration;
//这个参数是插值器Interpolation的标准值时间范围是0-1,这个是个进度的值
float normalizedTime;
if (duration != 0) {
//这里是这个值的计算核心,很明显 进度值=(外部时间的插值)/持续的时间
normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
(float) duration;
} else {
// time is a step-change with a zero duration
normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
}
final boolean expired = normalizedTime >= 1.0f || isCanceled();
mMore = !expired;
if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
if (!mStarted) {
fireAnimationStart();
mStarted = true;
if (NoImagePreloadHolder.USE_CLOSEGUARD) {
guard.open("cancel or detach or getTransformation");
}
}
if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
if (mCycleFlip) {
normalizedTime = 1.0f - normalizedTime;
}
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
//这里调用了applyTransformation,这个方法是子类需要实现的,
applyTransformation(interpolatedTime, outTransformation);
}
//这里面的逻辑主要是实现重复执行的控制
//只有一圈执行完了 才能进入这里判断
if (expired) {
if (mRepeatCount == mRepeated || isCanceled()) {
if (!mEnded) {
mEnded = true;
guard.close();
fireAnimationEnd();
}
} else {
//如果mRepeatCount>0 那么mRepeated,到时候mRepeatCount == mRepeated的时候就停止动画了
//如果mRepeatCount==-1 那么就无线循环,一直动画,因为mRepeatCount == mRepeated 永远 //不可能为true,
//后面就一直返回mMore=true
if (mRepeatCount > 0) {
mRepeated++;
}
if (mRepeatMode == REVERSE) {
mCycleFlip = !mCycleFlip;
}
mStartTime = -1;
//这里设置为true,这最终会传入到View中的draw方法,作为返回值的一部分
//如果为true 就是动画正在进行,没有完,还要继续画
mMore = true;
//回调AnimationLister接口
fireAnimationRepeat();
}
}
if (!mMore && mOneMoreTime) {
mOneMoreTime = false;
return true;
}
return mMore;
}
Animation在View的执行逻辑
我们先前说过这个动画的绘制是在View的draw中执行的,那我们就来找一下,有这么段代码
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
/*
省去部分代码
*/
if ((parentFlags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) != 0) {
parent.getChildTransformation().clear();
parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
Transformation transformToApply = null;
boolean concatMatrix = false;
final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
final Animation a = getAnimation();
//判断这个Animation
if (a != null) {
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();
}
继续看,我们在View#applyLegacyAnimation方法中找到,那我们来看下吧
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);
if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
onAnimationStart();
}
final Transformation t = parent.getChildTransformation();
/*
这个就是调用前面说的getTransformation方法的调用地方
如果返回true,就标识动画要继续执行
*/
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;
}
//如果true,就继续调用parent的invalidate方法 继续循环draw
//上层的ViewGroup会执行dispathchDraw 最后调用的子View的draw中 往复执行
if (more) {
if (!a.willChangeBounds()) {
if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==
ViewGroup.FLAG_OPTIMIZE_INVALIDATE) {
parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED;
} else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) {
// The child need to draw an animation, potentially offscreen, so
// make sure we do not cancel invalidate requests
parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
//继续执行动画
parent.invalidate(mLeft, mTop, mRight, mBottom);
}
} else {
if (parent.mInvalidateRegion == null) {
parent.mInvalidateRegion = new RectF();
}
final RectF region = parent.mInvalidateRegion;
a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
invalidationTransform);
// The child need to draw an animation, potentially offscreen, so
// make sure we do not cancel invalidate requests
parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
final int left = mLeft + (int) region.left;
final int top = mTop + (int) region.top;
//继续执行动画
parent.invalidate(left, top, left + (int) (region.width() + .5f),
top + (int) (region.height() + .5f));
}
}
return more;
}
三、总结
通过上面的分析,Animation核心还是通过view的重绘来实现动画效果,重绘的过程需要设置矩阵Matrix来实现不同动画,控件的实际位置没有该改变
Animation补间动画用法没那么灵活,扩展性也不好,取而代之的是属性动画
但是通过对android-Animation的分析和理解来拓展我们的思路