在项目开发中,我们经常会用到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 { 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) { ...... 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) { ...... if (mDisappearingChildren != null) { final ArrayList<View> disappearingChildren = mDisappearingChildren; final int disappearingCount = disappearingChildren.size() - 1; for (int i = disappearingCount; i >= 0; i--) { final View child = disappearingChildren.get(i); more |= drawChild(canvas, child, drawingTime); } } }
|
由此得出,当一个View正在执行动画时,暂时是无法被Gone掉或者从View Hierarchy中remove的,必须先将View的动画停止才行