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