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