先上代碼:
ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(textView1, "translationY", 0, 200, 0);
ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(textView2, "translationY", 0, 200, 0);
objectAnimator1.setStartDelay(2000);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(objectAnimator1).with(objectAnimator2);
animatorSet.setStartDelay(2000);
animatorSet.setDuration(2000);
animatorSet.start();
簡單的兩個TextView控件位移動畫,放在一個AnimatorSet中調用play,with一起執行,第一個動畫setStartDelay(2000);第二個動畫沒有設置開始延遲,然後在設置AnimatorSet.setStartDelay(2000);調用start開始,實際運行結果和預想的不太一樣,預想結果是動畫開始後先延遲2秒然後第二動畫執行2秒後第一個動畫纔開始執行,實際運行結果爲延遲4s後,兩個TextView控件同時進行位移動畫後結束。
這裏調用animatorSet.play(objectAnimator1).with(objectAnimator2);和調用animatorSet.playTogether本質上一樣,因爲通過看源碼playTogether也是通過play和with方法實現的。
playTogether方法實現:
public void playTogether(Animator... items) {
if (items != null) {
Builder builder = play(items[0]);
for (int i = 1; i < items.length; ++i) {
builder.with(items[i]);
}
}
}
因爲結果與預想不同,於是來看一下源碼到底爲什麼會出現這個效果。
看play方法:
public Builder play(Animator anim) {
if (anim != null) {
mNeedsSort = true;
return new Builder(anim);
}
return null;
}
play方法中直接返回一個Builder對象。接着看Builder的構造函數:
Builder(Animator anim) {
mCurrentNode = mNodeMap.get(anim);
if (mCurrentNode == null) {
mCurrentNode = new Node(anim);
mNodeMap.put(anim, mCurrentNode);
mNodes.add(mCurrentNode);
}
}
AnimatorSet中的mNodeMap和mNodes用來存放所有節點,mNodeMap的key是animator,value是node。將傳入的動畫封裝成一個Node加入集合中:
/**
* Contains all nodes, mapped to their respective Animators. When new
* dependency information is added for an Animator, we want to add it
* to a single node representing that Animator, not create a new Node
* if one already exists.
*/
private HashMap<Animator, Node> mNodeMap = new HashMap<Animator, Node>();
/**
* Set of all nodes created for this AnimatorSet. This list is used upon
* starting the set, and the nodes are placed in sorted order into the
* sortedNodes collection.
*/
private ArrayList<Node> mNodes = new ArrayList<Node>();
Builder中mCurrentNode表示當前動畫的節點:
/**
* 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;
接下來看with方法:
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;
}
with方法中除了同樣把傳入的動畫封裝成節點添加到集合中之外,還將當前動畫節點封裝成一個Dependency,傳入的類型是Dependency.WITH,接着調用Node的addDependency方法添加Dependency。
接下來進入Node中:
/**
* These are the dependencies that this node's animation has on other
* nodes. For example, if this node's animation should begin with some
* other animation ends, then there will be an item in this node's
* dependencies list for that other animation's node.
*/
這些是此節點的動畫在其他節點上的依賴關係。
例如,如果此節點的動畫應以某些其他動畫結束開始,則此節點的依賴關係列表中將有該項用於該其他動畫的節點。
public ArrayList<Dependency> dependencies = null;
/**
* nodeDependencies is just a list of the nodes that this Node is dependent upon.
* This information is used in sortNodes(), to determine when a node is a root.
*/
nodeDependencies只是此節點所依賴的節點的列表。
此信息在sortNodes()中使用,以確定節點何時是根。
public ArrayList<Node> nodeDependencies = null;
/**
* nodeDepdendents is the list of nodes that have this node as a dependency. This
* is a utility field used in sortNodes to facilitate removing this node as a
* dependency when it is a root node.
*/
nodeDepdendents是將此節點作爲依賴項的節點列表。
這是sortNodes中使用的實用程序字段,以便在作爲根節點時將此節點作爲依賴項刪除。
public ArrayList<Node> nodeDependents = null;
Node中上述有三個集合,分別存放了與當前節點有從屬關係的節點。
private static class Dependency {
static final int WITH = 0; // dependent node must start with this dependency node
static final int AFTER = 1; // dependent node must start when this dependency node finishes
// The node that the other node with this Dependency is dependent upon
public Node node;
// The nature of the dependency (WITH or AFTER)
public int rule;
public Dependency(Node node, int rule) {
this.node = node;
this.rule = rule;
}
}
Dependency類非常簡單,裏面只封裝了一個節點對象和一個從屬依賴關係,以看到依賴關係一共就兩種分別爲WITH和AFTER。接下來看addDependency方法:
public void addDependency(Dependency dependency) {
if (dependencies == null) {
dependencies = new ArrayList<Dependency>();
nodeDependencies = new ArrayList<Node>();
}
dependencies.add(dependency);
if (!nodeDependencies.contains(dependency.node)) {
nodeDependencies.add(dependency.node);
}
Node dependencyNode = dependency.node;
if (dependencyNode.nodeDependents == null) {
dependencyNode.nodeDependents = new ArrayList<Node>();
}
dependencyNode.nodeDependents.add(this);
}
addDependency方法中將Dependency對象和其Node添加到對應的集合中,最後來看最後的一步start方法:
public void start() {
mTerminated = false;
mStarted = true;
mPaused = false;
if (mDuration >= 0) {
// If the duration was set on this AnimatorSet, pass it along to all child animations
for (Node node : mNodes) {
// TODO: don't set the duration of the timing-only nodes created by AnimatorSet to
// insert "play-after" delays
node.animation.setDuration(mDuration);
}
}
if (mInterpolator != null) {
for (Node node : mNodes) {
node.animation.setInterpolator(mInterpolator);
}
}
// First, sort the nodes (if necessary). This will ensure that sortedNodes
// contains the animation nodes in the correct order.
sortNodes();
int numSortedNodes = mSortedNodes.size();
for (int i = 0; i < numSortedNodes; ++i) {
Node node = mSortedNodes.get(i);
// First, clear out the old listeners
ArrayList<AnimatorListener> oldListeners = node.animation.getListeners();
if (oldListeners != null && oldListeners.size() > 0) {
final ArrayList<AnimatorListener> clonedListeners = new
ArrayList<AnimatorListener>(oldListeners);
for (AnimatorListener listener : clonedListeners) {
if (listener instanceof DependencyListener ||
listener instanceof AnimatorSetListener) {
node.animation.removeListener(listener);
}
}
}
}
// nodesToStart holds the list of nodes to be started immediately. We don't want to
// start the animations in the loop directly because we first need to set up
// dependencies on all of the nodes. For example, we don't want to start an animation
// when some other animation also wants to start when the first animation begins.
final ArrayList<Node> nodesToStart = new ArrayList<Node>();
for (int i = 0; i < numSortedNodes; ++i) {
Node node = mSortedNodes.get(i);
if (mSetListener == null) {
mSetListener = new AnimatorSetListener(this);
}
if (node.dependencies == null || node.dependencies.size() == 0) {
nodesToStart.add(node);
} else {
int numDependencies = node.dependencies.size();
for (int j = 0; j < numDependencies; ++j) {
Dependency dependency = node.dependencies.get(j);
dependency.node.animation.addListener(
new DependencyListener(this, node, dependency.rule));
}
node.tmpDependencies = (ArrayList<Dependency>) node.dependencies.clone();
}
node.animation.addListener(mSetListener);
}
// Now that all dependencies are set up, start the animations that should be started.
if (mStartDelay <= 0) {
for (Node node : nodesToStart) {
node.animation.start();
mPlayingSet.add(node.animation);
}
} else {
mDelayAnim = ValueAnimator.ofFloat(0f, 1f);
mDelayAnim.setDuration(mStartDelay);
mDelayAnim.addListener(new AnimatorListenerAdapter() {
boolean canceled = false;
public void onAnimationCancel(Animator anim) {
canceled = true;
}
public void onAnimationEnd(Animator anim) {
if (!canceled) {
int numNodes = nodesToStart.size();
for (int i = 0; i < numNodes; ++i) {
Node node = nodesToStart.get(i);
node.animation.start();
mPlayingSet.add(node.animation);
}
}
mDelayAnim = null;
}
});
mDelayAnim.start();
}
if (mListeners != null) {
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
tmpListeners.get(i).onAnimationStart(this);
}
}
if (mNodes.size() == 0 && mStartDelay == 0) {
// Handle unusual case where empty AnimatorSet is started - should send out
// end event immediately since the event will not be sent out at all otherwise
mStarted = false;
if (mListeners != null) {
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
tmpListeners.get(i).onAnimationEnd(this);
}
}
}
}
在start方法中,首先判斷mDuration是否大於0,大於0就遍歷保存所有節點的集合mNodes,給每個節點動畫重新設置持續時間,接着同樣判斷AnimatorSet是否設置插值器,如果設置了同樣也是遍歷保存所有節點的集合mNodes,給每個節點動畫重新設置插值器。
接着sortNodes();方法中將所有節點根據從屬關係進行排序,排序之後再遍歷所有節點,獲取每個節點動畫的監聽器,移除所有DependencyListener和AnimatorSetListener。這兩個監聽器決定了動畫的播放順序,不能讓單個動畫自己設置。
接着又實例化一個nodesToStart的集合,用來存放所有節點。同樣遍歷mSortedNodes,判斷,沒有從屬依賴的節點直接添加入nodesToStart集合,有依賴的重新添加DependencyListener和AnimatorSetListener監聽之後加入nodesToStart集合。可以看到new DependencyListener(this, node, dependency.rule)監聽器,將節點和依賴方式都傳入進去。
接着判斷AnimatorSet是否設置延遲時間mStartDelay,如果延遲時間小於等於0就直接遍歷nodesToStart,調用每個節點動畫的start方法執行動畫,然後將執行的動畫界點加入mPlayingSet。如果AnimatorSet的延遲時間大於0,就新建一個ValueAnimator將延遲時間設爲ValueAnimator動畫的持續時間,監聽ValueAnimator的結束,在結束回調中遍歷nodesToStart,調用每個節點動畫的start方法執行動畫,然後將執行的動畫界點加入mPlayingSet。
現在回頭來看DependencyListener的源碼:
private static class DependencyListener implements AnimatorListener {
private AnimatorSet mAnimatorSet;
// The node upon which the dependency is based.
private Node mNode;
// The Dependency rule (WITH or AFTER) that the listener should wait for on
// the node
private int mRule;
public DependencyListener(AnimatorSet animatorSet, Node node, int rule) {
this.mAnimatorSet = animatorSet;
this.mNode = node;
this.mRule = rule;
}
/**
* Ignore cancel events for now. We may want to handle this eventually,
* to prevent follow-on animations from running when some dependency
* animation is canceled.
*/
public void onAnimationCancel(Animator animation) {
}
/**
* An end event is received - see if this is an event we are listening for
*/
public void onAnimationEnd(Animator animation) {
if (mRule == Dependency.AFTER) {
startIfReady(animation);
}
}
/**
* Ignore repeat events for now
*/
public void onAnimationRepeat(Animator animation) {
}
/**
* A start event is received - see if this is an event we are listening for
*/
public void onAnimationStart(Animator animation) {
if (mRule == Dependency.WITH) {
startIfReady(animation);
}
}
/**
* Check whether the event received is one that the node was waiting for.
* If so, mark it as complete and see whether it's time to start
* the animation.
* @param dependencyAnimation the animation that sent the event.
*/
private void startIfReady(Animator dependencyAnimation) {
if (mAnimatorSet.mTerminated) {
// if the parent AnimatorSet was canceled, then don't start any dependent anims
return;
}
Dependency dependencyToRemove = null;
int numDependencies = mNode.tmpDependencies.size();
for (int i = 0; i < numDependencies; ++i) {
Dependency dependency = mNode.tmpDependencies.get(i);
if (dependency.rule == mRule &&
dependency.node.animation == dependencyAnimation) {
// rule fired - remove the dependency and listener and check to
// see whether it's time to start the animation
dependencyToRemove = dependency;
dependencyAnimation.removeListener(this);
break;
}
}
mNode.tmpDependencies.remove(dependencyToRemove);
if (mNode.tmpDependencies.size() == 0) {
// all dependencies satisfied: start the animation
mNode.animation.start();
mAnimatorSet.mPlayingSet.add(mNode.animation);
}
}
}
在onAnimationStart和onAnimationEnd回調,也就是動畫的開始和結束的監聽回調中判斷從屬類型,如果是With則是在onAnimationStart動畫開始時調用startIfReady(animation);執行下一個動畫。如果是After則是在onAnimationEnd動畫結束時調用startIfReady(animation);執行下一個動畫。
真相大白,因爲在第一個動畫的onAnimationStart回調,纔會啓動with的第二個動畫,所以開始會延遲4s。