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

com.google.gwt.animation.client.Animation Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * Copyright 2008 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.animation.client;

import com.google.gwt.core.client.Duration;
import com.google.gwt.user.client.Timer;

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

/**
 * An {@link Animation} is a continuous event that updates progressively over
 * time at a non-fixed frame rate.
 */
public abstract class Animation {
  /**
   * The default time in milliseconds between frames.
   */
  private static final int DEFAULT_FRAME_DELAY = 25;

  /**
   * The {@link Animation Animations} that are currently in progress.
   */
  private static List animations = null;

  /**
   * The {@link Timer} that applies the animations.
   */
  private static Timer animationTimer = null;

  /**
   * Update all {@link Animation Animations}.
   */
  private static void updateAnimations() {
    // Duplicate the animations list in case it changes as we iterate over it
    Animation[] curAnimations = new Animation[animations.size()];
    curAnimations = animations.toArray(curAnimations);

    // Iterator through the animations
    double curTime = Duration.currentTimeMillis();
    for (Animation animation : curAnimations) {
      if (animation.running && animation.update(curTime)) {
        // We can't just remove the animation at the index, because calling
        // animation.update may have the side effect of canceling this
        // animation, running new animations, or canceling other animations.
        animations.remove(animation);
      }
    }

    // Reschedule the timer
    if (animations.size() > 0) {
      animationTimer.schedule(DEFAULT_FRAME_DELAY);
    }
  }

  /**
   * The duration of the {@link Animation} in milliseconds.
   */
  private int duration = -1;

  /**
   * Is the {@link Animation} running, even if {@link #onStart()} has not yet
   * been called.
   */
  private boolean running = false;

  /**
   * Has the {@link Animation} actually started.
   */
  private boolean started = false;

  /**
   * The start time of the {@link Animation}.
   */
  private double startTime = -1;

  /**
   * Immediately cancel this animation. If the animation is running or is
   * scheduled to run, {@link #onCancel()} will be called.
   */
  public void cancel() {
    // Ignore if the animation is not currently running
    if (!running) {
      return;
    }

    animations.remove(this);
    onCancel();
    started = false;
    running = false;
  }

  /**
   * Immediately run this animation. If the animation is already running, it
   * will be canceled first.
   * 
   * @param duration the duration of the animation in milliseconds
   */
  public void run(int duration) {
    run(duration, Duration.currentTimeMillis());
  }

  /**
   * Run this animation at the given startTime. If the startTime has already
   * passed, the animation will be synchronize as if it started at the specified
   * start time. If the animation is already running, it will be canceled first.
   * 
   * @param duration the duration of the animation in milliseconds
   * @param startTime the synchronized start time in milliseconds
   */
  public void run(int duration, double startTime) {
    // Cancel the animation if it is running
    cancel();

    // Save the duration and startTime
    this.running = true;
    this.duration = duration;
    this.startTime = startTime;

    // Start synchronously if start time has passed
    if (update(Duration.currentTimeMillis())) {
      return;
    }

    // Add to the list of animations

    // We use a static list of animations and a single timer, and create them
    // only if we are the only active animation. This is safe since JS is
    // single-threaded.
    if (animations == null) {
      animations = new ArrayList();
      animationTimer = new Timer() {
        @Override
        public void run() {
          updateAnimations();
        }
      };
    }
    animations.add(this);

    // Restart the timer if there is the only animation
    if (animations.size() == 1) {
      animationTimer.schedule(DEFAULT_FRAME_DELAY);
    }
  }

  /**
   * Interpolate the linear progress into a more natural easing function.
   * 
   * Depending on the {@link Animation}, the return value of this method can be
   * less than 0.0 or greater than 1.0.
   * 
   * @param progress the linear progress, between 0.0 and 1.0
   * @return the interpolated progress
   */
  protected double interpolate(double progress) {
    return (1 + Math.cos(Math.PI + progress * Math.PI)) / 2;
  }

  /**
   * Called immediately after the animation is canceled. The default
   * implementation of this method calls {@link #onComplete()} only if the
   * animation has actually started running.
   */
  protected void onCancel() {
    if (started) {
      onComplete();
    }
  }

  /**
   * Called immediately after the animation completes.
   */
  protected void onComplete() {
    onUpdate(interpolate(1.0));
  }

  /**
   * Called immediately before the animation starts.
   */
  protected void onStart() {
    onUpdate(interpolate(0.0));
  }

  /**
   * Called when the animation should be updated.
   * 
   * The value of progress is between 0.0 and 1.0 (inclusive) (unless you
   * override the {@link #interpolate(double)} method to provide a wider range
   * of values). You can override {@link #onStart()} and {@link #onComplete()}
   * to perform setup and tear down procedures.
   * 
   * @param progress a double, normally between 0.0 and 1.0 (inclusive)
   */
  protected abstract void onUpdate(double progress);

  /**
   * Update the {@link Animation}.
   * 
   * @param curTime the current time
   * @return true if the animation is complete, false if still running
   */
  private boolean update(double curTime) {
    boolean finished = curTime >= startTime + duration;
    if (started && !finished) {
      // Animation is in progress.
      double progress = (curTime - startTime) / duration;
      onUpdate(interpolate(progress));
      return false;
    }
    if (!started && curTime >= startTime) {
      // Start the animation.
      started = true;
      onStart();
      // Intentional fall through to possibly end the animation.
    }
    if (finished) {
      // Animation is complete.
      onComplete();
      started = false;
      running = false;
      return true;
    }
    return false;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy