上一篇讲解了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()方法,因为新接口已经为我们封装了启动动画的功能。

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



更多相关文章

  1. LayoutTransition 容器布局动画
  2. Android L / 5.0 帮助文档 API21文档 sample demo源码 下载
  3. Android中XML的命名空间、自定义属性
  4. 【Android】AsyncTask原理应用及源码关键部分解析
  5. Android系统定制和源码开发以及源码编译

随机推荐

  1. android客户端利用套接字访问网络调试助
  2. android中的生命周期(新增2个函数)
  3. Android多媒体开发
  4. android翻译应用、地图轨迹、视频广告、R
  5. 自定义Android键盘
  6. Android新的menu实现——ActionMode
  7. 一个逆向程序猿的必备技能(Android)
  8. Android 自定义camera压缩图片到指定大小
  9. 如何在eclipse中查看Android提供的源码
  10. 【Android(安卓)开发教程】DialogFragmen