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