补间动画移动后,点击事件的响应为什么还在原来的位置?
那今天我们就来从源码解析原理
补间动画可以在一个视图容器内执行一系列简单变换(具体的变换步骤有:位置、大小、旋转、透明度);
我们可以通过平移、旋转、缩放、透明度等API进行具体的操作;
补间动画的实现方式可以通过 XML或通过Android代码两种方式 去定义;
文件名:animator_translate.xml
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:fromXDelta="0"
- android:fromYDelta="0"
- android:toYDelta="0"
- android:toXDelta="200"
- android:duration="500"
- android:fillAfter="true">
代码加载xml文件获取动画
- //加载动画
- Animation animation = AnimationUtils.loadAnimation(this, R.anim.animator_translate);
- //执行动画
- testBtn.startAnimation(animation);
- TranslateAnimation translateAnimation = new TranslateAnimation(0,200,0,0);
- translateAnimation.setDuration(500);//动画执行时间
- translateAnimation.setFillAfter(true);//动画执行完成后保持状态
- //执行动画
- testBtn.startAnimation(translateAnimation);
startAnimation(rotateAnimation)方法进入源码;
- //View.java
- public void startAnimation(Animation animation) {
- animation.setStartTime(Animation.START_ON_FIRST_FRAME);
- setAnimation(animation);
- invalidateParentCaches();
- invalidate(true);
- }
首先是通过setStartTime()设置了动画的开始时间;
- //View.java
- public void setStartTime(long startTimeMillis) {
- mStartTime = startTimeMillis;
- mStarted = mEnded = false;
- mCycleFlip = false;
- mRepeated = 0;
- mMore = true;
- }
这里只是对一些变量进行赋值,再来看看下一个方法;
设置动画setAnimation(animation):
- //View.java
- public void setAnimation(Animation animation) {
- mCurrentAnimation = animation;
- if (animation != null) {
- if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF
- && animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
- animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
- }
- animation.reset();
- }
- }
这里面也是将动画实例赋值给当前的成员变量;
分析startAnimation()方法里的invalidateParentCaches();
- //View.java
- protected void invalidateParentCaches()
- if (mParent instanceof View) {
- ((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED;
- }
- }
可以看到这里仅仅是设置动画标记,在视图构建或者属性改变时是必要的;
再回到startAnimation()方法里面invalidate(true);
- //View.java
- public void invalidate(boolean invalidateCache) {
- invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
- }
- void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
- boolean fullInvalidate) {
- if (mGhostView != null) {
- mGhostView.invalidate(true);
- return;
- }
- .................
- // Propagate the damage rectangle to the parent view.
- final AttachInfo ai = mAttachInfo;
- final ViewParent p = mParent;
- if (p != null && ai != null && l < r && t < b) {
- final Rect damage = ai.mTmpInvalRect;
- damage.set(l, t, r, b);
- p.invalidateChild(this, damage);
- }
- }
- }
这里着重看p.invalidateChild(this, damage);
- //ViewGroup.java
- @Deprecated
- @Override
- public final void invalidateChild(View child, final Rect dirty) {
- final AttachInfo attachInfo = mAttachInfo;
- if (attachInfo != null && attachInfo.mHardwareAccelerated) {
- // HW accelerated fast path
- onDescendantInvalidated(child, child);
- return;
- }
- ViewParent parent = this;
- .........
- do {
- View view = null;
- if (parent instanceof View) {
- view = (View) parent;
- }
- .........
- parent = parent.invalidateChildInParent(location, dirty);
- } while (parent != null);
- }
- }
- //ViewGroup.java
- @Deprecated
- @Override
- public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
- if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
- .......
- return mParent;
- }
- return null;
- }
- //ViewRootImpl.java
- @Override
- public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
- checkThread();
- if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
- if (dirty == null) {
- invalidate();
- return null;
- } else if (dirty.isEmpty() && !mIsAnimating) {
- return null;
- }
- .......
- invalidateRectOnScreen(dirty);
- return null;
- }
这里所有的返回值都变为null了,之前执行的do{}while()循坏也会停止。
- //ViewRootImpl.java
- private void invalidateRectOnScreen(Rect dirty) {
- ......
- if (!mWillDrawSoon && (intersected || mIsAnimating)) {
- scheduleTraversals();
- }
- }
- //ViewRootImpl.java
- void scheduleTraversals() {
- if (!mTraversalScheduled) {
- mTraversalScheduled = true;
- mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
- mChoreographer.postCallback(
- Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
- if (!mUnbufferedInputDispatch) {
- scheduleConsumeBatchedInput();
- }
- notifyRendererOfFramePending();
- pokeDrawLockIfNeeded();
- }
- }
主要看mTraversalRunnable,我们找到mTraversalRunnable这个类;
- //ViewRootImpl.java
- final class TraversalRunnable implements Runnable {
- @Override
- public void run() {
- doTraversal();
- }
- }
- final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
doTraversal()方法;
- //ViewRootImpl.java
- void doTraversal() {
- .......
- performTraversals();
- .......
- }
- }
- boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
- ...
- //清除上次动画保存的Transformation
- if ((parentFlags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) != 0) {
- parent.getChildTransformation().clear();
- parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION;
- }
- ......
- final Animation a = getAnimation();
- if (a != null) {
- //根据当前时间计算当前帧的动画,more表示是否需要执行更多帧的动画
- more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
- concatMatrix = a.willChangeTransformationMatrix();
- if (concatMatrix) {
- mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
- }
- //拿到当前帧需要的变换 ,这个值会在applyLegacyAnimation中进行设置
- transformToApply = parent.getChildTransformation();
- }
- ....
- if (transformToApply != null) {
- if (concatMatrix) {
- if (drawingWithRenderNode) {
- renderNode.setAnimationMatrix(transformToApply.getMatrix());
- } else {
- // Undo the scroll translation, apply the transformation matrix,
- // then redo the scroll translate to get the correct result.
- canvas.translate(-transX, -transY);
- canvas.concat(transformToApply.getMatrix());//在这里调用canvas的concat方法,实现最终的平移效果 (做矩阵相乘)
- canvas.translate(transX, transY);
- }
- //标记需要清除Tranformation
- parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
- }
- float transformAlpha = transformToApply.getAlpha();
- if (transformAlpha < 1) {
- alpha *= transformAlpha;
- parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
- }
- }
- ...
- }
- private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
- Animation a, boolean scalingRequired) {
- ...
- //获取Transformation 每个ViewGroup中的子View共同使用一个Transformation 为了多个View有动画时频繁创建多个Transformation
- //这个和在draw方法中取出的transformToApply是一个对象 就是最终应用到Canvas上的Transform
- final Transformation t = parent.getChildTransformation();
- //调用Animation的getTransformation方法来根据当前时间计算Transformation 这个对象的值最终会由getTransformation方法中进行赋值
- boolean more = a.getTransformation(drawingTime, t, 1f);
- invalidationTransform = t;
- ...
- //如果动画还没有播放完成 需要让动画循环起来 实际上是继续调用invalidate
- if (more) {
- if (parent.mInvalidateRegion == null) {
- parent.mInvalidateRegion = new RectF();
- }
- final RectF region = parent.mInvalidateRegion;
- //调用Animation 的getInvalidateRegion来根据invalidationTransform计算 parent的invalidateRegion
- a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
- invalidationTransform);
- // 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;
- //调用invalidate执行下一次绘制请求,这样动画就动起来了
- parent.invalidate(left, top, left + (int) (region.width() + .5f),
- top + (int) (region.height() + .5f));
- }
- }
- //Animation.java
- //返回值表示动画是否没有播放完成 并且需要计算outTransformation 也就是动画需要做的变化
- public boolean getTransformation(long currentTime, Transformation outTransformation) {
- if (mStartTime == -1) {
- mStartTime = currentTime;//记录第一帧的时间
- }
- if (duration != 0) {
- normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) / //计算运行的进度(0-1) (当前时间-开始时间+偏移量)/动画总时长
- (float) duration;
- }
- final boolean expired = normalizedTime >= 1.0f || isCanceled(); //判断动画是否播放完成 或者被取消
- mMore = !expired;
- if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f); //处理最大值
- final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);//根据插值器计算的当前动画运行进度
- applyTransformation(interpolatedTime, outTransformation);//根据动画进度 计算最终的outTransformation
- return mMore;
- }
- //applyTransformation每种类型的动画都有自己的实现 这里以位移动画为例
- //TranslateAnimation.java
- @Override
- protected void applyTransformation(float interpolatedTime, Transformation t) {
- //Transformation可以理解成 存储View的一些变换信息,将变化信息保存到成员变量matrix中
- float dx = mFromXDelta;
- float dy = mFromYDelta;
- if (mFromXDelta != mToXDelta) {
- dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);//计算X方向需要移动的距离
- }
- if (mFromYDelta != mToYDelta) {
- dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);//计算Y方向需要移动的距离
- }
- t.getMatrix().setTranslate(dx, dy); //将最终的结果设置到Matrix上面去
- }
至于未来会怎样,要走下去才知道,反正路还很长,天总会亮;
加油老铁们!
本文转载自微信公众号「Android开发编程」
标题名称:补间动画源码中分析机制原理
文章出自:http://www.shufengxianlan.com/qtweb/news21/328921.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联