Java程序  |  544行  |  14.76 KB

package aurelienribon.tweenengine;

/**
 * BaseTween is the base class of Tween and Timeline. It defines the
 * iteration engine used to play animations for any number of times, and in
 * any direction, at any speed.
 * <p/>
 *
 * It is responsible for calling the different callbacks at the right moments,
 * and for making sure that every callbacks are triggered, even if the update
 * engine gets a big delta time at once.
 *
 * @see Tween
 * @see Timeline
 * @author Aurelien Ribon | http://www.aurelienribon.com/
 */
public abstract class BaseTween<T> {
	// General
	private int step;
	private int repeatCnt;
	private boolean isIterationStep;
	private boolean isYoyo;

	// Timings
	protected float delay;
	protected float duration;
	private float repeatDelay;
	private float currentTime;
	private float deltaTime;
	private boolean isStarted; // true when the object is started
	private boolean isInitialized; // true after the delay
	private boolean isFinished; // true when all repetitions are done
	private boolean isKilled; // true if kill() was called
	private boolean isPaused; // true if pause() was called

	// Misc
	private TweenCallback callback;
	private int callbackTriggers;
	private Object userData;

	// Package access
	boolean isAutoRemoveEnabled;
	boolean isAutoStartEnabled;

	// -------------------------------------------------------------------------

	protected void reset() {
		step = -2;
		repeatCnt = 0;
		isIterationStep = isYoyo = false;

		delay = duration = repeatDelay = currentTime = deltaTime = 0;
		isStarted = isInitialized = isFinished = isKilled = isPaused = false;

		callback = null;
		callbackTriggers = TweenCallback.COMPLETE;
		userData = null;

		isAutoRemoveEnabled = isAutoStartEnabled = true;
	}

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

	/**
	 * Builds and validates the object. Only needed if you want to finalize a
	 * tween or timeline without starting it, since a call to ".start()" also
	 * calls this method.
	 *
	 * @return The current object, for chaining instructions.
	 */
	public T build() {
		return (T) this;
	}

	/**
	 * Starts or restarts the object unmanaged. You will need to take care of
	 * its life-cycle. If you want the tween to be managed for you, use a
	 * {@link TweenManager}.
	 *
	 * @return The current object, for chaining instructions.
	 */
	public T start() {
		build();
		currentTime = 0;
		isStarted = true;
		return (T) this;
	}

	/**
	 * Convenience method to add an object to a manager. Its life-cycle will be
	 * handled for you. Relax and enjoy the animation.
	 *
	 * @return The current object, for chaining instructions.
	 */
	public T start(TweenManager manager) {
		manager.add(this);
		return (T) this;
	}

	/**
	 * Adds a delay to the tween or timeline.
	 *
	 * @param delay A duration.
	 * @return The current object, for chaining instructions.
	 */
	public T delay(float delay) {
		this.delay += delay;
		return (T) this;
	}

	/**
	 * Kills the tween or timeline. If you are using a TweenManager, this object
	 * will be removed automatically.
	 */
	public void kill() {
		isKilled = true;
	}

	/**
	 * Stops and resets the tween or timeline, and sends it to its pool, for
+	 * later reuse. Note that if you use a {@link TweenManager}, this method
+	 * is automatically called once the animation is finished.
	 */
	public void free() {
	}

	/**
	 * Pauses the tween or timeline. Further update calls won't have any effect.
	 */
	public void pause() {
		isPaused = true;
	}

	/**
	 * Resumes the tween or timeline. Has no effect is it was no already paused.
	 */
	public void resume() {
		isPaused = false;
	}

	/**
	 * Repeats the tween or timeline for a given number of times.
	 * @param count The number of repetitions. For infinite repetition,
	 * use Tween.INFINITY, or a negative number.
	 *
	 * @param delay A delay between each iteration.
	 * @return The current tween or timeline, for chaining instructions.
	 */
	public T repeat(int count, float delay) {
		if (isStarted) throw new RuntimeException("You can't change the repetitions of a tween or timeline once it is started");
		repeatCnt = count;
		repeatDelay = delay >= 0 ? delay : 0;
		isYoyo = false;
		return (T) this;
	}

	/**
	 * Repeats the tween or timeline for a given number of times.
	 * Every two iterations, it will be played backwards.
	 *
	 * @param count The number of repetitions. For infinite repetition,
	 * use Tween.INFINITY, or '-1'.
	 * @param delay A delay before each repetition.
	 * @return The current tween or timeline, for chaining instructions.
	 */
	public T repeatYoyo(int count, float delay) {
		if (isStarted) throw new RuntimeException("You can't change the repetitions of a tween or timeline once it is started");
		repeatCnt = count;
		repeatDelay = delay >= 0 ? delay : 0;
		isYoyo = true;
		return (T) this;
	}

	/**
	 * Sets the callback. By default, it will be fired at the completion of the
	 * tween or timeline (event COMPLETE). If you want to change this behavior
	 * and add more triggers, use the {@link setCallbackTriggers()} method.
	 *
	 * @see TweenCallback
	 */
	public T setCallback(TweenCallback callback) {
		this.callback = callback;
		return (T) this;
	}

	/**
	 * Changes the triggers of the callback. The available triggers, listed as
	 * members of the {@link TweenCallback} interface, are:
	 * <p/>
	 *
	 * <b>BEGIN</b>: right after the delay (if any)<br/>
	 * <b>START</b>: at each iteration beginning<br/>
	 * <b>END</b>: at each iteration ending, before the repeat delay<br/>
	 * <b>COMPLETE</b>: at last END event<br/>
	 * <b>BACK_BEGIN</b>: at the beginning of the first backward iteration<br/>
	 * <b>BACK_START</b>: at each backward iteration beginning, after the repeat delay<br/>
	 * <b>BACK_END</b>: at each backward iteration ending<br/>
	 * <b>BACK_COMPLETE</b>: at last BACK_END event
	 * <p/>
	 *
	 * <pre> {@code
	 * forward :      BEGIN                                   COMPLETE
	 * forward :      START    END      START    END      START    END
	 * |--------------[XXXXXXXXXX]------[XXXXXXXXXX]------[XXXXXXXXXX]
	 * backward:      bEND  bSTART      bEND  bSTART      bEND  bSTART
	 * backward:      bCOMPLETE                                 bBEGIN
	 * }</pre>
	 *
	 * @param flags one or more triggers, separated by the '|' operator.
	 * @see TweenCallback
	 */
	public T setCallbackTriggers(int flags) {
		this.callbackTriggers = flags;
		return (T) this;
	}

	/**
	 * Attaches an object to this tween or timeline. It can be useful in order
	 * to retrieve some data from a TweenCallback.
	 *
	 * @param data Any kind of object.
	 * @return The current tween or timeline, for chaining instructions.
	 */
	public T setUserData(Object data) {
		userData = data;
		return (T) this;
	}

	// -------------------------------------------------------------------------
	// Getters
	// -------------------------------------------------------------------------

	/**
	 * Gets the delay of the tween or timeline. Nothing will happen before
	 * this delay.
	 */
	public float getDelay() {
		return delay;
	}

	/**
	 * Gets the duration of a single iteration.
	 */
	public float getDuration() {
		return duration;
	}

	/**
	 * Gets the number of iterations that will be played.
	 */
	public int getRepeatCount() {
		return repeatCnt;
	}

	/**
	 * Gets the delay occuring between two iterations.
	 */
	public float getRepeatDelay() {
		return repeatDelay;
	}

	/**
	 * Returns the complete duration, including initial delay and repetitions.
	 * The formula is as follows:
	 * <pre>
	 * fullDuration = delay + duration + (repeatDelay + duration) * repeatCnt
	 * </pre>
	 */
	public float getFullDuration() {
		if (repeatCnt < 0) return -1;
		return delay + duration + (repeatDelay + duration) * repeatCnt;
	}

	/**
	 * Gets the attached data, or null if none.
	 */
	public Object getUserData() {
		return userData;
	}

	/**
	 * Gets the id of the current step. Values are as follows:<br/>
	 * <ul>
	 * <li>even numbers mean that an iteration is playing,<br/>
	 * <li>odd numbers mean that we are between two iterations,<br/>
	 * <li>-2 means that the initial delay has not ended,<br/>
	 * <li>-1 means that we are before the first iteration,<br/>
	 * <li>repeatCount*2 + 1 means that we are after the last iteration
	 */
	public int getStep() {
		return step;
	}

	/**
	 * Gets the local time.
	 */
	public float getCurrentTime() {
		return currentTime;
	}

	/**
	 * Returns true if the tween or timeline has been started.
	 */
	public boolean isStarted() {
		return isStarted;
	}

	/**
	 * Returns true if the tween or timeline has been initialized. Starting
	 * values for tweens are stored at initialization time. This initialization
	 * takes place right after the initial delay, if any.
	 */
	public boolean isInitialized() {
		return isInitialized;
	}

	/**
	 * Returns true if the tween is finished (i.e. if the tween has reached
	 * its end or has been killed). If you don't use a TweenManager, you may
	 * want to call {@link free()} to reuse the object later.
	 */
	public boolean isFinished() {
		return isFinished || isKilled;
	}

	/**
	 * Returns true if the iterations are played as yoyo. Yoyo means that
	 * every two iterations, the animation will be played backwards.
	 */
	public boolean isYoyo() {
		return isYoyo;
	}

	/**
	 * Returns true if the tween or timeline is currently paused.
	 */
	public boolean isPaused() {
		return isPaused;
	}

	// -------------------------------------------------------------------------
	// Abstract API
	// -------------------------------------------------------------------------

	protected abstract void forceStartValues();
	protected abstract void forceEndValues();

	protected abstract boolean containsTarget(Object target);
	protected abstract boolean containsTarget(Object target, int tweenType);

	// -------------------------------------------------------------------------
	// Protected API
	// -------------------------------------------------------------------------

	protected void initializeOverride() {
	}

	protected void updateOverride(int step, int lastStep, boolean isIterationStep, float delta) {
	}

	protected void forceToStart() {
		currentTime = -delay;
		step = -1;
		isIterationStep = false;
		if (isReverse(0)) forceEndValues();
		else forceStartValues();
	}

	protected void forceToEnd(float time) {
		currentTime = time - getFullDuration();
		step = repeatCnt*2 + 1;
		isIterationStep = false;
		if (isReverse(repeatCnt*2)) forceStartValues();
		else forceEndValues();
	}

	protected void callCallback(int type) {
		if (callback != null && (callbackTriggers & type) > 0) callback.onEvent(type, this);
	}

	protected boolean isReverse(int step) {
		return isYoyo && Math.abs(step%4) == 2;
	}

	protected boolean isValid(int step) {
		return (step >= 0 && step <= repeatCnt*2) || repeatCnt < 0;
	}

	protected void killTarget(Object target) {
		if (containsTarget(target)) kill();
	}

	protected void killTarget(Object target, int tweenType) {
		if (containsTarget(target, tweenType)) kill();
	}

	// -------------------------------------------------------------------------
	// Update engine
	// -------------------------------------------------------------------------

	/**
	 * Updates the tween or timeline state. <b>You may want to use a
	 * TweenManager to update objects for you.</b>
	 *
	 * Slow motion, fast motion and backward play can be easily achieved by
	 * tweaking this delta time. Multiply it by -1 to play the animation
	 * backward, or by 0.5 to play it twice slower than its normal speed.
	 *
	 * @param delta A delta time between now and the last call.
	 */
	public void update(float delta) {
		if (!isStarted || isPaused || isKilled) return;

		deltaTime = delta;

		if (!isInitialized) {
			initialize();
		}

		if (isInitialized) {
			testRelaunch();
			updateStep();
			testCompletion();
		}

		currentTime += deltaTime;
		deltaTime = 0;
	}

	private void initialize() {
		if (currentTime+deltaTime >= delay) {
			initializeOverride();
			isInitialized = true;
			isIterationStep = true;
			step = 0;
			deltaTime -= delay-currentTime;
			currentTime = 0;
			callCallback(TweenCallback.BEGIN);
			callCallback(TweenCallback.START);
		}
	}

	private void testRelaunch() {
		if (!isIterationStep && repeatCnt >= 0 && step < 0 && currentTime+deltaTime >= 0) {
			assert step == -1;
			isIterationStep = true;
			step = 0;
			float delta = 0-currentTime;
			deltaTime -= delta;
			currentTime = 0;
			callCallback(TweenCallback.BEGIN);
			callCallback(TweenCallback.START);
			updateOverride(step, step-1, isIterationStep, delta);

		} else if (!isIterationStep && repeatCnt >= 0 && step > repeatCnt*2 && currentTime+deltaTime < 0) {
			assert step == repeatCnt*2 + 1;
			isIterationStep = true;
			step = repeatCnt*2;
			float delta = 0-currentTime;
			deltaTime -= delta;
			currentTime = duration;
			callCallback(TweenCallback.BACK_BEGIN);
			callCallback(TweenCallback.BACK_START);
			updateOverride(step, step+1, isIterationStep, delta);
		}
	}

	private void updateStep() {
		while (isValid(step)) {
			if (!isIterationStep && currentTime+deltaTime <= 0) {
				isIterationStep = true;
				step -= 1;

				float delta = 0-currentTime;
				deltaTime -= delta;
				currentTime = duration;

				if (isReverse(step)) forceStartValues(); else forceEndValues();
				callCallback(TweenCallback.BACK_START);
				updateOverride(step, step+1, isIterationStep, delta);

			} else if (!isIterationStep && currentTime+deltaTime >= repeatDelay) {
				isIterationStep = true;
				step += 1;

				float delta = repeatDelay-currentTime;
				deltaTime -= delta;
				currentTime = 0;

				if (isReverse(step)) forceEndValues(); else forceStartValues();
				callCallback(TweenCallback.START);
				updateOverride(step, step-1, isIterationStep, delta);

			} else if (isIterationStep && currentTime+deltaTime < 0) {
				isIterationStep = false;
				step -= 1;

				float delta = 0-currentTime;
				deltaTime -= delta;
				currentTime = 0;

				updateOverride(step, step+1, isIterationStep, delta);
				callCallback(TweenCallback.BACK_END);

				if (step < 0 && repeatCnt >= 0) callCallback(TweenCallback.BACK_COMPLETE);
				else currentTime = repeatDelay;

			} else if (isIterationStep && currentTime+deltaTime > duration) {
				isIterationStep = false;
				step += 1;

				float delta = duration-currentTime;
				deltaTime -= delta;
				currentTime = duration;

				updateOverride(step, step-1, isIterationStep, delta);
				callCallback(TweenCallback.END);

				if (step > repeatCnt*2 && repeatCnt >= 0) callCallback(TweenCallback.COMPLETE);
				currentTime = 0;

			} else if (isIterationStep) {
				float delta = deltaTime;
				deltaTime -= delta;
				currentTime += delta;
				updateOverride(step, step, isIterationStep, delta);
				break;

			} else {
				float delta = deltaTime;
				deltaTime -= delta;
				currentTime += delta;
				break;
			}
		}
	}

	private void testCompletion() {
		isFinished = repeatCnt >= 0 && (step > repeatCnt*2 || step < 0);
	}
}