1 /* 2 * Copyright (C) 2019 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 package com.android.quickstep.util; 17 18 import static com.android.app.animation.Interpolators.LINEAR; 19 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; 20 import static com.android.launcher3.LauncherState.BACKGROUND_APP; 21 import static com.android.launcher3.LauncherState.NORMAL; 22 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback; 23 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER; 24 import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER; 25 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW; 26 import static com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM; 27 28 import android.animation.Animator; 29 import android.animation.AnimatorListenerAdapter; 30 import android.animation.AnimatorSet; 31 import android.animation.ObjectAnimator; 32 import android.animation.ValueAnimator; 33 import android.view.View; 34 import android.view.ViewGroup; 35 36 import androidx.annotation.Nullable; 37 38 import com.android.launcher3.CellLayout; 39 import com.android.launcher3.DeviceProfile; 40 import com.android.launcher3.Hotseat; 41 import com.android.launcher3.LauncherState; 42 import com.android.launcher3.QuickstepTransitionManager; 43 import com.android.launcher3.R; 44 import com.android.launcher3.ShortcutAndWidgetContainer; 45 import com.android.launcher3.Workspace; 46 import com.android.launcher3.anim.PendingAnimation; 47 import com.android.launcher3.anim.SpringAnimationBuilder; 48 import com.android.launcher3.celllayout.CellLayoutLayoutParams; 49 import com.android.launcher3.statehandlers.DepthController; 50 import com.android.launcher3.states.StateAnimationConfig; 51 import com.android.launcher3.uioverrides.QuickstepLauncher; 52 import com.android.launcher3.util.DynamicResource; 53 import com.android.quickstep.views.RecentsView; 54 import com.android.systemui.plugins.ResourceProvider; 55 56 /** 57 * Creates an animation where all the workspace items are moved into their final location, 58 * staggered row by row from the bottom up. 59 * This is used in conjunction with the swipe up to home animation. 60 */ 61 public class StaggeredWorkspaceAnim { 62 63 private static final int APP_CLOSE_ROW_START_DELAY_MS = 10; 64 // Should be used for animations running alongside this StaggeredWorkspaceAnim. 65 public static final int DURATION_MS = 250; 66 public static final int DURATION_TASKBAR_MS = 67 QuickstepTransitionManager.getTaskbarToHomeDuration(); 68 69 private static final float MAX_VELOCITY_PX_PER_S = 22f; 70 71 private final float mVelocity; 72 private final float mSpringTransY; 73 74 private final AnimatorSet mAnimators = new AnimatorSet(); 75 private final @Nullable View mIgnoredView; 76 StaggeredWorkspaceAnim(QuickstepLauncher launcher, float velocity, boolean animateOverviewScrim, @Nullable View ignoredView)77 public StaggeredWorkspaceAnim(QuickstepLauncher launcher, float velocity, 78 boolean animateOverviewScrim, @Nullable View ignoredView) { 79 this(launcher, velocity, animateOverviewScrim, ignoredView, true); 80 } 81 StaggeredWorkspaceAnim(QuickstepLauncher launcher, float velocity, boolean animateOverviewScrim, @Nullable View ignoredView, boolean staggerWorkspace)82 public StaggeredWorkspaceAnim(QuickstepLauncher launcher, float velocity, 83 boolean animateOverviewScrim, @Nullable View ignoredView, boolean staggerWorkspace) { 84 prepareToAnimate(launcher, animateOverviewScrim); 85 86 mIgnoredView = ignoredView; 87 mVelocity = velocity; 88 89 // Scale the translationY based on the initial velocity to better sync the workspace items 90 // with the floating view. 91 float transFactor = 0.2f + 0.9f * Math.abs(velocity) / MAX_VELOCITY_PX_PER_S; 92 mSpringTransY = transFactor * launcher.getResources() 93 .getDimensionPixelSize(R.dimen.swipe_up_max_workspace_trans_y); 94 95 DeviceProfile grid = launcher.getDeviceProfile(); 96 long duration = grid.isTaskbarPresent ? DURATION_TASKBAR_MS : DURATION_MS; 97 if (staggerWorkspace) { 98 Workspace<?> workspace = launcher.getWorkspace(); 99 Hotseat hotseat = launcher.getHotseat(); 100 101 boolean staggerHotseat = !grid.isVerticalBarLayout() && !grid.isTaskbarPresent; 102 boolean staggerQsb = 103 !grid.isVerticalBarLayout() && !(grid.isTaskbarPresent && grid.isQsbInline); 104 int totalRows = grid.inv.numRows + (staggerHotseat ? 1 : 0) + (staggerQsb ? 1 : 0); 105 106 // Add animation for all the visible workspace pages 107 workspace.forEachVisiblePage( 108 page -> addAnimationForPage((CellLayout) page, totalRows, duration)); 109 110 boolean workspaceClipChildren = workspace.getClipChildren(); 111 boolean workspaceClipToPadding = workspace.getClipToPadding(); 112 boolean hotseatClipChildren = hotseat.getClipChildren(); 113 boolean hotseatClipToPadding = hotseat.getClipToPadding(); 114 115 workspace.setClipChildren(false); 116 workspace.setClipToPadding(false); 117 hotseat.setClipChildren(false); 118 hotseat.setClipToPadding(false); 119 120 // Set up springs for the hotseat and qsb. 121 ViewGroup hotseatIcons = hotseat.getShortcutsAndWidgets(); 122 if (grid.isVerticalBarLayout()) { 123 for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) { 124 View child = hotseatIcons.getChildAt(i); 125 CellLayoutLayoutParams lp = ((CellLayoutLayoutParams) child.getLayoutParams()); 126 addStaggeredAnimationForView(child, lp.getCellY() + 1, 127 totalRows, duration); 128 } 129 } else { 130 final int hotseatRow, qsbRow; 131 if (grid.isTaskbarPresent) { 132 if (grid.isQsbInline) { 133 qsbRow = grid.inv.numRows + 1; 134 hotseatRow = grid.inv.numRows + 1; 135 } else { 136 qsbRow = grid.inv.numRows + 1; 137 hotseatRow = grid.inv.numRows + 2; 138 } 139 } else { 140 hotseatRow = grid.inv.numRows + 1; 141 qsbRow = grid.inv.numRows + 2; 142 } 143 144 // Do not stagger hotseat as a whole when taskbar is present, and stagger QSB only 145 // if it's not inline. 146 if (staggerHotseat) { 147 for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) { 148 View child = hotseatIcons.getChildAt(i); 149 addStaggeredAnimationForView(child, hotseatRow, totalRows, duration); 150 } 151 } 152 if (staggerQsb) { 153 addStaggeredAnimationForView(hotseat.getQsb(), qsbRow, totalRows, duration); 154 } 155 } 156 157 mAnimators.addListener(new AnimatorListenerAdapter() { 158 @Override 159 public void onAnimationEnd(Animator animation) { 160 workspace.setClipChildren(workspaceClipChildren); 161 workspace.setClipToPadding(workspaceClipToPadding); 162 hotseat.setClipChildren(hotseatClipChildren); 163 hotseat.setClipToPadding(hotseatClipToPadding); 164 } 165 }); 166 } 167 168 launcher.pauseExpensiveViewUpdates(); 169 mAnimators.addListener(forEndCallback(launcher::resumeExpensiveViewUpdates)); 170 171 if (animateOverviewScrim) { 172 PendingAnimation pendingAnimation = new PendingAnimation(duration); 173 launcher.getWorkspace().getStateTransitionAnimation() 174 .setScrim(pendingAnimation, NORMAL, new StateAnimationConfig()); 175 mAnimators.play(pendingAnimation.buildAnim()); 176 } 177 178 addDepthAnimationForState(launcher, NORMAL, duration); 179 180 mAnimators.play(launcher.getRootView().getSysUiScrim().getSysUIMultiplier() 181 .animateToValue(0f, 1f).setDuration(duration)); 182 } 183 addAnimationForPage(CellLayout page, int totalRows, long duration)184 private void addAnimationForPage(CellLayout page, int totalRows, long duration) { 185 ShortcutAndWidgetContainer itemsContainer = page.getShortcutsAndWidgets(); 186 187 boolean pageClipChildren = page.getClipChildren(); 188 boolean pageClipToPadding = page.getClipToPadding(); 189 190 page.setClipChildren(false); 191 page.setClipToPadding(false); 192 193 // Set up springs on workspace items. 194 for (int i = itemsContainer.getChildCount() - 1; i >= 0; i--) { 195 View child = itemsContainer.getChildAt(i); 196 CellLayoutLayoutParams lp = ((CellLayoutLayoutParams) child.getLayoutParams()); 197 addStaggeredAnimationForView(child, lp.getCellY() + lp.cellVSpan, 198 totalRows, duration); 199 } 200 201 mAnimators.addListener(new AnimatorListenerAdapter() { 202 @Override 203 public void onAnimationEnd(Animator animation) { 204 page.setClipChildren(pageClipChildren); 205 page.setClipToPadding(pageClipToPadding); 206 } 207 }); 208 } 209 210 /** 211 * Setup workspace with 0 duration to prepare for our staggered animation. 212 */ prepareToAnimate(QuickstepLauncher launcher, boolean animateOverviewScrim)213 private void prepareToAnimate(QuickstepLauncher launcher, boolean animateOverviewScrim) { 214 StateAnimationConfig config = new StateAnimationConfig(); 215 config.animFlags = SKIP_OVERVIEW | SKIP_DEPTH_CONTROLLER | SKIP_SCRIM; 216 config.duration = 0; 217 // setRecentsAttachedToAppWindow() will animate recents out. 218 launcher.getStateManager().createAtomicAnimation(BACKGROUND_APP, NORMAL, config).start(); 219 220 // Stop scrolling so that it doesn't interfere with the translation offscreen. 221 launcher.<RecentsView>getOverviewPanel().forceFinishScroller(); 222 223 if (animateOverviewScrim) { 224 launcher.getWorkspace().getStateTransitionAnimation() 225 .setScrim(NO_ANIM_PROPERTY_SETTER, BACKGROUND_APP, config); 226 } 227 } 228 getAnimators()229 public AnimatorSet getAnimators() { 230 return mAnimators; 231 } 232 addAnimatorListener(Animator.AnimatorListener listener)233 public StaggeredWorkspaceAnim addAnimatorListener(Animator.AnimatorListener listener) { 234 mAnimators.addListener(listener); 235 return this; 236 } 237 238 /** 239 * Starts the animation. 240 */ start()241 public void start() { 242 mAnimators.start(); 243 } 244 245 /** 246 * Adds an alpha/trans animator for {@param v}, with a start delay based on the view's row. 247 * 248 * @param v A view on the workspace. 249 * @param row The bottom-most row that contains the view. 250 * @param totalRows Total number of rows. 251 * @param duration duration of the animation 252 */ addStaggeredAnimationForView(View v, int row, int totalRows, long duration)253 private void addStaggeredAnimationForView(View v, int row, int totalRows, long duration) { 254 if (mIgnoredView != null && mIgnoredView == v) return; 255 // Invert the rows, because we stagger starting from the bottom of the screen. 256 int invertedRow = totalRows - row; 257 // Add 1 to the inverted row so that the bottom most row has a start delay. 258 long startDelay = (long) ((invertedRow + 1) * APP_CLOSE_ROW_START_DELAY_MS); 259 260 v.setTranslationY(mSpringTransY); 261 262 ResourceProvider rp = DynamicResource.provider(v.getContext()); 263 float stiffness = rp.getFloat(R.dimen.staggered_stiffness); 264 float damping = rp.getFloat(R.dimen.staggered_damping_ratio); 265 float endTransY = 0; 266 float springVelocity = Math.abs(mVelocity) * Math.signum(endTransY - mSpringTransY); 267 ValueAnimator springTransY = new SpringAnimationBuilder(v.getContext()) 268 .setStiffness(stiffness) 269 .setDampingRatio(damping) 270 .setMinimumVisibleChange(1f) 271 .setStartValue(mSpringTransY) 272 .setEndValue(endTransY) 273 .setStartVelocity(springVelocity) 274 .build(v, VIEW_TRANSLATE_Y); 275 springTransY.setStartDelay(startDelay); 276 springTransY.addListener(new AnimatorListenerAdapter() { 277 @Override 278 public void onAnimationEnd(Animator animation) { 279 v.setTranslationY(0f); 280 } 281 }); 282 mAnimators.play(springTransY); 283 284 v.setAlpha(0); 285 ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 0f, 1f); 286 alpha.setInterpolator(LINEAR); 287 alpha.setDuration(duration); 288 alpha.setStartDelay(startDelay); 289 alpha.addListener(new AnimatorListenerAdapter() { 290 @Override 291 public void onAnimationEnd(Animator animation) { 292 v.setAlpha(1f); 293 } 294 }); 295 mAnimators.play(alpha); 296 } 297 addDepthAnimationForState(QuickstepLauncher launcher, LauncherState state, long duration)298 private void addDepthAnimationForState(QuickstepLauncher launcher, LauncherState state, 299 long duration) { 300 PendingAnimation builder = new PendingAnimation(duration); 301 DepthController depthController = launcher.getDepthController(); 302 depthController.setStateWithAnimation(state, new StateAnimationConfig(), builder); 303 mAnimators.play(builder.buildAnim()); 304 } 305 } 306