@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);
        }
      }
    }
  }