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