@Override public AnimatorSet clone() { final AnimatorSet anim = (AnimatorSet) super.clone(); /* * The basic clone() operation copies all items. This doesn't work very well for * AnimatorSet, because it will copy references that need to be recreated and state * that may not apply. What we need to do now is put the clone in an uninitialized * state, with fresh, empty data structures. Then we will build up the nodes list * manually, as we clone each Node (and its animation). The clone will then be sorted, * and will populate any appropriate lists, when it is started. */ anim.mNeedsSort = true; anim.mTerminated = false; anim.mStarted = false; anim.mPlayingSet = new ArrayList<Animator>(); anim.mNodeMap = new HashMap<Animator, Node>(); anim.mNodes = new ArrayList<Node>(); anim.mSortedNodes = new ArrayList<Node>(); // Walk through the old nodes list, cloning each node and adding it to the new nodemap. // One problem is that the old node dependencies point to nodes in the old AnimatorSet. // We need to track the old/new nodes in order to reconstruct the dependencies in the clone. HashMap<Node, Node> nodeCloneMap = new HashMap<Node, Node>(); // <old, new> for (Node node : mNodes) { Node nodeClone = node.clone(); nodeCloneMap.put(node, nodeClone); anim.mNodes.add(nodeClone); anim.mNodeMap.put(nodeClone.animation, nodeClone); // Clear out the dependencies in the clone; we'll set these up manually later nodeClone.dependencies = null; nodeClone.tmpDependencies = null; nodeClone.nodeDependents = null; nodeClone.nodeDependencies = null; // clear out any listeners that were set up by the AnimatorSet; these will // be set up when the clone's nodes are sorted ArrayList<AnimatorListener> cloneListeners = nodeClone.animation.getListeners(); if (cloneListeners != null) { ArrayList<AnimatorListener> listenersToRemove = null; for (AnimatorListener listener : cloneListeners) { if (listener instanceof AnimatorSetListener) { if (listenersToRemove == null) { listenersToRemove = new ArrayList<AnimatorListener>(); } listenersToRemove.add(listener); } } if (listenersToRemove != null) { for (AnimatorListener listener : listenersToRemove) { cloneListeners.remove(listener); } } } } // Now that we've cloned all of the nodes, we're ready to walk through their // dependencies, mapping the old dependencies to the new nodes for (Node node : mNodes) { Node nodeClone = nodeCloneMap.get(node); if (node.dependencies != null) { for (Dependency dependency : node.dependencies) { Node clonedDependencyNode = nodeCloneMap.get(dependency.node); Dependency cloneDependency = new Dependency(clonedDependencyNode, dependency.rule); nodeClone.addDependency(cloneDependency); } } } return anim; }
/** * {@inheritDoc} * * <p>Starting this <code>AnimatorSet</code> will, in turn, start the animations for which it is * responsible. The details of when exactly those animations are started depends on the dependency * relationships that have been set up between the animations. */ @Override public void start() { mTerminated = false; mStarted = true; // 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.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); } } } }