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