Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2011, 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 java.util.Arrays; 032 033import javafx.beans.InvalidationListener; 034import javafx.beans.Observable; 035import javafx.beans.property.ObjectProperty; 036import javafx.beans.property.SimpleObjectProperty; 037import javafx.collections.ListChangeListener.Change; 038import javafx.collections.ObservableList; 039import javafx.event.ActionEvent; 040import javafx.event.EventHandler; 041import javafx.scene.Node; 042import javafx.util.Duration; 043 044import com.sun.javafx.collections.TrackableObservableList; 045import com.sun.javafx.collections.VetoableListDecorator; 046import com.sun.scenario.animation.AbstractMasterTimer; 047import java.util.HashSet; 048import java.util.List; 049import java.util.Set; 050import javafx.beans.value.ChangeListener; 051import javafx.beans.value.ObservableValue; 052 053/** 054 * This {@link Transition} plays a list of {@link javafx.animation.Animation 055 * Animations} in sequential order. 056 * <p> 057 * Children of this {@code Transition} inherit {@link #nodeProperty() node}, if their 058 * {@code node} property is not specified. 059 * 060 * <p> 061 * Code Segment Example: 062 * </p> 063 * 064 * <pre> 065 * <code> 066 * Rectangle rect = new Rectangle (100, 40, 100, 100); 067 * rect.setArcHeight(50); 068 * rect.setArcWidth(50); 069 * rect.setFill(Color.VIOLET); 070 * 071 * final Duration SEC_2 = Duration.millis(2000); 072 * final Duration SEC_3 = Duration.millis(3000); 073 * 074 * PauseTransition pt = new PauseTransition(Duration.millis(1000)); 075 * FadeTransition ft = new FadeTransition(SEC_3); 076 * ft.setFromValue(1.0f); 077 * ft.setToValue(0.3f); 078 * ft.setCycleCount(2f); 079 * ft.setAutoReverse(true); 080 * TranslateTransition tt = new TranslateTransition(SEC_2); 081 * tt.setFromX(-100f); 082 * tt.setToX(100f); 083 * tt.setCycleCount(2f); 084 * tt.setAutoReverse(true); 085 * RotateTransition rt = new RotateTransition(SEC_3); 086 * rt.setByAngle(180f); 087 * rt.setCycleCount(4f); 088 * rt.setAutoReverse(true); 089 * ScaleTransition st = new ScaleTransition(SEC_2); 090 * st.setByX(1.5f); 091 * st.setByY(1.5f); 092 * st.setCycleCount(2f); 093 * st.setAutoReverse(true); 094 * 095 * SequentialTransition seqT = new SequentialTransition (rect, pt, ft, tt, rt, st); 096 * seqT.play(); 097 * </code> 098 * </pre> 099 * 100 * @see Transition 101 * @see Animation 102 * 103 */ 104public final class SequentialTransition extends Transition { 105 106 private static final Animation[] EMPTY_ANIMATION_ARRAY = new Animation[0]; 107 private static final int BEFORE = -1; 108 private static final double EPSILON = 1e-12; 109 110 private Animation[] cachedChildren = EMPTY_ANIMATION_ARRAY; 111 private long[] startTimes; 112 private long[] durations; 113 private long[] delays; 114 private double[] rates; 115 private boolean[] forceChildSync; 116 private int end; 117 private int curIndex = BEFORE; 118 private long oldTicks = 0L; 119 private long offsetTicks; 120 private boolean childrenChanged = true; 121 private boolean toggledRate; 122 123 private final InvalidationListener childrenListener = new InvalidationListener() { 124 @Override 125 public void invalidated(Observable observable) { 126 childrenChanged = true; 127 if (getStatus() == Status.STOPPED) { 128 setCycleDuration(computeCycleDuration()); 129 } 130 } 131 }; 132 133 private final ChangeListener<Number> rateListener = new ChangeListener<Number>() { 134 135 @Override 136 public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { 137 if (oldValue.doubleValue() * newValue.doubleValue() < 0) { 138 for (int i = 0; i < cachedChildren.length; ++i) { 139 Animation child = cachedChildren[i]; 140 child.clipEnvelope.setRate(rates[i] * Math.signum(getCurrentRate())); 141 } 142 toggledRate = true; 143 } 144 } 145 146 }; 147 148 /** 149 * This {@link javafx.scene.Node} is used in all child {@link Transition 150 * Transitions}, that do not define a target {@code Node} themselves. This 151 * can be used if a number of {@code Transitions} should be applied to a 152 * single {@code Node}. 153 * <p> 154 * It is not possible to change the target {@code node} of a running 155 * {@code Transition}. If the value of {@code node} is changed for a 156 * running {@code Transition}, the animation has to be stopped and started again to 157 * pick up the new value. 158 */ 159 private ObjectProperty<Node> node; 160 private static final Node DEFAULT_NODE = null; 161 162 public final void setNode(Node value) { 163 if ((node != null) || (value != null /* DEFAULT_NODE */)) { 164 nodeProperty().set(value); 165 } 166 } 167 168 public final Node getNode() { 169 return (node == null)? DEFAULT_NODE : node.get(); 170 } 171 172 public final ObjectProperty<Node> nodeProperty() { 173 if (node == null) { 174 node = new SimpleObjectProperty<Node>(this, "node", DEFAULT_NODE); 175 } 176 return node; 177 } 178 179 private final Set<Animation> childrenSet = new HashSet<Animation>(); 180 181 private final ObservableList<Animation> children = new VetoableListDecorator<Animation>(new TrackableObservableList<Animation>() { 182 @Override 183 protected void onChanged(Change<Animation> c) { 184 while (c.next()) { 185 for (final Animation animation : c.getRemoved()) { 186 animation.parent = null; 187 animation.rateProperty().removeListener(childrenListener); 188 animation.totalDurationProperty().removeListener(childrenListener); 189 animation.delayProperty().removeListener(childrenListener); 190 } 191 for (final Animation animation : c.getAddedSubList()) { 192 animation.parent = SequentialTransition.this; 193 animation.rateProperty().addListener(childrenListener); 194 animation.totalDurationProperty().addListener(childrenListener); 195 animation.delayProperty().addListener(childrenListener); 196 } 197 } 198 childrenListener.invalidated(children); 199 } 200 }) { 201 202 @Override 203 protected void onProposedChange(List<Animation> toBeAdded, int... indexes) { 204 IllegalArgumentException exception = null; 205 for (int i = 0; i < indexes.length; i+=2) { 206 for (int idx = indexes[i]; idx < indexes[i+1]; ++idx) { 207 childrenSet.remove(children.get(idx)); 208 } 209 } 210 for (Animation child : toBeAdded) { 211 if (child == null) { 212 exception = new IllegalArgumentException("Child cannot be null"); 213 break; 214 } 215 if (!childrenSet.add(child)) { 216 exception = new IllegalArgumentException("Attempting to add a duplicate to the list of children"); 217 break; 218 } 219 if (checkCycle(child, SequentialTransition.this)) { 220 exception = new IllegalArgumentException("This change would create cycle"); 221 break; 222 } 223 } 224 225 if (exception != null) { 226 childrenSet.clear(); 227 childrenSet.addAll(children); 228 throw exception; 229 } 230 } 231 232 }; 233 234 private static boolean checkCycle(Animation child, Animation parent) { 235 Animation a = parent; 236 while (a != child) { 237 if (a.parent != null) { 238 a = a.parent; 239 } else { 240 return false; 241 } 242 } 243 return true; 244 } 245 246 /** 247 * A list of {@link javafx.animation.Animation Animations} that will be 248 * played sequentially. 249 * <p> 250 * It is not possible to change the children of a running 251 * {@code SequentialTransition}. If the children are changed for a running 252 * {@code SequentialTransition}, the animation has to be stopped and started 253 * again to pick up the new value. 254 */ 255 public final ObservableList<Animation> getChildren() { 256 return children; 257 } 258 259 /** 260 * The constructor of {@code SequentialTransition}. 261 * 262 * @param node 263 * The target {@link javafx.scene.Node} to be used in child 264 * {@link Transition Transitions} that have no {@code Node} specified 265 * themselves 266 * @param children 267 * The child {@link javafx.animation.Animation Animations} of 268 * this {@code SequentialTransition} 269 */ 270 public SequentialTransition(Node node, Animation... children) { 271 setInterpolator(Interpolator.LINEAR); 272 setNode(node); 273 getChildren().setAll(children); 274 } 275 276 /** 277 * The constructor of {@code SequentialTransition}. 278 * 279 * @param children 280 * The child {@link javafx.animation.Animation Animations} of 281 * this {@code SequentialTransition} 282 */ 283 public SequentialTransition(Animation... children) { 284 this(null, children); 285 } 286 287 /** 288 * The constructor of {@code SequentialTransition}. 289 * 290 * @param node 291 * The target {@link javafx.scene.Node} to be used in child 292 * {@link Transition Transitions} that have no {@code Node} specified 293 * themselves 294 */ 295 public SequentialTransition(Node node) { 296 setInterpolator(Interpolator.LINEAR); 297 setNode(node); 298 } 299 300 /** 301 * The constructor of {@code SequentialTransition}. 302 */ 303 public SequentialTransition() { 304 this((Node) null); 305 } 306 307 // For testing purposes 308 SequentialTransition(AbstractMasterTimer timer) { 309 super(timer); 310 setInterpolator(Interpolator.LINEAR); 311 } 312 313 /** 314 * {@inheritDoc} 315 */ 316 @Override 317 protected Node getParentTargetNode() { 318 final Node _node = getNode(); 319 return (_node != null) ? _node : ((parent != null && parent instanceof Transition) ? 320 ((Transition)parent).getParentTargetNode() : null); 321 } 322 323 private Duration computeCycleDuration() { 324 Duration currentDur = Duration.ZERO; 325 326 for (final Animation animation : getChildren()) { 327 currentDur = currentDur.add(animation.getDelay()); 328 final double absRate = Math.abs(animation.getRate()); 329 currentDur = currentDur.add((absRate < EPSILON) ? 330 animation.getTotalDuration() : animation.getTotalDuration().divide(absRate)); 331 if (currentDur.isIndefinite()) { 332 break; 333 } 334 } 335 return currentDur; 336 } 337 338 private double calculateFraction(long currentTicks, long cycleTicks) { 339 final double frac = (double) currentTicks / cycleTicks; 340 return (frac <= 0.0) ? 0 : (frac >= 1.0) ? 1.0 : frac; 341 } 342 343 private int findNewIndex(long ticks) { 344 if ((curIndex != BEFORE) 345 && (curIndex != end) 346 && (startTimes[curIndex] <= ticks) 347 && (ticks <= startTimes[curIndex + 1])) { 348 return curIndex; 349 } 350 351 final boolean indexUndefined = (curIndex == BEFORE) || (curIndex == end); 352 final int fromIndex = (indexUndefined || (ticks < oldTicks)) ? 0 : curIndex + 1; 353 final int toIndex = (indexUndefined || (oldTicks < ticks)) ? end : curIndex; 354 final int index = Arrays.binarySearch(startTimes, fromIndex, toIndex, ticks); 355 return (index < 0) ? -index - 2 : (index > 0) ? index - 1 : 0; 356 } 357 358 @Override 359 void impl_sync(boolean forceSync) { 360 super.impl_sync(forceSync); 361 362 if ((forceSync && childrenChanged) || (startTimes == null)) { 363 cachedChildren = getChildren().toArray(EMPTY_ANIMATION_ARRAY); 364 end = cachedChildren.length; 365 startTimes = new long[end + 1]; 366 durations = new long[end]; 367 delays = new long[end]; 368 rates = new double[end]; 369 forceChildSync = new boolean[end]; 370 long cycleTicks = 0L; 371 int i = 0; 372 for (final Animation animation : cachedChildren) { 373 startTimes[i] = cycleTicks; 374 rates[i] = Math.abs(animation.getRate()); 375 if (rates[i] < EPSILON) { 376 rates[i] = 1; 377 } 378 durations[i] = fromDuration(animation.getTotalDuration(), rates[i]); 379 delays[i] = fromDuration(animation.getDelay()); 380 if ((durations[i] == Long.MAX_VALUE) || (delays[i] == Long.MAX_VALUE) || (cycleTicks == Long.MAX_VALUE)) { 381 cycleTicks = Long.MAX_VALUE; 382 } else { 383 cycleTicks = add(cycleTicks, add(durations[i], delays[i])); 384 } 385 forceChildSync[i] = true; 386 i++; 387 } 388 startTimes[end] = cycleTicks; 389 childrenChanged = false; 390 } else if (forceSync) { 391 final int n = forceChildSync.length; 392 for (int i=0; i<n; i++) { 393 forceChildSync[i] = true; 394 } 395 } 396 } 397 398 @Override 399 void impl_start(boolean forceSync) { 400 super.impl_start(forceSync); 401 toggledRate = false; 402 rateProperty().addListener(rateListener); 403 offsetTicks = 0L; 404 double curRate = getCurrentRate(); 405 final long currentTicks = TickCalculation.fromDuration(getCurrentTime()); 406 if (curRate < 0) { 407 jumpToEnd(); 408 curIndex = end; 409 if (currentTicks < startTimes[end]) { 410 impl_jumpTo(currentTicks, startTimes[end], false); 411 } 412 } else { 413 jumpToBefore(); 414 curIndex = BEFORE; 415 if (currentTicks > 0) { 416 impl_jumpTo(currentTicks, startTimes[end], false); 417 } 418 } 419 } 420 421 @Override 422 void impl_pause() { 423 super.impl_pause(); 424 if ((curIndex != BEFORE) && (curIndex != end)) { 425 final Animation current = cachedChildren[curIndex]; 426 if (current.getStatus() == Status.RUNNING) { 427 current.impl_pause(); 428 } 429 } 430 } 431 432 @Override 433 void impl_resume() { 434 super.impl_resume(); 435 if ((curIndex != BEFORE) && (curIndex != end)) { 436 final Animation current = cachedChildren[curIndex]; 437 if (current.getStatus() == Status.PAUSED) { 438 current.impl_resume(); 439 current.clipEnvelope.setRate(rates[curIndex] * Math.signum(getCurrentRate())); 440 } 441 } 442 } 443 444 @Override 445 void impl_stop() { 446 super.impl_stop(); 447 if ((curIndex != BEFORE) && (curIndex != end)) { 448 final Animation current = cachedChildren[curIndex]; 449 if (current.getStatus() != Status.STOPPED) { 450 current.impl_stop(); 451 } 452 } 453 if (childrenChanged) { 454 setCycleDuration(computeCycleDuration()); 455 } 456 rateProperty().removeListener(rateListener); 457 } 458 459 private boolean startChild(Animation child, int index) { 460 final boolean forceSync = forceChildSync[index]; 461 if (child.impl_startable(forceSync)) { 462 child.clipEnvelope.setRate(rates[index] * Math.signum(getCurrentRate())); 463 child.impl_start(forceSync); 464 forceChildSync[index] = false; 465 return true; 466 } 467 return false; 468 } 469 470 @Override void impl_playTo(long currentTicks, long cycleTicks) { 471 impl_setCurrentTicks(currentTicks); 472 final double frac = calculateFraction(currentTicks, cycleTicks); 473 final long newTicks = Math.max(0, Math.min(getCachedInterpolator().interpolate(0, cycleTicks, frac), cycleTicks)); 474 final int newIndex = findNewIndex(newTicks); 475 final Animation current = ((curIndex == BEFORE) || (curIndex == end)) ? null : cachedChildren[curIndex]; 476 if (toggledRate) { 477 if (current != null && current.getStatus() == Status.RUNNING) { 478 offsetTicks -= Math.signum(getCurrentRate()) * (durations[curIndex] - 2 * (oldTicks - delays[curIndex] - startTimes[curIndex])); 479 } 480 toggledRate = false; 481 } 482 if (curIndex == newIndex) { 483 if (getCurrentRate() > 0) { 484 final long currentDelay = add(startTimes[curIndex], delays[curIndex]); 485 if (newTicks >= currentDelay) { 486 if ((oldTicks <= currentDelay) || (current.getStatus() == Status.STOPPED)) { 487 final boolean enteringCycle = oldTicks <= currentDelay; 488 if (enteringCycle) { 489 current.clipEnvelope.jumpTo(0); 490 } 491 if (!startChild(current, curIndex)) { 492 if (enteringCycle) { 493 final EventHandler<ActionEvent> handler = current.getOnFinished(); 494 if (handler != null) { 495 handler.handle(new ActionEvent(this, null)); 496 } 497 } 498 oldTicks = newTicks; 499 return; 500 } 501 } 502 if (newTicks >= startTimes[curIndex+1]) { 503 current.impl_timePulse(sub(durations[curIndex], offsetTicks)); 504 if (newTicks == cycleTicks) { 505 curIndex = end; 506 } 507 } else { 508 final long localTicks = sub(newTicks - currentDelay, offsetTicks); 509 current.impl_timePulse(localTicks); 510 } 511 } 512 } else { // getCurrentRate() < 0 513 final long currentDelay = add(startTimes[curIndex], delays[curIndex]); 514 if ((oldTicks >= startTimes[curIndex+1]) || ((oldTicks >= currentDelay) && (current.getStatus() == Status.STOPPED))){ 515 final boolean enteringCycle = oldTicks >= startTimes[curIndex+1]; 516 if (enteringCycle) { 517 current.clipEnvelope.jumpTo(Math.round(durations[curIndex] * rates[curIndex])); 518 } 519 if (!startChild(current, curIndex)) { 520 if (enteringCycle) { 521 final EventHandler<ActionEvent> handler = current.getOnFinished(); 522 if (handler != null) { 523 handler.handle(new ActionEvent(this, null)); 524 } 525 } 526 oldTicks = newTicks; 527 return; 528 } 529 } 530 if (newTicks <= currentDelay) { 531 current.impl_timePulse(sub(durations[curIndex], offsetTicks)); 532 if (newTicks == 0) { 533 curIndex = BEFORE; 534 } 535 } else { 536 final long localTicks = sub(startTimes[curIndex + 1] - newTicks, offsetTicks); 537 current.impl_timePulse(localTicks); 538 } 539 } 540 } else { // curIndex != newIndex 541 if (curIndex < newIndex) { 542 if (current != null) { 543 final long oldDelay = add(startTimes[curIndex], delays[curIndex]); 544 if ((oldTicks <= oldDelay) || ((current.getStatus() == Status.STOPPED) && (oldTicks != startTimes[curIndex + 1]))) { 545 final boolean enteringCycle = oldTicks <= oldDelay; 546 if (enteringCycle) { 547 current.clipEnvelope.jumpTo(0); 548 } 549 if (!startChild(current, curIndex)) { 550 if (enteringCycle) { 551 final EventHandler<ActionEvent> handler = current.getOnFinished(); 552 if (handler != null) { 553 handler.handle(new ActionEvent(this, null)); 554 } 555 } 556 } 557 } 558 if (current.getStatus() == Status.RUNNING) { 559 current.impl_timePulse(sub(durations[curIndex], offsetTicks)); 560 } 561 oldTicks = startTimes[curIndex + 1]; 562 } 563 offsetTicks = 0; 564 curIndex++; 565 for (; curIndex < newIndex; curIndex++) { 566 final Animation animation = cachedChildren[curIndex]; 567 animation.clipEnvelope.jumpTo(0); 568 if (startChild(animation, curIndex)) { 569 animation.impl_timePulse(durations[curIndex]); // No need to subtract offsetTicks ( == 0) 570 } else { 571 final EventHandler<ActionEvent> handler = animation.getOnFinished(); 572 if (handler != null) { 573 handler.handle(new ActionEvent(this, null)); 574 } 575 } 576 oldTicks = startTimes[curIndex + 1]; 577 } 578 final Animation newAnimation = cachedChildren[curIndex]; 579 newAnimation.clipEnvelope.jumpTo(0); 580 if (startChild(newAnimation, curIndex)) { 581 if (newTicks >= startTimes[curIndex+1]) { 582 newAnimation.impl_timePulse(durations[curIndex]); // No need to subtract offsetTicks ( == 0) 583 if (newTicks == cycleTicks) { 584 curIndex = end; 585 } 586 } else { 587 final long localTicks = sub(newTicks, add(startTimes[curIndex], delays[curIndex])); 588 newAnimation.impl_timePulse(localTicks); 589 } 590 } else { 591 final EventHandler<ActionEvent> handler = newAnimation.getOnFinished(); 592 if (handler != null) { 593 handler.handle(new ActionEvent(this, null)); 594 } 595 } 596 } else { 597 if (current != null) { 598 final long oldDelay = add(startTimes[curIndex], delays[curIndex]); 599 if ((oldTicks >= startTimes[curIndex+1]) || ((oldTicks > oldDelay) && (current.getStatus() == Status.STOPPED))){ 600 final boolean enteringCycle = oldTicks >= startTimes[curIndex+1]; 601 if (enteringCycle) { 602 current.clipEnvelope.jumpTo(Math.round(durations[curIndex] * rates[curIndex])); 603 } 604 if (!startChild(current, curIndex)) { 605 if (enteringCycle) { 606 final EventHandler<ActionEvent> handler = current.getOnFinished(); 607 if (handler != null) { 608 handler.handle(new ActionEvent(this, null)); 609 } 610 } 611 } 612 } 613 if (current.getStatus() == Status.RUNNING) { 614 current.impl_timePulse(sub(durations[curIndex], offsetTicks)); 615 } 616 oldTicks = startTimes[curIndex]; 617 } 618 offsetTicks = 0; 619 curIndex--; 620 for (; curIndex > newIndex; curIndex--) { 621 final Animation animation = cachedChildren[curIndex]; 622 animation.clipEnvelope.jumpTo(Math.round(durations[curIndex] * rates[curIndex])); 623 if (startChild(animation, curIndex)) { 624 animation.impl_timePulse(durations[curIndex]); // No need to subtract offsetTicks ( == 0) 625 } else { 626 final EventHandler<ActionEvent> handler = animation.getOnFinished(); 627 if (handler != null) { 628 handler.handle(new ActionEvent(this, null)); 629 } 630 } 631 oldTicks = startTimes[curIndex]; 632 } 633 final Animation newAnimation = cachedChildren[curIndex]; 634 newAnimation.clipEnvelope.jumpTo(Math.round(durations[curIndex] * rates[curIndex])); 635 if (startChild(newAnimation, curIndex)) { 636 if (newTicks <= add(startTimes[curIndex], delays[curIndex])) { 637 newAnimation.impl_timePulse(durations[curIndex]); // No need to subtract offsetTicks ( == 0) 638 if (newTicks == 0) { 639 curIndex = BEFORE; 640 } 641 } else { 642 final long localTicks = sub(startTimes[curIndex + 1], newTicks); 643 newAnimation.impl_timePulse(localTicks); 644 } 645 } else { 646 final EventHandler<ActionEvent> handler = newAnimation.getOnFinished(); 647 if (handler != null) { 648 handler.handle(new ActionEvent(this, null)); 649 } 650 } 651 } 652 } 653 oldTicks = newTicks; 654 } 655 656 @Override void impl_jumpTo(long currentTicks, long cycleTicks, boolean forceJump) { 657 impl_setCurrentTicks(currentTicks); 658 final Status status = getStatus(); 659 660 if (status == Status.STOPPED && !forceJump) { 661 return; 662 } 663 664 impl_sync(false); 665 final double frac = calculateFraction(currentTicks, cycleTicks); 666 final long newTicks = Math.max(0, Math.min(getCachedInterpolator().interpolate(0, cycleTicks, frac), cycleTicks)); 667 final int oldIndex = curIndex; 668 curIndex = findNewIndex(newTicks); 669 final Animation newAnimation = cachedChildren[curIndex]; 670 final double currentRate = getCurrentRate(); 671 if (curIndex != oldIndex) { 672 if (status != Status.STOPPED) { 673 if ((oldIndex != BEFORE) && (oldIndex != end)) { 674 final Animation oldChild = cachedChildren[oldIndex]; 675 if (oldChild.getStatus() != Status.STOPPED) { 676 cachedChildren[oldIndex].impl_stop(); 677 } 678 } 679 if (curIndex < oldIndex) { 680 for (int i = oldIndex == end ? end - 1 : oldIndex; i > curIndex; --i) { 681 cachedChildren[i].impl_jumpTo(0, durations[i], true); 682 } 683 } else { //curIndex > oldIndex as curIndex != oldIndex 684 for (int i = oldIndex == BEFORE? 0 : oldIndex; i < curIndex; ++i) { 685 cachedChildren[i].impl_jumpTo(durations[i], durations[i], true); 686 } 687 } 688 startChild(newAnimation, curIndex); 689 if (status == Status.PAUSED) { 690 newAnimation.impl_pause(); 691 } 692 } 693 } 694 if (oldIndex == curIndex) { 695 if (currentRate == 0) { 696 offsetTicks += (newTicks - oldTicks) * Math.signum(this.clipEnvelope.getCurrentRate()); 697 } else { 698 offsetTicks += currentRate > 0 ? newTicks - oldTicks : oldTicks - newTicks; 699 } 700 } else { 701 if (currentRate == 0) { 702 if (this.clipEnvelope.getCurrentRate() > 0) { 703 offsetTicks = newTicks - add(startTimes[curIndex], delays[curIndex]); 704 } else { 705 offsetTicks = startTimes[curIndex] + durations[curIndex] - newTicks; 706 } 707 } else { 708 offsetTicks = currentRate > 0 ? newTicks - add(startTimes[curIndex], delays[curIndex]) : startTimes[curIndex + 1] - newTicks; 709 } 710 } 711 newAnimation.clipEnvelope.jumpTo(Math.round(sub(newTicks, add(startTimes[curIndex], delays[curIndex])) * rates[curIndex])); 712 oldTicks = newTicks; 713 } 714 715 private void jumpToEnd() { 716 for (int i = 0 ; i < end; ++i) { 717 cachedChildren[i].impl_jumpTo(durations[i], durations[i], true); 718 } 719 } 720 721 private void jumpToBefore() { 722 for (int i = end - 1 ; i >= 0; --i) { 723 cachedChildren[i].impl_jumpTo(0, durations[i], true); 724 } 725 } 726 727 /** 728 * {@inheritDoc} 729 */ 730 @Override 731 protected void interpolate(double frac) { 732 // no-op 733 } 734 735}