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