前言:
我们选择了android开发或ios开发,是不是见到不错的app,就想看看他们的一些动画效果,交互体验,以及一些有创意的功能。今天我们就来一起学习讲解android中的动画,以及动画原理,以便后续独立开发一些复杂的动画。
动画分类:
那android为我们提供了哪些动画呢:目前是三种:Tween动画,Frame动画,以及3.0后推出的Property动画即属性动画。
Tween动画:通过对场景的对象进行图形变化(包括缩放,平移,旋转,改变透明度)来产生动画效果。
Frame动画:顺序的播放事先做好的图像。类似放电影。
Property动画:即通过改变对象的实际属性来达到动画效果。
(这里为啥说实际属性呢,因为上述两种动画虽然对象在视觉上的确是移动了,但通过真实测试,并没有改变属性,这也是经常遇到的一个面试题目)
动画的代码分析:
我们在写布局定义控件一个道理,既可以layout定义,也可以代码定义,没疑问吧,Tween动画与Frame动画也可以用XML和代码两种方式来完成,殊途同归都是为了构建Animation对象:(说到这,其实大家学习编程,也要类比)
Animation trans = new TranslateAnimation(...);
这时候最重要的一个对象Animation就出现了,我们先看看Animation对象:
/**
* Abstraction for an Animation that can be applied to Views, Surfaces, or
* other objects. See the {@link android.view.animation animation package
* description file}.
*/
public abstract class Animation implements Cloneable
从源码可以看粗,
Animation对象是一个抽象类。TranslateAnimation,ScaleAnimation等都继承自Animation。那我们在看看具体类
TranslateAnimation的代码
@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);
}
t.getMatrix().setTranslate(dx, dy);
}
@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 以及 initialize方法。
这两个方法都是重写的方法。我们继续寻根求源,看看子类里的方法在什么时候调用的。经过搜索,applyTransformation实在Animation类的getTransfromation类中调用。
public boolean getTransformation(long currentTime, Transformation outTransformation) {
if (mStartTime == -1) {
mStartTime = currentTime;
}
final long startOffset = getStartOffset();
final long duration = mDuration;
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;
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 (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(interpolatedTime, outTransformation);
}
而getTransformation方法实际是由系统调用的,根据动画当前时间计算Transformation信息,而实现类里的applyTransformation则是根据getTransformation里计算出的信息进行实际的动画实现。那我们一直说的Transformation是啥呢(Animation类的两个比较重要的属性就是监听和这个Transformation,其中AnimationListener是监听动画的开始,结束等),我们继续回到animation源码里去查找,然后看看Transformation到底都有啥:
public class Transformation {
/**
* Indicates a transformation that has no effect (alpha = 1 and identity matrix.)
*/
public static final int TYPE_IDENTITY = 0x0;
/**
* Indicates a transformation that applies an alpha only (uses an identity matrix.)
*/
public static final int TYPE_ALPHA = 0x1;
/**
* Indicates a transformation that applies a matrix only (alpha = 1.)
*/
public static final int TYPE_MATRIX = 0x2;
/**
* Indicates a transformation that applies an alpha and a matrix.
*/
public static final int TYPE_BOTH = TYPE_ALPHA | TYPE_MATRIX;
protected Matrix mMatrix;
protected float mAlpha;
protected int mTransformationType;
private boolean mHasClipRect;
private Rect mClipRect = new Rect();
这里包含了Matrix,mAlpha等,mAlpha是存放透明度度信息的,而Matrix,则存放了平移,旋转等信息,这二者构成了Transformation信息的载体。
是否注意到了上边applyTransformation里有个matrix的调用 t.getMatrix()。
这个Matrix到底是什么,但从字母看是矩阵,我们线性代数,数据结构都经常接触到矩阵,动画里的矩阵干嘛用的,我们看回Matrix类
public class Matrix {
public static final int MSCALE_X = 0; //!< use with getValues/setValues
public static final int MSKEW_X = 1; //!< use with getValues/setValues
public static final int MTRANS_X = 2; //!< use with getValues/setValues
public static final int MSKEW_Y = 3; //!< use with getValues/setValues
public static final int MSCALE_Y = 4; //!< use with getValues/setValues
public static final int MTRANS_Y = 5; //!< use with getValues/setValues
public static final int MPERSP_0 = 6; //!< use with getValues/setValues
public static final int MPERSP_1 = 7; //!< use with getValues/setValues
public static final int MPERSP_2 = 8; //!< use with getValues/setValues
/** @hide */
public final static Matrix IDENTITY_MATRIX = new Matrix() {
void oops() {
throw new IllegalStateException("Matrix can not be modified");
}
@Override
public void set(Matrix src) {
oops();
}
@Override
public void reset() {
oops();
}
@Override
public void setTranslate(float dx, float dy) {
oops();
}
@Override
public void setScale(float sx, float sy, float px, float py) {
oops();
}
@Override
public void setScale(float sx, float sy) {
oops();
}
@Override
public void setRotate(float degrees, float px, float py) {
oops();
}
@Override
public void setRotate(float degrees) {
oops();
}
@Override
public void setSinCos(float sinValue, float cosValue, float px, float py) {
oops();
}
@Override
public void setSinCos(float sinValue, float cosValue) {
oops();
}
@Override
public void setSkew(float kx, float ky, float px, float py) {
oops();
}
@Override
public void setSkew(float kx, float ky) {
oops();
}
@Override
public boolean setConcat(Matrix a, Matrix b) {
oops();
return false;
}
@Override
public boolean preTranslate(float dx, float dy) {
oops();
return false;
}
@Override
public boolean preScale(float sx, float sy, float px, float py) {
oops();
return false;
}
@Override
public boolean preScale(float sx, float sy) {
oops();
return false;
}
@Override
public boolean preRotate(float degrees, float px, float py) {
oops();
return false;
}
@Override
public boolean preRotate(float degrees) {
oops();
return false;
}
@Override
public boolean preSkew(float kx, float ky, float px, float py) {
oops();
return false;
}
@Override
public boolean preSkew(float kx, float ky) {
oops();
return false;
}
@Override
public boolean preConcat(Matrix other) {
oops();
return false;
}
@Override
public boolean postTranslate(float dx, float dy) {
oops();
return false;
}
@Override
public boolean postScale(float sx, float sy, float px, float py) {
oops();
return false;
}
@Override
public boolean postScale(float sx, float sy) {
oops();
return false;
}
@Override
public boolean postRotate(float degrees, float px, float py) {
oops();
return false;
}
@Override
public boolean postRotate(float degrees) {
oops();
return false;
}
我们看到上述源码,setXXX,PreXXX,postXXX,是对translate,scale,rotate的设置。也就是getMatrix()是获取得到当前帧的动画中在矩阵中的信息。
其实android 中的 Matrix是个3*3的矩阵,包含的是平移,旋转,缩放的区域,进而实现对平移,旋转,缩放的动画实现。
Matrix的具体使用,放在后续讲解,今天只从源代码分析。如果同学急于学习,请先看一篇讲解吧:
Matrix
动画的使用:
我们在执行动画时,经常是view.startAnimation.看下源码:
/**
* Start the specified animation now.
*
* @param animation the animation to start now
*/
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
这个是view的启动执行动画的方法,setAnimation设置动画对象,并赋给成员变量mCurrentAnimation,然后invalidate() 重绘自身。
重绘视图的时候调用到drawChild,这时候获取与View绑定的Animation,在设置的动画时间不结束,就会变换矩阵Matrix,绘制完成,在获取新的一帧的换换矩阵,知道动画结束。在绘制子视图时drawChild(),会调用方法:
/**
* This method is called by ViewGroup.drawChild() to have each child view draw itself.
*
* This is where the View specializes rendering behavior based on layer type,
* and hardware acceleration.
*/
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
/* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList.
*
* If a view is dettached, its DisplayList shouldn't exist. If the canvas isn't
* HW accelerated, it can't handle drawing RenderNodes.
*/
boolean drawingWithRenderNode = mAttachInfo != null
&& mAttachInfo.mHardwareAccelerated
&& hardwareAcceleratedCanvas;
boolean more = false;
final boolean childHasIdentityMatrix = hasIdentityMatrix();
final int parentFlags = parent.mGroupFlags;
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();
if (a != null) {
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();
} else {
if ((mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_TRANSFORM) != 0) {
// No longer animating: clear out old animation matrix
mRenderNode.setAnimationMatrix(null);
mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
if (!drawingWithRenderNode
此方法里有调用了View.applyLegacyAnimation()方法,我们继续,可以看到源码里调用了Animation类的getTransformation方法,进而一步一步又回到上述分析的applyTransformation方法。这样,这个调用过程就关联起来了。
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();
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;
通过上述分析,Tween动画的流程也就走通了。大体总结时:
1.view.startAnimation 启动动画
2.invalidate() 重绘
3.执行drawChlild,进而调用draw方法获取animation对象
4.View.applyLegacyAnimation方法里调用Animation类的getTransformation方法
5.在具体类如Scaleanimation类实现Animation类的applyTransformation方法
6.在applyTransformation方法里通过矩阵(Matrix)变换实现动画
从而动画原理就是这个过程,对于矩阵的实现,后续会有文章展现。需要注意的是还有AnimationUtils(辅助类),AnimationSet(继承Animation),大家可以去分析分析。
最后需要注意的是:执行动画时并不是控件本身在改变,而是它的父view完成的,控件本身的位置并没有改变,这点需要与属性动画区分开。