com.google.gwt.animation.client.Animation.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.animation.client.Animation.java

Source

/*
 * 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.animation.client.AnimationScheduler.AnimationCallback;
import com.google.gwt.animation.client.AnimationScheduler.AnimationHandle;
import com.google.gwt.core.client.Duration;
import com.google.gwt.dom.client.Element;

/**
 * An {@link Animation} is a continuous event that updates progressively over
 * time at a non-fixed frame rate.
 */
public abstract class Animation {

    private final AnimationCallback callback = new AnimationCallback() {
        @Override
        public void execute(double timestamp) {
            if (update(timestamp)) {
                // Schedule the next animation frame.
                requestHandle = scheduler.requestAnimationFrame(callback, element);
            } else {
                requestHandle = null;
            }
        }
    };

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

    /**
     * The element being animated.
     */
    private Element element;

    /**
     * Is the animation running, even if it hasn't started yet.
     */
    private boolean isRunning = false;

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

    /**
     * The ID of the pending animation request.
     */
    private AnimationHandle requestHandle;

    /**
     * The unique ID of the current run. Used to handle cases where an animation
     * is restarted within an execution block.
     */
    private int runId = -1;

    private final AnimationScheduler scheduler;

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

    /**
     * Did the animation start before {@link #cancel()} was called.
     */
    private boolean wasStarted = false;

    /**
     * Construct a new {@link Animation}.
     */
    public Animation() {
        this(AnimationScheduler.get());
    }

    /**
     * Construct a new {@link AnimationScheduler} using the specified scheduler to
     * sheduler request frames.
     * 
     * @param scheduler an {@link AnimationScheduler} instance
     */
    protected Animation(AnimationScheduler scheduler) {
        this.scheduler = scheduler;
    }

    /**
     * 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 (!isRunning) {
            return;
        }

        // Reset the state.
        wasStarted = isStarted; // Used by onCancel.
        element = null;
        isRunning = false;
        isStarted = false;

        // Cancel the animation request.
        if (requestHandle != null) {
            requestHandle.cancel();
            requestHandle = null;
        }

        onCancel();
    }

    /**
     * Immediately run this animation. If the animation is already running, it
     * will be canceled first.
     * <p>
     * This is equivalent to <code>run(duration, null)</code>.
     * 
     * @param duration the duration of the animation in milliseconds
     * @see #run(int, Element)
     */
    public void run(int duration) {
        run(duration, null);
    }

    /**
     * Immediately run this animation. If the animation is already running, it
     * will be canceled first.
     * <p>
     * If the element is not <code>null</code>, the {@link #onUpdate(double)}
     * method might be called only if the element may be visible (generally left
     * at the appreciation of the browser). Otherwise, it will be called
     * unconditionally.
     * 
     * @param duration the duration of the animation in milliseconds
     * @param element the element that visually bounds the entire animation
     */
    public void run(int duration, Element element) {
        run(duration, Duration.currentTimeMillis(), element);
    }

    /**
     * Run this animation at the given startTime. If the startTime has already
     * passed, the animation will run synchronously as if it started at the
     * specified start time. If the animation is already running, it will be
     * canceled first.
     * <p>
     * This is equivalent to <code>run(duration, startTime, null)</code>.
     * 
     * @param duration the duration of the animation in milliseconds
     * @param startTime the synchronized start time in milliseconds
     * @see #run(int, double, Element)
     */
    public void run(int duration, double startTime) {
        run(duration, startTime, null);
    }

    /**
     * Run this animation at the given startTime. If the startTime has already
     * passed, the animation will run synchronously as if it started at the
     * specified start time. If the animation is already running, it will be
     * canceled first.
     * <p>
     * If the element is not <code>null</code>, the {@link #onUpdate(double)}
     * method might be called only if the element may be visible (generally left
     * at the appreciation of the browser). Otherwise, it will be called
     * unconditionally.
     * 
     * @param duration the duration of the animation in milliseconds
     * @param startTime the synchronized start time in milliseconds
     * @param element the element that visually bounds the entire animation
     */
    public void run(int duration, double startTime, Element element) {
        // Cancel the animation if it is running
        cancel();

        // Save the duration and startTime
        isRunning = true;
        isStarted = false;
        this.duration = duration;
        this.startTime = startTime;
        this.element = element;
        ++runId;

        // Execute the first callback.
        callback.execute(Duration.currentTimeMillis());
    }

    /**
     * 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 (wasStarted) {
            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);

    /**
     * Check if the specified run ID is still being run.
     * 
     * @param curRunId the current run ID to check
     * @return true if running, false if canceled or restarted
     */
    private boolean isRunning(int curRunId) {
        return isRunning && (runId == curRunId);
    }

    /**
     * Update the {@link Animation}.
     * 
     * @param curTime the current time
     * @return true if the animation should run again, false if it is complete
     */
    private boolean update(double curTime) {
        /*
         * Save the run id. If the runId is incremented during this execution block,
         * we know that this run has been canceled.
         */
        final int curRunId = runId;

        boolean finished = curTime >= startTime + duration;
        if (isStarted && !finished) {
            // Animation is in progress.
            double progress = (curTime - startTime) / duration;
            onUpdate(interpolate(progress));
            return isRunning(curRunId); // Check if this run was canceled.
        }
        if (!isStarted && curTime >= startTime) {
            /*
             * Start the animation. We do not call onUpdate() because onStart() calls
             * onUpdate() by default.
             */
            isStarted = true;
            onStart();
            if (!isRunning(curRunId)) {
                // This run was canceled.
                return false;
            }
            // Intentional fall through to possibly end the animation.
        }
        if (finished) {
            // Animation is complete.
            isRunning = false;
            isStarted = false;
            onComplete();
            return false;
        }
        return true;
    }
}