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 17 package com.android.launcher3; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.AnimatorSet; 22 import android.animation.ObjectAnimator; 23 import android.animation.PropertyValuesHolder; 24 import android.animation.TimeInterpolator; 25 import android.annotation.SuppressLint; 26 import android.content.res.Resources; 27 import android.util.Log; 28 import android.view.View; 29 import android.view.animation.AccelerateInterpolator; 30 import android.view.animation.DecelerateInterpolator; 31 32 import com.android.launcher3.allapps.AllAppsContainerView; 33 import com.android.launcher3.util.UiThreadCircularReveal; 34 import com.android.launcher3.util.Thunk; 35 import com.android.launcher3.widget.WidgetsContainerView; 36 37 import 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 */ 80 public class LauncherStateTransitionAnimation { 81 82 /** 83 * Private callbacks made during transition setup. 84 */ 85 static abstract class PrivateTransitionCallbacks { getMaterialRevealViewFinalAlpha(View revealView)86 float getMaterialRevealViewFinalAlpha(View revealView) { 87 return 0; 88 } getMaterialRevealViewStartFinalRadius()89 float getMaterialRevealViewStartFinalRadius() { 90 return 0; 91 } getMaterialRevealViewAnimatorListener(View revealView, View buttonView)92 AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView, 93 View buttonView) { 94 return null; 95 } onTransitionComplete()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 LauncherStateTransitionAnimation(Launcher l)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 */ startAnimationToAllApps(final Workspace.State fromWorkspaceState, final boolean animated, final boolean startSearchAfterTransition)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 */ startAnimationToWidgets(final Workspace.State fromWorkspaceState, final boolean animated)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 166 PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() { 167 @Override 168 public float getMaterialRevealViewFinalAlpha(View revealView) { 169 return 0.3f; 170 } 171 }; 172 mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState, 173 Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, toView.getContentView(), 174 toView.getRevealView(), null, animated, cb); 175 } 176 177 /** 178 * Starts and animation to the workspace from the current overlay view. 179 */ startAnimationToWorkspace(final Launcher.State fromState, final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable)180 public void startAnimationToWorkspace(final Launcher.State fromState, 181 final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, 182 final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) { 183 if (toWorkspaceState != Workspace.State.NORMAL && 184 toWorkspaceState != Workspace.State.SPRING_LOADED && 185 toWorkspaceState != Workspace.State.OVERVIEW) { 186 Log.e(TAG, "Unexpected call to startAnimationToWorkspace"); 187 } 188 189 if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) { 190 startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState, toWorkspacePage, 191 animated, onCompleteRunnable); 192 } else { 193 startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState, toWorkspacePage, 194 animated, onCompleteRunnable); 195 } 196 } 197 198 /** 199 * Creates and starts a new animation to a particular overlay view. 200 */ 201 @SuppressLint("NewApi") startAnimationToOverlay(final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, final View buttonView, final View toView, final View contentView, final View revealView, final View overlaySearchBarView, final boolean animated, final PrivateTransitionCallbacks pCb)202 private AnimatorSet startAnimationToOverlay(final Workspace.State fromWorkspaceState, 203 final Workspace.State toWorkspaceState, final View buttonView, final View toView, 204 final View contentView, final View revealView, final View overlaySearchBarView, 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 = 211 res.getInteger(R.integer.config_overlayItemsAlphaStagger); 212 213 final View fromView = mLauncher.getWorkspace(); 214 215 final HashMap<View, Integer> layerViews = new HashMap<>(); 216 217 // If for some reason our views aren't initialized, don't animate 218 boolean initialized = buttonView != null; 219 220 // Cancel the current animation 221 cancelAnimation(); 222 223 // Create the workspace animation. 224 // NOTE: this call apparently also sets the state for the workspace if !animated 225 Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, -1, 226 animated, layerViews); 227 228 // Animate the search bar 229 startWorkspaceSearchBarAnimation(animation, fromWorkspaceState, toWorkspaceState, 230 animated ? revealDuration : 0, overlaySearchBarView); 231 232 if (animated && initialized) { 233 // Setup the reveal view animation 234 int width = revealView.getMeasuredWidth(); 235 int height = revealView.getMeasuredHeight(); 236 float revealRadius = (float) Math.hypot(width / 2, height / 2); 237 revealView.setVisibility(View.VISIBLE); 238 revealView.setAlpha(0f); 239 revealView.setTranslationY(0f); 240 revealView.setTranslationX(0f); 241 242 // Calculate the final animation values 243 final float revealViewToAlpha; 244 final float revealViewToXDrift; 245 final float revealViewToYDrift; 246 if (material) { 247 int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, 248 buttonView, null); 249 revealViewToAlpha = pCb.getMaterialRevealViewFinalAlpha(revealView); 250 revealViewToYDrift = buttonViewToPanelDelta[1]; 251 revealViewToXDrift = buttonViewToPanelDelta[0]; 252 } else { 253 revealViewToAlpha = 0f; 254 revealViewToYDrift = 2 * height / 3; 255 revealViewToXDrift = 0; 256 } 257 258 // Create the animators 259 PropertyValuesHolder panelAlpha = 260 PropertyValuesHolder.ofFloat("alpha", revealViewToAlpha, 1f); 261 PropertyValuesHolder panelDriftY = 262 PropertyValuesHolder.ofFloat("translationY", revealViewToYDrift, 0); 263 PropertyValuesHolder panelDriftX = 264 PropertyValuesHolder.ofFloat("translationX", revealViewToXDrift, 0); 265 ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView, 266 panelAlpha, panelDriftY, panelDriftX); 267 panelAlphaAndDrift.setDuration(revealDuration); 268 panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 269 270 // Play the animation 271 layerViews.put(revealView, BUILD_AND_SET_LAYER); 272 animation.play(panelAlphaAndDrift); 273 274 if (overlaySearchBarView != null) { 275 overlaySearchBarView.setAlpha(0f); 276 ObjectAnimator searchBarAlpha = ObjectAnimator.ofFloat(overlaySearchBarView, "alpha", 0f, 1f); 277 searchBarAlpha.setDuration(100); 278 searchBarAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); 279 layerViews.put(overlaySearchBarView, BUILD_AND_SET_LAYER); 280 animation.play(searchBarAlpha); 281 } 282 283 // Setup the animation for the content view 284 contentView.setVisibility(View.VISIBLE); 285 contentView.setAlpha(0f); 286 contentView.setTranslationY(revealViewToYDrift); 287 layerViews.put(contentView, BUILD_AND_SET_LAYER); 288 289 // Create the individual animators 290 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", 291 revealViewToYDrift, 0); 292 pageDrift.setDuration(revealDuration); 293 pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); 294 pageDrift.setStartDelay(itemsAlphaStagger); 295 animation.play(pageDrift); 296 297 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f); 298 itemsAlpha.setDuration(revealDuration); 299 itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); 300 itemsAlpha.setStartDelay(itemsAlphaStagger); 301 animation.play(itemsAlpha); 302 303 if (material) { 304 float startRadius = pCb.getMaterialRevealViewStartFinalRadius(); 305 AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener( 306 revealView, buttonView); 307 Animator reveal = UiThreadCircularReveal.createCircularReveal(revealView, width / 2, 308 height / 2, startRadius, revealRadius); 309 reveal.setDuration(revealDuration); 310 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); 311 if (listener != null) { 312 reveal.addListener(listener); 313 } 314 animation.play(reveal); 315 } 316 317 animation.addListener(new AnimatorListenerAdapter() { 318 @Override 319 public void onAnimationEnd(Animator animation) { 320 dispatchOnLauncherTransitionEnd(fromView, animated, false); 321 dispatchOnLauncherTransitionEnd(toView, animated, false); 322 323 // Hide the reveal view 324 revealView.setVisibility(View.INVISIBLE); 325 326 // Disable all necessary layers 327 for (View v : layerViews.keySet()) { 328 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 329 v.setLayerType(View.LAYER_TYPE_NONE, null); 330 } 331 } 332 333 // This can hold unnecessary references to views. 334 cleanupAnimation(); 335 pCb.onTransitionComplete(); 336 } 337 338 }); 339 340 // Play the workspace animation 341 if (workspaceAnim != null) { 342 animation.play(workspaceAnim); 343 } 344 345 // Dispatch the prepare transition signal 346 dispatchOnLauncherTransitionPrepare(fromView, animated, false); 347 dispatchOnLauncherTransitionPrepare(toView, animated, false); 348 349 350 final AnimatorSet stateAnimation = animation; 351 final Runnable startAnimRunnable = new Runnable() { 352 public void run() { 353 // Check that mCurrentAnimation hasn't changed while 354 // we waited for a layout/draw pass 355 if (mCurrentAnimation != stateAnimation) 356 return; 357 dispatchOnLauncherTransitionStart(fromView, animated, false); 358 dispatchOnLauncherTransitionStart(toView, animated, false); 359 360 // Enable all necessary layers 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 (Utilities.ATLEAST_LOLLIPOP && 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 */ startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable)407 private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState, 408 final Workspace.State toWorkspaceState, final int toWorkspacePage, 409 final boolean animated, 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 toWorkspacePage, 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 */ startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable)452 private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState, 453 final Workspace.State toWorkspaceState, final int toWorkspacePage, 454 final boolean animated, 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, toWorkspacePage, 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 */ startAnimationToWorkspaceFromOverlay(final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, final int toWorkspacePage, final View buttonView, final View fromView, final View contentView, final View revealView, final View overlaySearchBarView, final boolean animated, final Runnable onCompleteRunnable, final PrivateTransitionCallbacks pCb)481 private AnimatorSet startAnimationToWorkspaceFromOverlay(final Workspace.State fromWorkspaceState, 482 final Workspace.State toWorkspaceState, final int toWorkspacePage, 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.ATLEAST_LOLLIPOP; 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 toWorkspacePage, 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 for (View v : layerViews.keySet()) { 671 if (layerViews.get(v) == BUILD_AND_SET_LAYER) { 672 v.setLayerType(View.LAYER_TYPE_HARDWARE, null); 673 } 674 if (Utilities.ATLEAST_LOLLIPOP && Utilities.isViewAttachedToWindow(v)) { 675 v.buildLayer(); 676 } 677 } 678 stateAnimation.start(); 679 } 680 }; 681 fromView.post(startAnimRunnable); 682 683 return animation; 684 } else { 685 fromView.setVisibility(View.GONE); 686 dispatchOnLauncherTransitionPrepare(fromView, animated, true); 687 dispatchOnLauncherTransitionStart(fromView, animated, true); 688 dispatchOnLauncherTransitionEnd(fromView, animated, true); 689 dispatchOnLauncherTransitionPrepare(toView, animated, true); 690 dispatchOnLauncherTransitionStart(toView, animated, true); 691 dispatchOnLauncherTransitionEnd(toView, animated, true); 692 pCb.onTransitionComplete(); 693 694 // Run any queued runnables 695 if (onCompleteRunnable != null) { 696 onCompleteRunnable.run(); 697 } 698 699 return null; 700 } 701 } 702 703 /** 704 * Coordinates the workspace search bar animation along with the launcher state animation. 705 */ startWorkspaceSearchBarAnimation(AnimatorSet animation, final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, int duration, View overlaySearchBar)706 private void startWorkspaceSearchBarAnimation(AnimatorSet animation, 707 final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, int duration, 708 View overlaySearchBar) { 709 final SearchDropTargetBar.State toSearchBarState = 710 toWorkspaceState.getSearchDropTargetBarState(); 711 712 if (overlaySearchBar != null) { 713 if ((toWorkspaceState == Workspace.State.NORMAL) && 714 (fromWorkspaceState == Workspace.State.NORMAL_HIDDEN)) { 715 // If we are transitioning from the overlay to the workspace, then show the 716 // workspace search bar immediately and let the overlay search bar fade out on top 717 mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, 0); 718 } else if (fromWorkspaceState == Workspace.State.NORMAL) { 719 // If we are transitioning from the workspace to the overlay, then keep the 720 // workspace search bar visible until the overlay search bar fades in on top 721 animation.addListener(new AnimatorListenerAdapter() { 722 @Override 723 public void onAnimationEnd(Animator animation) { 724 mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, 0); 725 } 726 }); 727 } else { 728 // Otherwise, then just animate the workspace search bar normally 729 mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, duration); 730 } 731 } else { 732 // If there is no overlay search bar, then just animate the workspace search bar 733 mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, duration); 734 } 735 } 736 737 /** 738 * Dispatches the prepare-transition event to suitable views. 739 */ dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace)740 void dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace) { 741 if (v instanceof LauncherTransitionable) { 742 ((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated, 743 toWorkspace); 744 } 745 } 746 747 /** 748 * Dispatches the start-transition event to suitable views. 749 */ dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace)750 void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) { 751 if (v instanceof LauncherTransitionable) { 752 ((LauncherTransitionable) v).onLauncherTransitionStart(mLauncher, animated, 753 toWorkspace); 754 } 755 756 // Update the workspace transition step as well 757 dispatchOnLauncherTransitionStep(v, 0f); 758 } 759 760 /** 761 * Dispatches the step-transition event to suitable views. 762 */ dispatchOnLauncherTransitionStep(View v, float t)763 void dispatchOnLauncherTransitionStep(View v, float t) { 764 if (v instanceof LauncherTransitionable) { 765 ((LauncherTransitionable) v).onLauncherTransitionStep(mLauncher, t); 766 } 767 } 768 769 /** 770 * Dispatches the end-transition event to suitable views. 771 */ dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace)772 void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) { 773 if (v instanceof LauncherTransitionable) { 774 ((LauncherTransitionable) v).onLauncherTransitionEnd(mLauncher, animated, 775 toWorkspace); 776 } 777 778 // Update the workspace transition step as well 779 dispatchOnLauncherTransitionStep(v, 1f); 780 } 781 782 /** 783 * Cancels the current animation. 784 */ cancelAnimation()785 private void cancelAnimation() { 786 if (mCurrentAnimation != null) { 787 mCurrentAnimation.setDuration(0); 788 mCurrentAnimation.cancel(); 789 mCurrentAnimation = null; 790 } 791 } 792 cleanupAnimation()793 @Thunk void cleanupAnimation() { 794 mCurrentAnimation = null; 795 } 796 } 797