补间动画TweenAnimation源码分析(api28)

补间动画TweenAnimation

Animation 动画的扩展性很高,系统只是简单的为我们封装了几个基本的动画:平移、旋转、透明度、缩放等等。它们都继承自 Animation 类,然后实现了 applyTransformation() 方法,在这个方法里通过 Transformation 和 Matrix 实现各种各样炫酷的动画。

  • TranslateAnimation 位移动画
  • ScaleAnimation 缩放动画
  • RotateAnimation 旋转动画
  • AlphaAnimation 透明度动画

示例

Button mButton = findViewById(R.id.button1);
Animation translateAnimation = new TranslateAnimation(0,500,0,500);
translateAnimation.setDuration(3000);
mButton.startAnimation(translateAnimation);
源码解析
startAnimation初始化操作
  • 初始化操作,绑定view和animation
  • view.invalidate() -> viewGroup.invalidateChild() -> ViewRootImpl.invalidateChildInParent ->ViewRootImpl.scheduleTraversals()

ViewRootImpl是decorView的parent

当调用了 View.startAniamtion() 之后,动画并没有马上就被执行,这个方法只是做了一些变量初始化操作,接着将 View 和 Animation 绑定起来,然后调用重绘请求操作,内部层层寻找 mParent,最终走到 ViewRootImpl 的 scheduleTraversals 里发起一个遍历 View 树的请求,这个请求会在最近的一个屏幕刷新信号到来的时候被执行,调用 performTraversals 从根布局 DecorView 开始遍历 View 树,进行测量、布局、绘制。

//View.java
	//启动动画
	public void startAnimation(Animation animation) {
        animation.setStartTime(Animation.START_ON_FIRST_FRAME); // -1
        setAnimation(animation);
        invalidateParentCaches();
        invalidate(true);
    }

	//绑定View和Animation
 	public void setAnimation(Animation animation) {
        mCurrentAnimation = animation;
    }

	protected void invalidateParentCaches() {
        if (mParent instanceof View) {
            ((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED;
        }
    }
view绘制时应用动画效果

View绘制流程
在这里插入图片描述
view.draw()

  • 绘制背景
  • 绘制自己
  • dispatchDraw() ,如果有子 View,将绘制事件通知给子 View。ViewGroup 重写了 dispatchDraw(),调用了 drawChild(),而 drawChild() 调用了子 View 的 draw(Canvas, ViewGroup, long),而这个方法又会去调用到 draw(Canvas) 方法,所以这样就达到了遍历的效果。

当动画如果还没执行完,就会再调用 invalidate() 方法,层层通知到 ViewRootImpl 再次发起一次遍历请求,当下一帧屏幕刷新信号来的时候,再通过 performTraversals() 遍历 View 树绘制时,该 View 的 draw 收到通知被调用时,会再次去调用 applyLegacyAnimation() 方法去执行动画相关操作,包括调用 getTransformation() 计算动画进度,调用 applyTransformation() 应用动画。

//View.java
	public void draw(Canvas canvas) {
            // Step 4, draw the children
            dispatchDraw(canvas); //ViewGroup实现了dispatchDraw
    }
    
    //ViewGroup实现了dispatchDraw又调用child.draw
	boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        final Animation a = getAnimation();
        if (a != null) {
            more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
        } 
        //省略。。。
        return more;
    }

	private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
            Animation a, boolean scalingRequired) {
       
        final Transformation t = parent.getChildTransformation();
        //计算动画执行进度并应用动画效果
        boolean more = a.getTransformation(drawingTime, t, 1f); 
        //如果需要更多动画效果,则继续注册监听屏幕刷新信号,重绘view
        if (more) {
             parent.invalidate(mLeft, mTop, mRight, mBottom);
        }
        return more;
    }
//ViewGroup.java
	protected void dispatchDraw(Canvas canvas) {

        boolean more = false;
        final long drawingTime = getDrawingTime();
        for (int i = 0; i < childrenCount; i++) {
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                more |= drawChild(canvas, child, drawingTime);
            }
        }
    }
//Animation.java
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) { //计算进度 0-1之前
            normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
                    (float) duration;
        } else {
            // time is a step-change with a zero duration
            normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
        }
        final boolean expired = normalizedTime >= 1.0f || isCanceled(); //是否动画已经过期
        mMore = !expired; //是否需要执行更多动画效果
		//校正,确保在0.0-1.0之间
        if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);

        if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
    
                mStarted = true;
           //根据插值器计算实际的动画进度
            final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
            //应用动画效果
            applyTransformation(interpolatedTime, outTransformation);
        }
        //省略,计算动画是否执行完成,more为true,表示need more
        return mMore;
    }
补间动画只影响显示效果,不会改变view 属性

以TranslateAnimation为例,applyTransformation里,只是对matrix进行改变,没有改变view的属性。
也就是是说绘制画布进行了平移、缩放、旋转等操作,但view属性没有改变。

public class TranslateAnimation extends Animation {
   
    protected float mFromXValue = 0.0f;
    protected float mToXValue = 0.0f;
    protected float mFromYValue = 0.0f;
    protected float mToYValue = 0.0f;

    public TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta) {
        mFromXValue = fromXDelta;
        mToXValue = toXDelta;
        mFromYValue = fromYDelta;
        mToYValue = toYDelta;
    }

    @Override
    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);
    }
}
总结
  • 首先,当调用了 View.startAnimation() 时动画并没有马上就执行,而是通过 invalidate() 层层通知到 ViewRootImpl 发起一次遍历 View 树的请求,而这次请求会等到接收到最近一帧到了的信号时才去发起遍历 View 树绘制操作。

  • 从 DecorView 开始遍历,绘制流程在遍历时会调用到 View 的 draw() 方法,当该方法被调用时,如果 View 有绑定动画,那么会去调用applyLegacyAnimation(),这个方法是专门用来处理动画相关逻辑的。

  • 在 applyLegacyAnimation() 这个方法里,如果动画还没有执行过初始化,先调用动画的初始化方法 initialized(),同时调用 onAnimationStart() 通知动画开始了,然后调用 getTransformation() 来根据当前时间计算动画进度,紧接着调用 applyTransformation() 并传入动画进度来应用动画。

  • getTransformation() 这个方法有返回值,如果动画还没结束会返回 true,动画已经结束或者被取消了返回 false。所以 applyLegacyAnimation() 会根据 getTransformation() 的返回值来决定是否通知 ViewRootImpl 再发起一次遍历请求,返回值是 true 表示动画没结束,那么就去通知 ViewRootImpl 再次发起一次遍历请求。然后当下一帧到来时,再从 DecorView 开始遍历 View 树绘制,重复上面的步骤,这样直到动画结束。

  • 有一点需要注意,动画是在每一帧的绘制流程里被执行,所以动画并不是单独执行的,也就是说,如果这一帧里有一些 View 需要重绘,那么这些工作同样是在这一帧里的这次遍历 View 树的过程中完成的。每一帧只会发起一次 perfromTraversals() 操作。

参考:
View 补间动画运行原理
属性动画运行原理
View.animate运行原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值