AndroID 平台目前提供了两大类动画,在 AndroID 3.0 之前,一大类是 VIEw Animation,包括 Tween animation(补间动画),Frame animation(帧动画),在 AndroID 3.0 中又引入了一个新的动画系统:Property Animation,即属性动画。本篇文章主要介绍 VIEw Animation 的运行原理。
VIEw Animation 可以使视图执行补间动画。即给定两个关键帧,然后中间部分按照一定的算法自动计算补充。
补间动画的继承关系:
下面看一个示例,比如想要对透明度做一个变化:
Animation AlphaAnimation = new AlphaAnimation(1,0);AlphaAnimation.setDuration(1000);imageVIEw.startAnimation(AlphaAnimation);@H_419_31@
那么这个动画是如何实现的呢,下面就要讲述其实现原理。
为了探究补间动画的实现原理,需要对相关源码进行解读,源码版本为 AndroID API 29 Platform。在解读之前,大家可以试着回答下面这些问题。
为什么移动位置后,点击事件的响应依旧是在原来位置上?
如果想知道动画的执行进度,是如何获取呢?
如果对 VIEw 做放大缩小得动画,那么其宽度高度值是否会变化。
相关类介绍下面开始源码分析。首先看下基类 Animation。
public abstract class Animation implements Cloneable {}@H_419_31@Animation 是一个抽象类,里面包含了各种动画相关的属性(时长,起始时间,重复次数,插值器等),回调(Listeners)。该类整体比较简单,大家直接看源码就好。
AlphaAnimation下面来看下 Animation 子类,为了方便,本次就只说说 AlphaAnimation。
class AlphaAnimation extends Animation { private float mFromAlpha; mToAlpha; /** * Constructor used when an AlphaAnimation is loaded from a resource. * * @param context Application context to use * attrs Attribute set from which to read values */ public AlphaAnimation(Context context,AttributeSet attrs) { super(context,attrs); TypedArray a = context.obtainStyledAttributes(attrs,com.androID.internal.R.styleable.AlphaAnimation); mFromAlpha = a.getfloat(com.androID.internal.R.styleable.AlphaAnimation_fromAlpha,1.0f); mToAlpha = a.getfloat(com.androID.internal.R.styleable.AlphaAnimation_toAlpha,1)">); a.recycle(); } * Constructor to use when building an AlphaAnimation from code * * fromAlpha Starting Alpha value for the animation,where 1.0 means * fully opaque and 0.0 means fully transparent. * toAlpha Ending Alpha value for the animation. public AlphaAnimation(float fromAlpha, toAlpha) { mFromAlpha = fromAlpha; mToAlpha = toAlpha; } * Changes the Alpha property of the supplIEd {@link transformation}
* 实现动画的关键函数,这里通过当前的播放进度,计算当前的透明度,然后将其赋值给 transformation 实例 */ @OverrIDe protected voID applytransformation( interpolatedTime,transformation t) { final float Alpha = mFromAlpha; t.setAlpha(Alpha + ((mToAlpha - Alpha) * interpolatedTime)); } @OverrIDe boolean willChangetransformationMatrix() { return false; } @OverrIDe // 不改变边界 willChangeBounds() { ; } * @hIDe hasAlpha() { true; }}@H_419_31@整个代码也是很简单,其实很多逻辑都在基类处理了。然后子类只需要重写一些和自己动画相关的方法就好。其中 applytransformation 是实现某一种动画的关键,每个子类都必须重写。
这里需要注意的是,transformation 就是用来存储每一次动画的参数。其中平移,旋转,缩放都是通过改变 Matrix 实现的,而透明度则是改变 Alpha 值实现的。
为了便于大家进一步理解,可以在看看 AnimationSet。
AnimationSet因为 AnimationSet 是其他几个子类得集合体,所以看看它的代码逻辑还是可以发现一些不一样的。其内部代码比较多,就不贴出来了。只是挑一部分讲下:
* Add a child animation to this animation set. * The transforms of the child animations are applIEd in the order * that they were added * a Animation to add. voID addAnimation(Animation a) {
// 数组来保存动画 mAnimations.add(a); boolean noMatrix = (mFlags & PROPERTY_MORPH_MATRIX_MASK) == 0; if (noMatrix && a.willChangetransformationMatrix()) { mFlags |= PROPERTY_MORPH_MATRIX_MASK; } boolean changeBounds = (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == 0; if (changeBounds && a.willChangeBounds()) { mFlags |= PROPERTY_CHANGE_BOUNDS_MASK; } if ((mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK) { mLastEnd = mStartOffset + mDuration; } else { if (mAnimations.size() == 1) { mDuration = a.getStartOffset() + a.getDuration(); mLastEnd = mStartOffset + mDuration; } { mLastEnd = Math.max(mLastEnd,mStartOffset + a.getStartOffset() + a.getDuration()); mDuration = mLastEnd - mStartOffset; } } mDirty = ; } * The transformation of an animation set is the concatenation of all of its * component animations. * * @see androID.vIEw.animation.Animation#gettransformation
* true 表示动画还在运行 boolean gettransformation(long currentTime,1)">int count = mAnimations.size(); final ArrayList<Animation> animations = mAnimations; final transformation temp = mTemptransformation; boolean more = boolean started = boolean ended = ; t.clear(); for (int i = count - 1; i >= 0; --i) { final Animation a = animations.get(i); // 清除上一个的数据 temp.clear();
// 通过 temp 来获取每个 Animation 的 transformation more = a.gettransformation(currentTime,temp,getScaleFactor()) || more;
// 将各种动画参数组合在一起,注意 t 是引用对象,所以这里改了之后,外面拿到的也是改了的。 t.compose(temp); started = started || a.hasstarted(); ended = a.hasEnded() && ended; } // 是否开始了 if (started && !mStarted) { dispatchAnimationStart(); mStarted = ; } if (ended != mEnded) { dispatchAnimationEnd(); mEnded = ended; } return more; }@H_419_31@上面是 AnimationSet 中我认为两个比较重要的方法:
addAnimation:将其他动画类型添加到 set 里面,内部实际上是通过一个 List 来保存的。然后将每个动画的各种属性都记录下。
gettransformation:获取每个动画的下一个动画参数,然后将其组合在一起。
前面介绍了 Animation 一些背景知识。到这里,大家多少会有一些认识了。接下去就按照调用流程来分析动画的执行。
源码解析VIEw.startAnimation()startAnimation(Animation animation) {
// 传入的值是-1,代表准备动画了 animation.setStartTime(Animation.START_ON_FirsT_FRAME); setAnimation(animation); invalIDateParentCaches(); // 给 parent 的 mPrivateFlag 加了一个 PFLAG_INVALIDATED invalIDate(); // 其目的就是将其和子 vIEw 的 drawing 缓存都标记为无效,然后可以 redrawn } setAnimation(Animation animation) {
// VIEw 中有个属性是用来存储当前的 Animation 的 mCurrentAnimation = animation; if (animation != null) { // If the screen is off assume the animation start time is Now instead of the next frame we draw. KeePing the START_ON_FirsT_FRAME start time would cause the animation to start when the screen turns back on if (mAttachInfo != null && mAttachInfo.mdisplayState == display.STATE_OFF && animation.getStartTime() == Animation.START_ON_FirsT_FRAME) { animation.setStartTime(AnimationUtils.currentAnimationTimeMillis()); } animation.reset(); } }@H_419_31@简而言之 startAnimation 主要是做这么几件事情:
开始动画前,先告知 Animation,可以做一些准备,包括部分参数的赋值;
更新 VIEw 自身的 Animation 的属性值;
给父 VIEw 的 mPrivateFlag 加上 PFLAG_INVALIDATED 属性;
将自身和子 vIEw 的 draw cache 都标记为无效的,通过 父 VIEw 调用 invalIDateChild 促发 redrawn;
VIEwGroup.invalIDateChild
下面看下 invalIDateChild 是怎么促发重绘的:
VIEwGroup voID invalIDateChild(VIEw child,1)">final Rect dirty) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null && attachInfo.mHarDWareAccelerated) { HW accelerated fast path onDescendantInvalIDated(child,child); ; } VIEwParent parent = this) { ..... 省略非关键性代码 do { // 无限循环 VIEw vIEw = ; if (parent instanceof VIEw) { vIEw = (VIEw) parent; } if (drawAnimation) { if (vIEw != ) { vIEw.mPrivateFlags |= PFLAG_DRAW_ANIMATION; } else VIEwRootImpl) { ((VIEwRootImpl) parent).mIsAnimating = ; } } If the parent is dirty opaque or not dirty,mark it dirty with the opaque flag coming from the child that initiated the invalIDate ) { if ((vIEw.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) { vIEw.mPrivateFlags = (vIEw.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY; } } // 最终调用的是该方法来重绘 parent = parent.invalIDateChildInParent(location,dirty); .... 省略非关键性代码 } while (parent != ); // 终止条件是找不到父 VIEw 了。 } }@H_419_31@这里很主要的一件事,找到 rooVIEw ,然后调用了 invalIDateChildInParent 方法。这里的 rootVIEw 其实就是 VIEwRootImpl,至于为啥不是 DecorVIEw,这个可以去看看这篇文章:
Android View 绘制流程之 DecorView 与 ViewRootImplVIEwRootImpl.invalIDateChildInParent@OverrIDe public VIEwParent invalIDateChildInParent(int[] location,Rect dirty) { checkThread(); // 检查是否是主线程 if (DEBUG_DRAW) Log.v(mTag,"InvalIDate child: " + dirty); if (dirty == ) { // 从上面路径来看,这里是不可能为空的 invalIDate(); ; } if (dirty.isEmpty() && !mIsAnimating) { ; } // ... 跳过一段无关的代码
invalIDateRectOnScreen(dirty); ; }@H_419_31@这里可以看出的是,最终会调用 invalIDateRectOnScreen 方法。
VIEwRootImpl.invalIDateRectOnScreeninvalIDateRectOnScreen(Rect dirty) { final Rect localDirty = mDirty; Add the new dirty rect to the current one 其实就是把两个矩阵融合在一起 localDirty.union(dirty.left,dirty.top,dirty.right,dirty.bottom); Intersect with the bounds of the window to skip updates that lIE outsIDe of the visible region float appScale = mAttachInfo.mApplicationScale;
// 主要就是检查菊矩阵边界对不对 boolean intersected = localDirty.intersect(0,0,(int) (mWIDth * appScale + 0.5f),(int) (mHeight * appScale + 0.5f));
// 边界不对,就会直接置空 if (!intersected) { localDirty.setEmpty(); }
// mWillDrawSoon 是当前是否马上就要开始绘制了,如果开始绘制,就不去发起绘制了 if (!mWillDrawSoon && (intersected || mIsAnimating)) { scheduleTraversals(); } }@H_419_31@invalIDateRectOnScreen 主要是就是把 dirty 这个矩阵和已有的进行融合,然后再看看需不需要发起刷新。
scheduleTraversals()
作用是将performTraversals()
封装到一个 Runnable 里面,然后扔到 Choreographer 的待执行队列里,这些待执行的 Runnable 将会在最近的一个 16.6 ms 屏幕刷新信号到来的时候被执行。而performTraversals()
是 VIEw 的三大 *** 作:测量、布局、绘制的发起者。小结:
当调用了 VIEw.startAniamtion() 之后,动画并没有马上就被执行,这个方法只是做了一些变量初始化 *** 作,接着将 VIEw 和 Animation 绑定起来,然后调用重绘请求 *** 作,内部层层寻找 mParent,最终走到 VIEwRootImpl 的 scheduleTraversals 里发起一个遍历 VIEw 树的请求,这个请求会在最近的一个屏幕刷新信号到来的时候被执行,调用 performTraversals 从根布局 DecorVIEw 开始遍历 VIEw 树。
开始动画前面说到了动画的开始最终是通过促发 VIEw 绘制来形成的。此处不会再讲 VIEw 的绘制原理,不懂得可以看下面两篇文章:Android View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)那么在 VIEw 绘制过程中,是在哪里开始绘制的呢? 答案是 VIEw 的 draw 方法里面开始的。
但是这个 draw 不是我们自定义 vIEw 时常见的 draw 方法,该 draw 方法有三个参数,是用于 VIEw 自身绘制用的。
VIEw.draw该方法比较长,截取部分来讲:
* This method is called by VIEwGroup.drawChild() to have each child vIEw draw itself. * * This is where the VIEw specializes rendering behavior based on layer type,* and harDWare acceleration. boolean draw(Canvas canvas,VIEwGroup parent,1)"> drawingTime) {
// 用于判断是否支持硬件加速 boolean harDWareAcceleratedCanvas = canvas.isHarDWareAccelerated(); /* If an attached vIEw draws to a HW canvas,it may use its RenderNode + displayList. * * If a vIEw is dettached,its displayList shouldn't exist. If the canvas isn't * HW accelerated,it can't handle drawing RenderNodes. */ boolean drawingWithRenderNode = mAttachInfo != null && mAttachInfo.mHarDWareAccelerated && harDWareAcceleratedCanvas; ..... 跳过一些代码
// 获取之前存储的 animation getAnimation(); if (a != ) {
// 不为空就说明是有动画的 more = applyLegacyAnimation(parent,drawingTime,a,scalingrequired); concatMatrix = a.willChangetransformationMatrix(); (concatMatrix) { mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_transform; }
// 这里是拿到动画参数,后面会再次讲到 transformToApply = parent.getChildtransformation(); } ...... 省略代码}@H_419_31@首先来看这个 draw 方法的三个参数:
canvas:这个没有什么好分析的,是用来绘制用的
parent:这个其实就是父 VIEw,方便获取一些数据;
drawingTime:这个很重要,就是当前的绘制时间,后续做动画的时候,会计算时间差,然后更新插值器;
一进到 draw 方法,就先获取当前是否支持硬件加速。有硬件加速和没有硬件加速走的是两套逻辑。然后是获取保之前存储的 animation。
VIEw.applyLegacyAnimation接着调用 applyLegacyAnimation 开始处理动画相关的逻辑。下面看下其方法内部的逻辑。
* Utility function,called by draw(canvas,parent,drawingTime) to handle the less common * case of an active Animation being run on the vIEw. boolean applyLegacyAnimation(VIEwGroup parent,1)"> drawingTime,Animation a, scalingrequired) {
// 用于保存此时重绘的变换 transformation invalIDationtransform; int flags = parent.mGroupFlags; boolean initialized = a.isInitialized();
// 判断动画有没有开始初始化,没有的化先进行初始化 initialized) { a.initialize(mRight - mleft,mBottom - mtop,parent.getWIDth(),parent.getHeight()); a.initializeInvalIDateRegion(0,mRight - mleft,1)"> mtop); ) a.setListenerHandler(mAttachInfo.mHandler);
// 同时调用开始动画回调 onAnimationStart(); } // 这里是从父类中获取当前的t,但是如果一个父类存在多个子 vIEw 需要运动,那获取的岂不是一样了?其实每个子vIEw 都会重新赋值,不会影响。 final transformation t = parent.getChildtransformation();
// 这里是根据时间,t,缩放因子来计算 t,这里 t 是一个对象,在 animation 中进行赋值后,在这里也可以用到 boolean more = a.gettransformation(drawingTime,t,1f);
// 对于需要缩放的子vIEw,需要重新计算t,可是调用方法确是一样的?那结果有啥不一样吗?这里是为了将缩放和不缩放的 t 分出来 if (scalingrequired && mAttachInfo.mApplicationScale != 1f) { if (parent.mInvalIDationtransformation == ) { parent.mInvalIDationtransformation = new transformation(); } invalIDationtransform = parent.mInvalIDationtransformation; a.gettransformation(drawingTime,invalIDationtransform,1f); } { invalIDationtransform = t; } // more 为 true ,代表动画还未结束 (more) { a.willChangeBounds()) { if ((flags & (VIEwGroup.FLAG_OPTIMIZE_INVALIDATE | VIEwGroup.FLAG_ANIMATION_DONE)) == VIEwGroup.FLAG_OPTIMIZE_INVALIDATE) { parent.mGroupFlags |= VIEwGroup.FLAG_INVALIDATE_required; } if ((flags & VIEwGroup.FLAG_INVALIDATE_required) == 0 The child need to draw an animation,potentially offscreen,so make sure we do not cancel invalIDate requests parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
// 发起下一次重绘 parent.invalIDate(mleft,mtop,mRight,mBottom); } } { if (parent.mInvalIDateRegion == ) { parent.mInvalIDateRegion = RectF(); } final RectF region = parent.mInvalIDateRegion;
// 对于会改变自己边界的动画,比如缩放,这时候需要计算当前缩放的尺寸范围 a.getInvalIDateRegion(0,region,invalIDationtransform); make sure we do not cancel invalIDate requests parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION; // region 此时是更新尺寸后的范围了 int left = mleft + () region.left; int top = mtop + () region.top;
// 发起下一次重绘 parent.invalIDate(left,top,left + (int) (region.wIDth() + .5f),top + (int) (region.height() + .5f)); } } more; }@H_419_31@这个方法其实理解起来也很简单,主要就是为了得到一个根据当前时间计算得到 transformation 实例,里面包含了下一次动画所需要的信息。
transformation 里面的内容如下:
transformationclass transformation { * Indicates a transformation that has no effect (Alpha = 1 and IDentity matrix.) static int TYPE_IDENTITY = 0x0; * Indicates a transformation that applIEs an Alpha only (uses an IDentity matrix.) int TYPE_Alpha = 0x1 * Indicates a transformation that applIEs a matrix only (Alpha = 1.) int TYPE_MATRIX = 0x2 * Indicates a transformation that applIEs an Alpha and a matrix. int TYPE_BOTH = TYPE_Alpha | TYPE_MATRIX; // 矩阵,控制缩放,平移,旋转 protected Matrix mMatrix;
// 透明度 mAlpha; mtransformationType; mHasClipRect; private Rect mClipRect = Rect(); ...... 省略一大串代码 }@H_419_31@
上述代码还省略很多方法,其实都是对矩阵的 *** 作。
这里提一下:Matrix 方法中的 setRotate() 方法会先清除该矩阵,即设为单位矩阵。之后设置旋转 *** 作的,同样,setTranslate() 等方法也是一样的。所以是不能叠加各种效果在一起的.如果是想多种效果同时使用的话,postRotate(),postTranslate()等类似的矩阵变换方法吧。
想进一步了解的可直接阅读代码。
下面讲下是如何获取 transformation 的。
Animation.gettransformation// 等于-1,说明是刚开始动画,记录第一帧动画时间 if (mStartTime == -1) { mStartTime = currentTime; } // 相当于是延迟多少时间执行 long startOffset = getStartOffset(); long duration = mDuration; normalizedTime; if (duration != 0 // 归一化,也就是转化为百分比,当前动画进度 normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) / () duration; } time is a step-change with a zero duration normalizedTime = currentTime < mStartTime ? 0.0f : 1.0fboolean expired = normalizedTime >= 1.0f || isCanceled(); mMore = !expired; // 确保动画在 0-1 之间 if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime,1.0f),0.0f); if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) { mStarted) {
// 通知动画开始了。onAnimationStart 就是在这里被调用 fireAnimationStart(); mStarted = (NoImagePreloadHolder.USE_CLOSEGUARD) { guard.open("cancel or detach or gettransformation"); } } if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime,1)">); (mCycleFlip) { normalizedTime = 1.0f - normalizedTime; } // 根据进度获取当前插值器的值 float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
// 这里 out 前缀就是这个是要传出去的,这个方法每个 Animation 子类都要自己实现,然后其实我们可以重写这个方法,把进度传出去你就知道当前动画的进度了 applytransformation(interpolatedTime,outtransformation); } // 如果动画被取消或者已经完成了 (expired) { if (mRepeatCount == mRepeated || isCanceled()) { mEnded) { mEnded = ; guard.close();
// 这里就是 onAnimationEnd 调用的地方 fireAnimationEnd(); } } {
// else 说明动画是重复的,这是需要计算重复次数,还有是不是无限循环的 if (mRepeatCount > 0) { mRepeated++; } if (mRepeatMode == REVERSE) { mCycleFlip = !mCycleFlip; } mStartTime = -1; mMore = ; // 这里就是 onAnimationRepeat 调用的地方 fireAnimationRepeat(); } } if (!mMore && mOneMoreTime) { mOneMoreTime = ; mMore; }@H_419_31@
gettransformation 主要就是管理动画状态的。到底是开始(记录开始时间),还是正在进行(计算进度),还是已经结束了(通知结束了)。
其中调用的 applytransformation,每个 Animation 子类都要自己实现,然后其实我们可以重写这个方法,把进度传出去你就知道当前动画的进度了。子类其实是把最后的计算结果保存在 transformation 里面了,这样就拿到了下一帧动画参数。
还有大家平时用到的 AnimationListener 也是在这里进行通知回调的。
那拿到 transformation 后,是怎么用的呢,这个就得 回到 vIEw.draw 方法了。
vIEw.draw前面讲到了 transformation 其实是从 parent 中获取,赋值给 transformToApply;
drawingTime) { ...... 省略一大部分代码 float Alpha = drawingWithRenderNode ? 1 : (getAlpha() * getTransitionAlpha());
// 下面这个if 会进入动画的真正的绘制时期 if (transformToApply != null || Alpha < 1 || !hasIDentityMatrix() || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_Alpha) != 0null || !childHasIDentityMatrix) { int transX = 0int transY = 0; (offsetForScroll) { transX = -sx; transY = -sy; } (concatMatrix) {
// 为TRUE,代表是使用硬件加速来进行绘制 (drawingWithRenderNode) { renderNode.setAnimationMatrix(transformToApply.getMatrix()); } { Undo the scroll translation,apply the transformation matrix,1)"> then redo the scroll translate to get the correct result. canvas.translate(-transX,-transY); canvas.concat(transformToApply.getMatrix()); canvas.translate(transX,transY); } parent.mGroupFlags |= VIEwGroup.FLAG_CLEAR_transformATION; } float transformAlpha = transformToApply.getAlpha();
// 下面是关于透明度的动画 if (transformAlpha < 1) { Alpha *= transformAlpha; parent.mGroupFlags |= VIEwGroup.FLAG_CLEAR_transformATION; } } if (!childHasIDentityMatrix && !drawingWithRenderNode) { canvas.translate(-transX,1)">transY); canvas.concat(getMatrix()); canvas.translate(transX,transY); } } Deal with Alpha if it is or used to be <1 if (Alpha < 1 || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_Alpha) != 0) { if (Alpha < 1) { mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_Alpha; } { mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_Alpha; } parent.mGroupFlags |= VIEwGroup.FLAG_CLEAR_transformATION; drawingWithDrawingCache) { int multiplIEdAlpha = (int) (255 * Alpha); onSetAlpha(multiplIEdAlpha)) { (drawingWithRenderNode) { renderNode.setAlpha(Alpha * getAlpha() * getTransitionAlpha()); } if (layerType == LAYER_TYPE_NONE) { canvas.saveLayerAlpha(sx,sy,sx + getWIDth(),sy + getHeight(),multiplIEdAlpha); } } { Alpha is handled by the child directly,clobber the layer's Alpha mPrivateFlags |= PFLAG_Alpha_SET; } } } } if ((mPrivateFlags & PFLAG_Alpha_SET) == PFLAG_Alpha_SET) { onSetAlpha(255); mPrivateFlags &= ~PFLAG_Alpha_SET; } @H_419_31@那么对于 AnimationSet 又是如何处理的呢?
首先他也是继承了了 Animation,其次,它有个数组装门用来存放 Animation 集合。也是通过 gettransformation 来获取transformation的。
AnimationSet.
animations.get(i); temp.clear(); more = a.gettransformation(currentTime,1)"> more; t.compose(temp); started = started || more; }@H_419_31@通过 for 循环,依次获取对应的 annimation 的矩阵,然后再将矩阵效果合到一起。
到此,对于 动画应该是有自己的认识了。 VIEw Animation 的整个执行逻辑也就讲完了。
总结
那么这里回答开头的三个问题:
为什么移动位置后,点击事件的响应依旧是在原来位置上?
因为动画是在 draw 时候形成的,也就是说只是视觉效果。其并没有改变它本身在父类中的位置;
如果想知道动画的执行进度,是如何获取呢?
继承 Animation 对应的子类,然后重写 applytransformation 方法,就可以从中获取到进度。
如果对 VIEw 做放大缩小得动画,那么其宽度高度值是否会变化。
动画发生在 draw 时期,并不会改变测量结果
VIEw Animation 是在绘制的时候,改变 vIEw 的视觉效果来实现动画的。所以不会对 vIEw 的测量和布局过程有影响。
VIEw 的动画是通过触发绘制过程来执行 draw 的。因为动画是连续的,所以需要不停的触发。
参考文章:
View 动画 Animation 运行原理解析
总结
以上是内存溢出为你收集整理的View Animation 运行原理解析 Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)全部内容,希望文章能够帮你解决View Animation 运行原理解析 Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)