上一篇讲解了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();
}
我们来看看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
ObjectAnimator类:
/**
* 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;
}
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);
需要注意的是我们这样执行,并不会再重新调用start()方法,因为新接口已经为我们封装了启动动画的功能。
小结:今天讲得是从源码分析的属性动画,因为有时候我们只关注使用,甚至不屑去深究下源码,今天就是借这篇博客,希望大家养成看源码的好习惯,例子不多,因为我们以后使用非常的频繁,等你熟练之后,代码也就那么回事,但如果文章能起到带大家阅读源码的习惯,也就算没白写。