欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android补间动画源码阅读

程序员文章站 2022-04-27 13:18:13
...

前言

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还可以支持其他的图形变换操作。
Android补间动画源码阅读

接下来查看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使得每次获取的动画数据不同,那么就可以实现自定义的补间动画效果。

相关标签: Tween Animation