All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.facebook.rebound.AnimationQueue Maven / Gradle / Ivy

/*
 *  Copyright (c) 2013, Facebook, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.rebound;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

/**
 * AnimationQueue provides a way to trigger a delayed stream of animations off of a stream of
 * values. Each callback that is added the AnimationQueue will be process the stream delayed by
 * the number of animation frames equal to its position in the callback list. This makes it easy
 * to build cascading animations.
 *
 * TODO: Add options for changing the delay after which a callback receives a value from the
 *       animation queue value stream.
 */
public class AnimationQueue {

  /**
   * AnimationQueue.Callback receives the value from the stream that it should use in its onFrame
   * method.
   */
  public interface Callback {
    void onFrame(Double value);
  }

  private final ChoreographerCompat mChoreographer;
  private final Queue mPendingQueue = new LinkedList();
  private final Queue mAnimationQueue = new LinkedList();
  private final List mCallbacks = new ArrayList();
  private final ArrayList mTempValues = new ArrayList();
  private final ChoreographerCompat.FrameCallback mChoreographerCallback;
  private boolean mRunning;

  public AnimationQueue() {
    mChoreographer = ChoreographerCompat.getInstance();
    mChoreographerCallback = new ChoreographerCompat.FrameCallback() {
      @Override
      public void doFrame(long frameTimeNanos) {
        onFrame(frameTimeNanos);
      }
    };
  }

  /* Values */

  /**
   * Add a single value to the pending animation queue.
   * @param value the single value to add
   */
  public void addValue(Double value) {
    mPendingQueue.add(value);
    runIfIdle();
  }

  /**
   * Add a collection of values to the pending animation value queue
   * @param values the collection of values to add
   */
  public void addAllValues(Collection values) {
    mPendingQueue.addAll(values);
    runIfIdle();
  }

  /**
   * Clear all pending animation values.
   */
  public void clearValues() {
    mPendingQueue.clear();
  }

  /* Callbacks */

  /**
   * Add a callback to the AnimationQueue.
   * @param callback the callback to add
   */
  public void addCallback(Callback callback) {
    mCallbacks.add(callback);
  }

  /**
   * Remove the specified callback from the AnimationQueue.
   * @param callback the callback to remove
   */
  public void removeCallback(Callback callback) {
    mCallbacks.remove(callback);
  }

  /**
   * Remove any callbacks from the AnimationQueue.
   */
  public void clearCallbacks() {
    mCallbacks.clear();
  }

  /**
   * Start the animation loop if it is not currently running.
   */
  private void runIfIdle() {
    if (!mRunning) {
      mRunning = true;
      mChoreographer.postFrameCallback(mChoreographerCallback);
    }
  }

  /**
   * Called every time a new frame is ready to be rendered.
   *
   * Values are processed FIFO and each callback is given a chance to handle each value when its
   * turn comes before a value is poll'd off the AnimationQueue.
   *
   * @param frameTimeNanos The time in nanoseconds when the frame started being rendered, in the
   *                       nanoTime() timebase. Divide this value by 1000000 to convert it to the
   *                       uptimeMillis() time base.
   */
  private void onFrame(long frameTimeNanos) {
    Double nextPendingValue = mPendingQueue.poll();

    int drainingOffset;
    if (nextPendingValue != null) {
      mAnimationQueue.offer(nextPendingValue);
      drainingOffset = 0;
    } else {
      drainingOffset = Math.max(mCallbacks.size() - mAnimationQueue.size(), 0);
    }

    // Copy the values into a temporary ArrayList for processing.
    mTempValues.addAll(mAnimationQueue);
    for (int i = mTempValues.size() - 1; i > -1; i--) {
      Double val = mTempValues.get(i);
      int cbIdx = mTempValues.size() - 1 - i + drainingOffset;
      if (mCallbacks.size() > cbIdx) {
        mCallbacks.get(cbIdx).onFrame(val);
      }
    }
    mTempValues.clear();

    while (mAnimationQueue.size() + drainingOffset >= mCallbacks.size()) {
      mAnimationQueue.poll();
    }

    if (mAnimationQueue.isEmpty() && mPendingQueue.isEmpty()) {
      mRunning = false;
    } else {
      mChoreographer.postFrameCallback(mChoreographerCallback);
    }
  }

}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy