AnimatorSet.setStartDelay在Android4.4上運行問題

先上代碼:

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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章