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 static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE;
20 
21 import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
22 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
23 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
24 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
25 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
26 import static com.android.launcher3.LauncherState.FLAG_HAS_SYS_UI_SCRIM;
27 import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_HAS_BACKGROUNDS;
28 import static com.android.launcher3.LauncherState.HINT_STATE;
29 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
30 import static com.android.launcher3.LauncherState.NORMAL;
31 import static com.android.launcher3.anim.Interpolators.LINEAR;
32 import static com.android.launcher3.anim.Interpolators.ZOOM_OUT;
33 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
34 import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
35 import static com.android.launcher3.graphics.WorkspaceAndHotseatScrim.SYSUI_PROGRESS;
36 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
37 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
38 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
39 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
40 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
41 
42 import android.animation.ValueAnimator;
43 import android.view.View;
44 import android.view.animation.Interpolator;
45 
46 import com.android.launcher3.LauncherState.PageAlphaProvider;
47 import com.android.launcher3.LauncherState.ScaleAndTranslation;
48 import com.android.launcher3.allapps.AllAppsContainerView;
49 import com.android.launcher3.anim.PendingAnimation;
50 import com.android.launcher3.anim.PropertySetter;
51 import com.android.launcher3.anim.SpringAnimationBuilder;
52 import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
53 import com.android.launcher3.states.StateAnimationConfig;
54 import com.android.launcher3.util.DynamicResource;
55 import com.android.systemui.plugins.ResourceProvider;
56 
57 /**
58  * Manages the animations between each of the workspace states.
59  */
60 public class WorkspaceStateTransitionAnimation {
61 
62     private final Launcher mLauncher;
63     private final Workspace mWorkspace;
64 
65     private float mNewScale;
66 
WorkspaceStateTransitionAnimation(Launcher launcher, Workspace workspace)67     public WorkspaceStateTransitionAnimation(Launcher launcher, Workspace workspace) {
68         mLauncher = launcher;
69         mWorkspace = workspace;
70     }
71 
setState(LauncherState toState)72     public void setState(LauncherState toState) {
73         setWorkspaceProperty(toState, NO_ANIM_PROPERTY_SETTER, new StateAnimationConfig());
74     }
75 
76     /**
77      * @see com.android.launcher3.statemanager.StateManager.StateHandler#setStateWithAnimation
78      */
setStateWithAnimation( LauncherState toState, StateAnimationConfig config, PendingAnimation animation)79     public void setStateWithAnimation(
80             LauncherState toState, StateAnimationConfig config, PendingAnimation animation) {
81         setWorkspaceProperty(toState, animation, config);
82     }
83 
getFinalScale()84     public float getFinalScale() {
85         return mNewScale;
86     }
87 
88     /**
89      * Starts a transition animation for the workspace.
90      */
setWorkspaceProperty(LauncherState state, PropertySetter propertySetter, StateAnimationConfig config)91     private void setWorkspaceProperty(LauncherState state, PropertySetter propertySetter,
92             StateAnimationConfig config) {
93         ScaleAndTranslation scaleAndTranslation = state.getWorkspaceScaleAndTranslation(mLauncher);
94         ScaleAndTranslation hotseatScaleAndTranslation = state.getHotseatScaleAndTranslation(
95                 mLauncher);
96         ScaleAndTranslation qsbScaleAndTranslation = state.getQsbScaleAndTranslation(mLauncher);
97         mNewScale = scaleAndTranslation.scale;
98         PageAlphaProvider pageAlphaProvider = state.getWorkspacePageAlphaProvider(mLauncher);
99         final int childCount = mWorkspace.getChildCount();
100         for (int i = 0; i < childCount; i++) {
101             applyChildState(state, (CellLayout) mWorkspace.getChildAt(i), i, pageAlphaProvider,
102                     propertySetter, config);
103         }
104 
105         int elements = state.getVisibleElements(mLauncher);
106         Interpolator fadeInterpolator = config.getInterpolator(ANIM_WORKSPACE_FADE,
107                 pageAlphaProvider.interpolator);
108         boolean playAtomicComponent = config.playAtomicOverviewScaleComponent();
109         Hotseat hotseat = mWorkspace.getHotseat();
110         // Since we set the pivot relative to mWorkspace, we need to scale a sibling of Workspace.
111         AllAppsContainerView qsbScaleView = mLauncher.getAppsView();
112         View qsbView = qsbScaleView.getSearchView();
113         if (playAtomicComponent) {
114             Interpolator scaleInterpolator = config.getInterpolator(ANIM_WORKSPACE_SCALE, ZOOM_OUT);
115             LauncherState fromState = mLauncher.getStateManager().getState();
116             boolean shouldSpring = propertySetter instanceof PendingAnimation
117                     && fromState == HINT_STATE && state == NORMAL;
118             if (shouldSpring) {
119                 ((PendingAnimation) propertySetter).add(getSpringScaleAnimator(mLauncher,
120                         mWorkspace, mNewScale));
121             } else {
122                 propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
123             }
124 
125             setPivotToScaleWithWorkspace(hotseat);
126             setPivotToScaleWithWorkspace(qsbScaleView);
127             float hotseatScale = hotseatScaleAndTranslation.scale;
128             if (shouldSpring) {
129                 PendingAnimation pa = (PendingAnimation) propertySetter;
130                 pa.add(getSpringScaleAnimator(mLauncher, hotseat, hotseatScale));
131                 pa.add(getSpringScaleAnimator(mLauncher, qsbScaleView,
132                         qsbScaleAndTranslation.scale));
133             } else {
134                 Interpolator hotseatScaleInterpolator = config.getInterpolator(ANIM_HOTSEAT_SCALE,
135                         scaleInterpolator);
136                 propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale,
137                         hotseatScaleInterpolator);
138                 propertySetter.setFloat(qsbScaleView, SCALE_PROPERTY, qsbScaleAndTranslation.scale,
139                         hotseatScaleInterpolator);
140             }
141 
142             float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
143             propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha, fadeInterpolator);
144             propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
145                     hotseatIconsAlpha, fadeInterpolator);
146         }
147 
148         if (config.onlyPlayAtomicComponent()) {
149             // Only the alpha and scale, handled above, are included in the atomic animation.
150             return;
151         }
152 
153         Interpolator translationInterpolator = !playAtomicComponent
154                 ? LINEAR
155                 : config.getInterpolator(ANIM_WORKSPACE_TRANSLATE, ZOOM_OUT);
156         propertySetter.setFloat(mWorkspace, VIEW_TRANSLATE_X,
157                 scaleAndTranslation.translationX, translationInterpolator);
158         propertySetter.setFloat(mWorkspace, VIEW_TRANSLATE_Y,
159                 scaleAndTranslation.translationY, translationInterpolator);
160 
161         Interpolator hotseatTranslationInterpolator = config.getInterpolator(
162                 ANIM_HOTSEAT_TRANSLATE, translationInterpolator);
163         propertySetter.setFloat(hotseat, VIEW_TRANSLATE_Y,
164                 hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator);
165         propertySetter.setFloat(mWorkspace.getPageIndicator(), VIEW_TRANSLATE_Y,
166                 hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator);
167         propertySetter.setFloat(qsbView, VIEW_TRANSLATE_Y,
168                 qsbScaleAndTranslation.translationY, hotseatTranslationInterpolator);
169 
170         setScrim(propertySetter, state);
171     }
172 
173     /**
174      * Set the given view's pivot point to match the workspace's, so that it scales together. Since
175      * both this view and workspace can move, transform the point manually instead of using
176      * dragLayer.getDescendantCoordRelativeToSelf and related methods.
177      */
setPivotToScaleWithWorkspace(View sibling)178     private void setPivotToScaleWithWorkspace(View sibling) {
179         sibling.setPivotY(mWorkspace.getPivotY() + mWorkspace.getTop()
180                 - sibling.getTop() - sibling.getTranslationY());
181         sibling.setPivotX(mWorkspace.getPivotX() + mWorkspace.getLeft()
182                 - sibling.getLeft() - sibling.getTranslationX());
183     }
184 
setScrim(PropertySetter propertySetter, LauncherState state)185     public void setScrim(PropertySetter propertySetter, LauncherState state) {
186         WorkspaceAndHotseatScrim scrim = mLauncher.getDragLayer().getScrim();
187         propertySetter.setFloat(scrim, SCRIM_PROGRESS, state.getWorkspaceScrimAlpha(mLauncher),
188                 LINEAR);
189         propertySetter.setFloat(scrim, SYSUI_PROGRESS,
190                 state.hasFlag(FLAG_HAS_SYS_UI_SCRIM) ? 1 : 0, LINEAR);
191     }
192 
applyChildState(LauncherState state, CellLayout cl, int childIndex)193     public void applyChildState(LauncherState state, CellLayout cl, int childIndex) {
194         applyChildState(state, cl, childIndex, state.getWorkspacePageAlphaProvider(mLauncher),
195                 NO_ANIM_PROPERTY_SETTER, new StateAnimationConfig());
196     }
197 
applyChildState(LauncherState state, CellLayout cl, int childIndex, PageAlphaProvider pageAlphaProvider, PropertySetter propertySetter, StateAnimationConfig config)198     private void applyChildState(LauncherState state, CellLayout cl, int childIndex,
199             PageAlphaProvider pageAlphaProvider, PropertySetter propertySetter,
200             StateAnimationConfig config) {
201         float pageAlpha = pageAlphaProvider.getPageAlpha(childIndex);
202         int drawableAlpha = state.hasFlag(FLAG_WORKSPACE_HAS_BACKGROUNDS)
203                 ? Math.round(pageAlpha * 255) : 0;
204 
205         if (!config.onlyPlayAtomicComponent()) {
206             // Don't update the scrim during the atomic animation.
207             propertySetter.setInt(cl.getScrimBackground(),
208                     DRAWABLE_ALPHA, drawableAlpha, ZOOM_OUT);
209         }
210         if (config.playAtomicOverviewScaleComponent()) {
211             Interpolator fadeInterpolator = config.getInterpolator(ANIM_WORKSPACE_FADE,
212                     pageAlphaProvider.interpolator);
213             propertySetter.setFloat(cl.getShortcutsAndWidgets(), VIEW_ALPHA,
214                     pageAlpha, fadeInterpolator);
215         }
216     }
217 
218     /**
219      * Returns a spring based animator for the scale property of {@param v}.
220      */
getSpringScaleAnimator(Launcher launcher, View v, float scale)221     public static ValueAnimator getSpringScaleAnimator(Launcher launcher, View v, float scale) {
222         ResourceProvider rp = DynamicResource.provider(launcher);
223         float damping = rp.getFloat(R.dimen.hint_scale_damping_ratio);
224         float stiffness = rp.getFloat(R.dimen.hint_scale_stiffness);
225         float velocityPxPerS = rp.getDimension(R.dimen.hint_scale_velocity_dp_per_s);
226 
227         return new SpringAnimationBuilder(v.getContext())
228                 .setStiffness(stiffness)
229                 .setDampingRatio(damping)
230                 .setMinimumVisibleChange(MIN_VISIBLE_CHANGE_SCALE)
231                 .setEndValue(scale)
232                 .setStartValue(SCALE_PROPERTY.get(v))
233                 .setStartVelocity(velocityPxPerS)
234                 .build(v, SCALE_PROPERTY);
235 
236     }
237 }