Android 动画分析之属性动画

上一篇讲解了android中Tween动画的源码分析,今天接着讲android 3.0后出现的属性动画


Property动画的引入:

在3.0之前,android的提供的补间动画其实能满足大部分需求,比如平移,缩放等等,但后来一些不足,体现出来了,比如改变view的属性,又比如你改变Button位置,发现移动后Button并不能点击等。当然额外的做些处理,如在最终位置隐藏一个相同大小的控件,处理点击事件,给人假象,但补间动画的不足已经体现,因此,在3.0引入了属性动画。


Property动画分析:

我们先来看以下代码:

 private  void showAnimation(){
        Animator ani = ObjectAnimator.ofFloat(header_view, "translationY", header_view.getTranslationY(), 0);
        ani.setDuration(500);
        ani.start();
    }

    private void showToolbar() {
        AnimatorSet as = new AnimatorSet();
        Animator animator = ObjectAnimator.ofFloat(header_view, "translationY", header_view.getTranslationY(), 0);
        Animator animatorTools = ObjectAnimator.ofFloat(buttom_view, "translationY", buttom_view.getHeight(), 0);
        as.play(animator);
        as.play(animatorTools);
        as.start();
    }

上述是属性动画的小demo,里面有三个重要的类: Animator,ObjectAnimator,AnimatorSet,还有最重要的类,比如ValueAnimator等,其中AnimatorSet是控制一组动画的执行。

我们来看看ObjectAnimator动画:

public final class ObjectAnimator extends ValueAnimator {
    private static final String LOG_TAG = "ObjectAnimator";

    private static final boolean DBG = false;

    /**
     * A weak reference to the target object on which the property exists, set
     * in the constructor. We'll cancel the animation if this goes away.
     */
    private WeakReference mTarget; 
  


ObjectAnimator类:


ObjectAnimator继承自ValueAnimator,而ValueAnimator又继承的Animator类。ObjectAnimator提供了ofFloat,ofInt,ofObjectd等方法,这些方法设置动画的目标,属性以及开始,结束,及中间的任意属性值。以ofInt为例源码:

    /**
     * Constructs and returns an ObjectAnimator that animates between int values. A single
     * value implies that that value is the one being animated to. Two values imply starting
     * and ending values. More than two values imply a starting value, values to animate through
     * along the way, and an ending value (these values will be distributed evenly across
     * the duration of the animation).
     *
     * @param target The object whose property is to be animated. This object should
     * have a public method on it called setName(), where name is
     * the value of the propertyName parameter.
     * @param propertyName The name of the property being animated.
     * @param values A set of values that the animation will animate between over time.
     * @return An ObjectAnimator object that is set up to animate between the given values.
     */
    public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setIntValues(values);
        return anim;
    }

看注释,我们很容易理解这些参数的含义,第一个是动画作用的对象,第二个是对对象的哪个属性操作,第三个是动态参数,想完成什么怎样的动画。以开始代码为例:

Animator animator = ObjectAnimator.ofFloat(header_view, "translationY", header_view.getTranslationY(), 0);

以这句为例,在我代码里header_view是toolBar的view,属性是Y轴方向移动,最后参数是让view从当前Y值,移动到0的位置,也就是控件从当前位置,慢慢的向上移动直到消失的过程(稍后补动态图,勿喷)


我们是否发现,第一个参数,第三个参数都没问题,那第二个参数具体是啥呢。我们怎么知道translationY,translationX呢,这些怎么来的呢,那有人问,我没发现控件存在这些属性啊,何来属性动画一说,确实是,控件本身并没有这些属性,父类查看了也不存在,其实对于ObjectAnimator而言,它找的是这些属性的get,set方法,而不是直接属性,那我们看一下View源码,确实发现了set,get方法:

/**
     * Sets the horizontal location of this view relative to its {@link #getLeft() left} position.
     * This effectively positions the object post-layout, in addition to wherever the object's
     * layout placed it.
     *
     * @param translationX The horizontal position of this view relative to its left position,
     * in pixels.
     *
     * @attr ref android.R.styleable#View_translationX
     */
    public void setTranslationX(float translationX) {
        if (translationX != getTranslationX()) {
            invalidateViewProperty(true, false);
            mRenderNode.setTranslationX(translationX);
            invalidateViewProperty(false, true);

            invalidateParentIfNeededAndWasQuickRejected();
            notifySubtreeAccessibilityStateChangedIfNeeded();
        }
    }
那我们在看看View里还有哪些属性直接拿来用呢:

 // drawing
        stream.addProperty("drawing:elevation", getElevation());
        stream.addProperty("drawing:translationX", getTranslationX());
        stream.addProperty("drawing:translationY", getTranslationY());
        stream.addProperty("drawing:translationZ", getTranslationZ());
        stream.addProperty("drawing:rotation", getRotation());
        stream.addProperty("drawing:rotationX", getRotationX());
        stream.addProperty("drawing:rotationY", getRotationY());
        stream.addProperty("drawing:scaleX", getScaleX());
        stream.addProperty("drawing:scaleY", getScaleY());
        stream.addProperty("drawing:pivotX", getPivotX());
        stream.addProperty("drawing:pivotY", getPivotY());
        stream.addProperty("drawing:alpha", getAlpha());
        stream.addProperty("drawing:transitionAlpha", getTransitionAlpha());
        stream.addProperty("drawing:solidColor", getSolidColor());

哇,是不是我们经常用的缩放,旋转,平移,透明度都在里面了。还是之前那句话,多看源码,多看源码,奥秘就在其中。


那有人问了,那我自定义的view的自定义属性可不可以作为第二个参数了,那当然可以了,当要保证自定义view里的这个属性对应有set和get方法。


2.ValueAnimator

上边我们已经看到了ObjectAnimator继承的是ValueAnimator,ValueAnimator算是属性动画中最核心的类,我们通常用的是ObjectAnimator。属性动画的机制是通过不断改变目标对象的属性值实现动画,其实我们已经看到了,在ofInt等方法里,并没有太多的逻辑处理,而从初始值到结束值之间的变化其实就是在ValueAnimator里实现的。

3.AnimatorSet

还是看最开始代码的例子,用到了AnimatorSet组合动画,因为我们单纯的一个动画无法满足效果,这时候就需要组合,比如上述代码的例子,点击屏幕中央,标题栏和底部导航栏都逐渐隐藏,就是组合动画的实现。

Animator animator = ObjectAnimator.ofFloat(header_view, "translationY", header_view.getTranslationY(), 0);
        Animator animatorTools = ObjectAnimator.ofFloat(buttom_view, "translationY", buttom_view.getHeight(), 0);
        as.play(animator);
        as.play(animatorTools);

里面用到了play()方法,返回的是AnimatorSet.Builder。

public Builder play(Animator anim) {
        if (anim != null) {
            mNeedsSort = true;
            return new Builder(anim);
        }
        return null;
    }

继续研究Builder中有四个方法:分别是with,before,after。

   public class Builder {

        /**
         * This tracks the current node being processed. It is supplied to the play() method
         * of AnimatorSet and passed into the constructor of Builder.
         */
        private Node mCurrentNode;

        /**
         * package-private constructor. Builders are only constructed by AnimatorSet, when the
         * play() method is called.
         *
         * @param anim The animation that is the dependency for the other animations passed into
         * the other methods of this Builder object.
         */
        Builder(Animator anim) {
            mCurrentNode = mNodeMap.get(anim);
            if (mCurrentNode == null) {
                mCurrentNode = new Node(anim);
                mNodeMap.put(anim, mCurrentNode);
                mNodes.add(mCurrentNode);
            }
        }

        /**
         * Sets up the given animation to play at the same time as the animation supplied in the
         * {@link AnimatorSet#play(Animator)} call that created this Builder object.
         *
         * @param anim The animation that will play when the animation supplied to the
         * {@link AnimatorSet#play(Animator)} method starts.
         */
        public Builder with(Animator anim) {
            Node node = mNodeMap.get(anim);
            if (node == null) {
                node = new Node(anim);
                mNodeMap.put(anim, node);
                mNodes.add(node);
            }
            Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH);
            node.addDependency(dependency);
            return this;
        }

        /**
         * Sets up the given animation to play when the animation supplied in the
         * {@link AnimatorSet#play(Animator)} call that created this Builder object
         * ends.
         *
         * @param anim The animation that will play when the animation supplied to the
         * {@link AnimatorSet#play(Animator)} method ends.
         */
        public Builder before(Animator anim) {
            mReversible = false;
            Node node = mNodeMap.get(anim);
            if (node == null) {
                node = new Node(anim);
                mNodeMap.put(anim, node);
                mNodes.add(node);
            }
            Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER);
            node.addDependency(dependency);
            return this;
        }

        /**
         * Sets up the given animation to play when the animation supplied in the
         * {@link AnimatorSet#play(Animator)} call that created this Builder object
         * to start when the animation supplied in this method call ends.
         *
         * @param anim The animation whose end will cause the animation supplied to the
         * {@link AnimatorSet#play(Animator)} method to play.
         */
        public Builder after(Animator anim) {
            mReversible = false;
            Node node = mNodeMap.get(anim);
            if (node == null) {
                node = new Node(anim);
                mNodeMap.put(anim, node);
                mNodes.add(node);
            }
            Dependency dependency = new Dependency(node, Dependency.AFTER);
            mCurrentNode.addDependency(dependency);
            return this;
        }

        /**
         * Sets up the animation supplied in the
         * {@link AnimatorSet#play(Animator)} call that created this Builder object
         * to play when the given amount of time elapses.
         *
         * @param delay The number of milliseconds that should elapse before the
         * animation starts.
         */
        public Builder after(long delay) {
            // setup dummy ValueAnimator just to run the clock
            ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
            anim.setDuration(delay);
            after(anim);
            return this;
        }

    }

上边四个重要方法,分别是:

after(Animator anim) :将现有动画插入到传入动画之后执行

after(long delay) 将现有动画延迟指定毫秒后执行

before(Animator anim) 将现有动画插入到传入的动画之前执行

with(Animator anim)  将现有动画和传入的动画同时执行


比如上边的动画如果不同时执行,比如底部导航栏隐藏后再隐藏标题栏可以这样:

as.play(animator).after(animatorTools);
可以链式语法执行的。

4.ViewPropertyAnimator:

在android 3.1之后,android为大家提供了ViewPropertyAnimator类,其实我们发现上述使用是不是感觉有点繁琐,明明一个TextView设置内容:

textView.setText(..)这种语法格式是不是很简洁,一步了然。而ViewPropertyAnimator就是为了简化而出现的,比如设置TextView的缩放,可以使用

tv.animate().scaleX(0.5f);

如果想移动textview的位置,比如10,10的坐标点,可以这样写:

tv.animate().x(10).y(10);


是不是看上去简洁多了,其实很多语言,语法特点都有这方面的共性,比如拉姆达表达式,swift的语法,rxjava等等

需要注意的是我们这样执行,并不会再重新调用start()方法,因为新接口已经为我们封装了启动动画的功能。

小结:今天讲得是从源码分析的属性动画,因为有时候我们只关注使用,甚至不屑去深究下源码,今天就是借这篇博客,希望大家养成看源码的好习惯,例子不多,因为我们以后使用非常的频繁,等你熟练之后,代码也就那么回事,但如果文章能起到带大家阅读源码的习惯,也就算没白写。



你可能感兴趣的:(Android)