Java程序  |  364行  |  10.06 KB

package aurelienribon.tweenengine;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * A Timeline can be used to create complex animations made of sequences and
 * parallel sets of Tweens.
 * <p/>
 *
 * The following example will create an animation sequence composed of 5 parts:
 * <p/>
 *
 * 1. First, opacity and scale are set to 0 (with Tween.set() calls).<br/>
 * 2. Then, opacity and scale are animated in parallel.<br/>
 * 3. Then, the animation is paused for 1s.<br/>
 * 4. Then, position is animated to x=100.<br/>
 * 5. Then, rotation is animated to 360°.
 * <p/>
 *
 * This animation will be repeated 5 times, with a 500ms delay between each
 * iteration:
 * <br/><br/>
 *
 * <pre> {@code
 * Timeline.createSequence()
 *     .push(Tween.set(myObject, OPACITY).target(0))
 *     .push(Tween.set(myObject, SCALE).target(0, 0))
 *     .beginParallel()
 *          .push(Tween.to(myObject, OPACITY, 0.5f).target(1).ease(Quad.INOUT))
 *          .push(Tween.to(myObject, SCALE, 0.5f).target(1, 1).ease(Quad.INOUT))
 *     .end()
 *     .pushPause(1.0f)
 *     .push(Tween.to(myObject, POSITION_X, 0.5f).target(100).ease(Quad.INOUT))
 *     .push(Tween.to(myObject, ROTATION, 0.5f).target(360).ease(Quad.INOUT))
 *     .repeat(5, 0.5f)
 *     .start(myManager);
 * }</pre>
 *
 * @see Tween
 * @see TweenManager
 * @see TweenCallback
 * @author Aurelien Ribon | http://www.aurelienribon.com/
 */
public final class Timeline extends BaseTween<Timeline> {
	// -------------------------------------------------------------------------
	// Static -- pool
	// -------------------------------------------------------------------------

	private static final Pool.Callback<Timeline> poolCallback = new Pool.Callback<Timeline>() {
		@Override public void onPool(Timeline obj) {obj.reset();}
		@Override public void onUnPool(Timeline obj) {obj.reset();}
	};

	static final Pool<Timeline> pool = new Pool<Timeline>(10, poolCallback) {
		@Override protected Timeline create() {return new Timeline();}
	};

	/**
	 * Used for debug purpose. Gets the current number of empty timelines that
	 * are waiting in the Timeline pool.
	 */
	public static int getPoolSize() {
		return pool.size();
	}

	/**
	 * Increases the minimum capacity of the pool. Capacity defaults to 10.
	 */
	public static void ensurePoolCapacity(int minCapacity) {
		pool.ensureCapacity(minCapacity);
	}

	// -------------------------------------------------------------------------
	// Static -- factories
	// -------------------------------------------------------------------------

	/**
	 * Creates a new timeline with a 'sequence' behavior. Its children will
	 * be delayed so that they are triggered one after the other.
	 */
	public static Timeline createSequence() {
		Timeline tl = pool.get();
		tl.setup(Modes.SEQUENCE);
		return tl;
	}

	/**
	 * Creates a new timeline with a 'parallel' behavior. Its children will be
	 * triggered all at once.
	 */
	public static Timeline createParallel() {
		Timeline tl = pool.get();
		tl.setup(Modes.PARALLEL);
		return tl;
	}

	// -------------------------------------------------------------------------
	// Attributes
	// -------------------------------------------------------------------------

	private enum Modes {SEQUENCE, PARALLEL}

	private final List<BaseTween<?>> children = new ArrayList<BaseTween<?>>(10);
	private Timeline current;
	private Timeline parent;
	private Modes mode;
	private boolean isBuilt;

	// -------------------------------------------------------------------------
	// Setup
	// -------------------------------------------------------------------------

	private Timeline() {
		reset();
	}

	@Override
	protected void reset() {
		super.reset();

		children.clear();
		current = parent = null;

		isBuilt = false;
	}

	private void setup(Modes mode) {
		this.mode = mode;
		this.current = this;
	}

	// -------------------------------------------------------------------------
	// Public API
	// -------------------------------------------------------------------------

	/**
	 * Adds a Tween to the current timeline.
	 *
	 * @return The current timeline, for chaining instructions.
	 */
	public Timeline push(Tween tween) {
		if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
		current.children.add(tween);
		return this;
	}

	/**
	 * Nests a Timeline in the current one.
	 *
	 * @return The current timeline, for chaining instructions.
	 */
	public Timeline push(Timeline timeline) {
		if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
		if (timeline.current != timeline) throw new RuntimeException("You forgot to call a few 'end()' statements in your pushed timeline");
		timeline.parent = current;
		current.children.add(timeline);
		return this;
	}

	/**
	 * Adds a pause to the timeline. The pause may be negative if you want to
	 * overlap the preceding and following children.
	 *
	 * @param time A positive or negative duration.
	 * @return The current timeline, for chaining instructions.
	 */
	public Timeline pushPause(float time) {
		if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
		current.children.add(Tween.mark().delay(time));
		return this;
	}

	/**
	 * Starts a nested timeline with a 'sequence' behavior. Don't forget to
	 * call {@link end()} to close this nested timeline.
	 *
	 * @return The current timeline, for chaining instructions.
	 */
	public Timeline beginSequence() {
		if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
		Timeline tl = pool.get();
		tl.parent = current;
		tl.mode = Modes.SEQUENCE;
		current.children.add(tl);
		current = tl;
		return this;
	}

	/**
	 * Starts a nested timeline with a 'parallel' behavior. Don't forget to
	 * call {@link end()} to close this nested timeline.
	 *
	 * @return The current timeline, for chaining instructions.
	 */
	public Timeline beginParallel() {
		if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
		Timeline tl = pool.get();
		tl.parent = current;
		tl.mode = Modes.PARALLEL;
		current.children.add(tl);
		current = tl;
		return this;
	}

	/**
	 * Closes the last nested timeline.
	 *
	 * @return The current timeline, for chaining instructions.
	 */
	public Timeline end() {
		if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
		if (current == this) throw new RuntimeException("Nothing to end...");
		current = current.parent;
		return this;
	}

	/**
	 * Gets a list of the timeline children. If the timeline is started, the
	 * list will be immutable.
	 */
	public List<BaseTween<?>> getChildren() {
		if (isBuilt) return Collections.unmodifiableList(current.children);
		else return current.children;
	}

	// -------------------------------------------------------------------------
	// Overrides
	// -------------------------------------------------------------------------

	@Override
	public Timeline build() {
		if (isBuilt) return this;

		duration = 0;

		for (int i=0; i<children.size(); i++) {
			BaseTween<?> obj = children.get(i);

			if (obj.getRepeatCount() < 0) throw new RuntimeException("You can't push an object with infinite repetitions in a timeline");
			obj.build();

			switch (mode) {
				case SEQUENCE:
					float tDelay = duration;
					duration += obj.getFullDuration();
					obj.delay += tDelay;
					break;

				case PARALLEL:
					duration = Math.max(duration, obj.getFullDuration());
					break;
			}
		}

		isBuilt = true;
		return this;
	}

	@Override
	public Timeline start() {
		super.start();

		for (int i=0; i<children.size(); i++) {
			BaseTween<?> obj = children.get(i);
			obj.start();
		}

		return this;
	}

	@Override
	public void free() {
		for (int i=children.size()-1; i>=0; i--) {
			BaseTween<?> obj = children.remove(i);
			obj.free();
		}

		pool.free(this);
	}

	@Override
	protected void updateOverride(int step, int lastStep, boolean isIterationStep, float delta) {
		if (!isIterationStep && step > lastStep) {
			assert delta >= 0;
			float dt = isReverse(lastStep) ? -delta-1 : delta+1;
			for (int i=0, n=children.size(); i<n; i++) children.get(i).update(dt);
			return;
		}

		if (!isIterationStep && step < lastStep) {
			assert delta <= 0;
			float dt = isReverse(lastStep) ? -delta-1 : delta+1;
			for (int i=children.size()-1; i>=0; i--) children.get(i).update(dt);
			return;
		}

		assert isIterationStep;

		if (step > lastStep) {
			if (isReverse(step)) {
				forceEndValues();
				for (int i=0, n=children.size(); i<n; i++) children.get(i).update(delta);
			} else {
				forceStartValues();
				for (int i=0, n=children.size(); i<n; i++) children.get(i).update(delta);
			}

		} else if (step < lastStep) {
			if (isReverse(step)) {
				forceStartValues();
				for (int i=children.size()-1; i>=0; i--) children.get(i).update(delta);
			} else {
				forceEndValues();
				for (int i=children.size()-1; i>=0; i--) children.get(i).update(delta);
			}

		} else {
			float dt = isReverse(step) ? -delta : delta;
			if (delta >= 0) for (int i=0, n=children.size(); i<n; i++) children.get(i).update(dt);
			else for (int i=children.size()-1; i>=0; i--) children.get(i).update(dt);
		}
	}

	// -------------------------------------------------------------------------
	// BaseTween impl.
	// -------------------------------------------------------------------------

	@Override
	protected void forceStartValues() {
		for (int i=children.size()-1; i>=0; i--) {
			BaseTween<?> obj = children.get(i);
			obj.forceToStart();
		}
	}

	@Override
	protected void forceEndValues() {
		for (int i=0, n=children.size(); i<n; i++) {
			BaseTween<?> obj = children.get(i);
			obj.forceToEnd(duration);
		}
	}

	@Override
	protected boolean containsTarget(Object target) {
		for (int i=0, n=children.size(); i<n; i++) {
			BaseTween<?> obj = children.get(i);
			if (obj.containsTarget(target)) return true;
		}
		return false;
	}

	@Override
	protected boolean containsTarget(Object target, int tweenType) {
		for (int i=0, n=children.size(); i<n; i++) {
			BaseTween<?> obj = children.get(i);
			if (obj.containsTarget(target, tweenType)) return true;
		}
		return false;
	}
}