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.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; 19 import static com.android.launcher3.LauncherState.BACKGROUND_APP; 20 import static com.android.launcher3.LauncherState.NORMAL; 21 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; 22 import static com.android.launcher3.anim.Interpolators.LINEAR; 23 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER; 24 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS; 25 import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER; 26 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW; 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 com.android.launcher3.BaseQuickstepLauncher; 37 import com.android.launcher3.CellLayout; 38 import com.android.launcher3.DeviceProfile; 39 import com.android.launcher3.Launcher; 40 import com.android.launcher3.LauncherState; 41 import com.android.launcher3.R; 42 import com.android.launcher3.ShortcutAndWidgetContainer; 43 import com.android.launcher3.Workspace; 44 import com.android.launcher3.anim.PendingAnimation; 45 import com.android.launcher3.anim.PropertySetter; 46 import com.android.launcher3.anim.SpringAnimationBuilder; 47 import com.android.launcher3.graphics.OverviewScrim; 48 import com.android.launcher3.statehandlers.DepthController; 49 import com.android.launcher3.states.StateAnimationConfig; 50 import com.android.launcher3.util.DynamicResource; 51 import com.android.quickstep.views.RecentsView; 52 import com.android.systemui.plugins.ResourceProvider; 53 54 /** 55 * Creates an animation where all the workspace items are moved into their final location, 56 * staggered row by row from the bottom up. 57 * This is used in conjunction with the swipe up to home animation. 58 */ 59 public class StaggeredWorkspaceAnim { 60 61 private static final int APP_CLOSE_ROW_START_DELAY_MS = 10; 62 private static final int ALPHA_DURATION_MS = 250; 63 64 private static final float MAX_VELOCITY_PX_PER_S = 22f; 65 66 private final float mVelocity; 67 private final float mSpringTransY; 68 69 private final AnimatorSet mAnimators = new AnimatorSet(); 70 StaggeredWorkspaceAnim(Launcher launcher, float velocity, boolean animateOverviewScrim)71 public StaggeredWorkspaceAnim(Launcher launcher, float velocity, boolean animateOverviewScrim) { 72 prepareToAnimate(launcher, animateOverviewScrim); 73 74 mVelocity = velocity; 75 76 // Scale the translationY based on the initial velocity to better sync the workspace items 77 // with the floating view. 78 float transFactor = 0.2f + 0.9f * Math.abs(velocity) / MAX_VELOCITY_PX_PER_S; 79 mSpringTransY = transFactor * launcher.getResources() 80 .getDimensionPixelSize(R.dimen.swipe_up_max_workspace_trans_y); 81 82 DeviceProfile grid = launcher.getDeviceProfile(); 83 Workspace workspace = launcher.getWorkspace(); 84 CellLayout cellLayout = (CellLayout) workspace.getChildAt(workspace.getCurrentPage()); 85 ShortcutAndWidgetContainer currentPage = cellLayout.getShortcutsAndWidgets(); 86 ViewGroup hotseat = launcher.getHotseat(); 87 88 boolean workspaceClipChildren = workspace.getClipChildren(); 89 boolean workspaceClipToPadding = workspace.getClipToPadding(); 90 boolean cellLayoutClipChildren = cellLayout.getClipChildren(); 91 boolean cellLayoutClipToPadding = cellLayout.getClipToPadding(); 92 boolean hotseatClipChildren = hotseat.getClipChildren(); 93 boolean hotseatClipToPadding = hotseat.getClipToPadding(); 94 95 workspace.setClipChildren(false); 96 workspace.setClipToPadding(false); 97 cellLayout.setClipChildren(false); 98 cellLayout.setClipToPadding(false); 99 hotseat.setClipChildren(false); 100 hotseat.setClipToPadding(false); 101 102 // Hotseat and QSB takes up two additional rows. 103 int totalRows = grid.inv.numRows + (grid.isVerticalBarLayout() ? 0 : 2); 104 105 // Set up springs on workspace items. 106 for (int i = currentPage.getChildCount() - 1; i >= 0; i--) { 107 View child = currentPage.getChildAt(i); 108 CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams()); 109 addStaggeredAnimationForView(child, lp.cellY + lp.cellVSpan, totalRows); 110 } 111 112 // Set up springs for the hotseat and qsb. 113 ViewGroup hotseatChild = (ViewGroup) hotseat.getChildAt(0); 114 if (grid.isVerticalBarLayout()) { 115 for (int i = hotseatChild.getChildCount() - 1; i >= 0; i--) { 116 View child = hotseatChild.getChildAt(i); 117 CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams()); 118 addStaggeredAnimationForView(child, lp.cellY + 1, totalRows); 119 } 120 } else { 121 for (int i = hotseatChild.getChildCount() - 1; i >= 0; i--) { 122 View child = hotseatChild.getChildAt(i); 123 addStaggeredAnimationForView(child, grid.inv.numRows + 1, totalRows); 124 } 125 126 if (launcher.getAppsView().getSearchUiManager() 127 .isQsbVisible(NORMAL.getVisibleElements(launcher))) { 128 addStaggeredAnimationForView(launcher.getAppsView().getSearchView(), 129 grid.inv.numRows + 2, totalRows); 130 } 131 } 132 133 if (animateOverviewScrim) { 134 PendingAnimation pendingAnimation = new PendingAnimation(ALPHA_DURATION_MS); 135 addScrimAnimationForState(launcher, NORMAL, pendingAnimation); 136 mAnimators.play(pendingAnimation.buildAnim()); 137 } 138 139 addDepthAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS); 140 141 mAnimators.play(launcher.getDragLayer().getScrim().createSysuiMultiplierAnim(0f, 1f) 142 .setDuration(ALPHA_DURATION_MS)); 143 mAnimators.addListener(new AnimatorListenerAdapter() { 144 @Override 145 public void onAnimationEnd(Animator animation) { 146 workspace.setClipChildren(workspaceClipChildren); 147 workspace.setClipToPadding(workspaceClipToPadding); 148 cellLayout.setClipChildren(cellLayoutClipChildren); 149 cellLayout.setClipToPadding(cellLayoutClipToPadding); 150 hotseat.setClipChildren(hotseatClipChildren); 151 hotseat.setClipToPadding(hotseatClipToPadding); 152 } 153 }); 154 } 155 156 /** 157 * Setup workspace with 0 duration to prepare for our staggered animation. 158 */ prepareToAnimate(Launcher launcher, boolean animateOverviewScrim)159 private void prepareToAnimate(Launcher launcher, boolean animateOverviewScrim) { 160 StateAnimationConfig config = new StateAnimationConfig(); 161 config.animFlags = ANIM_ALL_COMPONENTS | SKIP_OVERVIEW | SKIP_DEPTH_CONTROLLER; 162 config.duration = 0; 163 // setRecentsAttachedToAppWindow() will animate recents out. 164 launcher.getStateManager().createAtomicAnimation(BACKGROUND_APP, NORMAL, config).start(); 165 166 // Stop scrolling so that it doesn't interfere with the translation offscreen. 167 launcher.<RecentsView>getOverviewPanel().getScroller().forceFinished(true); 168 169 if (animateOverviewScrim) { 170 addScrimAnimationForState(launcher, BACKGROUND_APP, NO_ANIM_PROPERTY_SETTER); 171 } 172 } 173 getAnimators()174 public AnimatorSet getAnimators() { 175 return mAnimators; 176 } 177 addAnimatorListener(Animator.AnimatorListener listener)178 public StaggeredWorkspaceAnim addAnimatorListener(Animator.AnimatorListener listener) { 179 mAnimators.addListener(listener); 180 return this; 181 } 182 183 /** 184 * Starts the animation. 185 */ start()186 public void start() { 187 mAnimators.start(); 188 } 189 190 /** 191 * Adds an alpha/trans animator for {@param v}, with a start delay based on the view's row. 192 * 193 * @param v A view on the workspace. 194 * @param row The bottom-most row that contains the view. 195 * @param totalRows Total number of rows. 196 */ addStaggeredAnimationForView(View v, int row, int totalRows)197 private void addStaggeredAnimationForView(View v, int row, int totalRows) { 198 // Invert the rows, because we stagger starting from the bottom of the screen. 199 int invertedRow = totalRows - row; 200 // Add 1 to the inverted row so that the bottom most row has a start delay. 201 long startDelay = (long) ((invertedRow + 1) * APP_CLOSE_ROW_START_DELAY_MS); 202 203 v.setTranslationY(mSpringTransY); 204 205 ResourceProvider rp = DynamicResource.provider(v.getContext()); 206 float stiffness = rp.getFloat(R.dimen.staggered_stiffness); 207 float damping = rp.getFloat(R.dimen.staggered_damping_ratio); 208 ValueAnimator springTransY = new SpringAnimationBuilder(v.getContext()) 209 .setStiffness(stiffness) 210 .setDampingRatio(damping) 211 .setMinimumVisibleChange(1f) 212 .setStartValue(mSpringTransY) 213 .setEndValue(0) 214 .setStartVelocity(mVelocity) 215 .build(v, VIEW_TRANSLATE_Y); 216 springTransY.setStartDelay(startDelay); 217 springTransY.addListener(new AnimatorListenerAdapter() { 218 @Override 219 public void onAnimationEnd(Animator animation) { 220 v.setTranslationY(0f); 221 } 222 }); 223 mAnimators.play(springTransY); 224 225 v.setAlpha(0); 226 ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 0f, 1f); 227 alpha.setInterpolator(LINEAR); 228 alpha.setDuration(ALPHA_DURATION_MS); 229 alpha.setStartDelay(startDelay); 230 alpha.addListener(new AnimatorListenerAdapter() { 231 @Override 232 public void onAnimationEnd(Animator animation) { 233 v.setAlpha(1f); 234 } 235 }); 236 mAnimators.play(alpha); 237 } 238 addScrimAnimationForState(Launcher launcher, LauncherState state, PropertySetter setter)239 private void addScrimAnimationForState(Launcher launcher, LauncherState state, 240 PropertySetter setter) { 241 launcher.getWorkspace().getStateTransitionAnimation().setScrim(setter, state); 242 setter.setFloat( 243 launcher.getDragLayer().getOverviewScrim(), 244 OverviewScrim.SCRIM_PROGRESS, 245 state.getOverviewScrimAlpha(launcher), 246 ACCEL_DEACCEL); 247 } 248 addDepthAnimationForState(Launcher launcher, LauncherState state, long duration)249 private void addDepthAnimationForState(Launcher launcher, LauncherState state, long duration) { 250 if (!(launcher instanceof BaseQuickstepLauncher)) { 251 return; 252 } 253 PendingAnimation builder = new PendingAnimation(duration); 254 DepthController depthController = ((BaseQuickstepLauncher) launcher).getDepthController(); 255 depthController.setStateWithAnimation(state, new StateAnimationConfig(), builder); 256 mAnimators.play(builder.buildAnim()); 257 } 258 } 259