补间动画分为如下四类
动画属性 | 相关类 |
---|---|
淡入淡出 | AlphaAnimation |
位移 | TranslateAnimation |
缩放 | ScaleAnimation |
旋转 | RotateAnimation |
我们以 AlphaAnimation 为例来说明一下动画的使用,其他动画使用方式如有雷同,纯属设计。
首先定义 alpha_anim.xml 动画相关属性
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromAlpha="1.0"
android:toAlpha="0.0" />
然后在代码中加载该动画并使用
Animation alphaAnimation = AnimationUtils.loadAnimation(this,R.anim.alpha_anim);
//设置动画结束后保留结束状态
alphaAnimation.setFillAfter(true);
imageView.startAnimation(alphaAnimation);
Animation alphaAnimation = new AlphaAnimation(1,0);
alphaAnimation.setDuration(3000);
mButton.startAnimation(alphaAnimation);
通过使用方式可以看出,不管是 xml 方式还是 java 代码方式,最终都会生成一个 Animation 对象进行调用。
我们先来看一下 xml 是怎么生成 Animation 对象的。
首先我们以 AnimationUtils.loadAnimation()
为入口,从源码角度分析一下 xml 是怎么生成 Animation 对象的
可以看到我们的动画资源文件通过 XmlPullParser
进行解析,然后根据节点值进行完全匹配,new 出相应的动画类型。和我们在代码中 new 出来相应的类型殊途同归。
public static Animation loadAnimation(Context context, @AnimRes int id)
throws NotFoundException {
...
XmlResourceParser parser = context.getResources();
...
return createAnimationFromXml(context, parser);
...
}
private static Animation createAnimationFromXml(Context c, XmlPullParser parser,
AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException {
Animation anim = null;
// Make sure we are on a start tag.
int type;
int depth = parser.getDepth();
while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
&& type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
if (name.equals("alpha")) {
anim = new AlphaAnimation(c, attrs);
} else if (name.equals("scale")) {
anim = new ScaleAnimation(c, attrs);
} else if (name.equals("rotate")) {
anim = new RotateAnimation(c, attrs);
} else if (name.equals("translate")) {
anim = new TranslateAnimation(c, attrs);
}
}
return anim;
}
动画的加载已经分析完成,接下来就从 View 的 startAnimation
方法作为入口,来分析一个动画的执行流程。
从源码中发现,这玩意只是名字起了个 start,里面完全就没有开始相关的操作啊,就是进行了个动画的赋值操作,那动画咋开始的???
/**
* Can be used as the start time to indicate the start time should be the current
* time when {@link #getTransformation(long, Transformation)} is invoked for the
* first animation frame. This can is useful for short animations.
*
* 注:大致意思就是动画从第一帧开始
*/
public static final int START_ON_FIRST_FRAME = -1;
/**
* 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);
}
public void setAnimation(Animation animation) {
mCurrentAnimation = animation;
}
既然你这个 start 只做了赋值操作,那你肯定就在某个地方使用了。顺腾摸瓜,咱们就看这个 mCurrentAnimation
在那块使用了
可以看到,在 View 进行绘制的时候。会获取当前的动画。如果不为空,则应用当前的动画。所以,当我们在 start 方法中设置动画后,这里的条件就为 true 了。
View.java
public Animation getAnimation() {
return mCurrentAnimation;
}
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
...
Transformation transformToApply = null;
final Animation a = getAnimation();
...
if (a != null) {
applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
...
transformToApply = parent.getChildTransformation();
}
...
}
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
Animation a, boolean scalingRequired) {
...
final Transformation t = parent.getChildTransformation();
boolean more = a.getTransformation(drawingTime, t, 1f);
...
}
Animation.java
public boolean getTransformation(long currentTime, Transformation outTransformation,
float scale) {
mScaleFactor = scale;
return getTransformation(currentTime, outTransformation);
}
public boolean getTransformation(long currentTime, Transformation outTransformation) {
...
applyTransformation(interpolatedTime, outTransformation);
...
}
/**
* 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.
*
* 注:大致意思就是让子类实现,计算相应的值
*/
protected void applyTransformation(float interpolatedTime, Transformation t) {
}
顺着调用链走下去,会发现最终调用到 Animation 的 applyTransformation
方法,而该方法是一个空实现,注释标明是让子类重写该方法,看了一下重写这个方子类就是开头我们说的那几个动画类型
有点意思,进去看下。看到了没,这里面就是对透明度、位置、缩放等相关属性根据当前进度计算出相应的值,然后保存到 Transformation 中
其中平移、旋转、缩放都是对 Matrix 进行操作。
AlphaAnimation.java
/**
* Changes the alpha property of the supplied {@link Transformation}
*/
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float alpha = mFromAlpha;
t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
}
TranslateAnimation.java
@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);
}
RotateAnimation.java
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);
float scale = getScaleFactor();
if (mPivotX == 0.0f && mPivotY == 0.0f) {
t.getMatrix().setRotate(degrees);
} else {
t.getMatrix().setRotate(degrees, mPivotX * scale, mPivotY * scale);
}
}
ScaleAnimation.java
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float sx = 1.0f;
float sy = 1.0f;
float scale = getScaleFactor();
if (mFromX != 1.0f || mToX != 1.0f) {
sx = mFromX + ((mToX - mFromX) * interpolatedTime);
}
if (mFromY != 1.0f || mToY != 1.0f) {
sy = mFromY + ((mToY - mFromY) * interpolatedTime);
}
if (mPivotX == 0 && mPivotY == 0) {
t.getMatrix().setScale(sx, sy);
} else {
t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
}
}
/**
* Defines the transformation to be applied at
* one point in time of an Animation.
* 动画某个时间点的变换
*/
public class Transformation {
protected Matrix mMatrix;
protected float mAlpha;
}
这里只是对 Transformation 进行了赋值操作,那么在哪用的呢,别急呀,继续往下看。
发现在标注1处应用完动画后,在标注2处获取到了相应时间的变换值。根据 Transformation 中的 Matrix 和 Alpha 做相应的变化,最终在屏幕上就呈现出连贯的动画效果了。
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
...
Transformation transformToApply = null;
...
if (a != null) {
//标注1
applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
...
//标注2
transformToApply = parent.getChildTransformation();
}
...
canvas.concat(transformToApply.getMatrix());
...
float transformAlpha = transformToApply.getAlpha();
if (transformAlpha < 1) {
alpha *= transformAlpha;
}
final int multipliedAlpha = (int) (255 * alpha);
canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(), multipliedAlpha);
...
}
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);
animator.setDuration(5000);
animator.start();
首先我们看声明的 ObjectAnimator 内部只是做了一些初始化的操作
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
}
@Override
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);
}
}
public void setValues(PropertyValuesHolder... values) {
int numValues = values.length;
mValues = values;
mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
for (int i = 0; i < numValues; ++i) {
PropertyValuesHolder valuesHolder = values[i];
mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
}
// New property/values/target should cause re-initialization prior to starting
mInitialized = false;
}
其中涉及到了一个 PropertyValuesHolder 类,这个类就和补间动画的 Transformation 类似。包含了动画的相关属性和动画过程中相应的值
public class PropertyValuesHolder implements Cloneable {
/**
* 属性名称
*/
String mPropertyName;
/**
* @hide
*/
protected Property mProperty;
/**
* 对应属性的 set 方法,比如 setScale 方法
*/
Method mSetter = null;
/**
* 对应属性的 get 方法
*/
private Method mGetter = null;
/**
* 对应值的相应类,比如 int.class 、 float.class
*/
Class mValueType;
/**
* 动画的关键帧,其他帧都是根据关键帧计算而出
*/
Keyframes mKeyframes = null;
// 相应的估值器
private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
}
接着我们以 start 方法作为入口,看看属性动画是怎么执行的。其中省略了一些代码,我们只看关键部分。
注意看,这里的 start 只是将当前的监听添加到了 AnimationHandler 中。而这个 AnimationHandler 的注释也很明确,用于执行此动画的更新
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
...
addAnimationCallback(0);
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
startAnimation();
}
}
private void startAnimation() {
mAnimationEndRequested = false;
initAnimation();
mRunning = true;
if (mSeekFraction >= 0) {
mOverallFraction = mSeekFraction;
} else {
mOverallFraction = 0f;
}
if (mListeners != null) {
notifyStartListeners();
}
}
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
/**
* @return The {@link AnimationHandler} that will be used to schedule updates for this animator.
* 用于执行此动画的更新
*/
public AnimationHandler getAnimationHandler() {
return AnimationHandler.getInstance();
}
而这个类 AnimationHandler 的注释也比较详细,大致就相当于一个动画更新的管理者。
/**
* This custom, static handler handles the timing pulse that is shared by all active
* ValueAnimators. This approach ensures that the setting of animation values will happen on the
* same thread that animations start on, and that all animations will share the same times for
* calculating their values, which makes synchronizing animations possible.
*
* The handler uses the Choreographer by default for doing periodic callbacks. A custom
* AnimationFrameCallbackProvider can be set on the handler to provide timing pulse that
* may be independent of UI frame update. This could be useful in testing.
*
* 大致意思就是,通过编舞者 Choreographer 进行定期的回调,然后分发相应的监听,进行动画的更新。
*/
public class AnimationHandler {
private final ArrayList<AnimationFrameCallback> mAnimationCallbacks = new ArrayList<>();
/**
* 执行动画帧的回调。
*/
interface AnimationFrameCallback {
/**
* 执行动画帧
*/
boolean doAnimationFrame(long frameTime);
}
}
而 ValueAnimator 实现了 AnimationFrameCallback 这个接口,从而通过回调计算并更新当前动画的相关值,然后通过相应的 set方法进行相关属性的设置,从而达到更新动画的效果
public final boolean doAnimationFrame(long frameTime) {
...
boolean finished = animateBasedOnTime(currentTime);
...
return finished;
}
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
...
float currentIterationFraction = getCurrentIterationFraction(
mOverallFraction, mReversing);
animateValue(currentIterationFraction);
}
return done;
}
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);
}
}
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());
}
}
}
例如我们前面传入的 alpha 参数,最终的 mSetter 就是 View 的 setAlpha
方法,然后通过反射的方式进行更新即可。