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