Android 动画使用及源码分析


Android 动画有如下三类,逐帧动画已经很少使用了,这里就不在说了,这里主要看一下 补间动画属性动画 的加载和渲染

  • 逐帧动画
  • 补间动画
  • 属性动画

补间动画

补间动画分为如下四类

动画属性相关类
淡入淡出AlphaAnimation
位移TranslateAnimation
缩放ScaleAnimation
旋转RotateAnimation

补间动画的使用

我们以 AlphaAnimation 为例来说明一下动画的使用,其他动画使用方式如有雷同,纯属设计。

xml 方式

首先定义 alpha_anim.xml 动画相关属性

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromAlpha="1.0"
    android:toAlpha="0.0" />

然后在代码中加载该动画并使用

Animation alphaAnimation = AnimationUtils.loadAnimation(this,R.anim.alpha_anim);
//设置动画结束后保留结束状态
alphaAnimation.setFillAfter(true);
imageView.startAnimation(alphaAnimation);
java 代码方式
Animation alphaAnimation = new AlphaAnimation(1,0);
alphaAnimation.setDuration(3000);
mButton.startAnimation(alphaAnimation);

补间动画源码分析

加载

通过使用方式可以看出,不管是 xml 方式还是 java 代码方式,最终都会生成一个 Animation 对象进行调用。

我们先来看一下 xml 是怎么生成 Animation 对象的。

首先我们以 AnimationUtils.loadAnimation() 为入口,从源码角度分析一下 xml 是怎么生成 Animation 对象的

可以看到我们的动画资源文件通过 XmlPullParser 进行解析,然后根据节点值进行完全匹配,new 出相应的动画类型。和我们在代码中 new 出来相应的类型殊途同归。

    public static Animation loadAnimation(Context context, @AnimRes int id)
            throws NotFoundException {

        ...
        XmlResourceParser parser = context.getResources();
        ...
        return createAnimationFromXml(context, parser);
        ...
    }

    private static Animation createAnimationFromXml(Context c, XmlPullParser parser,
            AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException {

        Animation anim = null;

        // Make sure we are on a start tag.
        int type;
        int depth = parser.getDepth();

        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
               && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            String  name = parser.getName();

            if (name.equals("alpha")) {
                anim = new AlphaAnimation(c, attrs);
            } else if (name.equals("scale")) {
                anim = new ScaleAnimation(c, attrs);
            }  else if (name.equals("rotate")) {
                anim = new RotateAnimation(c, attrs);
            }  else if (name.equals("translate")) {
                anim = new TranslateAnimation(c, attrs);
            }
        }

        return anim;

    }
执行

动画的加载已经分析完成,接下来就从 ViewstartAnimation 方法作为入口,来分析一个动画的执行流程。

从源码中发现,这玩意只是名字起了个 start,里面完全就没有开始相关的操作啊,就是进行了个动画的赋值操作,那动画咋开始的???

    /**
     * Can be used as the start time to indicate the start time should be the current
     * time when {@link #getTransformation(long, Transformation)} is invoked for the
     * first animation frame. This can is useful for short animations.
     *
     * 注:大致意思就是动画从第一帧开始
     */
    public static final int START_ON_FIRST_FRAME = -1;

    /**
     * Start the specified animation now.
     *
     * @param animation the animation to start now
     */
    public void startAnimation(Animation animation) {
        animation.setStartTime(Animation.START_ON_FIRST_FRAME);
        setAnimation(animation);
        invalidateParentCaches();
        invalidate(true);
    }

    public void setAnimation(Animation animation) {
        mCurrentAnimation = animation;
    }

既然你这个 start 只做了赋值操作,那你肯定就在某个地方使用了。顺腾摸瓜,咱们就看这个 mCurrentAnimation 在那块使用了

可以看到,在 View 进行绘制的时候。会获取当前的动画。如果不为空,则应用当前的动画。所以,当我们在 start 方法中设置动画后,这里的条件就为 true 了。

View.java
    public Animation getAnimation() {
        return mCurrentAnimation;
    }
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        ...
        Transformation transformToApply = null;
        final Animation a = getAnimation();
        ...
        if (a != null) {
            applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
            ...
            transformToApply = parent.getChildTransformation();
        }
        ...
    }
    private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
            Animation a, boolean scalingRequired) {
        ...
        final Transformation t = parent.getChildTransformation();
        boolean more = a.getTransformation(drawingTime, t, 1f);
        ...
    }

Animation.java
    public boolean getTransformation(long currentTime, Transformation outTransformation,
            float scale) {
        mScaleFactor = scale;
        return getTransformation(currentTime, outTransformation);
    }
    public boolean getTransformation(long currentTime, Transformation outTransformation) {
        ...
        applyTransformation(interpolatedTime, outTransformation);
        ...
    }

    /**
     * Helper for getTransformation. Subclasses should implement this to apply
     * their transforms given an interpolation value.  Implementations of this
     * method should always replace the specified Transformation or document
     * they are doing otherwise.
     *
     * 注:大致意思就是让子类实现,计算相应的值
     */
    protected void applyTransformation(float interpolatedTime, Transformation t) {
    }

顺着调用链走下去,会发现最终调用到 Animation 的 applyTransformation 方法,而该方法是一个空实现,注释标明是让子类重写该方法,看了一下重写这个方子类就是开头我们说的那几个动画类型

在这里插入图片描述

有点意思,进去看下。看到了没,这里面就是对透明度、位置、缩放等相关属性根据当前进度计算出相应的值,然后保存到 Transformation

其中平移、旋转、缩放都是对 Matrix 进行操作。

AlphaAnimation.java
    /**
     * Changes the alpha property of the supplied {@link Transformation}
     */
    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        final float alpha = mFromAlpha;
        t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
    }

TranslateAnimation.java
    @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);
    }

RotateAnimation.java
    @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);
        }
    }

ScaleAnimation.java
    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        float sx = 1.0f;
        float sy = 1.0f;
        float scale = getScaleFactor();

        if (mFromX != 1.0f || mToX != 1.0f) {
            sx = mFromX + ((mToX - mFromX) * interpolatedTime);
        }
        if (mFromY != 1.0f || mToY != 1.0f) {
            sy = mFromY + ((mToY - mFromY) * interpolatedTime);
        }

        if (mPivotX == 0 && mPivotY == 0) {
            t.getMatrix().setScale(sx, sy);
        } else {
            t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
        }
    }

/**
 * Defines the transformation to be applied at
 * one point in time of an Animation.
 * 动画某个时间点的变换
 */
public class Transformation {
    protected Matrix mMatrix;
    protected float mAlpha;
}

这里只是对 Transformation 进行了赋值操作,那么在哪用的呢,别急呀,继续往下看。

发现在标注1处应用完动画后,在标注2处获取到了相应时间的变换值。根据 Transformation 中的 MatrixAlpha 做相应的变化,最终在屏幕上就呈现出连贯的动画效果了。

    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        ...
        Transformation transformToApply = null;
        ...
        if (a != null) {
            //标注1
            applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
            ...
            //标注2
            transformToApply = parent.getChildTransformation();
        }
        ...
        canvas.concat(transformToApply.getMatrix());
        ...
        float transformAlpha = transformToApply.getAlpha();
        if (transformAlpha < 1) {
            alpha *= transformAlpha;
        }
        final int multipliedAlpha = (int) (255 * alpha);
        canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(), multipliedAlpha);
        ...
    }

属性动画

属性动画的使用

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);
animator.setDuration(5000);
animator.start();

属性动画源码分析

首先我们看声明的 ObjectAnimator 内部只是做了一些初始化的操作

    public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }
    @Override
    public void setFloatValues(float... values) {
        if (mValues == null || mValues.length == 0) {
            // No values yet - this animator is being constructed piecemeal. Init the values with
            // whatever the current propertyName is
            if (mProperty != null) {
                setValues(PropertyValuesHolder.ofFloat(mProperty, values));
            } else {
                setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
            }
        } else {
            super.setFloatValues(values);
        }
    }
    public void setValues(PropertyValuesHolder... values) {
        int numValues = values.length;
        mValues = values;
        mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
        for (int i = 0; i < numValues; ++i) {
            PropertyValuesHolder valuesHolder = values[i];
            mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
        }
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }

其中涉及到了一个 PropertyValuesHolder 类,这个类就和补间动画的 Transformation 类似。包含了动画的相关属性和动画过程中相应的值

public class PropertyValuesHolder implements Cloneable {

    /**
     * 属性名称
     */
    String mPropertyName;

    /**
     * @hide
     */
    protected Property mProperty;

    /**
     * 对应属性的 set 方法,比如 setScale 方法
     */
    Method mSetter = null;

    /**
     * 对应属性的 get 方法
     */
    private Method mGetter = null;

    /**
     * 对应值的相应类,比如 int.class 、 float.class
     */
    Class mValueType;

    /**
     * 动画的关键帧,其他帧都是根据关键帧计算而出
     */
    Keyframes mKeyframes = null;

    // 相应的估值器
    private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
    private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
}

接着我们以 start 方法作为入口,看看属性动画是怎么执行的。其中省略了一些代码,我们只看关键部分。

注意看,这里的 start 只是将当前的监听添加到了 AnimationHandler 中。而这个 AnimationHandler 的注释也很明确,用于执行此动画的更新

    private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        ...
        addAnimationCallback(0);
        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            startAnimation();
        }
    }
    private void startAnimation() {
        mAnimationEndRequested = false;
        initAnimation();
        mRunning = true;
        if (mSeekFraction >= 0) {
            mOverallFraction = mSeekFraction;
        } else {
            mOverallFraction = 0f;
        }
        if (mListeners != null) {
            notifyStartListeners();
        }
    }
    private void addAnimationCallback(long delay) {
        if (!mSelfPulse) {
            return;
        }
        getAnimationHandler().addAnimationFrameCallback(this, delay);
    }
    /**
     * @return The {@link AnimationHandler} that will be used to schedule updates for this animator.
     * 用于执行此动画的更新
     */
    public AnimationHandler getAnimationHandler() {
        return AnimationHandler.getInstance();
    }

而这个类 AnimationHandler 的注释也比较详细,大致就相当于一个动画更新的管理者。

/**
 * This custom, static handler handles the timing pulse that is shared by all active
 * ValueAnimators. This approach ensures that the setting of animation values will happen on the
 * same thread that animations start on, and that all animations will share the same times for
 * calculating their values, which makes synchronizing animations possible.
 *
 * The handler uses the Choreographer by default for doing periodic callbacks. A custom
 * AnimationFrameCallbackProvider can be set on the handler to provide timing pulse that
 * may be independent of UI frame update. This could be useful in testing.
 *
 * 大致意思就是,通过编舞者 Choreographer 进行定期的回调,然后分发相应的监听,进行动画的更新。
 */
public class AnimationHandler {

    private final ArrayList<AnimationFrameCallback> mAnimationCallbacks = new ArrayList<>();

    /**
     * 执行动画帧的回调。
     */
    interface AnimationFrameCallback {
        /**
         * 执行动画帧
         */
        boolean doAnimationFrame(long frameTime);
    }
}

ValueAnimator 实现了 AnimationFrameCallback 这个接口,从而通过回调计算并更新当前动画的相关值,然后通过相应的 set方法进行相关属性的设置,从而达到更新动画的效果

    public final boolean doAnimationFrame(long frameTime) {
        ...
        boolean finished = animateBasedOnTime(currentTime);
        ...
        return finished;
    }
    boolean animateBasedOnTime(long currentTime) {
        boolean done = false;
        if (mRunning) {
            ...
            float currentIterationFraction = getCurrentIterationFraction(
                    mOverallFraction, mReversing);
            animateValue(currentIterationFraction);
        }
        return done;
    }
    void animateValue(float fraction) {
        final Object target = getTarget();
        if (mTarget != null && target == null) {
            // We lost the target reference, cancel and clean up. Note: we allow null target if the
            /// target has never been set.
            cancel();
            return;
        }

        super.animateValue(fraction);
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].setAnimatedValue(target);
        }
    }
    void setAnimatedValue(Object target) {
        if (mProperty != null) {
            mProperty.set(target, getAnimatedValue());
        }
        if (mSetter != null) {
            try {
                mTmpValueArray[0] = getAnimatedValue();
                mSetter.invoke(target, mTmpValueArray);
            } catch (InvocationTargetException e) {
                Log.e("PropertyValuesHolder", e.toString());
            } catch (IllegalAccessException e) {
                Log.e("PropertyValuesHolder", e.toString());
            }
        }
    }

例如我们前面传入的 alpha 参数,最终的 mSetter 就是 ViewsetAlpha 方法,然后通过反射的方式进行更新即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值