1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3.statemanager; 18 19 import static android.animation.ValueAnimator.areAnimatorsEnabled; 20 21 import static com.android.launcher3.anim.AnimatorPlaybackController.callListenerCommandRecursively; 22 import static com.android.launcher3.states.StateAnimationConfig.SKIP_ALL_ANIMATIONS; 23 24 import android.animation.Animator; 25 import android.animation.Animator.AnimatorListener; 26 import android.animation.AnimatorListenerAdapter; 27 import android.animation.AnimatorSet; 28 import android.os.Handler; 29 import android.os.Looper; 30 import android.util.Log; 31 32 import androidx.annotation.FloatRange; 33 34 import com.android.launcher3.anim.AnimationSuccessListener; 35 import com.android.launcher3.anim.AnimatorPlaybackController; 36 import com.android.launcher3.anim.PendingAnimation; 37 import com.android.launcher3.states.StateAnimationConfig; 38 import com.android.launcher3.states.StateAnimationConfig.AnimationFlags; 39 import com.android.launcher3.testing.shared.TestProtocol; 40 41 import java.io.PrintWriter; 42 import java.util.ArrayList; 43 44 /** 45 * Class to manage transitions between different states for a StatefulActivity based on different 46 * states 47 */ 48 public class StateManager<STATE_TYPE extends BaseState<STATE_TYPE>> { 49 50 public static final String TAG = "StateManager"; 51 52 private final AnimationState mConfig = new AnimationState(); 53 private final Handler mUiHandler; 54 private final StatefulActivity<STATE_TYPE> mActivity; 55 private final ArrayList<StateListener<STATE_TYPE>> mListeners = new ArrayList<>(); 56 private final STATE_TYPE mBaseState; 57 58 // Animators which are run on properties also controlled by state animations. 59 private final AtomicAnimationFactory mAtomicAnimationFactory; 60 61 private StateHandler<STATE_TYPE>[] mStateHandlers; 62 private STATE_TYPE mState; 63 64 private STATE_TYPE mLastStableState; 65 private STATE_TYPE mCurrentStableState; 66 67 private STATE_TYPE mRestState; 68 StateManager(StatefulActivity<STATE_TYPE> l, STATE_TYPE baseState)69 public StateManager(StatefulActivity<STATE_TYPE> l, STATE_TYPE baseState) { 70 mUiHandler = new Handler(Looper.getMainLooper()); 71 mActivity = l; 72 mBaseState = baseState; 73 mState = mLastStableState = mCurrentStableState = baseState; 74 mAtomicAnimationFactory = l.createAtomicAnimationFactory(); 75 } 76 getState()77 public STATE_TYPE getState() { 78 return mState; 79 } 80 getTargetState()81 public STATE_TYPE getTargetState() { 82 return (STATE_TYPE) mConfig.targetState; 83 } 84 getCurrentStableState()85 public STATE_TYPE getCurrentStableState() { 86 return mCurrentStableState; 87 } 88 89 @Override toString()90 public String toString() { 91 return " StateManager(mLastStableState:" + mLastStableState 92 + ", mCurrentStableState:" + mCurrentStableState 93 + ", mState:" + mState 94 + ", mRestState:" + mRestState 95 + ", isInTransition:" + isInTransition() + ")"; 96 } 97 dump(String prefix, PrintWriter writer)98 public void dump(String prefix, PrintWriter writer) { 99 writer.println(prefix + "StateManager:"); 100 writer.println(prefix + "\tmLastStableState:" + mLastStableState); 101 writer.println(prefix + "\tmCurrentStableState:" + mCurrentStableState); 102 writer.println(prefix + "\tmState:" + mState); 103 writer.println(prefix + "\tmRestState:" + mRestState); 104 writer.println(prefix + "\tisInTransition:" + isInTransition()); 105 } 106 getStateHandlers()107 public StateHandler[] getStateHandlers() { 108 if (mStateHandlers == null) { 109 ArrayList<StateHandler> handlers = new ArrayList<>(); 110 mActivity.collectStateHandlers(handlers); 111 mStateHandlers = handlers.toArray(new StateHandler[handlers.size()]); 112 } 113 return mStateHandlers; 114 } 115 addStateListener(StateListener listener)116 public void addStateListener(StateListener listener) { 117 mListeners.add(listener); 118 } 119 removeStateListener(StateListener listener)120 public void removeStateListener(StateListener listener) { 121 mListeners.remove(listener); 122 } 123 124 /** 125 * Returns true if the state changes should be animated. 126 */ shouldAnimateStateChange()127 public boolean shouldAnimateStateChange() { 128 return !mActivity.isForceInvisible() && mActivity.isStarted(); 129 } 130 131 /** 132 * @return {@code true} if the state matches the current state and there is no active 133 * transition to different state. 134 */ isInStableState(STATE_TYPE state)135 public boolean isInStableState(STATE_TYPE state) { 136 return mState == state && mCurrentStableState == state 137 && (mConfig.targetState == null || mConfig.targetState == state); 138 } 139 140 /** 141 * @return {@code true} If there is an active transition. 142 */ isInTransition()143 public boolean isInTransition() { 144 return mConfig.currentAnimation != null; 145 } 146 147 /** 148 * @see #goToState(STATE_TYPE, boolean, AnimatorListener) 149 */ goToState(STATE_TYPE state)150 public void goToState(STATE_TYPE state) { 151 goToState(state, shouldAnimateStateChange()); 152 } 153 154 /** 155 * @see #goToState(STATE_TYPE, boolean, AnimatorListener) 156 */ goToState(STATE_TYPE state, boolean animated)157 public void goToState(STATE_TYPE state, boolean animated) { 158 goToState(state, animated, 0, null); 159 } 160 161 /** 162 * Changes the Launcher state to the provided state. 163 * 164 * @param animated false if the state should change immediately without any animation, 165 * true otherwise 166 * @paras onCompleteRunnable any action to perform at the end of the transition, of null. 167 */ goToState(STATE_TYPE state, boolean animated, AnimatorListener listener)168 public void goToState(STATE_TYPE state, boolean animated, AnimatorListener listener) { 169 goToState(state, animated, 0, listener); 170 } 171 172 /** 173 * Changes the Launcher state to the provided state after the given delay. 174 */ goToState(STATE_TYPE state, long delay, AnimatorListener listener)175 public void goToState(STATE_TYPE state, long delay, AnimatorListener listener) { 176 goToState(state, true, delay, listener); 177 } 178 179 /** 180 * Changes the Launcher state to the provided state after the given delay. 181 */ goToState(STATE_TYPE state, long delay)182 public void goToState(STATE_TYPE state, long delay) { 183 goToState(state, true, delay, null); 184 } 185 reapplyState()186 public void reapplyState() { 187 reapplyState(false); 188 } 189 reapplyState(boolean cancelCurrentAnimation)190 public void reapplyState(boolean cancelCurrentAnimation) { 191 boolean wasInAnimation = mConfig.currentAnimation != null; 192 if (cancelCurrentAnimation) { 193 // Animation canceling can trigger a cleanup routine, causing problems when we are in a 194 // launcher state that relies on member variable data. So if we are in one of those 195 // states, accelerate the current animation to its end point rather than canceling it 196 // outright. 197 if (mState.shouldPreserveDataStateOnReapply() && mConfig.currentAnimation != null) { 198 mConfig.currentAnimation.end(); 199 } 200 mAtomicAnimationFactory.cancelAllStateElementAnimation(); 201 cancelAnimation(); 202 } 203 if (mConfig.currentAnimation == null) { 204 for (StateHandler handler : getStateHandlers()) { 205 handler.setState(mState); 206 } 207 if (wasInAnimation) { 208 onStateTransitionEnd(mState); 209 } 210 } 211 } 212 213 /** Handles backProgress in predictive back gesture by passing it to state handlers. */ onBackProgressed( STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress)214 public void onBackProgressed( 215 STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) { 216 for (StateHandler handler : getStateHandlers()) { 217 handler.onBackProgressed(toState, backProgress); 218 } 219 } 220 221 /** Handles back cancelled event in predictive back gesture by passing it to state handlers. */ onBackCancelled(STATE_TYPE toState)222 public void onBackCancelled(STATE_TYPE toState) { 223 for (StateHandler handler : getStateHandlers()) { 224 handler.onBackCancelled(toState); 225 } 226 } 227 goToState( STATE_TYPE state, boolean animated, long delay, AnimatorListener listener)228 private void goToState( 229 STATE_TYPE state, boolean animated, long delay, AnimatorListener listener) { 230 Log.d(TestProtocol.OVERVIEW_OVER_HOME, "go to state " + state); 231 232 animated &= areAnimatorsEnabled(); 233 if (mActivity.isInState(state)) { 234 if (mConfig.currentAnimation == null) { 235 // Run any queued runnable 236 if (listener != null) { 237 listener.onAnimationEnd(null); 238 } 239 return; 240 } else if ((!mConfig.userControlled && animated && mConfig.targetState == state) 241 || mState.shouldPreserveDataStateOnReapply()) { 242 // We are running the same animation as requested, and/or target state should not be 243 // reset -- allow the current animation to complete instead of canceling it. 244 if (listener != null) { 245 mConfig.currentAnimation.addListener(listener); 246 } 247 return; 248 } 249 } 250 251 // Cancel the current animation. This will reset mState to mCurrentStableState, so store it. 252 STATE_TYPE fromState = mState; 253 cancelAnimation(); 254 255 if (!animated) { 256 mAtomicAnimationFactory.cancelAllStateElementAnimation(); 257 onStateTransitionStart(state); 258 for (StateHandler handler : getStateHandlers()) { 259 handler.setState(state); 260 } 261 262 onStateTransitionEnd(state); 263 264 // Run any queued runnable 265 if (listener != null) { 266 listener.onAnimationEnd(null); 267 } 268 return; 269 } 270 271 if (delay > 0) { 272 // Create the animation after the delay as some properties can change between preparing 273 // the animation and running the animation. 274 int startChangeId = mConfig.changeId; 275 mUiHandler.postDelayed(() -> { 276 if (mConfig.changeId == startChangeId) { 277 goToStateAnimated(state, fromState, listener); 278 } 279 }, delay); 280 } else { 281 goToStateAnimated(state, fromState, listener); 282 } 283 } 284 goToStateAnimated(STATE_TYPE state, STATE_TYPE fromState, AnimatorListener listener)285 private void goToStateAnimated(STATE_TYPE state, STATE_TYPE fromState, 286 AnimatorListener listener) { 287 // Since state mBaseState can be reached from multiple states, just assume that the 288 // transition plays in reverse and use the same duration as previous state. 289 mConfig.duration = state == mBaseState 290 ? fromState.getTransitionDuration(mActivity, false /* isToState */) 291 : state.getTransitionDuration(mActivity, true /* isToState */); 292 prepareForAtomicAnimation(fromState, state, mConfig); 293 AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).buildAnim(); 294 if (listener != null) { 295 animation.addListener(listener); 296 } 297 mUiHandler.post(new StartAnimRunnable(animation)); 298 } 299 300 /** 301 * Prepares for a non-user controlled animation from fromState to toState. Preparations include: 302 * - Setting interpolators for various animations included in the state transition. 303 * - Setting some start values (e.g. scale) for views that are hidden but about to be shown. 304 */ prepareForAtomicAnimation(STATE_TYPE fromState, STATE_TYPE toState, StateAnimationConfig config)305 public void prepareForAtomicAnimation(STATE_TYPE fromState, STATE_TYPE toState, 306 StateAnimationConfig config) { 307 mAtomicAnimationFactory.prepareForAtomicAnimation(fromState, toState, config); 308 } 309 310 /** 311 * Creates an animation representing atomic transitions between the provided states 312 */ createAtomicAnimation( STATE_TYPE fromState, STATE_TYPE toState, StateAnimationConfig config)313 public AnimatorSet createAtomicAnimation( 314 STATE_TYPE fromState, STATE_TYPE toState, StateAnimationConfig config) { 315 PendingAnimation builder = new PendingAnimation(config.duration); 316 prepareForAtomicAnimation(fromState, toState, config); 317 318 for (StateHandler handler : mActivity.getStateManager().getStateHandlers()) { 319 handler.setStateWithAnimation(toState, config, builder); 320 } 321 return builder.buildAnim(); 322 } 323 324 /** 325 * Creates a {@link AnimatorPlaybackController} that can be used for a controlled 326 * state transition. 327 * @param state the final state for the transition. 328 * @param duration intended duration for state playback. Use higher duration for better 329 * accuracy. 330 */ createAnimationToNewWorkspace( STATE_TYPE state, long duration)331 public AnimatorPlaybackController createAnimationToNewWorkspace( 332 STATE_TYPE state, long duration) { 333 return createAnimationToNewWorkspace(state, duration, 0 /* animFlags */); 334 } 335 createAnimationToNewWorkspace( STATE_TYPE state, long duration, @AnimationFlags int animFlags)336 public AnimatorPlaybackController createAnimationToNewWorkspace( 337 STATE_TYPE state, long duration, @AnimationFlags int animFlags) { 338 StateAnimationConfig config = new StateAnimationConfig(); 339 config.duration = duration; 340 config.animFlags = animFlags; 341 return createAnimationToNewWorkspace(state, config); 342 } 343 createAnimationToNewWorkspace(STATE_TYPE state, StateAnimationConfig config)344 public AnimatorPlaybackController createAnimationToNewWorkspace(STATE_TYPE state, 345 StateAnimationConfig config) { 346 config.userControlled = true; 347 cancelAnimation(); 348 config.copyTo(mConfig); 349 mConfig.playbackController = createAnimationToNewWorkspaceInternal(state) 350 .createPlaybackController(); 351 return mConfig.playbackController; 352 } 353 createAnimationToNewWorkspaceInternal(final STATE_TYPE state)354 private PendingAnimation createAnimationToNewWorkspaceInternal(final STATE_TYPE state) { 355 PendingAnimation builder = new PendingAnimation(mConfig.duration); 356 if (!mConfig.hasAnimationFlag(SKIP_ALL_ANIMATIONS)) { 357 for (StateHandler handler : getStateHandlers()) { 358 handler.setStateWithAnimation(state, mConfig, builder); 359 } 360 } 361 builder.addListener(createStateAnimationListener(state)); 362 mConfig.setAnimation(builder.buildAnim(), state); 363 return builder; 364 } 365 createStateAnimationListener(STATE_TYPE state)366 private AnimatorListener createStateAnimationListener(STATE_TYPE state) { 367 return new AnimationSuccessListener() { 368 369 @Override 370 public void onAnimationStart(Animator animation) { 371 // Change the internal state only when the transition actually starts 372 onStateTransitionStart(state); 373 } 374 375 @Override 376 public void onAnimationSuccess(Animator animator) { 377 onStateTransitionEnd(state); 378 } 379 }; 380 } 381 382 private void onStateTransitionStart(STATE_TYPE state) { 383 mState = state; 384 mActivity.onStateSetStart(mState); 385 386 Log.d(TestProtocol.OVERVIEW_OVER_HOME, "Notifying listeners for state transition start" 387 + " to state: " + state.toString()); 388 for (int i = mListeners.size() - 1; i >= 0; i--) { 389 mListeners.get(i).onStateTransitionStart(state); 390 } 391 } 392 393 private void onStateTransitionEnd(STATE_TYPE state) { 394 // Only change the stable states after the transitions have finished 395 if (state != mCurrentStableState) { 396 mLastStableState = state.getHistoryForState(mCurrentStableState); 397 mCurrentStableState = state; 398 } 399 400 mActivity.onStateSetEnd(state); 401 if (state == mBaseState) { 402 setRestState(null); 403 } 404 405 Log.d(TestProtocol.OVERVIEW_OVER_HOME, "Notifying " + mListeners.size() + " listeners " 406 + "for end transition for state: " + state.toString()); 407 for (int i = mListeners.size() - 1; i >= 0; i--) { 408 mListeners.get(i).onStateTransitionComplete(state); 409 } 410 } 411 412 public STATE_TYPE getLastState() { 413 return mLastStableState; 414 } 415 416 public void moveToRestState() { 417 moveToRestState(shouldAnimateStateChange()); 418 } 419 420 public void moveToRestState(boolean isAnimated) { 421 if (mConfig.currentAnimation != null && mConfig.userControlled) { 422 // The user is doing something. Lets not mess it up 423 return; 424 } 425 if (mState.shouldDisableRestore()) { 426 goToState(getRestState(), isAnimated); 427 // Reset history 428 mLastStableState = mBaseState; 429 } 430 } 431 432 public STATE_TYPE getRestState() { 433 return mRestState == null ? mBaseState : mRestState; 434 } 435 436 public void setRestState(STATE_TYPE restState) { 437 mRestState = restState; 438 } 439 440 /** 441 * Cancels the current animation. 442 */ 443 public void cancelAnimation() { 444 Log.d(TestProtocol.OVERVIEW_OVER_HOME, "current animation cancelled"); 445 mConfig.reset(); 446 // It could happen that a new animation is set as a result of an endListener on the 447 // existing animation. 448 while (mConfig.currentAnimation != null || mConfig.playbackController != null) { 449 mConfig.reset(); 450 } 451 } 452 453 public void setCurrentUserControlledAnimation(AnimatorPlaybackController controller) { 454 clearCurrentAnimation(); 455 setCurrentAnimation(controller.getTarget()); 456 mConfig.userControlled = true; 457 mConfig.playbackController = controller; 458 } 459 460 /** 461 * @see #setCurrentAnimation(AnimatorSet, Animator...). Using this method tells the StateManager 462 * that this is a custom animation to the given state, and thus the StateManager will add an 463 * animation listener to call {@link #onStateTransitionStart} and {@link #onStateTransitionEnd}. 464 * @param anim The custom animation to the given state. 465 * @param toState The state we are animating towards. 466 */ 467 public void setCurrentAnimation(AnimatorSet anim, STATE_TYPE toState) { 468 Log.d(TestProtocol.OVERVIEW_OVER_HOME, "setting animation to " + toState.toString()); 469 cancelAnimation(); 470 setCurrentAnimation(anim); 471 anim.addListener(createStateAnimationListener(toState)); 472 } 473 474 /** 475 * Sets the animation as the current state animation, i.e., canceled when 476 * starting another animation and may block some launcher interactions while running. 477 * 478 * @param childAnimations Set of animations with the new target is controlling. 479 */ 480 public void setCurrentAnimation(AnimatorSet anim, Animator... childAnimations) { 481 for (Animator childAnim : childAnimations) { 482 if (childAnim == null) { 483 continue; 484 } 485 if (mConfig.playbackController != null 486 && mConfig.playbackController.getTarget() == childAnim) { 487 clearCurrentAnimation(); 488 break; 489 } else if (mConfig.currentAnimation == childAnim) { 490 clearCurrentAnimation(); 491 break; 492 } 493 } 494 boolean reapplyNeeded = mConfig.currentAnimation != null; 495 cancelAnimation(); 496 if (reapplyNeeded) { 497 reapplyState(); 498 // Dispatch on transition end, so that any transient property is cleared. 499 onStateTransitionEnd(mState); 500 } 501 mConfig.setAnimation(anim, null); 502 } 503 504 /** 505 * Cancels a currently running gesture animation 506 */ 507 public void cancelStateElementAnimation(int index) { 508 if (mAtomicAnimationFactory.mStateElementAnimators[index] != null) { 509 mAtomicAnimationFactory.mStateElementAnimators[index].cancel(); 510 } 511 } 512 513 public Animator createStateElementAnimation(int index, float... values) { 514 cancelStateElementAnimation(index); 515 Animator anim = mAtomicAnimationFactory.createStateElementAnimation(index, values); 516 mAtomicAnimationFactory.mStateElementAnimators[index] = anim; 517 anim.addListener(new AnimatorListenerAdapter() { 518 @Override 519 public void onAnimationEnd(Animator animation) { 520 mAtomicAnimationFactory.mStateElementAnimators[index] = null; 521 } 522 }); 523 return anim; 524 } 525 526 private void clearCurrentAnimation() { 527 if (mConfig.currentAnimation != null) { 528 mConfig.currentAnimation.removeListener(mConfig); 529 mConfig.currentAnimation = null; 530 } 531 mConfig.playbackController = null; 532 } 533 534 private class StartAnimRunnable implements Runnable { 535 536 private final AnimatorSet mAnim; 537 538 public StartAnimRunnable(AnimatorSet anim) { 539 mAnim = anim; 540 } 541 542 @Override 543 public void run() { 544 if (mConfig.currentAnimation != mAnim) { 545 return; 546 } 547 mAnim.start(); 548 } 549 } 550 551 private static class AnimationState<STATE_TYPE> extends StateAnimationConfig 552 implements AnimatorListener { 553 554 private static final StateAnimationConfig DEFAULT = new StateAnimationConfig(); 555 556 public AnimatorPlaybackController playbackController; 557 public AnimatorSet currentAnimation; 558 public STATE_TYPE targetState; 559 560 // Id to keep track of config changes, to tie an animation with the corresponding request 561 public int changeId = 0; 562 563 /** 564 * Cancels the current animation and resets config variables. 565 */ 566 public void reset() { 567 AnimatorSet anim = currentAnimation; 568 AnimatorPlaybackController pc = playbackController; 569 570 DEFAULT.copyTo(this); 571 targetState = null; 572 currentAnimation = null; 573 playbackController = null; 574 changeId++; 575 576 if (pc != null) { 577 pc.getAnimationPlayer().cancel(); 578 pc.dispatchOnCancel().dispatchOnEnd(); 579 } else if (anim != null) { 580 anim.setDuration(0); 581 if (!anim.isStarted()) { 582 // If the animation is not started the listeners do not get notified, 583 // notify manually. 584 callListenerCommandRecursively(anim, AnimatorListener::onAnimationCancel); 585 callListenerCommandRecursively(anim, AnimatorListener::onAnimationEnd); 586 } 587 anim.cancel(); 588 } 589 } 590 591 @Override 592 public void onAnimationEnd(Animator animation) { 593 if (playbackController != null && playbackController.getTarget() == animation) { 594 playbackController = null; 595 } 596 if (currentAnimation == animation) { 597 currentAnimation = null; 598 } 599 } 600 601 public void setAnimation(AnimatorSet animation, STATE_TYPE targetState) { 602 currentAnimation = animation; 603 this.targetState = targetState; 604 currentAnimation.addListener(this); 605 } 606 607 @Override 608 public void onAnimationStart(Animator animator) { } 609 610 @Override 611 public void onAnimationCancel(Animator animator) { } 612 613 @Override 614 public void onAnimationRepeat(Animator animator) { } 615 } 616 617 public interface StateHandler<STATE_TYPE> { 618 619 /** 620 * Updates the UI to {@param state} without any animations 621 */ 622 void setState(STATE_TYPE state); 623 624 /** 625 * Sets the UI to {@param state} by animating any changes. 626 */ 627 void setStateWithAnimation( 628 STATE_TYPE toState, StateAnimationConfig config, PendingAnimation animation); 629 630 /** Handles backProgress in predictive back gesture for target state. */ 631 default void onBackProgressed( 632 STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {}; 633 634 /** Handles back cancelled event in predictive back gesture for target state. */ 635 default void onBackCancelled(STATE_TYPE toState) {}; 636 } 637 638 public interface StateListener<STATE_TYPE> { 639 640 default void onStateTransitionStart(STATE_TYPE toState) { } 641 642 default void onStateTransitionComplete(STATE_TYPE finalState) { } 643 } 644 645 /** 646 * Factory class to configure and create atomic animations. 647 */ 648 public static class AtomicAnimationFactory<STATE_TYPE> { 649 650 protected static final int NEXT_INDEX = 0; 651 652 private final Animator[] mStateElementAnimators; 653 654 /** 655 * 656 * @param sharedElementAnimCount number of animations which run on state properties 657 */ 658 public AtomicAnimationFactory(int sharedElementAnimCount) { 659 mStateElementAnimators = new Animator[sharedElementAnimCount]; 660 } 661 662 void cancelAllStateElementAnimation() { 663 for (Animator animator : mStateElementAnimators) { 664 if (animator != null) { 665 animator.cancel(); 666 } 667 } 668 } 669 670 /** 671 * Creates animations for elements which can be also be part of state transitions. The 672 * actual definition of the animation is up to the app to define. 673 * 674 */ 675 public Animator createStateElementAnimation(int index, float... values) { 676 throw new RuntimeException("Unknown gesture animation " + index); 677 } 678 679 /** 680 * Prepares for a non-user controlled animation from fromState to this state. Preparations 681 * include: 682 * - Setting interpolators for various animations included in the state transition. 683 * - Setting some start values (e.g. scale) for views that are hidden but about to be shown. 684 */ 685 public void prepareForAtomicAnimation( 686 STATE_TYPE fromState, STATE_TYPE toState, StateAnimationConfig config) { } 687 } 688 } 689