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 com.sun.javafx.animation.TickCalculation; 029import static com.sun.javafx.animation.TickCalculation.*; 030 031import javafx.beans.InvalidationListener; 032import javafx.beans.Observable; 033import javafx.beans.property.ObjectProperty; 034import javafx.collections.ListChangeListener.Change; 035import javafx.collections.ObservableList; 036import javafx.event.ActionEvent; 037import javafx.event.EventHandler; 038import javafx.scene.Node; 039import javafx.util.Duration; 040 041import com.sun.javafx.collections.TrackableObservableList; 042import com.sun.javafx.collections.VetoableListDecorator; 043import com.sun.scenario.animation.AbstractMasterTimer; 044import java.util.HashSet; 045import java.util.List; 046import java.util.Set; 047import javafx.beans.value.ChangeListener; 048import javafx.beans.value.ObservableValue; 049 050/** 051 * This {@link Transition} plays a list of {@link javafx.animation.Animation 052 * Animations} in parallel. 053 * <p> 054 * Children of this {@code Transition} inherit {@link #nodeProperty() node}, if their 055 * {@code node} property is not specified. 056 * 057 * <p> 058 * Code Segment Example: 059 * </p> 060 * 061 * <pre> 062 * <code> 063 * Rectangle rect = new Rectangle (100, 40, 100, 100); 064 * rect.setArcHeight(50); 065 * rect.setArcWidth(50); 066 * rect.setFill(Color.VIOLET); 067 * 068 * final Duration SEC_2 = Duration.millis(2000); 069 * final Duration SEC_3 = Duration.millis(3000); 070 * 071 * FadeTransition ft = new FadeTransition(SEC_3); 072 * ft.setFromValue(1.0f); 073 * ft.setToValue(0.3f); 074 * ft.setCycleCount(2f); 075 * ft.setAutoReverse(true); 076 * TranslateTransition tt = new TranslateTransition(SEC_2); 077 * tt.setFromX(-100f); 078 * tt.setToX(100f); 079 * tt.setCycleCount(2f); 080 * tt.setAutoReverse(true); 081 * RotateTransition rt = new RotateTransition(SEC_3); 082 * rt.setByAngle(180f); 083 * rt.setCycleCount(4f); 084 * rt.setAutoReverse(true); 085 * ScaleTransition st = new ScaleTransition(SEC_2); 086 * st.setByX(1.5f); 087 * st.setByY(1.5f); 088 * st.setCycleCount(2f); 089 * st.setAutoReverse(true); 090 * 091 * ParallelTransition pt = new ParallelTransition(rect, ft, tt, rt, st); 092 * pt.play(); 093 * </code> 094 * </pre> 095 * 096 * @see Transition 097 * @see Animation 098 * 099 */ 100public final class ParallelTransition extends Transition { 101 102 private static final Animation[] EMPTY_ANIMATION_ARRAY = new Animation[0]; 103 private static final double EPSILON = 1e-12; 104 105 private Animation[] cachedChildren = EMPTY_ANIMATION_ARRAY; 106 private long[] durations; 107 private long[] delays; 108 private double[] rates; 109 private long[] offsetTicks; 110 private boolean[] forceChildSync; 111 private long oldTicks; 112 private long cycleTime; 113 private boolean childrenChanged = true; 114 private boolean toggledRate; 115 116 private final InvalidationListener childrenListener = new InvalidationListener() { 117 @Override 118 public void invalidated(Observable observable) { 119 childrenChanged = true; 120 if (getStatus() == Status.STOPPED) { 121 setCycleDuration(computeCycleDuration()); 122 } 123 } 124 }; 125 126 private final ChangeListener<Number> rateListener = new ChangeListener<Number>() { 127 128 @Override 129 public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { 130 if (oldValue.doubleValue() * newValue.doubleValue() < 0) { 131 for (int i = 0; i < cachedChildren.length; ++i) { 132 Animation child = cachedChildren[i]; 133 child.clipEnvelope.setRate(rates[i] * Math.signum(getCurrentRate())); 134 } 135 toggledRate = true; 136 } 137 } 138 139 }; 140 /** 141 * This {@link javafx.scene.Node} is used in all child {@link Transition 142 * Transitions}, that do not define a target {@code Node} themselves. This 143 * can be used if a number of {@code Transitions} should be applied to a 144 * single {@code Node}. 145 * <p> 146 * It is not possible to change the target {@code node} of a running 147 * {@code Transition}. If the value of {@code node} is changed for a running 148 * {@code Transition}, the animation has to be stopped and started again to 149 * pick up the new value. 150 */ 151 private ObjectProperty<Node> node; 152 private static final Node DEFAULT_NODE = null; 153 154 public final void setNode(Node value) { 155 if ((node != null) || (value != null /* DEFAULT_NODE */)) { 156 nodeProperty().set(value); 157 } 158 } 159 160 public final Node getNode() { 161 return (node == null)? DEFAULT_NODE : node.get(); 162 } 163 164 public final ObjectProperty<Node> nodeProperty() { 165 if (node == null) { 166 node = new javafx.beans.property.SimpleObjectProperty<Node>(this, "node", DEFAULT_NODE); 167 } 168 return node; 169 } 170 171 private final Set<Animation> childrenSet = new HashSet<Animation>(); 172 173 private final ObservableList<Animation> children = new VetoableListDecorator<Animation>(new TrackableObservableList<Animation>() { 174 @Override 175 protected void onChanged(Change<Animation> c) { 176 while (c.next()) { 177 for (final Animation animation : c.getRemoved()) { 178 animation.parent = null; 179 animation.rateProperty().removeListener(childrenListener); 180 animation.totalDurationProperty().removeListener(childrenListener); 181 animation.delayProperty().removeListener(childrenListener); 182 } 183 for (final Animation animation : c.getAddedSubList()) { 184 animation.parent = ParallelTransition.this; 185 animation.rateProperty().addListener(childrenListener); 186 animation.totalDurationProperty().addListener(childrenListener); 187 animation.delayProperty().addListener(childrenListener); 188 } 189 } 190 childrenListener.invalidated(children); 191 } 192 }) { 193 194 @Override 195 protected void onProposedChange(List<Animation> toBeAdded, int... indexes) { 196 IllegalArgumentException exception = null; 197 for (int i = 0; i < indexes.length; i+=2) { 198 for (int idx = indexes[i]; idx < indexes[i+1]; ++idx) { 199 childrenSet.remove(children.get(idx)); 200 } 201 } 202 for (Animation child : toBeAdded) { 203 if (child == null) { 204 exception = new IllegalArgumentException("Child cannot be null"); 205 break; 206 } 207 if (!childrenSet.add(child)) { 208 exception = new IllegalArgumentException("Attempting to add a duplicate to the list of children"); 209 break; 210 } 211 if (checkCycle(child, ParallelTransition.this)) { 212 exception = new IllegalArgumentException("This change would create cycle"); 213 break; 214 } 215 } 216 217 if (exception != null) { 218 childrenSet.clear(); 219 childrenSet.addAll(children); 220 throw exception; 221 } 222 } 223 224 }; 225 226 private static boolean checkCycle(Animation child, Animation parent) { 227 Animation a = parent; 228 while (a != child) { 229 if (a.parent != null) { 230 a = a.parent; 231 } else { 232 return false; 233 } 234 } 235 return true; 236 } 237 238 /** 239 * A list of {@link javafx.animation.Animation Animations} that will be 240 * played sequentially. 241 * <p> 242 * It is not possible to change the children of a running 243 * {@code ParallelTransition}. If the children are changed for a running 244 * {@code ParallelTransition}, the animation has to be stopped and started 245 * again to pick up the new value. 246 * 247 * @return the list of {@link javafx.animation.Animation Animations} 248 */ 249 public final ObservableList<Animation> getChildren() { 250 return children; 251 } 252 253 /** 254 * The constructor of {@code ParallelTransition}. 255 * 256 * @param node 257 * The target {@link javafx.scene.Node} to be used in child 258 * {@link Transition Transitions} that have no {@code Node} specified 259 * themselves 260 * @param children 261 * The child {@link javafx.animation.Animation Animations} of 262 * this {@code ParallelTransition} 263 */ 264 public ParallelTransition(Node node, Animation... children) { 265 setInterpolator(Interpolator.LINEAR); 266 setNode(node); 267 getChildren().setAll(children); 268 } 269 270 /** 271 * The constructor of {@code ParallelTransition}. 272 * 273 * @param children 274 * The child {@link javafx.animation.Animation Animations} of 275 * this {@code ParallelTransition} 276 */ 277 public ParallelTransition(Animation... children) { 278 this(null, children); 279 } 280 281 /** 282 * The constructor of {@code ParallelTransition}. 283 * 284 * @param node 285 * The target {@link javafx.scene.Node} to be used in child 286 * {@link Transition Transitions} that have no {@code Node} specified 287 * themselves 288 */ 289 public ParallelTransition(Node node) { 290 setInterpolator(Interpolator.LINEAR); 291 setNode(node); 292 } 293 294 /** 295 * The constructor of {@code ParallelTransition}. 296 */ 297 public ParallelTransition() { 298 this((Node) null); 299 } 300 301 // For testing purposes 302 ParallelTransition(AbstractMasterTimer timer) { 303 super(timer); 304 setInterpolator(Interpolator.LINEAR); 305 } 306 307 /** 308 * {@inheritDoc} 309 */ 310 @Override 311 protected Node getParentTargetNode() { 312 final Node node = getNode(); 313 return (node != null) ? node : (parent != null && parent instanceof Transition) ? 314 ((Transition)parent).getParentTargetNode() : null; 315 } 316 317 private Duration computeCycleDuration() { 318 Duration maxTime = Duration.ZERO; 319 for (final Animation animation : getChildren()) { 320 final double absRate = Math.abs(animation.getRate()); 321 final Duration totalDuration = (absRate < EPSILON) ? 322 animation.getTotalDuration() : animation.getTotalDuration().divide(absRate); 323 final Duration childDuration = totalDuration.add(animation.getDelay()); 324 if (childDuration.isIndefinite()) { 325 return Duration.INDEFINITE; 326 } else { 327 if (childDuration.greaterThan(maxTime)) { 328 maxTime = childDuration; 329 } 330 } 331 } 332 return maxTime; 333 } 334 335 private double calculateFraction(long currentTicks, long cycleTicks) { 336 final double frac = (double) currentTicks / cycleTicks; 337 return (frac <= 0.0) ? 0 : (frac >= 1.0) ? 1.0 : frac; 338 } 339 340 private boolean startChild(Animation child, int index) { 341 final boolean forceSync = forceChildSync[index]; 342 if (child.impl_startable(forceSync)) { 343 child.clipEnvelope.setRate(rates[index] * Math.signum(getCurrentRate())); 344 child.impl_start(forceSync); 345 forceChildSync[index] = false; 346 return true; 347 } 348 return false; 349 } 350 351 @Override 352 void impl_sync(boolean forceSync) { 353 super.impl_sync(forceSync); 354 if ((forceSync && childrenChanged) || (durations == null)) { 355 cachedChildren = getChildren().toArray(EMPTY_ANIMATION_ARRAY); 356 final int n = cachedChildren.length; 357 durations = new long[n]; 358 delays = new long[n]; 359 rates = new double[n]; 360 offsetTicks = new long[n]; 361 forceChildSync = new boolean[n]; 362 cycleTime = 0; 363 int i = 0; 364 for (final Animation animation : cachedChildren) { 365 rates[i] = Math.abs(animation.getRate()); 366 if (rates[i] < EPSILON) { 367 rates[i] = 1; 368 } 369 durations[i] = fromDuration(animation.getTotalDuration(), rates[i]); 370 delays[i] = fromDuration(animation.getDelay()); 371 cycleTime = Math.max(cycleTime, add(durations[i], delays[i])); 372 forceChildSync[i] = true; 373 i++; 374 } 375 childrenChanged = false; 376 } else if (forceSync) { 377 final int n = forceChildSync.length; 378 for (int i=0; i<n; i++) { 379 forceChildSync[i] = true; 380 } 381 } 382 } 383 384 @Override 385 void impl_pause() { 386 super.impl_pause(); 387 for (final Animation animation : cachedChildren) { 388 if (animation.getStatus() == Status.RUNNING) { 389 animation.impl_pause(); 390 } 391 } 392 } 393 394 @Override 395 void impl_resume() { 396 super.impl_resume(); 397 int i = 0; 398 for (final Animation animation : cachedChildren) { 399 if (animation.getStatus() == Status.PAUSED) { 400 animation.impl_resume(); 401 animation.clipEnvelope.setRate(rates[i] * Math.signum(getCurrentRate())); 402 } 403 i++; 404 } 405 } 406 407 @Override 408 void impl_start(boolean forceSync) { 409 super.impl_start(forceSync); 410 toggledRate = false; 411 rateProperty().addListener(rateListener); 412 double curRate = getCurrentRate(); 413 final long currentTicks = TickCalculation.fromDuration(getCurrentTime()); 414 if (curRate < 0) { 415 jumpToEnd(); 416 if (currentTicks < cycleTime) { 417 impl_jumpTo(currentTicks, cycleTime, false); 418 } 419 } else { 420 jumpToStart(); 421 if (currentTicks > 0) { 422 impl_jumpTo(currentTicks, cycleTime, false); 423 } 424 } 425 } 426 427 @Override 428 void impl_stop() { 429 super.impl_stop(); 430 for (final Animation animation : cachedChildren) { 431 if (animation.getStatus() != Status.STOPPED) { 432 animation.impl_stop(); 433 } 434 } 435 if (childrenChanged) { 436 setCycleDuration(computeCycleDuration()); 437 } 438 rateProperty().removeListener(rateListener); 439 } 440 441 442 /** 443 * @treatAsPrivate implementation detail 444 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 445 */ 446 @Deprecated 447 @Override public void impl_playTo(long currentTicks, long cycleTicks) { 448 impl_setCurrentTicks(currentTicks); 449 final double frac = calculateFraction(currentTicks, cycleTicks); 450 final long newTicks = Math.max(0, Math.min(getCachedInterpolator().interpolate(0, cycleTicks, frac), cycleTicks)); 451 if (toggledRate) { 452 for (int i = 0; i < cachedChildren.length; ++i) { 453 if (cachedChildren[i].getStatus() == Status.RUNNING) { 454 offsetTicks[i] -= Math.signum(getCurrentRate()) * (durations[i] - 2 * (oldTicks - delays[i])); 455 } 456 } 457 toggledRate = false; 458 } 459 if (getCurrentRate() > 0) { 460 int i = 0; 461 for (final Animation animation : cachedChildren) { 462 if ((newTicks >= delays[i]) && ((oldTicks <= delays[i]) || 463 ((newTicks < add(delays[i], durations[i])) && (animation.getStatus() == Status.STOPPED)))) { 464 final boolean enteringCycle = oldTicks <= delays[i]; 465 if (startChild(animation, i)) { 466 animation.clipEnvelope.jumpTo(0); 467 } else { 468 if (enteringCycle) { 469 final EventHandler<ActionEvent> handler = animation.getOnFinished(); 470 if (handler != null) { 471 handler.handle(new ActionEvent(this, null)); 472 } 473 } 474 continue; 475 } 476 } 477 if (newTicks >= add(durations[i], delays[i])) { 478 if (animation.getStatus() == Status.RUNNING) { 479 animation.impl_timePulse(sub(durations[i], offsetTicks[i])); 480 offsetTicks[i] = 0; 481 } 482 } else if (newTicks > delays[i]) { 483 animation.impl_timePulse(sub(newTicks - delays[i], offsetTicks[i])); 484 } 485 i++; 486 } 487 } else { 488 int i = 0; 489 for (final Animation animation : cachedChildren) { 490 if (newTicks < add(durations[i], delays[i])) { 491 if ((oldTicks >= add(durations[i], delays[i])) || ((newTicks >= delays[i]) && (animation.getStatus() == Status.STOPPED))){ 492 final boolean enteringCycle = oldTicks >= add(durations[i], delays[i]); 493 if (startChild(animation, i)) { 494 animation.clipEnvelope.jumpTo(Math.round(durations[i] * rates[i])); 495 } else { 496 if (enteringCycle) { 497 final EventHandler<ActionEvent> handler = animation.getOnFinished(); 498 if (handler != null) { 499 handler.handle(new ActionEvent(this, null)); 500 } 501 } 502 continue; 503 } 504 } 505 if (newTicks <= delays[i]) { 506 if (animation.getStatus() == Status.RUNNING) { 507 animation.impl_timePulse(sub(durations[i], offsetTicks[i])); 508 offsetTicks[i] = 0; 509 } 510 } else { 511 animation.impl_timePulse(sub( add(durations[i], delays[i]) - newTicks, offsetTicks[i])); 512 } 513 } 514 i++; 515 } 516 } 517 oldTicks = newTicks; 518 } 519 520 /** 521 * @treatAsPrivate implementation detail 522 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 523 */ 524 @Deprecated 525 @Override public void impl_jumpTo(long currentTicks, long cycleTicks, boolean forceJump) { 526 impl_setCurrentTicks(currentTicks); 527 if (getStatus() == Status.STOPPED && !forceJump) { 528 return; 529 } 530 impl_sync(false); 531 final double frac = calculateFraction(currentTicks, cycleTicks); 532 final long newTicks = Math.max(0, Math.min(getCachedInterpolator().interpolate(0, cycleTicks, frac), cycleTicks)); 533 int i = 0; 534 for (final Animation animation : cachedChildren) { 535 final Status status = animation.getStatus(); 536 if (newTicks <= delays[i]) { 537 offsetTicks[i] = 0; 538 if (status != Status.STOPPED) { 539 animation.clipEnvelope.jumpTo(0); 540 animation.impl_stop(); 541 } else if(TickCalculation.fromDuration(animation.getCurrentTime()) != 0) { 542 animation.impl_jumpTo(0, durations[i], true); 543 } 544 } else if (newTicks >= add(durations[i], delays[i])) { 545 offsetTicks[i] = 0; 546 if (status != Status.STOPPED) { 547 animation.clipEnvelope.jumpTo(Math.round(durations[i] * rates[i])); 548 animation.impl_stop(); 549 } else if (TickCalculation.fromDuration(animation.getCurrentTime()) != durations[i]) { 550 animation.impl_jumpTo(durations[i], durations[i], true); 551 } 552 } else { 553 if (status == Status.STOPPED) { 554 startChild(animation, i); 555 if (getStatus() == Status.PAUSED) { 556 animation.impl_pause(); 557 } 558 559 offsetTicks[i] = (getCurrentRate() > 0)? newTicks - delays[i] : add(durations[i], delays[i]) - newTicks; 560 } else if (status == Status.PAUSED) { 561 offsetTicks[i] += (newTicks - oldTicks) * Math.signum(this.clipEnvelope.getCurrentRate()); 562 } else { 563 offsetTicks[i] += (getCurrentRate() > 0) ? newTicks - oldTicks : oldTicks - newTicks; 564 } 565 animation.clipEnvelope.jumpTo(Math.round(sub(newTicks, delays[i]) * rates[i])); 566 } 567 i++; 568 } 569 oldTicks = newTicks; 570 } 571 572 /** 573 * {@inheritDoc} 574 */ 575 @Override 576 protected void interpolate(double frac) { 577 // no-op 578 } 579 580 private void jumpToEnd() { 581 for (int i = 0 ; i < cachedChildren.length; ++i) { 582 cachedChildren[i].impl_jumpTo(durations[i], durations[i], true); 583 } 584 } 585 586 private void jumpToStart() { 587 for (int i = cachedChildren.length - 1 ; i >= 0; --i) { 588 cachedChildren[i].impl_jumpTo(0, durations[i], true); 589 } 590 } 591 592}