在项目开发中,我们经常会用到SwipeRefreshLayout这个控件来做下拉刷新。当SwipeRefreshLayout做为一个Fragment的顶层View时并且在触发下拉刷新动画的情况下,会发现这个Fragment “无法被remove或者hide”,只有当刷新动画停止时Fragment才能被 “正常remove或者hide”。解释这个原因,首先得分析Animation的执行原理

Android中提供了View.startAnimation()方法来执行一个动画效果。先来看看View中的startAnimation()方法的实现

1
2
3
4
5
6
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}

1.当执行View.startAnimation()方法后会先调用View.setAnimation(Animation)方法将这个Animation对象赋值给自己的一个名为mCurrentAnimation的成员变量

2.立即调用invalidate()方法触发View的重绘

当请求重绘时,重绘操作会从ViewRootImpl触发performDraw()开始,逐一向View Hierarchy分发重绘事件。首先会调用View.draw(canvas)函数,然后在这个draw()函数中又会调用View.dispatchDraw(canvas)函数

3.View.dispatchDraw(canvas)函数在View类中只是个空方法,它在ViewGroup中被重写。ViewGroup会遍历它的child view,如果该child是visible的或者child.getAnimation != null,则调用drawChild(canvas, child, drawingTime)所以由此看出当一个View正在执行动画时依然会被绘制,即使它的可见性不是visible的

1
2
3
4
5
6
7
8
9
10
11
12
protected void dispatchDraw(Canvas canvas) {
for (int i = 0; i < childrenCount; i++) {
......
int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
......
}

drawChild(canvas, child, drawingTime)函数实则调用了View.draw(canvas, this, drawingTime)方法

4.View.draw(canvas, this, drawingTime)函数中会判断自己是否有Animation存在,如果存在则调用View.applyLegecyAnimation(parent, drawingTime, a, s)函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
......
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();
}
......
}

5.applyLegacyAnimation()函数中主要调用了Animation的getTransformation(drawingTime, t, 1f)方法

1
2
3
4
5
6
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime, Animation a, boolean scalingRequired) {
......
final Transformation t = parent.getChildTransformation();
boolean more = a.getTransformation(drawingTime, t, 1f);
......
}

6.Animation.getTransformation(drawingTime, t, 1f)方法实则调用Animation.getTransformation(long currentTime, Transformation outTransformation)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;
}
......
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
......
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
applyTransformation(interpolatedTime, outTransformation);
}
}

该方法中先根据参数currentTime计算当前动画的进度,动画进度在[0.0, 1.0]之间,比如一个1000ms的动画,已经执行了500ms了,那么进度就是0.5;当进度超过1.0时即表示动画已经执行完成

然后将进度值传递给插值器,通过mInterpolator.getInterpolation(normalizedTime)得到最终的进度值

7.然后根据该进度值调用applyTransformation(float, Transformation)方法,该方法就是真正的处理动画变化的过程,Animation中只是一个空方法,具体由它的子类实现,例如在TranslateAnimation中是这么实现的

1
2
3
4
5
6
7
8
9
10
11
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);
}

8.现在回头再来看下第5步中的applyLegacyAnimation()函数,当调用Animation.getTransformation()方法后会得到返回值标识当前动画是否已经结束,如果动画还没结束则再次调用parent.invalidate()方法请求一次重绘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime, Animation a, boolean scalingRequired) {
boolean more = a.getTransformation(drawingTime, t, 1f);
......
if (more) {
......
// 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));
}
}

至此,Android Animation的执行流程就分析完了,现在在回过头来看看文章开始提出的问题:

1.在SwipeRefreshLayout触发下拉刷新时,会start一个无限次数的animation来刷新转圈动画

2.在remove fragment的时候会将fragment的mView对象(这时的mView对象就是SwipeRefreshLayout)从parent中remove掉,从ViewGroup.removeView()方法的具体实现可以得知,当被remove的view如果存在animation时,会将其先加入到一个disappearingChildren的列表中,这个列表是专门用来存放那些已经被remove但还在执行动画的View,最后再从child数组中remove该view

1
2
3
4
5
6
7
8
private void removeViewInternal(int index, View view) {
......
if (view.getAnimation() != null ||
(mTransitioningViews != null && mTransitioningViews.contains(view))) {
addDisappearingView(view);
}
......
}

3.当重绘事件分发到ViewGroup.dispatchDraw()时会遍历disappearingChildren这个列表元素并重绘这些view

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected void dispatchDraw(Canvas canvas) {
......
// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
final ArrayList<View> disappearingChildren = mDisappearingChildren;
final int disappearingCount = disappearingChildren.size() - 1;
// Go backwards -- we may delete as animations finish
for (int i = disappearingCount; i >= 0; i--) {
final View child = disappearingChildren.get(i);
more |= drawChild(canvas, child, drawingTime);
}
}
}

由此得出,当一个View正在执行动画时,暂时是无法被Gone掉或者从View Hierarchy中remove的,必须先将View的动画停止才行