Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package javafx.animation;
027
028import java.util.Collection;
029import java.util.Collections;
030import java.util.Set;
031import java.util.concurrent.CopyOnWriteArraySet;
032
033import javafx.event.ActionEvent;
034import javafx.event.EventHandler;
035import javafx.util.Duration;
036
037/**
038 * Defines target values at a specified point in time for a set of variables
039 * that are interpolated along a {@link Timeline}.
040 * <p>
041 * The developer controls the interpolation of a set of variables for the
042 * interval between successive key frames by providing a target value and an
043 * {@link Interpolator} associated with each variable. The variables are
044 * interpolated such that they will reach their target value at the specified
045 * time. An {@link #onFinished} function is invoked on each {@code KeyFrame} if one
046 * is provided. A {@code KeyFrame} can optionally have a {@link #name}, which
047 * will result in a cuepoint that is automatically added to the {@code Timeline}.
048 * 
049 * @see Timeline
050 * @see KeyValue
051 * @see Interpolator
052 * 
053 */
054public final class KeyFrame {
055
056    private static final EventHandler<ActionEvent> DEFAULT_ON_FINISHED = null;
057    private static final String DEFAULT_NAME = null;
058
059    /**
060     * Returns the time offset of this {@code KeyFrame}.
061     * 
062     * The returned {@link javafx.util.Duration} defines the time offset within
063     * a single cycle of a {@link Timeline} at which the {@link KeyValue
064     * KeyValues} will be set and at which the {@link #onFinished} function
065     * variable will be called.
066     * <p>
067     * The {@code time} of a {@code KeyFrame} has to be greater than or equal to
068     * {@link javafx.util.Duration#ZERO} and it cannot be
069     * {@link javafx.util.Duration#UNKNOWN}.
070     * 
071     * Note: While the unit of {@code time} is a millisecond, the granularity
072     * depends on the underlying operating system and will in general be larger.
073     * For example animations on desktop systems usually run with a maximum of
074     * 60fps which gives a granularity of ~17 ms.
075     */
076    public Duration getTime() {
077        return time;
078    }
079    private final Duration time;
080
081    /**
082     * Returns an immutable {@code Set} of {@link KeyValue} instances. 
083     * 
084     * A {@code KeyValue} defines a target and the desired value that should be
085     * interpolated at the specified time of this {@code KeyFrame}.
086     */
087    public Set<KeyValue> getValues() {
088        return values;
089    }
090    private final Set<KeyValue> values;
091
092    /**
093     * Returns the {@code onFinished} event handler of this {@code KeyFrame}.
094     * 
095     * The {@code onFinished} event handler is a function that is called when
096     * the elapsed time on a cycle passes the specified time of this
097     * {@code KeyFrame}. The {@code onFinished} function variable will be called
098     * if the elapsed time passes the indicated value, even if it never equaled
099     * the time value exactly.
100     */
101    public EventHandler<ActionEvent> getOnFinished() {
102        return onFinished;
103    }
104    private final EventHandler<ActionEvent> onFinished;
105
106    /**
107     * Returns the {@code name} of this {@code KeyFrame}.
108     * 
109     * If a named {@code KeyFrame} is added to a {@link Timeline}, a cuepoint
110     * with the {@code name} and the {@link #time} of the {@code KeyFrame} will
111     * be added automatically. If the {@code KeyFrame} is removed, the cuepoint
112     * will also be removed.
113     */
114    public String getName() {
115        return name;
116    }
117    private final String name;
118
119    /**
120     * Constructor of {@code KeyFrame}
121     * <p>
122     * If a passed in {@code KeyValue} is {@code null} or a duplicate, it will
123     * be ignored.
124     * 
125     * @param time
126     *            the {@link #time}
127     * @param name
128     *            the {@link #name}
129     * @param onFinished
130     *            the {@link #onFinished onFinished-handler}
131     * @param values
132     *            a {@link javafx.collections.ObservableList} of
133     *            {@link KeyValue} instances
134     * @throws NullPointerException
135     *             if {@code time} is null
136     * @throws IllegalArgumentException
137     *             if {@code time} is invalid (see {@link #time})
138     */
139    public KeyFrame(Duration time, String name,
140            EventHandler<ActionEvent> onFinished, Collection<KeyValue> values) {
141        if (time == null) {
142            throw new NullPointerException("The time has to be specified");
143        }
144        if (time.lessThan(Duration.ZERO) || time.equals(Duration.UNKNOWN)) {
145            throw new IllegalArgumentException("The time is invalid.");
146        }
147        this.time = time;
148        this.name = name;
149        if (values != null) {
150            final Set<KeyValue> set = new CopyOnWriteArraySet<KeyValue>(values);
151            set.remove(null);
152            this.values = (set.size() == 0) ? Collections.<KeyValue> emptySet()
153                    : (set.size() == 1) ? Collections.<KeyValue> singleton(set
154                            .iterator().next()) : Collections
155                            .unmodifiableSet(set);
156        } else {
157            this.values = Collections.<KeyValue> emptySet();
158        }
159        this.onFinished = onFinished;
160    }
161
162    /**
163     * Constructor of {@code KeyFrame}
164     * <p>
165     * If a passed in {@code KeyValue} is {@code null} or a duplicate, it will
166     * be ignored.
167     * 
168     * @param time
169     *            the {@link #time}
170     * @param name
171     *            the {@link #name}
172     * @param onFinished
173     *            the {@link #onFinished onFinished-handler}
174     * @param values
175     *            the {@link KeyValue} instances
176     * @throws NullPointerException
177     *             if {@code time} is null
178     * @throws IllegalArgumentException
179     *             if {@code time} is invalid (see {@link #time})
180     */
181    public KeyFrame(Duration time, String name,
182            EventHandler<ActionEvent> onFinished, KeyValue... values) {
183        if (time == null) {
184            throw new NullPointerException("The time has to be specified");
185        }
186        if (time.lessThan(Duration.ZERO) || time.equals(Duration.UNKNOWN)) {
187            throw new IllegalArgumentException("The time is invalid.");
188        }
189        this.time = time;
190        this.name = name;
191        if (values != null) {
192            final Set<KeyValue> set = new CopyOnWriteArraySet<KeyValue>();
193            for (final KeyValue keyValue : values) {
194                if (keyValue != null) {
195                    set.add(keyValue);
196                }
197            }
198            this.values = (set.size() == 0) ? Collections.<KeyValue> emptySet()
199                    : (set.size() == 1) ? Collections.<KeyValue> singleton(set
200                            .iterator().next()) : Collections
201                            .unmodifiableSet(set);
202        } else {
203            this.values = Collections.emptySet();
204        }
205        this.onFinished = onFinished;
206    }
207
208    /**
209     * Constructor of {@code KeyFrame}
210     * 
211     * @param time
212     *            the {@link #time}
213     * @param onFinished
214     *            the {@link #onFinished onFinished-handler}
215     * @param values
216     *            the {@link KeyValue} instances
217     * @throws NullPointerException
218     *             if {@code time} is null
219     * @throws IllegalArgumentException
220     *             if {@code time} is invalid (see {@link #time})
221     */
222    public KeyFrame(Duration time, EventHandler<ActionEvent> onFinished,
223            KeyValue... values) {
224        this(time, DEFAULT_NAME, onFinished, values);
225    }
226
227    /**
228     * Constructor of {@code KeyFrame}
229     * 
230     * @param time
231     *            the {@link #time}
232     * @param name
233     *            the {@link #name}
234     * @param values
235     *            the {@link KeyValue} instances
236     * @throws NullPointerException
237     *             if {@code time} is null
238     * @throws IllegalArgumentException
239     *             if {@code time} is invalid (see {@link #time})
240     */
241    public KeyFrame(Duration time, String name, KeyValue... values) {
242        this(time, name, DEFAULT_ON_FINISHED, values);
243    }
244
245    /**
246     * Constructor of {@code KeyFrame}
247     * 
248     * @param time
249     *            the {@link #time}
250     * @param values
251     *            the {@link KeyValue} instances
252     * @throws NullPointerException
253     *             if {@code time} is null
254     * @throws IllegalArgumentException
255     *             if {@code time} is invalid (see {@link #time})
256     */
257    public KeyFrame(Duration time, KeyValue... values) {
258        this(time, DEFAULT_NAME, DEFAULT_ON_FINISHED, values);
259    }
260
261    /** 
262     * Returns a string representation of this {@code KeyFrame} object. 
263     * @return a string representation of this {@code KeyFrame} object. 
264     */ 
265    @Override
266    public String toString() {
267        return "KeyFrame [time=" + time + ", values=" + values
268                + ", onFinished=" + onFinished + ", name=" + name + "]";
269    }
270
271    /** 
272     * Returns a hash code for this {@code KeyFrame} object. 
273     * @return a hash code for this {@code KeyFrame} object. 
274     */ 
275    @Override
276    public int hashCode() {
277        assert (time != null) && (values != null);
278        final int prime = 31;
279        int result = 1;
280        result = prime * result + time.hashCode();
281        result = prime * result + ((name == null) ? 0 : name.hashCode());
282        result = prime * result
283                + ((onFinished == null) ? 0 : onFinished.hashCode());
284        result = prime * result + values.hashCode();
285        return result;
286    }
287
288    /**
289     * Indicates whether some other object is "equal to" this one. 
290     * Two {@code KeyFrames} are considered equal, if their {@link #getTime()
291     * time}, {@link #onFinished onFinished}, and {@link #getValues() values}
292     * are equal.
293     */
294    @Override
295    public boolean equals(Object obj) {
296        if (this == obj) {
297            return true;
298        }
299        if (obj instanceof KeyFrame) {
300            final KeyFrame kf = (KeyFrame) obj;
301            assert (time != null) && (values != null) && (kf.time != null)
302                    && (kf.values != null);
303            return time.equals(kf.time)
304                    && ((name == null) ? kf.name == null : name.equals(kf.name))
305                    && ((onFinished == null) ? kf.onFinished == null
306                            : onFinished.equals(kf.onFinished))
307                    && values.equals(kf.values);
308        }
309        return false;
310    }
311
312}