LauncherStateTransitionAnimation.java revision f3e35d93318190f995e6a0fc9d0441ac844b67e4
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 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 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 || mAllAppsController.isTransitioning()) { 212 int animType = CIRCULAR_REVEAL; 213 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) { 214 animType = PULLUP; 215 } 216 startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState, 217 animated, animType, onCompleteRunnable); 218 } else if (fromState == Launcher.State.WIDGETS || 219 fromState == Launcher.State.WIDGETS_SPRING_LOADED) { 220 startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState, 221 animated, onCompleteRunnable); 222 } else { 223 startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState, 224 animated, onCompleteRunnable); 225 } 226 } 227 228 /** 229 * Creates and starts a new animation to a particular overlay view. 230 */ 231 @SuppressLint("NewApi") 232 private void startAnimationToOverlay( 233 final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, 234 final View buttonView, final BaseContainerView toView, 235 final boolean animated, int animType, final PrivateTransitionCallbacks pCb) { 236 final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); 237 final Resources res = mLauncher.getResources(); 238 final boolean material = Utilities.ATLEAST_LOLLIPOP; 239 final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime); 240 final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime); 241 242 final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger); 243 244 final View fromView = mLauncher.getWorkspace(); 245 246 final HashMap<View, Integer> layerViews = new HashMap<>(); 247 248 // If for some reason our views aren't initialized, don't animate 249 boolean initialized = buttonView != null; 250 251 // Cancel the current animation 252 cancelAnimation(); 253 254 final View contentView = toView.getContentView(); 255 playCommonTransitionAnimations(toWorkspaceState, fromView, toView, 256 animated, initialized, animation, layerViews); 257 if (!animated || !initialized) { 258 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && 259 toWorkspaceState == Workspace.State.NORMAL_HIDDEN) { 260 mAllAppsController.finishPullUp(); 261 } 262 toView.setTranslationX(0.0f); 263 toView.setTranslationY(0.0f); 264 toView.setScaleX(1.0f); 265 toView.setScaleY(1.0f); 266 toView.setAlpha(1.0f); 267 toView.setVisibility(View.VISIBLE); 268 269 // Show the content view 270 contentView.setVisibility(View.VISIBLE); 271 272 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 273 dispatchOnLauncherTransitionStart(fromView, animated, false); 274 dispatchOnLauncherTransitionEnd(fromView, animated, false); 275 dispatchOnLauncherTransitionPrepare(toView, animated, false); 276 dispatchOnLauncherTransitionStart(toView, animated, false); 277 dispatchOnLauncherTransitionEnd(toView, animated, false); 278 pCb.onTransitionComplete(); 279 return; 280 } 281 if (animType == CIRCULAR_REVEAL) { 282 // Setup the reveal view animation 283 final View revealView = toView.getRevealView(); 284 285 int width = revealView.getMeasuredWidth(); 286 int height = revealView.getMeasuredHeight(); 287 float revealRadius = (float) Math.hypot(width / 2, height / 2); 288 revealView.setVisibility(View.VISIBLE); 289 revealView.setAlpha(0f); 290 revealView.setTranslationY(0f); 291 revealView.setTranslationX(0f); 292 293 // Calculate the final animation values 294 final float revealViewToAlpha; 295 final float revealViewToXDrift; 296 final float revealViewToYDrift; 297 if (material) { 298 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace( 299 revealView, buttonView, null); 300 revealViewToAlpha = pCb.materialRevealViewFinalAlpha; 301 revealViewToYDrift = buttonViewToPanelDelta[1]; 302 revealViewToXDrift = buttonViewToPanelDelta[0]; 303 } else { 304 revealViewToAlpha = 0f; 305 revealViewToYDrift = 2 * height / 3; 306 revealViewToXDrift = 0; 307 } 308 309 // Create the animators 310 PropertyValuesHolder panelAlpha = 311 PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f); 312 PropertyValuesHolder panelDriftY = 313 PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0); 314 PropertyValuesHolder panelDriftX = 315 PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0); 316 ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView, 317 panelAlpha, panelDriftY, panelDriftX); 318 panelAlphaAndDrift.setDuration(revealDuration); 319 panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 320 321 // Play the animation 322 layerViews.put(revealView, BUILD_AND_SET_LAYER); 323 animation.play(panelAlphaAndDrift); 324 325 // Setup the animation for the content view 326 contentView.setVisibility(View.VISIBLE); 327 contentView.setAlpha(0f); 328 contentView.setTranslationY(revealViewToYDrift); 329 layerViews.put(contentView, BUILD_AND_SET_LAYER); 330 331 // Create the individual animators 332 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", 333 revealViewToYDrift, 0); 334 pageDrift.setDuration(revealDuration); 335 pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 336 pageDrift.setStartDelay(itemsAlphaStagger); 337 animation.play(pageDrift); 338 339 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f); 340 itemsAlpha.setDuration(revealDuration); 341 itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); 342 itemsAlpha.setStartDelay(itemsAlphaStagger); 343 animation.play(itemsAlpha); 344 345 if (material) { 346 float startRadius = pCb.getMaterialRevealViewStartFinalRadius(); 347 AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener( 348 revealView, buttonView); 349 Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2, 350 startRadius, revealRadius).createRevealAnimator(revealView); 351 reveal.setDuration(revealDuration); 352 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 353 if (listener != null) { 354 reveal.addListener(listener); 355 } 356 animation.play(reveal); 357 } 358 359 animation.addListener(new AnimatorListenerAdapter() { 360 @Override 361 public void onAnimationEnd(Animator animation) { 362 dispatchOnLauncherTransitionEnd(fromView, animated, false); 363 dispatchOnLauncherTransitionEnd(toView, animated, false); 364 365 // Hide the reveal view 366 revealView.setVisibility(View.INVISIBLE); 367 368 // Disable all necessary layers 369 for (View v : layerViews.keySet()) { 370 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 371 v.setLayerType(View.LAYER_TYPE_NONE, null); 372 } 373 } 374 375 // This can hold unnecessary references to views. 376 cleanupAnimation(); 377 pCb.onTransitionComplete(); 378 } 379 380 }); 381 382 // Dispatch the prepare transition signal 383 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 384 dispatchOnLauncherTransitionPrepare(toView, animated, false); 385 386 final AnimatorSet stateAnimation = animation; 387 final Runnable startAnimRunnable = new Runnable() { 388 public void run() { 389 // Check that mCurrentAnimation hasn't changed while 390 // we waited for a layout/draw pass 391 if (mCurrentAnimation != stateAnimation) 392 return; 393 dispatchOnLauncherTransitionStart(fromView, animated, false); 394 dispatchOnLauncherTransitionStart(toView, animated, false); 395 396 // Enable all necessary layers 397 for (View v : layerViews.keySet()) { 398 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 399 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 400 } 401 if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { 402 v.buildLayer(); 403 } 404 } 405 406 // Focus the new view 407 toView.requestFocus(); 408 409 stateAnimation.start(); 410 } 411 }; 412 toView.bringToFront(); 413 toView.setVisibility(View.VISIBLE); 414 toView.post(startAnimRunnable); 415 mCurrentAnimation = animation; 416 } else if (animType == PULLUP) { 417 // We are animating the content view alpha, so ensure we have a layer for it 418 layerViews.put(contentView, BUILD_AND_SET_LAYER); 419 420 animation.addListener(new AnimatorListenerAdapter() { 421 @Override 422 public void onAnimationEnd(Animator animation) { 423 dispatchOnLauncherTransitionEnd(fromView, animated, false); 424 dispatchOnLauncherTransitionEnd(toView, animated, false); 425 426 // Disable all necessary layers 427 for (View v : layerViews.keySet()) { 428 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 429 v.setLayerType(View.LAYER_TYPE_NONE, null); 430 } 431 } 432 433 cleanupAnimation(); 434 pCb.onTransitionComplete(); 435 } 436 }); 437 boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide); 438 439 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 440 dispatchOnLauncherTransitionPrepare(toView, animated, false); 441 442 final AnimatorSet stateAnimation = animation; 443 final Runnable startAnimRunnable = new Runnable() { 444 public void run() { 445 // Check that mCurrentAnimation hasn't changed while 446 // we waited for a layout/draw pass 447 if (mCurrentAnimation != stateAnimation) 448 return; 449 450 dispatchOnLauncherTransitionStart(fromView, animated, false); 451 dispatchOnLauncherTransitionStart(toView, animated, false); 452 453 // Enable all necessary layers 454 for (View v : layerViews.keySet()) { 455 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 456 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 457 } 458 if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { 459 v.buildLayer(); 460 } 461 } 462 463 toView.requestFocus(); 464 stateAnimation.start(); 465 } 466 }; 467 mCurrentAnimation = animation; 468 if (shouldPost) { 469 toView.post(startAnimRunnable); 470 } else { 471 startAnimRunnable.run(); 472 } 473 } 474 } 475 476 /** 477 * Plays animations used by various transitions. 478 */ 479 private void playCommonTransitionAnimations( 480 Workspace.State toWorkspaceState, View fromView, View toView, 481 boolean animated, boolean initialized, AnimatorSet animation, 482 HashMap<View, Integer> layerViews) { 483 // Create the workspace animation. 484 // NOTE: this call apparently also sets the state for the workspace if !animated 485 Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, 486 animated, layerViews); 487 488 if (animated && initialized) { 489 // Play the workspace animation 490 if (workspaceAnim != null) { 491 animation.play(workspaceAnim); 492 } 493 // Dispatch onLauncherTransitionStep() as the animation interpolates. 494 animation.play(dispatchOnLauncherTransitionStepAnim(fromView, toView)); 495 } 496 } 497 498 /** 499 * Returns an Animator that calls {@link #dispatchOnLauncherTransitionStep(View, float)} on 500 * {@param fromView} and {@param toView} as the animation interpolates. 501 * 502 * This is a bit hacky: we create a dummy ValueAnimator just for the AnimatorUpdateListener. 503 */ 504 private Animator dispatchOnLauncherTransitionStepAnim(final View fromView, final View toView) { 505 ValueAnimator updateAnimator = ValueAnimator.ofFloat(0, 1); 506 updateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 507 @Override 508 public void onAnimationUpdate(ValueAnimator animation) { 509 dispatchOnLauncherTransitionStep(fromView, animation.getAnimatedFraction()); 510 dispatchOnLauncherTransitionStep(toView, animation.getAnimatedFraction()); 511 } 512 }); 513 return updateAnimator; 514 } 515 516 /** 517 * Starts an animation to the workspace from the apps view. 518 */ 519 private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState, 520 final Workspace.State toWorkspaceState, final boolean animated, int type, 521 final Runnable onCompleteRunnable) { 522 AllAppsContainerView appsView = mLauncher.getAppsView(); 523 // No alpha anim from all apps 524 PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) { 525 @Override 526 float getMaterialRevealViewStartFinalRadius() { 527 int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize; 528 return allAppsButtonSize / 2; 529 } 530 @Override 531 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 532 final View revealView, final View allAppsButtonView) { 533 return new AnimatorListenerAdapter() { 534 public void onAnimationStart(Animator animation) { 535 // We set the alpha instead of visibility to ensure that the focus does not 536 // get taken from the all apps view 537 allAppsButtonView.setVisibility(View.VISIBLE); 538 allAppsButtonView.setAlpha(0f); 539 } 540 public void onAnimationEnd(Animator animation) { 541 // Hide the reveal view 542 revealView.setVisibility(View.INVISIBLE); 543 544 // Show the all apps button, and focus it 545 allAppsButtonView.setAlpha(1f); 546 } 547 }; 548 } 549 @Override 550 void onTransitionComplete() { 551 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 552 } 553 }; 554 // Only animate the search bar if animating to spring loaded mode from all apps 555 startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState, 556 mLauncher.getStartViewForAllAppsRevealAnimation(), appsView, 557 animated, type, onCompleteRunnable, cb); 558 } 559 560 /** 561 * Starts an animation to the workspace from the widgets view. 562 */ 563 private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState, 564 final Workspace.State toWorkspaceState, final boolean animated, 565 final Runnable onCompleteRunnable) { 566 final WidgetsContainerView widgetsView = mLauncher.getWidgetsView(); 567 PrivateTransitionCallbacks cb = 568 new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) { 569 @Override 570 public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener( 571 final View revealView, final View widgetsButtonView) { 572 return new AnimatorListenerAdapter() { 573 public void onAnimationEnd(Animator animation) { 574 // Hide the reveal view 575 revealView.setVisibility(View.INVISIBLE); 576 } 577 }; 578 } 579 @Override 580 void onTransitionComplete() { 581 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 582 } 583 }; 584 startAnimationToWorkspaceFromOverlay( 585 fromWorkspaceState, toWorkspaceState, 586 mLauncher.getWidgetsButton(), widgetsView, 587 animated, CIRCULAR_REVEAL, onCompleteRunnable, cb); 588 } 589 590 /** 591 * Starts an animation to the workspace from another workspace state, e.g. normal to overview. 592 */ 593 private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState, 594 final Workspace.State toWorkspaceState, final boolean animated, 595 final Runnable onCompleteRunnable) { 596 final View fromWorkspace = mLauncher.getWorkspace(); 597 final HashMap<View, Integer> layerViews = new HashMap<>(); 598 final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); 599 final int revealDuration = mLauncher.getResources() 600 .getInteger(R.integer.config_overlayRevealTime); 601 602 // Cancel the current animation 603 cancelAnimation(); 604 605 boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages; 606 607 playCommonTransitionAnimations(toWorkspaceState, fromWorkspace, null, 608 animated, animated, animation, layerViews); 609 610 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); 611 612 if (animated) { 613 dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible); 614 615 final AnimatorSet stateAnimation = animation; 616 final Runnable startAnimRunnable = new Runnable() { 617 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 618 public void run() { 619 // Check that mCurrentAnimation hasn't changed while 620 // we waited for a layout/draw pass 621 if (mCurrentAnimation != stateAnimation) 622 return; 623 624 dispatchOnLauncherTransitionStart(fromWorkspace, animated, true); 625 626 // Enable all necessary layers 627 for (View v : layerViews.keySet()) { 628 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 629 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 630 } 631 if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { 632 v.buildLayer(); 633 } 634 } 635 stateAnimation.start(); 636 } 637 }; 638 animation.addListener(new AnimatorListenerAdapter() { 639 @Override 640 public void onAnimationEnd(Animator animation) { 641 dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true); 642 643 // Run any queued runnables 644 if (onCompleteRunnable != null) { 645 onCompleteRunnable.run(); 646 } 647 648 // Disable all necessary layers 649 for (View v : layerViews.keySet()) { 650 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 651 v.setLayerType(View.LAYER_TYPE_NONE, null); 652 } 653 } 654 655 // This can hold unnecessary references to views. 656 cleanupAnimation(); 657 } 658 }); 659 fromWorkspace.post(startAnimRunnable); 660 mCurrentAnimation = animation; 661 } else /* if (!animated) */ { 662 dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible); 663 dispatchOnLauncherTransitionStart(fromWorkspace, animated, true); 664 dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true); 665 666 // Run any queued runnables 667 if (onCompleteRunnable != null) { 668 onCompleteRunnable.run(); 669 } 670 671 mCurrentAnimation = null; 672 } 673 } 674 675 /** 676 * Creates and starts a new animation to the workspace. 677 */ 678 private void startAnimationToWorkspaceFromOverlay( 679 final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, 680 final View buttonView, final BaseContainerView fromView, 681 final boolean animated, int animType, final Runnable onCompleteRunnable, 682 final PrivateTransitionCallbacks pCb) { 683 final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); 684 final Resources res = mLauncher.getResources(); 685 final boolean material = Utilities.ATLEAST_LOLLIPOP; 686 final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime); 687 final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime); 688 final int itemsAlphaStagger = 689 res.getInteger(R.integer.config_overlayItemsAlphaStagger); 690 691 final View toView = mLauncher.getWorkspace(); 692 final View revealView = fromView.getRevealView(); 693 final View contentView = fromView.getContentView(); 694 695 final HashMap<View, Integer> layerViews = new HashMap<>(); 696 697 // If for some reason our views aren't initialized, don't animate 698 boolean initialized = buttonView != null; 699 700 // Cancel the current animation 701 cancelAnimation(); 702 703 boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages; 704 705 playCommonTransitionAnimations(toWorkspaceState, fromView, toView, 706 animated, initialized, animation, layerViews); 707 if (!animated || !initialized) { 708 if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && 709 fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) { 710 mAllAppsController.finishPullDown(); 711 } 712 fromView.setVisibility(View.GONE); 713 dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible); 714 dispatchOnLauncherTransitionStart(fromView, animated, true); 715 dispatchOnLauncherTransitionEnd(fromView, animated, true); 716 dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible); 717 dispatchOnLauncherTransitionStart(toView, animated, true); 718 dispatchOnLauncherTransitionEnd(toView, animated, true); 719 pCb.onTransitionComplete(); 720 721 // Run any queued runnables 722 if (onCompleteRunnable != null) { 723 onCompleteRunnable.run(); 724 } 725 return; 726 } 727 if (animType == CIRCULAR_REVEAL) { 728 // hideAppsCustomizeHelper is called in some cases when it is already hidden 729 // don't perform all these no-op animations. In particularly, this was causing 730 // the all-apps button to pop in and out. 731 if (fromView.getVisibility() == View.VISIBLE) { 732 int width = revealView.getMeasuredWidth(); 733 int height = revealView.getMeasuredHeight(); 734 float revealRadius = (float) Math.hypot(width / 2, height / 2); 735 revealView.setVisibility(View.VISIBLE); 736 revealView.setAlpha(1f); 737 revealView.setTranslationY(0); 738 layerViews.put(revealView, BUILD_AND_SET_LAYER); 739 740 // Calculate the final animation values 741 final float revealViewToXDrift; 742 final float revealViewToYDrift; 743 if (material) { 744 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, 745 buttonView, null); 746 revealViewToYDrift = buttonViewToPanelDelta[1]; 747 revealViewToXDrift = buttonViewToPanelDelta[0]; 748 } else { 749 revealViewToYDrift = 2 * height / 3; 750 revealViewToXDrift = 0; 751 } 752 753 // The vertical motion of the apps panel should be delayed by one frame 754 // from the conceal animation in order to give the right feel. We correspondingly 755 // shorten the duration so that the slide and conceal end at the same time. 756 TimeInterpolator decelerateInterpolator = material ? 757 new LogDecelerateInterpolator(100, 0) : 758 new DecelerateInterpolator(1f); 759 ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY", 760 0, revealViewToYDrift); 761 panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY); 762 panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 763 panelDriftY.setInterpolator(decelerateInterpolator); 764 animation.play(panelDriftY); 765 766 ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX", 767 0, revealViewToXDrift); 768 panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY); 769 panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 770 panelDriftX.setInterpolator(decelerateInterpolator); 771 animation.play(panelDriftX); 772 773 // Setup animation for the reveal panel alpha 774 final float revealViewToAlpha = !material ? 0f : 775 pCb.materialRevealViewFinalAlpha; 776 if (revealViewToAlpha != 1f) { 777 ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha", 778 1f, revealViewToAlpha); 779 panelAlpha.setDuration(material ? revealDuration : 150); 780 panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY); 781 panelAlpha.setInterpolator(decelerateInterpolator); 782 animation.play(panelAlpha); 783 } 784 785 // Setup the animation for the content view 786 layerViews.put(contentView, BUILD_AND_SET_LAYER); 787 788 // Create the individual animators 789 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", 790 0, revealViewToYDrift); 791 contentView.setTranslationY(0); 792 pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY); 793 pageDrift.setInterpolator(decelerateInterpolator); 794 pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY); 795 animation.play(pageDrift); 796 797 contentView.setAlpha(1f); 798 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f); 799 itemsAlpha.setDuration(100); 800 itemsAlpha.setInterpolator(decelerateInterpolator); 801 animation.play(itemsAlpha); 802 803 // Invalidate the scrim throughout the animation to ensure the highlight 804 // cutout is correct throughout. 805 ValueAnimator invalidateScrim = ValueAnimator.ofFloat(0f, 1f); 806 invalidateScrim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 807 @Override 808 public void onAnimationUpdate(ValueAnimator animation) { 809 mLauncher.getDragLayer().invalidateScrim(); 810 } 811 }); 812 animation.play(invalidateScrim); 813 814 if (material) { 815 // Animate the all apps button 816 float finalRadius = pCb.getMaterialRevealViewStartFinalRadius(); 817 AnimatorListenerAdapter listener = 818 pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView); 819 Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2, 820 revealRadius, finalRadius).createRevealAnimator(revealView); 821 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 822 reveal.setDuration(revealDuration); 823 reveal.setStartDelay(itemsAlphaStagger); 824 if (listener != null) { 825 reveal.addListener(listener); 826 } 827 animation.play(reveal); 828 } 829 } 830 831 dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible); 832 dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible); 833 834 animation.addListener(new AnimatorListenerAdapter() { 835 @Override 836 public void onAnimationEnd(Animator animation) { 837 fromView.setVisibility(View.GONE); 838 dispatchOnLauncherTransitionEnd(fromView, animated, true); 839 dispatchOnLauncherTransitionEnd(toView, animated, true); 840 841 // Run any queued runnables 842 if (onCompleteRunnable != null) { 843 onCompleteRunnable.run(); 844 } 845 846 // Disable all necessary layers 847 for (View v : layerViews.keySet()) { 848 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 849 v.setLayerType(View.LAYER_TYPE_NONE, null); 850 } 851 } 852 853 // Reset page transforms 854 if (contentView != null) { 855 contentView.setTranslationX(0); 856 contentView.setTranslationY(0); 857 contentView.setAlpha(1); 858 } 859 860 // This can hold unnecessary references to views. 861 cleanupAnimation(); 862 pCb.onTransitionComplete(); 863 } 864 }); 865 866 final AnimatorSet stateAnimation = animation; 867 final Runnable startAnimRunnable = new Runnable() { 868 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 869 public void run() { 870 // Check that mCurrentAnimation hasn't changed while 871 // we waited for a layout/draw pass 872 if (mCurrentAnimation != stateAnimation) 873 return; 874 875 dispatchOnLauncherTransitionStart(fromView, animated, false); 876 dispatchOnLauncherTransitionStart(toView, animated, false); 877 878 // Enable all necessary layers 879 for (View v : layerViews.keySet()) { 880 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 881 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 882 } 883 if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { 884 v.buildLayer(); 885 } 886 } 887 stateAnimation.start(); 888 } 889 }; 890 mCurrentAnimation = animation; 891 fromView.post(startAnimRunnable); 892 } else if (animType == PULLUP) { 893 // We are animating the content view alpha, so ensure we have a layer for it 894 layerViews.put(contentView, BUILD_AND_SET_LAYER); 895 896 animation.addListener(new AnimatorListenerAdapter() { 897 boolean canceled = false; 898 @Override 899 public void onAnimationCancel(Animator animation) { 900 canceled = true; 901 } 902 903 @Override 904 public void onAnimationEnd(Animator animation) { 905 if (canceled) return; 906 dispatchOnLauncherTransitionEnd(fromView, animated, true); 907 dispatchOnLauncherTransitionEnd(toView, animated, true); 908 // Run any queued runnables 909 if (onCompleteRunnable != null) { 910 onCompleteRunnable.run(); 911 } 912 913 // Disable all necessary layers 914 for (View v : layerViews.keySet()) { 915 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 916 v.setLayerType(View.LAYER_TYPE_NONE, null); 917 } 918 } 919 920 cleanupAnimation(); 921 pCb.onTransitionComplete(); 922 } 923 924 }); 925 boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide); 926 927 // Dispatch the prepare transition signal 928 dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible); 929 dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible); 930 931 final AnimatorSet stateAnimation = animation; 932 final Runnable startAnimRunnable = new Runnable() { 933 public void run() { 934 // Check that mCurrentAnimation hasn't changed while 935 // we waited for a layout/draw pass 936 if (mCurrentAnimation != stateAnimation) 937 return; 938 939 dispatchOnLauncherTransitionStart(fromView, animated, false); 940 dispatchOnLauncherTransitionStart(toView, animated, false); 941 942 // Enable all necessary layers 943 for (View v : layerViews.keySet()) { 944 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 945 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 946 } 947 if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { 948 v.buildLayer(); 949 } 950 } 951 952 // Focus the new view 953 toView.requestFocus(); 954 stateAnimation.start(); 955 } 956 }; 957 mCurrentAnimation = animation; 958 if (shouldPost) { 959 fromView.post(startAnimRunnable); 960 } else { 961 startAnimRunnable.run(); 962 } 963 } 964 return; 965 } 966 967 /** 968 * Dispatches the prepare-transition event to suitable views. 969 */ 970 void dispatchOnLauncherTransitionPrepare(View v, boolean animated, 971 boolean multiplePagesVisible) { 972 if (v instanceof LauncherTransitionable) { 973 ((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated, 974 multiplePagesVisible); 975 } 976 } 977 978 /** 979 * Dispatches the start-transition event to suitable views. 980 */ 981 void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) { 982 if (v instanceof LauncherTransitionable) { 983 ((LauncherTransitionable) v).onLauncherTransitionStart(mLauncher, animated, 984 toWorkspace); 985 } 986 987 // Update the workspace transition step as well 988 dispatchOnLauncherTransitionStep(v, 0f); 989 } 990 991 /** 992 * Dispatches the step-transition event to suitable views. 993 */ 994 void dispatchOnLauncherTransitionStep(View v, float t) { 995 if (v instanceof LauncherTransitionable) { 996 ((LauncherTransitionable) v).onLauncherTransitionStep(mLauncher, t); 997 } 998 } 999 1000 /** 1001 * Dispatches the end-transition event to suitable views. 1002 */ 1003 void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) { 1004 if (v instanceof LauncherTransitionable) { 1005 ((LauncherTransitionable) v).onLauncherTransitionEnd(mLauncher, animated, 1006 toWorkspace); 1007 } 1008 1009 // Update the workspace transition step as well 1010 dispatchOnLauncherTransitionStep(v, 1f); 1011 } 1012 1013 /** 1014 * Cancels the current animation. 1015 */ 1016 private void cancelAnimation() { 1017 if (mCurrentAnimation != null) { 1018 mCurrentAnimation.setDuration(0); 1019 mCurrentAnimation.cancel(); 1020 mCurrentAnimation = null; 1021 } 1022 } 1023 1024 @Thunk void cleanupAnimation() { 1025 mCurrentAnimation = null; 1026 } 1027} 1028