LauncherStateTransitionAnimation.java revision b5e65c8bd3785409d4aeda21f2c88e75c9e22c9f
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 17package com.android.launcher3; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.AnimatorSet; 22import android.animation.ObjectAnimator; 23import android.animation.PropertyValuesHolder; 24import android.animation.TimeInterpolator; 25import android.animation.ValueAnimator; 26import android.annotation.SuppressLint; 27import android.annotation.TargetApi; 28import android.content.res.Resources; 29import android.os.Build; 30import android.util.Log; 31import android.view.View; 32import android.view.animation.AccelerateInterpolator; 33import android.view.animation.DecelerateInterpolator; 34 35import com.android.launcher3.allapps.AllAppsContainerView; 36import com.android.launcher3.allapps.AllAppsTransitionController; 37import com.android.launcher3.anim.AnimationLayerSet; 38import com.android.launcher3.config.FeatureFlags; 39import com.android.launcher3.util.CircleRevealOutlineProvider; 40import com.android.launcher3.util.Thunk; 41import com.android.launcher3.widget.WidgetsContainerView; 42 43/** 44 * TODO: figure out what kind of tests we can write for this 45 * 46 * Things to test when changing the following class. 47 * - Home from workspace 48 * - from center screen 49 * - from other screens 50 * - Home from all apps 51 * - from center screen 52 * - from other screens 53 * - Back from all apps 54 * - from center screen 55 * - from other screens 56 * - Launch app from workspace and quit 57 * - with back 58 * - with home 59 * - Launch app from all apps and quit 60 * - with back 61 * - with home 62 * - Go to a screen that's not the default, then all 63 * apps, and launch and app, and go back 64 * - with back 65 * -with home 66 * - On workspace, long press power and go back 67 * - with back 68 * - with home 69 * - On all apps, long press power and go back 70 * - with back 71 * - with home 72 * - On workspace, power off 73 * - On all apps, power off 74 * - Launch an app and turn off the screen while in that app 75 * - Go back with home key 76 * - Go back with back key TODO: make this not go to workspace 77 * - From all apps 78 * - From workspace 79 * - Enter and exit car mode (becuase it causes an extra configuration changed) 80 * - From all apps 81 * - From the center workspace 82 * - From another workspace 83 */ 84public class LauncherStateTransitionAnimation { 85 86 /** 87 * animation used for all apps and widget tray when 88 *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code false} 89 */ 90 public static final int CIRCULAR_REVEAL = 0; 91 /** 92 * animation used for all apps and not widget tray when 93 *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code true} 94 */ 95 public static final int PULLUP = 1; 96 97 private static final float FINAL_REVEAL_ALPHA_FOR_WIDGETS = 0.3f; 98 99 /** 100 * Private callbacks made during transition setup. 101 */ 102 private static class PrivateTransitionCallbacks { 103 private final float materialRevealViewFinalAlpha; 104 105 PrivateTransitionCallbacks(float revealAlpha) { 106 materialRevealViewFinalAlpha = revealAlpha; 107 } 108 109 float getMaterialRevealViewStartFinalRadius() { 110 return 0; 111 } 112 AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView, 113 View buttonView) { 114 return null; 115 } 116 void onTransitionComplete() {} 117 } 118 119 public static final String TAG = "LSTAnimation"; 120 121 public static final int SINGLE_FRAME_DELAY = 16; 122 123 @Thunk Launcher mLauncher; 124 @Thunk AnimatorSet mCurrentAnimation; 125 AllAppsTransitionController mAllAppsController; 126 127 public LauncherStateTransitionAnimation(Launcher l, AllAppsTransitionController allAppsController) { 128 mLauncher = l; 129 mAllAppsController = allAppsController; 130 } 131 132 /** 133 * Starts an animation to the apps view. 134 * 135 * @param startSearchAfterTransition Immediately starts app search after the transition to 136 * All Apps is completed. 137 */ 138 public void startAnimationToAllApps(final Workspace.State fromWorkspaceState, 139 final boolean animated, final boolean startSearchAfterTransition) { 140 final AllAppsContainerView toView = mLauncher.getAppsView(); 141 final View buttonView = mLauncher.getStartViewForAllAppsRevealAnimation(); 142 PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) { 143 @Override 144 public float getMaterialRevealViewStartFinalRadius() { 145 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize; 146 return allAppsButtonSize / 2; 147 } 148 @Override 149 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 150 final View revealView, final View allAppsButtonView) { 151 return new AnimatorListenerAdapter() { 152 public void onAnimationStart(Animator animation) { 153 allAppsButtonView.setVisibility(View.INVISIBLE); 154 } 155 public void onAnimationEnd(Animator animation) { 156 allAppsButtonView.setVisibility(View.VISIBLE); 157 } 158 }; 159 } 160 @Override 161 void onTransitionComplete() { 162 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 163 if (startSearchAfterTransition) { 164 toView.startAppsSearch(); 165 } 166 } 167 }; 168 int animType = CIRCULAR_REVEAL; 169 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { 170 animType = PULLUP; 171 } 172 // Only animate the search bar if animating from spring loaded mode back to all apps 173 startAnimationToOverlay(fromWorkspaceState, 174 Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, animType, cb); 175 } 176 177 /** 178 * Starts an animation to the widgets view. 179 */ 180 public void startAnimationToWidgets(final Workspace.State fromWorkspaceState, 181 final boolean animated) { 182 final WidgetsContainerView toView = mLauncher.getWidgetsView(); 183 final View buttonView = mLauncher.getWidgetsButton(); 184 startAnimationToOverlay(fromWorkspaceState, 185 Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, CIRCULAR_REVEAL, 186 new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS){ 187 @Override 188 void onTransitionComplete() { 189 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 190 } 191 }); 192 } 193 194 /** 195 * Starts an animation to the workspace from the current overlay view. 196 */ 197 public void startAnimationToWorkspace(final Launcher.State fromState, 198 final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, 199 final boolean animated, final Runnable onCompleteRunnable) { 200 if (toWorkspaceState != Workspace.State.NORMAL && 201 toWorkspaceState != Workspace.State.SPRING_LOADED && 202 toWorkspaceState != Workspace.State.OVERVIEW) { 203 Log.e(TAG, "Unexpected call to startAnimationToWorkspace"); 204 } 205 206 if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED 207 || mAllAppsController.isTransitioning()) { 208 int animType = CIRCULAR_REVEAL; 209 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { 210 animType = PULLUP; 211 } 212 startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState, 213 animated, animType, onCompleteRunnable); 214 } else if (fromState == Launcher.State.WIDGETS || 215 fromState == Launcher.State.WIDGETS_SPRING_LOADED) { 216 startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState, 217 animated, onCompleteRunnable); 218 } else { 219 startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState, 220 animated, onCompleteRunnable); 221 } 222 } 223 224 /** 225 * Creates and starts a new animation to a particular overlay view. 226 */ 227 @SuppressLint("NewApi") 228 private void startAnimationToOverlay( 229 final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, 230 final View buttonView, final BaseContainerView toView, 231 final boolean animated, int animType, final PrivateTransitionCallbacks pCb) { 232 final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); 233 final Resources res = mLauncher.getResources(); 234 final boolean material = Utilities.ATLEAST_LOLLIPOP; 235 final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime); 236 final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime); 237 238 final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger); 239 240 final View fromView = mLauncher.getWorkspace(); 241 242 final AnimationLayerSet layerViews = new AnimationLayerSet(); 243 244 // If for some reason our views aren't initialized, don't animate 245 boolean initialized = buttonView != null; 246 247 // Cancel the current animation 248 cancelAnimation(); 249 250 final View contentView = toView.getContentView(); 251 playCommonTransitionAnimations(toWorkspaceState, fromView, toView, 252 animated, initialized, animation, layerViews); 253 if (!animated || !initialized) { 254 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && 255 toWorkspaceState == Workspace.State.NORMAL_HIDDEN) { 256 mAllAppsController.finishPullUp(); 257 } 258 toView.setTranslationX(0.0f); 259 toView.setTranslationY(0.0f); 260 toView.setScaleX(1.0f); 261 toView.setScaleY(1.0f); 262 toView.setAlpha(1.0f); 263 toView.setVisibility(View.VISIBLE); 264 265 // Show the content view 266 contentView.setVisibility(View.VISIBLE); 267 268 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 269 dispatchOnLauncherTransitionStart(fromView, animated, false); 270 dispatchOnLauncherTransitionEnd(fromView, animated, false); 271 dispatchOnLauncherTransitionPrepare(toView, animated, false); 272 dispatchOnLauncherTransitionStart(toView, animated, false); 273 dispatchOnLauncherTransitionEnd(toView, animated, false); 274 pCb.onTransitionComplete(); 275 return; 276 } 277 if (animType == CIRCULAR_REVEAL) { 278 // Setup the reveal view animation 279 final View revealView = toView.getRevealView(); 280 281 int width = revealView.getMeasuredWidth(); 282 int height = revealView.getMeasuredHeight(); 283 float revealRadius = (float) Math.hypot(width / 2, height / 2); 284 revealView.setVisibility(View.VISIBLE); 285 revealView.setAlpha(0f); 286 revealView.setTranslationY(0f); 287 revealView.setTranslationX(0f); 288 289 // Calculate the final animation values 290 final float revealViewToAlpha; 291 final float revealViewToXDrift; 292 final float revealViewToYDrift; 293 if (material) { 294 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace( 295 revealView, buttonView, null); 296 revealViewToAlpha = pCb.materialRevealViewFinalAlpha; 297 revealViewToYDrift = buttonViewToPanelDelta[1]; 298 revealViewToXDrift = buttonViewToPanelDelta[0]; 299 } else { 300 revealViewToAlpha = 0f; 301 revealViewToYDrift = 2 * height / 3; 302 revealViewToXDrift = 0; 303 } 304 305 // Create the animators 306 PropertyValuesHolder panelAlpha = 307 PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f); 308 PropertyValuesHolder panelDriftY = 309 PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0); 310 PropertyValuesHolder panelDriftX = 311 PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0); 312 ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView, 313 panelAlpha, panelDriftY, panelDriftX); 314 panelAlphaAndDrift.setDuration(revealDuration); 315 panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 316 317 // Play the animation 318 layerViews.addView(revealView); 319 animation.play(panelAlphaAndDrift); 320 321 // Setup the animation for the content view 322 contentView.setVisibility(View.VISIBLE); 323 contentView.setAlpha(0f); 324 contentView.setTranslationY(revealViewToYDrift); 325 layerViews.addView(contentView); 326 327 // Create the individual animators 328 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", 329 revealViewToYDrift, 0); 330 pageDrift.setDuration(revealDuration); 331 pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 332 pageDrift.setStartDelay(itemsAlphaStagger); 333 animation.play(pageDrift); 334 335 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f); 336 itemsAlpha.setDuration(revealDuration); 337 itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); 338 itemsAlpha.setStartDelay(itemsAlphaStagger); 339 animation.play(itemsAlpha); 340 341 if (material) { 342 float startRadius = pCb.getMaterialRevealViewStartFinalRadius(); 343 AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener( 344 revealView, buttonView); 345 Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2, 346 startRadius, revealRadius).createRevealAnimator(revealView); 347 reveal.setDuration(revealDuration); 348 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 349 if (listener != null) { 350 reveal.addListener(listener); 351 } 352 animation.play(reveal); 353 } 354 355 animation.addListener(new AnimatorListenerAdapter() { 356 @Override 357 public void onAnimationEnd(Animator animation) { 358 dispatchOnLauncherTransitionEnd(fromView, animated, false); 359 dispatchOnLauncherTransitionEnd(toView, animated, false); 360 361 // Hide the reveal view 362 revealView.setVisibility(View.INVISIBLE); 363 364 // This can hold unnecessary references to views. 365 cleanupAnimation(); 366 pCb.onTransitionComplete(); 367 } 368 369 }); 370 371 // Dispatch the prepare transition signal 372 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 373 dispatchOnLauncherTransitionPrepare(toView, animated, false); 374 375 final AnimatorSet stateAnimation = animation; 376 final Runnable startAnimRunnable = new Runnable() { 377 public void run() { 378 // Check that mCurrentAnimation hasn't changed while 379 // we waited for a layout/draw pass 380 if (mCurrentAnimation != stateAnimation) 381 return; 382 dispatchOnLauncherTransitionStart(fromView, animated, false); 383 dispatchOnLauncherTransitionStart(toView, animated, false); 384 385 // Focus the new view 386 toView.requestFocus(); 387 388 stateAnimation.start(); 389 } 390 }; 391 toView.bringToFront(); 392 toView.setVisibility(View.VISIBLE); 393 394 animation.addListener(layerViews); 395 toView.post(startAnimRunnable); 396 mCurrentAnimation = animation; 397 } else if (animType == PULLUP) { 398 // We are animating the content view alpha, so ensure we have a layer for it 399 layerViews.addView(contentView); 400 401 animation.addListener(new AnimatorListenerAdapter() { 402 @Override 403 public void onAnimationEnd(Animator animation) { 404 dispatchOnLauncherTransitionEnd(fromView, animated, false); 405 dispatchOnLauncherTransitionEnd(toView, animated, false); 406 cleanupAnimation(); 407 pCb.onTransitionComplete(); 408 } 409 }); 410 boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide); 411 412 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 413 dispatchOnLauncherTransitionPrepare(toView, animated, false); 414 415 final AnimatorSet stateAnimation = animation; 416 final Runnable startAnimRunnable = new Runnable() { 417 public void run() { 418 // Check that mCurrentAnimation hasn't changed while 419 // we waited for a layout/draw pass 420 if (mCurrentAnimation != stateAnimation) 421 return; 422 423 dispatchOnLauncherTransitionStart(fromView, animated, false); 424 dispatchOnLauncherTransitionStart(toView, animated, false); 425 426 toView.requestFocus(); 427 stateAnimation.start(); 428 } 429 }; 430 mCurrentAnimation = animation; 431 mCurrentAnimation.addListener(layerViews); 432 if (shouldPost) { 433 toView.post(startAnimRunnable); 434 } else { 435 startAnimRunnable.run(); 436 } 437 } 438 } 439 440 /** 441 * Plays animations used by various transitions. 442 */ 443 private void playCommonTransitionAnimations( 444 Workspace.State toWorkspaceState, View fromView, View toView, 445 boolean animated, boolean initialized, AnimatorSet animation, 446 AnimationLayerSet layerViews) { 447 // Create the workspace animation. 448 // NOTE: this call apparently also sets the state for the workspace if !animated 449 Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, 450 animated, layerViews); 451 452 if (animated && initialized) { 453 // Play the workspace animation 454 if (workspaceAnim != null) { 455 animation.play(workspaceAnim); 456 } 457 // Dispatch onLauncherTransitionStep() as the animation interpolates. 458 animation.play(dispatchOnLauncherTransitionStepAnim(fromView, toView)); 459 } 460 } 461 462 /** 463 * Returns an Animator that calls {@link #dispatchOnLauncherTransitionStep(View, float)} on 464 * {@param fromView} and {@param toView} as the animation interpolates. 465 * 466 * This is a bit hacky: we create a dummy ValueAnimator just for the AnimatorUpdateListener. 467 */ 468 private Animator dispatchOnLauncherTransitionStepAnim(final View fromView, final View toView) { 469 ValueAnimator updateAnimator = ValueAnimator.ofFloat(0, 1); 470 updateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 471 @Override 472 public void onAnimationUpdate(ValueAnimator animation) { 473 dispatchOnLauncherTransitionStep(fromView, animation.getAnimatedFraction()); 474 dispatchOnLauncherTransitionStep(toView, animation.getAnimatedFraction()); 475 } 476 }); 477 return updateAnimator; 478 } 479 480 /** 481 * Starts an animation to the workspace from the apps view. 482 */ 483 private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState, 484 final Workspace.State toWorkspaceState, final boolean animated, int type, 485 final Runnable onCompleteRunnable) { 486 AllAppsContainerView appsView = mLauncher.getAppsView(); 487 // No alpha anim from all apps 488 PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) { 489 @Override 490 float getMaterialRevealViewStartFinalRadius() { 491 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize; 492 return allAppsButtonSize / 2; 493 } 494 @Override 495 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 496 final View revealView, final View allAppsButtonView) { 497 return new AnimatorListenerAdapter() { 498 public void onAnimationStart(Animator animation) { 499 // We set the alpha instead of visibility to ensure that the focus does not 500 // get taken from the all apps view 501 allAppsButtonView.setVisibility(View.VISIBLE); 502 allAppsButtonView.setAlpha(0f); 503 } 504 public void onAnimationEnd(Animator animation) { 505 // Hide the reveal view 506 revealView.setVisibility(View.INVISIBLE); 507 508 // Show the all apps button, and focus it 509 allAppsButtonView.setAlpha(1f); 510 } 511 }; 512 } 513 @Override 514 void onTransitionComplete() { 515 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 516 } 517 }; 518 // Only animate the search bar if animating to spring loaded mode from all apps 519 startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState, 520 mLauncher.getStartViewForAllAppsRevealAnimation(), appsView, 521 animated, type, onCompleteRunnable, cb); 522 } 523 524 /** 525 * Starts an animation to the workspace from the widgets view. 526 */ 527 private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState, 528 final Workspace.State toWorkspaceState, final boolean animated, 529 final Runnable onCompleteRunnable) { 530 final WidgetsContainerView widgetsView = mLauncher.getWidgetsView(); 531 PrivateTransitionCallbacks cb = 532 new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) { 533 @Override 534 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 535 final View revealView, final View widgetsButtonView) { 536 return new AnimatorListenerAdapter() { 537 public void onAnimationEnd(Animator animation) { 538 // Hide the reveal view 539 revealView.setVisibility(View.INVISIBLE); 540 } 541 }; 542 } 543 @Override 544 void onTransitionComplete() { 545 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 546 } 547 }; 548 startAnimationToWorkspaceFromOverlay( 549 fromWorkspaceState, toWorkspaceState, 550 mLauncher.getWidgetsButton(), widgetsView, 551 animated, CIRCULAR_REVEAL, onCompleteRunnable, cb); 552 } 553 554 /** 555 * Starts an animation to the workspace from another workspace state, e.g. normal to overview. 556 */ 557 private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState, 558 final Workspace.State toWorkspaceState, final boolean animated, 559 final Runnable onCompleteRunnable) { 560 final View fromWorkspace = mLauncher.getWorkspace(); 561 final AnimationLayerSet layerViews = new AnimationLayerSet(); 562 final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); 563 564 // Cancel the current animation 565 cancelAnimation(); 566 567 boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages; 568 569 playCommonTransitionAnimations(toWorkspaceState, fromWorkspace, null, 570 animated, animated, animation, layerViews); 571 572 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 573 574 if (animated) { 575 dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible); 576 577 final AnimatorSet stateAnimation = animation; 578 final Runnable startAnimRunnable = new Runnable() { 579 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 580 public void run() { 581 // Check that mCurrentAnimation hasn't changed while 582 // we waited for a layout/draw pass 583 if (mCurrentAnimation != stateAnimation) 584 return; 585 586 dispatchOnLauncherTransitionStart(fromWorkspace, animated, true); 587 stateAnimation.start(); 588 } 589 }; 590 animation.addListener(new AnimatorListenerAdapter() { 591 @Override 592 public void onAnimationEnd(Animator animation) { 593 dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true); 594 595 // Run any queued runnables 596 if (onCompleteRunnable != null) { 597 onCompleteRunnable.run(); 598 } 599 600 // This can hold unnecessary references to views. 601 cleanupAnimation(); 602 } 603 }); 604 stateAnimation.addListener(layerViews); 605 fromWorkspace.post(startAnimRunnable); 606 mCurrentAnimation = animation; 607 } else /* if (!animated) */ { 608 dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible); 609 dispatchOnLauncherTransitionStart(fromWorkspace, animated, true); 610 dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true); 611 612 // Run any queued runnables 613 if (onCompleteRunnable != null) { 614 onCompleteRunnable.run(); 615 } 616 617 mCurrentAnimation = null; 618 } 619 } 620 621 /** 622 * Creates and starts a new animation to the workspace. 623 */ 624 private void startAnimationToWorkspaceFromOverlay( 625 final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, 626 final View buttonView, final BaseContainerView fromView, 627 final boolean animated, int animType, final Runnable onCompleteRunnable, 628 final PrivateTransitionCallbacks pCb) { 629 final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); 630 final Resources res = mLauncher.getResources(); 631 final boolean material = Utilities.ATLEAST_LOLLIPOP; 632 final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime); 633 final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime); 634 final int itemsAlphaStagger = 635 res.getInteger(R.integer.config_overlayItemsAlphaStagger); 636 637 final View toView = mLauncher.getWorkspace(); 638 final View revealView = fromView.getRevealView(); 639 final View contentView = fromView.getContentView(); 640 641 final AnimationLayerSet layerViews = new AnimationLayerSet(); 642 643 // If for some reason our views aren't initialized, don't animate 644 boolean initialized = buttonView != null; 645 646 // Cancel the current animation 647 cancelAnimation(); 648 649 boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages; 650 651 playCommonTransitionAnimations(toWorkspaceState, fromView, toView, 652 animated, initialized, animation, layerViews); 653 if (!animated || !initialized) { 654 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && 655 fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) { 656 mAllAppsController.finishPullDown(); 657 } 658 fromView.setVisibility(View.GONE); 659 dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible); 660 dispatchOnLauncherTransitionStart(fromView, animated, true); 661 dispatchOnLauncherTransitionEnd(fromView, animated, true); 662 dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible); 663 dispatchOnLauncherTransitionStart(toView, animated, true); 664 dispatchOnLauncherTransitionEnd(toView, animated, true); 665 pCb.onTransitionComplete(); 666 667 // Run any queued runnables 668 if (onCompleteRunnable != null) { 669 onCompleteRunnable.run(); 670 } 671 return; 672 } 673 if (animType == CIRCULAR_REVEAL) { 674 // hideAppsCustomizeHelper is called in some cases when it is already hidden 675 // don't perform all these no-op animations. In particularly, this was causing 676 // the all-apps button to pop in and out. 677 if (fromView.getVisibility() == View.VISIBLE) { 678 int width = revealView.getMeasuredWidth(); 679 int height = revealView.getMeasuredHeight(); 680 float revealRadius = (float) Math.hypot(width / 2, height / 2); 681 revealView.setVisibility(View.VISIBLE); 682 revealView.setAlpha(1f); 683 revealView.setTranslationY(0); 684 layerViews.addView(revealView); 685 686 // Calculate the final animation values 687 final float revealViewToXDrift; 688 final float revealViewToYDrift; 689 if (material) { 690 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, 691 buttonView, null); 692 revealViewToYDrift = buttonViewToPanelDelta[1]; 693 revealViewToXDrift = buttonViewToPanelDelta[0]; 694 } else { 695 revealViewToYDrift = 2 * height / 3; 696 revealViewToXDrift = 0; 697 } 698 699 // The vertical motion of the apps panel should be delayed by one frame 700 // from the conceal animation in order to give the right feel. We correspondingly 701 // shorten the duration so that the slide and conceal end at the same time. 702 TimeInterpolator decelerateInterpolator = material ? 703 new LogDecelerateInterpolator(100, 0) : 704 new DecelerateInterpolator(1f); 705 ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY", 706 0, revealViewToYDrift); 707 panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY); 708 panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 709 panelDriftY.setInterpolator(decelerateInterpolator); 710 animation.play(panelDriftY); 711 712 ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX", 713 0, revealViewToXDrift); 714 panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY); 715 panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 716 panelDriftX.setInterpolator(decelerateInterpolator); 717 animation.play(panelDriftX); 718 719 // Setup animation for the reveal panel alpha 720 final float revealViewToAlpha = !material ? 0f : 721 pCb.materialRevealViewFinalAlpha; 722 if (revealViewToAlpha != 1f) { 723 ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha", 724 1f, revealViewToAlpha); 725 panelAlpha.setDuration(material ? revealDuration : 150); 726 panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY); 727 panelAlpha.setInterpolator(decelerateInterpolator); 728 animation.play(panelAlpha); 729 } 730 731 // Setup the animation for the content view 732 layerViews.addView(contentView); 733 734 // Create the individual animators 735 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", 736 0, revealViewToYDrift); 737 contentView.setTranslationY(0); 738 pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY); 739 pageDrift.setInterpolator(decelerateInterpolator); 740 pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 741 animation.play(pageDrift); 742 743 contentView.setAlpha(1f); 744 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f); 745 itemsAlpha.setDuration(100); 746 itemsAlpha.setInterpolator(decelerateInterpolator); 747 animation.play(itemsAlpha); 748 749 // Invalidate the scrim throughout the animation to ensure the highlight 750 // cutout is correct throughout. 751 ValueAnimator invalidateScrim = ValueAnimator.ofFloat(0f, 1f); 752 invalidateScrim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 753 @Override 754 public void onAnimationUpdate(ValueAnimator animation) { 755 mLauncher.getDragLayer().invalidateScrim(); 756 } 757 }); 758 animation.play(invalidateScrim); 759 760 if (material) { 761 // Animate the all apps button 762 float finalRadius = pCb.getMaterialRevealViewStartFinalRadius(); 763 AnimatorListenerAdapter listener = 764 pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView); 765 Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2, 766 revealRadius, finalRadius).createRevealAnimator(revealView); 767 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 768 reveal.setDuration(revealDuration); 769 reveal.setStartDelay(itemsAlphaStagger); 770 if (listener != null) { 771 reveal.addListener(listener); 772 } 773 animation.play(reveal); 774 } 775 } 776 777 dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible); 778 dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible); 779 780 animation.addListener(new AnimatorListenerAdapter() { 781 @Override 782 public void onAnimationEnd(Animator animation) { 783 fromView.setVisibility(View.GONE); 784 dispatchOnLauncherTransitionEnd(fromView, animated, true); 785 dispatchOnLauncherTransitionEnd(toView, animated, true); 786 787 // Run any queued runnables 788 if (onCompleteRunnable != null) { 789 onCompleteRunnable.run(); 790 } 791 792 // Reset page transforms 793 if (contentView != null) { 794 contentView.setTranslationX(0); 795 contentView.setTranslationY(0); 796 contentView.setAlpha(1); 797 } 798 799 // This can hold unnecessary references to views. 800 cleanupAnimation(); 801 pCb.onTransitionComplete(); 802 } 803 }); 804 805 final AnimatorSet stateAnimation = animation; 806 final Runnable startAnimRunnable = new Runnable() { 807 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 808 public void run() { 809 // Check that mCurrentAnimation hasn't changed while 810 // we waited for a layout/draw pass 811 if (mCurrentAnimation != stateAnimation) 812 return; 813 814 dispatchOnLauncherTransitionStart(fromView, animated, false); 815 dispatchOnLauncherTransitionStart(toView, animated, false); 816 stateAnimation.start(); 817 } 818 }; 819 mCurrentAnimation = animation; 820 mCurrentAnimation.addListener(layerViews); 821 fromView.post(startAnimRunnable); 822 } else if (animType == PULLUP) { 823 // We are animating the content view alpha, so ensure we have a layer for it 824 layerViews.addView(contentView); 825 826 animation.addListener(new AnimatorListenerAdapter() { 827 boolean canceled = false; 828 @Override 829 public void onAnimationCancel(Animator animation) { 830 canceled = true; 831 } 832 833 @Override 834 public void onAnimationEnd(Animator animation) { 835 if (canceled) return; 836 dispatchOnLauncherTransitionEnd(fromView, animated, true); 837 dispatchOnLauncherTransitionEnd(toView, animated, true); 838 // Run any queued runnables 839 if (onCompleteRunnable != null) { 840 onCompleteRunnable.run(); 841 } 842 843 cleanupAnimation(); 844 pCb.onTransitionComplete(); 845 } 846 847 }); 848 boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide); 849 850 // Dispatch the prepare transition signal 851 dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible); 852 dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible); 853 854 final AnimatorSet stateAnimation = animation; 855 final Runnable startAnimRunnable = new Runnable() { 856 public void run() { 857 // Check that mCurrentAnimation hasn't changed while 858 // we waited for a layout/draw pass 859 if (mCurrentAnimation != stateAnimation) 860 return; 861 862 dispatchOnLauncherTransitionStart(fromView, animated, false); 863 dispatchOnLauncherTransitionStart(toView, animated, false); 864 865 // Focus the new view 866 toView.requestFocus(); 867 stateAnimation.start(); 868 } 869 }; 870 mCurrentAnimation = animation; 871 mCurrentAnimation.addListener(layerViews); 872 if (shouldPost) { 873 fromView.post(startAnimRunnable); 874 } else { 875 startAnimRunnable.run(); 876 } 877 } 878 return; 879 } 880 881 /** 882 * Dispatches the prepare-transition event to suitable views. 883 */ 884 void dispatchOnLauncherTransitionPrepare(View v, boolean animated, 885 boolean multiplePagesVisible) { 886 if (v instanceof LauncherTransitionable) { 887 ((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated, 888 multiplePagesVisible); 889 } 890 } 891 892 /** 893 * Dispatches the start-transition event to suitable views. 894 */ 895 void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) { 896 if (v instanceof LauncherTransitionable) { 897 ((LauncherTransitionable) v).onLauncherTransitionStart(mLauncher, animated, 898 toWorkspace); 899 } 900 901 // Update the workspace transition step as well 902 dispatchOnLauncherTransitionStep(v, 0f); 903 } 904 905 /** 906 * Dispatches the step-transition event to suitable views. 907 */ 908 void dispatchOnLauncherTransitionStep(View v, float t) { 909 if (v instanceof LauncherTransitionable) { 910 ((LauncherTransitionable) v).onLauncherTransitionStep(mLauncher, t); 911 } 912 } 913 914 /** 915 * Dispatches the end-transition event to suitable views. 916 */ 917 void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) { 918 if (v instanceof LauncherTransitionable) { 919 ((LauncherTransitionable) v).onLauncherTransitionEnd(mLauncher, animated, 920 toWorkspace); 921 } 922 923 // Update the workspace transition step as well 924 dispatchOnLauncherTransitionStep(v, 1f); 925 } 926 927 /** 928 * Cancels the current animation. 929 */ 930 private void cancelAnimation() { 931 if (mCurrentAnimation != null) { 932 mCurrentAnimation.setDuration(0); 933 mCurrentAnimation.cancel(); 934 mCurrentAnimation = null; 935 } 936 } 937 938 @Thunk void cleanupAnimation() { 939 mCurrentAnimation = null; 940 } 941} 942