13518219792

建站动态

根据您的个性需求进行定制 先人一步 抢占小程序红利时代

补间动画源码中分析机制原理

[[438831]]

前言

补间动画移动后,点击事件的响应为什么还在原来的位置?

那今天我们就来从源码解析原理

一、补间动画

补间动画可以在一个视图容器内执行一系列简单变换(具体的变换步骤有:位置、大小、旋转、透明度);

我们可以通过平移、旋转、缩放、透明度等API进行具体的操作;

补间动画的实现方式可以通过 XML或通过Android代码两种方式 去定义;

1、xml方式实现

文件名:animator_translate.xml

 
 
 
 
  1.  
  2.     xmlns:android="http://schemas.android.com/apk/res/android" 
  3.     android:fromXDelta="0" 
  4.     android:fromYDelta="0" 
  5.     android:toYDelta="0" 
  6.     android:toXDelta="200" 
  7.     android:duration="500" 
  8.     android:fillAfter="true"> 
  9.  

代码加载xml文件获取动画

 
 
 
 
  1. //加载动画 
  2. Animation animation = AnimationUtils.loadAnimation(this, R.anim.animator_translate); 
  3. //执行动画 
  4. testBtn.startAnimation(animation); 

2、代码方式实现

 
 
 
 
  1. TranslateAnimation translateAnimation = new TranslateAnimation(0,200,0,0); 
  2. translateAnimation.setDuration(500);//动画执行时间 
  3. translateAnimation.setFillAfter(true);//动画执行完成后保持状态 
  4. //执行动画 
  5. testBtn.startAnimation(translateAnimation); 

二、补间动画原理解

1、startAnimation

startAnimation(rotateAnimation)方法进入源码;

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

首先是通过setStartTime()设置了动画的开始时间;

 
 
 
 
  1. //View.java 
  2.  public void setStartTime(long startTimeMillis) { 
  3.      mStartTime = startTimeMillis; 
  4.      mStarted = mEnded = false; 
  5.      mCycleFlip = false; 
  6.      mRepeated = 0; 
  7.      mMore = true; 
  8.  } 

这里只是对一些变量进行赋值,再来看看下一个方法;

设置动画setAnimation(animation):

 
 
 
 
  1. //View.java 
  2.   public void setAnimation(Animation animation) { 
  3.       mCurrentAnimation = animation; 
  4.       if (animation != null) { 
  5.                 if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF 
  6.                   && animation.getStartTime() == Animation.START_ON_FIRST_FRAME) { 
  7.               animation.setStartTime(AnimationUtils.currentAnimationTimeMillis()); 
  8.           } 
  9.           animation.reset(); 
  10.       } 
  11.   } 

这里面也是将动画实例赋值给当前的成员变量;

分析startAnimation()方法里的invalidateParentCaches();

 
 
 
 
  1. //View.java 
  2.  protected void invalidateParentCaches()  
  3.      if (mParent instanceof View) { 
  4.          ((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED; 
  5.      } 
  6.  } 

可以看到这里仅仅是设置动画标记,在视图构建或者属性改变时是必要的;

再回到startAnimation()方法里面invalidate(true);

2、invalidate

 
 
 
 
  1. //View.java 
  2.   public void invalidate(boolean invalidateCache) { 
  3.       invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); 
  4.   } 
  5.   void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, 
  6.           boolean fullInvalidate) { 
  7.       if (mGhostView != null) { 
  8.           mGhostView.invalidate(true); 
  9.           return; 
  10.       } 
  11.         ................. 
  12.           // Propagate the damage rectangle to the parent view. 
  13.           final AttachInfo ai = mAttachInfo; 
  14.           final ViewParent p = mParent; 
  15.           if (p != null && ai != null && l < r && t < b) { 
  16.               final Rect damage = ai.mTmpInvalRect; 
  17.               damage.set(l, t, r, b); 
  18.               p.invalidateChild(this, damage); 
  19.           } 
  20.       } 
  21.    } 

这里着重看p.invalidateChild(this, damage);

 
 
 
 
  1. //ViewGroup.java 
  2.   @Deprecated 
  3.   @Override 
  4.   public final void invalidateChild(View child, final Rect dirty) { 
  5.       final AttachInfo attachInfo = mAttachInfo; 
  6.       if (attachInfo != null && attachInfo.mHardwareAccelerated) { 
  7.           // HW accelerated fast path 
  8.           onDescendantInvalidated(child, child); 
  9.           return; 
  10.       } 
  11.       ViewParent parent = this; 
  12.         ......... 
  13.           do { 
  14.               View view = null; 
  15.               if (parent instanceof View) { 
  16.                   view = (View) parent; 
  17.               } 
  18.          ......... 
  19.               parent = parent.invalidateChildInParent(location, dirty); 
  20.           } while (parent != null); 
  21.       } 
  22.   } 
 
 
 
 
  1. //ViewGroup.java 
  2.   @Deprecated 
  3.   @Override 
  4.   public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) { 
  5.       if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) { 
  6.          ....... 
  7.           return mParent; 
  8.       } 
  9.       return null; 
  10.   } 
 
 
 
 
  1. //ViewRootImpl.java 
  2.    @Override 
  3.    public ViewParent invalidateChildInParent(int[] location, Rect dirty) { 
  4.        checkThread(); 
  5.        if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty); 
  6.        if (dirty == null) { 
  7.            invalidate(); 
  8.            return null; 
  9.        } else if (dirty.isEmpty() && !mIsAnimating) { 
  10.            return null; 
  11.        } 
  12.         ....... 
  13.        invalidateRectOnScreen(dirty); 
  14.        return null; 
  15.    } 

这里所有的返回值都变为null了,之前执行的do{}while()循坏也会停止。

3、scheduleTraversals()

 
 
 
 
  1.  //ViewRootImpl.java 
  2.   private void invalidateRectOnScreen(Rect dirty) { 
  3.       ...... 
  4.       if (!mWillDrawSoon && (intersected || mIsAnimating)) { 
  5.           scheduleTraversals(); 
  6.       } 
  7.   } 
  8. //ViewRootImpl.java 
  9. void scheduleTraversals() { 
  10.       if (!mTraversalScheduled) { 
  11.           mTraversalScheduled = true; 
  12.           mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); 
  13.           mChoreographer.postCallback( 
  14.                   Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); 
  15.           if (!mUnbufferedInputDispatch) { 
  16.               scheduleConsumeBatchedInput(); 
  17.           } 
  18.           notifyRendererOfFramePending(); 
  19.           pokeDrawLockIfNeeded(); 
  20.       } 
  21.   } 

主要看mTraversalRunnable,我们找到mTraversalRunnable这个类;

 
 
 
 
  1. //ViewRootImpl.java 
  2. final class TraversalRunnable implements Runnable { 
  3.       @Override 
  4.       public void run() { 
  5.           doTraversal(); 
  6.       } 
  7.   } 
  8.   final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); 

4、doTraversal()

doTraversal()方法;

 
 
 
 
  1. //ViewRootImpl.java 
  2. void doTraversal() { 
  3.          ....... 
  4.           performTraversals(); 
  5.          ....... 
  6.       } 
  7.   } 

5、draw

 
 
 
 
  1. boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { 
  2. ... 
  3. //清除上次动画保存的Transformation 
  4. if ((parentFlags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) != 0) { 
  5.     parent.getChildTransformation().clear(); 
  6.     parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION; 
  7. ...... 
  8. final Animation a = getAnimation(); 
  9. if (a != null) { 
  10.     //根据当前时间计算当前帧的动画,more表示是否需要执行更多帧的动画 
  11.     more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired); 
  12.     concatMatrix = a.willChangeTransformationMatrix(); 
  13.     if (concatMatrix) { 
  14.         mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM; 
  15.     } 
  16.    //拿到当前帧需要的变换 ,这个值会在applyLegacyAnimation中进行设置 
  17.     transformToApply = parent.getChildTransformation(); 
  18. .... 
  19. if (transformToApply != null) { 
  20.     if (concatMatrix) { 
  21.         if (drawingWithRenderNode) { 
  22.             renderNode.setAnimationMatrix(transformToApply.getMatrix()); 
  23.         } else { 
  24.             // Undo the scroll translation, apply the transformation matrix, 
  25.             // then redo the scroll translate to get the correct result. 
  26.             canvas.translate(-transX, -transY); 
  27.             canvas.concat(transformToApply.getMatrix());//在这里调用canvas的concat方法,实现最终的平移效果 (做矩阵相乘) 
  28.             canvas.translate(transX, transY); 
  29.         } 
  30.        //标记需要清除Tranformation 
  31.         parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION; 
  32.     } 
  33.     float transformAlpha = transformToApply.getAlpha(); 
  34.     if (transformAlpha < 1) { 
  35.         alpha *= transformAlpha; 
  36.         parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION; 
  37.     } 
  38. ... 

6、applyLegacyAnimation

 
 
 
 
  1. private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime, 
  2.         Animation a, boolean scalingRequired) { 
  3.     ... 
  4.     //获取Transformation 每个ViewGroup中的子View共同使用一个Transformation 为了多个View有动画时频繁创建多个Transformation 
  5.     //这个和在draw方法中取出的transformToApply是一个对象 就是最终应用到Canvas上的Transform 
  6.     final Transformation t = parent.getChildTransformation(); 
  7.     //调用Animation的getTransformation方法来根据当前时间计算Transformation 这个对象的值最终会由getTransformation方法中进行赋值 
  8.     boolean more = a.getTransformation(drawingTime, t, 1f); 
  9.     invalidationTransform = t; 
  10.     ... 
  11.     //如果动画还没有播放完成 需要让动画循环起来 实际上是继续调用invalidate 
  12.     if (more) { 
  13.      if (parent.mInvalidateRegion == null) { 
  14.                     parent.mInvalidateRegion = new RectF(); 
  15.                 } 
  16.                 final RectF region = parent.mInvalidateRegion; 
  17.                //调用Animation 的getInvalidateRegion来根据invalidationTransform计算 parent的invalidateRegion 
  18.                 a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region, 
  19.                         invalidationTransform); 
  20.                 // The child need to draw an animation, potentially offscreen, so 
  21.                 // make sure we do not cancel invalidate requests 
  22.                 parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION; 
  23.                 final int left = mLeft + (int) region.left; 
  24.                 final int top = mTop + (int) region.top; 
  25.                 //调用invalidate执行下一次绘制请求,这样动画就动起来了 
  26.                 parent.invalidate(left, top, left + (int) (region.width() + .5f), 
  27.                         top + (int) (region.height() + .5f)); 
  28.     } 
 
 
 
 
  1. //Animation.java 
  2. //返回值表示动画是否没有播放完成 并且需要计算outTransformation 也就是动画需要做的变化 
  3. public boolean getTransformation(long currentTime, Transformation outTransformation) { 
  4.     if (mStartTime == -1) { 
  5.       mStartTime = currentTime;//记录第一帧的时间 
  6.     } 
  7.      if (duration != 0) { 
  8.         normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) / //计算运行的进度(0-1) (当前时间-开始时间+偏移量)/动画总时长 
  9.             (float) duration; 
  10.         } 
  11.       final boolean expired = normalizedTime >= 1.0f || isCanceled(); //判断动画是否播放完成 或者被取消 
  12.       mMore = !expired; 
  13.       if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f); //处理最大值 
  14.       final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);//根据插值器计算的当前动画运行进度 
  15.       applyTransformation(interpolatedTime, outTransformation);//根据动画进度  计算最终的outTransformation 
  16.       return mMore; 
 
 
 
 
  1. //applyTransformation每种类型的动画都有自己的实现 这里以位移动画为例 
  2. //TranslateAnimation.java 
  3. @Override 
  4.     protected void applyTransformation(float interpolatedTime, Transformation t) { 
  5.         //Transformation可以理解成 存储View的一些变换信息,将变化信息保存到成员变量matrix中 
  6.         float dx = mFromXDelta; 
  7.         float dy = mFromYDelta; 
  8.         if (mFromXDelta != mToXDelta) { 
  9.             dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);//计算X方向需要移动的距离 
  10.         } 
  11.         if (mFromYDelta != mToYDelta) { 
  12.             dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);//计算Y方向需要移动的距离 
  13.         } 
  14.         t.getMatrix().setTranslate(dx, dy); //将最终的结果设置到Matrix上面去 
  15.     } 

7、动画总结

总结

至于未来会怎样,要走下去才知道,反正路还很长,天总会亮;

加油老铁们!

本文转载自微信公众号「Android开发编程」


分享题目:补间动画源码中分析机制原理
本文URL:http://cdbrznjsb.com/article/dhogopp.html

其他资讯

让你的专属顾问为你服务