android动画主要分成两类(view动画和属性动画),Animation是view动画的基类,Animatior是属性动画的基类。
1 View动画
使用view动画的使用:调用View当中的startAnimation(),设置animation开始时间,设置当前view关联的动画,然后重绘view。
/**
* 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);
}
1.1 Animation
Animation在android.view.animation包下,animation的start()方法,在setStartTime中初始化参数。当开始时间设置为 START_ON_FIRST_FRAME 时,动画将在第一次调用 getTransformation(long, Transformation) 时开始。传递给此方法的时间应通过调用 AnimationUtils.currentAnimationTimeMillis() 而不是 System.currentTimeMillis() 来获取。参数:startTimeMillis - 以毫秒为单位的开始时间
/**
* Convenience method to start the animation the first time
* {@link #getTransformation(long, Transformation)} is invoked.
*/
public void start() {
setStartTime(-1);
}
public void setStartTime(long startTimeMillis) {
mStartTime = startTimeMillis;
mStarted = mEnded = false;
mCycleFlip = false;
mRepeated = 0;
mMore = true;
}
获取要在指定时间点的动画,如果动画仍在运行则为返回ture。
public boolean getTransformation(long currentTime, Transformation outTransformation) {
if (mStartTime == -1) {
mStartTime = currentTime;
}
//动画开始的时间,之后动画必须开始,当start offset > 0时,动画的开始时间为startTime + startOffset
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;
}
//如果normalized大于1或者cancel()结束动画
final boolean expired = normalizedTime >= 1.0f || isCanceled();
mMore = !expired;
mFillEnabled指示是否应考虑 fillBefore, fillBefore:动画停止后是否保留在第一帧。
if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
if (!mStarted) {
//动画开始回调,方法内部最后都走了mListener.onAnimationStart(this);
fireAnimationStart();
mStarted = true;
//使用预加载容器将静态初始化隔离到内部类中,这允许 Animation 及其子类在编译时进行初始化
if (NoImagePreloadHolder.USE_CLOSEGUARD) {
guard.open("cancel or detach or getTransformation");
}
if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
//当动画以 REVERSE 模式重复时,由 getTransformation(long, Transformation) 设置。
if (mCycleFlip) {
normalizedTime = 1.0f - normalizedTime;
}
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
//应用动画,动画执行的关键方法
applyTransformation(interpolatedTime, outTransformation);
//...之后是判断动画是否结束,是否重复
}
}
applyTransformation是Animation的抽象方法,看TranslateAnimation中的实现,
protected void applyTransformation(float interpolatedTime, Transformation t) {
}
Transformation,应用在动画的某个时间点的transfortion,主要用于记录动画信息
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);
}
在view的draw中,获取到Animation
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
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();
//.....
}
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
Animation a, boolean scalingRequired) {
boolean more = a.getTransformation(drawingTime, t, 1f);
从整个Animation的执行过程中,可以看出View动画并没有改变view的top、bottom...等,使用Anmimation动画只是把显示的位置改变了,view.getTop()、getBottom()的值并没有改变,即View动画没有改变View的真正位置。
1.2 Animator
Animator是为动画提供基本支持父类,位于android.animation包下,Animator中主要定义了一些公共方法和接口,start()、cancel()都为空。
Animator类
/**
* Starts this animation. If the animation has a nonzero startDelay, the animation will start
* running after that delay elapses. A non-delayed animation will have its initial
* value(s) set immediately, followed by calls to
* {@link AnimatorListener#onAnimationStart(Animator)} for any listeners of this animator.
*/
public void start() {
}
public void cancel() {
}
ValueAnimator类
public void start() {
start(false);
}
/**
*@param playBackwards 是否反向播放动画
*/
private void start(boolean playBackwards) {
mStarted = true;
mPaused = false;
mRunning = false;
mAnimationEndRequested = false;
// Resets mLastFrameTime when start() is called, so that if the animation was running,
// calling start() would put the animation in the
// started-but-not-yet-reached-the-first-frame phase.
mLastFrameTime = -1;
mFirstFrameTime = -1;
mStartTime = -1;
//添加动画回调,关键方法
addAnimationCallback(0);
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
// If there's no start delay, init the animation and notify start listeners right away
// to be consistent with the previous behavior. Otherwise, postpone this until the first
// frame after the start delay.
startAnimation();
if (mSeekFraction == -1) {
// No seek, start at play time 0. Note that the reason we are not using fraction 0
// is because for animations with 0 duration, we want to be consistent with pre-N
// behavior: skip to the final value immediately.
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}
/**
* Called internally to start an animation by adding it to the active animations list. Must be
* called on the UI thread.
*/
private void startAnimation() {
mAnimationEndRequested = false;
initAnimation();
mRunning = true;
if (mSeekFraction >= 0) {
mOverallFraction = mSeekFraction;
} else {
mOverallFraction = 0f;
}
if (mListeners != null) {
notifyStartListeners();
}
}
/**
* Register to get a callback on the next frame after the delay.
*/
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
getProvider().postFrameCallback(mFrameCallback);
}
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
if (delay > 0) {
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
}
}
在addAnimationFrameCallBack()中获取到AnimationFrameCallbackProvider,添加mFragmentCallback,当收到屏幕刷新信号后,在回调中执行动画doAnimationFrame()
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};
private void doAnimationFrame(long frameTime) {
long currentTime = SystemClock.uptimeMillis();
final int size = mAnimationCallbacks.size();
for (int i = 0; i < size; i++) {
//取出AnimationCallback,ValueAnimator实现了该接口
final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
if (callback == null) {
continue;
}
if (isCallbackDue(callback, currentTime)) {
//调用ValueAnimatior的doAnimationFrame
callback.doAnimationFrame(frameTime);
if (mCommitCallbacks.contains(callback)) {
getProvider().postCommitCallback(new Runnable() {
@Override
public void run() {
commitAnimationFrame(callback, getProvider().getFrameTime());
}
});
}
}
}
cleanUpList();
}
ValueAnimator的doAnimationFrame()
public final boolean doAnimationFrame(long frameTime) {
mLastFrameTime = frameTime;
// The frame time might be before the start time during the first frame of
// an animation. The "current time" must always be on or after the start
// time to avoid animating frames at negative time intervals. In practice, this
// is very rare and only happens when seeking backwards.
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) {
mOverallFraction = clampFraction(fraction);
float currentIterationFraction = getCurrentIterationFraction(
mOverallFraction, mReversing);
animateValue(currentIterationFraction);
}
return done;
}
void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
//mValues是PropertyValuesHolder
mValues[i].calculateValue(fraction);
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
//动画进度更新回调
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
valueAnimtor只是计算动画执行的进度和位置,属性动画的运行机制是通过不断地对值进行操作来实现的,动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果
/**
* This class provides a simple timing engine for running animations
* which calculate animated values and set them on target objects.
*/
public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {
` 构造并返回一个在 int 值之间进行动画处理的 ValueAnimator`
public static ValueAnimator ofInt(int... values) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
return anim;
}
ValueAnimator的ofInt(int...values)、ofFloat(float...values)传入可变参数,比如int(0,100)表示动画从0到100.OfInt中调用setIntValues().
/**
* @param values A set of values that the animation will animate between over time.
* values 一组值,动画将随着时间的推移在这些值之间设置动画
*/
public void setFloatValues(float... values) {
if (values == null || values.length == 0) {
return;
}
//PropertyValuesHolder[] mValues--记录属性值的holder
if (mValues == null || mValues.length == 0) {
setValues(PropertyValuesHolder.ofFloat("", values));
} else {
PropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setFloatValues(values);
}
// New property/values/target should cause re-initialization prior to starting
mInitialized = false;
}
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;
}
总结
属性动画和view动画都通过矩阵变化来实现,在view.drawChild()中调用canvas.connat()移动view。
为什么属性动画移动一个控件后,目标位置仍然能响应用户事件?
我们知道,调用View的translationXX方法之后,虽然在屏幕上的位置是变了,但是它的[left,top,right,bottom]是不会变的。属性动画改变了translationXX的值,getleft、top...在重新布局的时候才会改变。
来捋一遍ViewGroup分派事件的大致流程:
当手指按下时,触摸事件会经过ViewGroup中的dispatchTouchEvent方法筛选符合条件(手指在边界范围内)的子View进行分派事件(如果未被onInterceptTouchEvent拦截的话)。在分发事件时,判断是否进行了动画平移,将点击事件也转换到对应View位置。
调用链ViewGroup类dispatchTouchEvent→dispatchTransformedTouchEvent
那么,如果某个子View刚好应用了translation属性动画,在ViewGroup筛选子View时,直接判断触摸点是否在[left,top,right,bottom]范围内,是肯定不行的。
那它是怎么判断的呢?
- 它会先调用子View的hasIdentityMatrix方法来判断这个View是否应用过位移、缩放、旋转之类的属性动画。
- 如果应用过的话,那接下来还会把触摸点映射到该子View的逆矩阵上(getInverseMatrix)。
- 判断处理后的触摸点,是否在该子View的边界范围内。
那么为什么只有属性动画可以这样,补间动画就不行呢?
View在draw的时候,会检测是否设置了Animation(补间动画),
如果有的话,会获取这个动画当前的值(旋转或位移或缩放,透明度等),应用到canvas上,然后把东西draw出来。
比如设置了位移动画,当前值是向右移动了100,那么效果就等于这样:
Matrix matrix = new Matrix();
matrix.setTranslate(100, 0);
canvas.setMatrix(matrix);
它的作用只会在draw的时候有效。
虽然大家都是操作Matrix,但是Matrix的对象不一样(属性动画操作的Matrix,是View的mRenderNode所对应的Matrix),
所以在ViewGroup筛选的时候,应用属性动画的View会被正确找到,而补间动画的不行。
3 Transition转场动画
Transition位于android.transition下 Transition 包含有关在场景更改期间将在其目标上运行的动画的信息。这个抽象类的子类可以编排几个子转换(TransitionSet)或者它们可以自己执行自定义动画。任何转换都有两个主要工作:(1)捕获属性值,以及(2)根据捕获的属性值的变化播放动画。自定义transition 知道它对 View 对象的哪些属性值感兴趣,也知道如何对这些值的更改进行动画处理。例如,Fade transition 跟踪对可见性相关属性的更改,并能够构建和运行使项目淡入淡出的动画或基于对这些属性的更改.