您的位置:首页 > 移动开发 > Android开发

Android 动画分析之属性动画

2017-03-21 23:13 141 查看
上一篇讲解了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<Object> 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 <code>setName()</code>, where <code>name</code> is
* the value of the <code>propertyName</code> 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 <code>Builder</code> 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 <code>Builder</code> 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 <code>Builder</code> 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 <code>Builder</code> 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()方法,因为新接口已经为我们封装了启动动画的功能。

小结:今天讲得是从源码分析的属性动画,因为有时候我们只关注使用,甚至不屑去深究下源码,今天就是借这篇博客,希望大家养成看源码的好习惯,例子不多,因为我们以后使用非常的频繁,等你熟练之后,代码也就那么回事,但如果文章能起到带大家阅读源码的习惯,也就算没白写。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息