Android补间动画源码阅读

前言

Android补间动画是一项相对古老的动画框架,补间动画通过在Canvas上做绘制操作实现动画效果,相对于后来的属性动画效率要高,如果有些动画只是展示效果,那么补间动画是一种不错的选择。系统内置了alpha、translate、scale和rotate四种动画效果,现在通过这四种简单的动画来看一下补间动画的实现原理。

内置效果

补间动画可以使用xml来定义也可以使用源代码的方式定义,两者是等价的,这里采用XML形式来定义这些简单动画效果。

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:fillAfter="true"
    android:fromDegrees="0"
    android:toDegrees="360"
    android:pivotX="50%"
    android:pivotY="50%">

</rotate>

然后使用AnimationUtils.loadAnimation方法加载动画XML实现,View需要指定动画只需要调用View.startAnimation就可以了。

rotateAnimation = (RotateAnimation) 
AnimationUtils.loadAnimation(this, R.anim.rotate);
button.startAnimation(rotateAnimation);

这里只是简单的实现了旋转动画效果,就从这个简单的示例开始分析实现的代码。

源码分析

查看View.startAnimation的源码实现,最开始设置了旋转动画的开始时间,然后清空父布局的缓存并且将当前View置为无效状态,invalidate会导致整个界面的重新绘制操作。

public void startAnimation(Animation animation) {
    animation.setStartTime(Animation.START_ON_FIRST_FRAME);
    setAnimation(animation);
    invalidateParentCaches();
    invalidate(true);
}

接下来查看View的父布局ViewGroup的dispatchDraw方法,在该方法内部又会回调各个子控件的draw方法实现各个控件元素的绘制操作。

// 遍历布局内部的所有子控件
for (int i = 0; i < childrenCount; i++) {
            ....

    final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
    final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
    // 如果child包含animation动画而且是可见的那么调用drawChild方法
    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
        more |= drawChild(canvas, child, drawingTime);
    }
}

dispatchDraw内部会在遍历所有的子控件时判断子控件是否有动画效果,如果有就会调用drawChild方法来绘制子控件,在drawChild方法里调用了super.draw方法,这个方法内部会判断当前View是否有动画效果执行。

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    ...
    Transformation transformToApply = null;
    boolean concatMatrix = false;
    final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
    // 判断当前的View是否包含动画效果
    final Animation a = getAnimation();
    if (a != null) {
        // 如果有动画效果就执行applyLegacyAnimation
        more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
        concatMatrix = a.willChangeTransformationMatrix();
        if (concatMatrix) {
            mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
        }
        transformToApply = parent.getChildTransformation();
    } 
    ...
}

注意这个返回结果如果为true代表当前的动画还没有执行完成,后面还有再invalidate继续重新执行重新绘制操作。

private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
        Animation a, boolean scalingRequired) {
    Transformation invalidationTransform;
    final int flags = parent.mGroupFlags;
    final boolean initialized = a.isInitialized();

    // 判断当前的补间动画是否已经被初始化了
    if (!initialized) {
        // 没有被初始化那么就传入当前view的宽度、高度和父控件的宽度和高度
        a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
        a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
        if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
        // 执行设置到Animation里的动画生命周期监听事件
        onAnimationStart();
    }

    // 新建一个Transformation对象
    final Transformation t = parent.getChildTransformation();
    // 通过动画对象获取transformation对象数据,同时返回是否已经执行完成
    boolean more =
    a.getTransformation(drawingTime, t, 1f);

    ...
    return more;
}

Transformation这个类又代表什么数据呢,为什么这里需要从Animation中获取这个对象的数据值呢?现在先看Transformation的代码实现。

public class Transformation {
    // 不做任何变换
    public static final int TYPE_IDENTITY = 0x0;
    // 改变View的alpha属性
    public static final int TYPE_ALPHA = 0x1;
    // 改变Matrix属性
    public static final int TYPE_MATRIX = 0x2;
    // 即改变alpha又改变Matrix
    public static final int TYPE_BOTH = TYPE_ALPHA | TYPE_MATRIX;

    // 保存Matrix和alpha当前值
    protected Matrix mMatrix;
    protected float mAlpha;
    protected int mTransformationType;

    private boolean mHasClipRect;
    private Rect mClipRect = new Rect();

    public Transformation() {
        clear();
    }

    // 清除已有值
    public void clear() {
        if (mMatrix == null) {
            mMatrix = new Matrix();
        } else {
            mMatrix.reset();
        }
        mClipRect.setEmpty();
        mHasClipRect = false;
        mAlpha = 1.0f;
        mTransformationType = TYPE_BOTH;
    }

    // 获取变换的方式
    public int getTransformationType() {
        return mTransformationType;
    }

    public void setTransformationType(int transformationType) {
        mTransformationType = transformationType;
    }

    // 克隆一个Transform对象
    public void set(Transformation t) {
        mAlpha = t.getAlpha();
        mMatrix.set(t.getMatrix());
        if (t.mHasClipRect) {
            setClipRect(t.getClipRect());
        } else {
            mHasClipRect = false;
            mClipRect.setEmpty();
        }
        mTransformationType = t.getTransformationType();
    }

    // 组合一个Transform对象
    public void compose(Transformation t) {
        mAlpha *= t.getAlpha();
        mMatrix.preConcat(t.getMatrix());
        if (t.mHasClipRect) {
            Rect bounds = t.getClipRect();
            if (mHasClipRect) {
                setClipRect(mClipRect.left + bounds.left, mClipRect.top + bounds.top,
                        mClipRect.right + bounds.right, mClipRect.bottom + bounds.bottom);
            } else {
                setClipRect(bounds);
            }
        }
    }


    public void postCompose(Transformation t) {
        mAlpha *= t.getAlpha();
        mMatrix.postConcat(t.getMatrix());
        if (t.mHasClipRect) {
            Rect bounds = t.getClipRect();
            if (mHasClipRect) {
                setClipRect(mClipRect.left + bounds.left, mClipRect.top + bounds.top,
                        mClipRect.right + bounds.right, mClipRect.bottom + bounds.bottom);
            } else {
                setClipRect(bounds);
            }
        }
    }

    // 各种getter/setter这里就省略了
    ...
}

如果对图形图像处理有稍微了解都知道3*3的Matrix可以操作各种图形变换,这里有一个公式可以表征矩阵中的每一个元素所能够支持的变换。也就是说Matrix封装了tranlate、scale和rotate三种内置的补间动画,mAlpha这个值就封装了alpha动画值,所以这个Transform对象其实时封装了所有Matrix支持的图形变换和Alpha动画数据,之所以没有说Matrix封装了translate、scale和roate三种动画是因为Matrix还可以支持其他的图形变换操作。
这里写图片描述

接下来查看RotateAnimation的实现代码逻辑,前面定义的rotate.xml里定义了旋转轴的位置,这里根据旋转轴的百分比和applyLegacyAnimation传入的当前view宽高和父控件宽高计算旋转轴位置。

@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
    super.initialize(width, height, parentWidth, parentHeight);
    mPivotX = resolveSize(mPivotXType, mPivotXValue, width, parentWidth);
    mPivotY = resolveSize(mPivotYType, mPivotYValue, height, parentHeight);
}

applyLegacyAnimation方法里调用了getTranformation获取封装动画效果的Transformation对象数据。

public boolean getTransformation(long currentTime, Transformation outTransformation) {
    // 规范化当前执行时间
    ...
    // 根据当前时间获取目前动画执行的数据
    final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
    applyTransformation(interpolatedTime, outTransformation);
}

getTransformation方法根据执行开始的流逝时间通过applyTransformation获取当前动画执行到的数据。查看RotateAnimation的applyTransformation方法发现它在内部正设置Matrix的rotate数值。

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);
    float scale = getScaleFactor();

    if (mPivotX == 0.0f && mPivotY == 0.0f) {
        t.getMatrix().setRotate(degrees);
    } else {
        t.getMatrix().setRotate(degrees, mPivotX * scale, mPivotY * scale);
    }
}

再回到前面的draw方法,查看后续的实现代码。

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    ...
    if (transformToApply != null) {
        if (concatMatrix) {
            // 执行matrix里设置的动画效果
            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, -transY);
        canvas.concat(getMatrix());
        canvas.translate(transX, transY);
    }
    ...

    // 处理alpha修改导致的界面透明度变化
    if (alpha < 1 || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
        ...
        if (!onSetAlpha(multipliedAlpha)) {
            if (drawingWithRenderNode) {
                renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha());
            } else if (layerType == LAYER_TYPE_NONE) {
                canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(),
                        multipliedAlpha);
            }
        } else {
            // Alpha is handled by the child directly, clobber the layer's alpha
            mPrivateFlags |= PFLAG_ALPHA_SET;
        }
    }
}

可以看到Matrix里包含的动画数据和alpha数据都会被Canvas对象应用,之后Canvas绘制View的内容时就会导致内容出现动画效果。

总结

Transformation对象封装了补间动画的动画执行数据,每次View重绘的时候都会从Animation里重新获取当前的变换数据,之后Matrix和Alpha的变化数据都会被Canvas重用,再绘制View内容时产生动画效果。如果能够重写Animation的applytransmation使得每次获取的动画数据不同,那么就可以实现自定义的补间动画效果。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值