• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.DECELERATE;
19 import static com.android.app.animation.Interpolators.LINEAR;
20 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
21 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
22 
23 import android.animation.TimeInterpolator;
24 import android.content.Context;
25 import android.graphics.Matrix;
26 import android.graphics.PointF;
27 import android.graphics.Rect;
28 import android.graphics.RectF;
29 import android.util.FloatProperty;
30 
31 import androidx.annotation.Nullable;
32 
33 import com.android.launcher3.DeviceProfile;
34 import com.android.launcher3.Launcher;
35 import com.android.launcher3.Utilities;
36 import com.android.launcher3.anim.AnimatorPlaybackController;
37 import com.android.launcher3.anim.PendingAnimation;
38 import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
39 import com.android.quickstep.views.RecentsView;
40 
41 /**
42  * Controls an animation that can go beyond progress = 1, at which point resistance should be
43  * applied. Internally, this is a wrapper around 2 {@link AnimatorPlaybackController}s, one that
44  * runs from progress 0 to 1 like normal, then one that seamlessly continues that animation but
45  * starts applying resistance as well.
46  */
47 public class AnimatorControllerWithResistance {
48 
49     private enum RecentsResistanceParams {
50         FROM_APP(0.75f, 0.5f, 1f, false),
51         FROM_APP_TABLET(1f, 0.7f, 1f, true),
52         FROM_OVERVIEW(1f, 0.75f, 0.5f, false);
53 
RecentsResistanceParams(float scaleStartResist, float scaleMaxResist, float translationFactor, boolean stopScalingAtTop)54         RecentsResistanceParams(float scaleStartResist, float scaleMaxResist,
55                 float translationFactor, boolean stopScalingAtTop) {
56             this.scaleStartResist = scaleStartResist;
57             this.scaleMaxResist = scaleMaxResist;
58             this.translationFactor = translationFactor;
59             this.stopScalingAtTop = stopScalingAtTop;
60         }
61 
62         /**
63          * Start slowing down the rate of scaling down when recents view is smaller than this scale.
64          */
65         public final float scaleStartResist;
66 
67         /**
68          * Recents view will reach this scale at the very end of the drag.
69          */
70         public final float scaleMaxResist;
71 
72         /**
73          * How much translation to apply to RecentsView when the drag reaches the top of the screen,
74          * where 0 will keep it centered and 1 will have it barely touch the top of the screen.
75          */
76         public final float translationFactor;
77 
78         /**
79          * Whether to end scaling effect when the scaled down version of TaskView's top reaches the
80          * non-scaled version of TaskView's top.
81          */
82         public final boolean stopScalingAtTop;
83     }
84 
85     private static final TimeInterpolator RECENTS_SCALE_RESIST_INTERPOLATOR = DECELERATE;
86     private static final TimeInterpolator RECENTS_TRANSLATE_RESIST_INTERPOLATOR = LINEAR;
87 
88     private static final Rect TEMP_RECT = new Rect();
89 
90     private final AnimatorPlaybackController mNormalController;
91     private final AnimatorPlaybackController mResistanceController;
92 
93     // Initialize to -1 so the first 0 gets applied.
94     private float mLastNormalProgress = -1;
95     private float mLastResistProgress;
96 
AnimatorControllerWithResistance(AnimatorPlaybackController normalController, AnimatorPlaybackController resistanceController)97     public AnimatorControllerWithResistance(AnimatorPlaybackController normalController,
98             AnimatorPlaybackController resistanceController) {
99         mNormalController = normalController;
100         mResistanceController = resistanceController;
101     }
102 
getNormalController()103     public AnimatorPlaybackController getNormalController() {
104         return mNormalController;
105     }
106 
107     /**
108      * Applies the current progress of the animation.
109      * @param progress From 0 to maxProgress, where 1 is the target we are animating towards.
110      * @param maxProgress > 1, this is where the resistance will be applied.
111      */
setProgress(float progress, float maxProgress)112     public void setProgress(float progress, float maxProgress) {
113         float normalProgress = Utilities.boundToRange(progress, 0, 1);
114         if (normalProgress != mLastNormalProgress) {
115             mLastNormalProgress = normalProgress;
116             mNormalController.setPlayFraction(normalProgress);
117         }
118         if (maxProgress <= 1) {
119             return;
120         }
121         float resistProgress = progress <= 1 ? 0 : Utilities.getProgress(progress, 1, maxProgress);
122         if (resistProgress != mLastResistProgress) {
123             mLastResistProgress = resistProgress;
124             mResistanceController.setPlayFraction(resistProgress);
125         }
126     }
127 
128     /**
129      * Applies resistance to recents when swiping up past its target position.
130      * @param normalController The controller to run from 0 to 1 before this resistance applies.
131      * @param context Used to compute start and end values.
132      * @param recentsOrientedState Used to compute start and end values.
133      * @param dp Used to compute start and end values.
134      * @param scaleTarget The target for the scaleProperty.
135      * @param scaleProperty Animate the value to change the scale of the window/recents view.
136      * @param translationTarget The target for the translationProperty.
137      * @param translationProperty Animate the value to change the translation of the recents view.
138      */
createForRecents( AnimatorPlaybackController normalController, Context context, RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget, FloatProperty<SCALE> scaleProperty, TRANSLATION translationTarget, FloatProperty<TRANSLATION> translationProperty)139     public static <SCALE, TRANSLATION> AnimatorControllerWithResistance createForRecents(
140             AnimatorPlaybackController normalController, Context context,
141             RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget,
142             FloatProperty<SCALE> scaleProperty, TRANSLATION translationTarget,
143             FloatProperty<TRANSLATION> translationProperty) {
144 
145         RecentsParams params = new RecentsParams(context, recentsOrientedState, dp, scaleTarget,
146                 scaleProperty, translationTarget, translationProperty);
147         PendingAnimation resistAnim = createRecentsResistanceAnim(params);
148         AnimatorPlaybackController resistanceController = resistAnim.createPlaybackController();
149         return new AnimatorControllerWithResistance(normalController, resistanceController);
150     }
151 
152     /**
153      * Creates the resistance animation for {@link #createForRecents}, or can be used separately
154      * when starting from recents, i.e. {@link #createRecentsResistanceFromOverviewAnim}.
155      */
createRecentsResistanceAnim( RecentsParams<SCALE, TRANSLATION> params)156     public static <SCALE, TRANSLATION> PendingAnimation createRecentsResistanceAnim(
157             RecentsParams<SCALE, TRANSLATION> params) {
158         Rect startRect = new Rect();
159         RecentsPagedOrientationHandler orientationHandler = params.recentsOrientedState
160                 .getOrientationHandler();
161         params.recentsOrientedState.getContainerInterface()
162                 .calculateTaskSize(params.context, params.dp, startRect, orientationHandler);
163         long distanceToCover = startRect.bottom;
164         PendingAnimation resistAnim = params.resistAnim != null
165                 ? params.resistAnim
166                 : new PendingAnimation(distanceToCover * 2);
167 
168         PointF pivot = new PointF();
169         float fullscreenScale = params.recentsOrientedState.getFullScreenScaleAndPivot(
170                 startRect, params.dp, pivot);
171 
172         // Compute where the task view would be based on the end scale.
173         RectF endRectF = new RectF(startRect);
174         Matrix temp = new Matrix();
175         temp.setScale(params.resistanceParams.scaleMaxResist,
176                 params.resistanceParams.scaleMaxResist, pivot.x, pivot.y);
177         temp.mapRect(endRectF);
178         // Translate such that the task view touches the top of the screen when drag does.
179         float endTranslation = endRectF.top
180                 * orientationHandler.getSecondaryTranslationDirectionFactor()
181                 * params.resistanceParams.translationFactor;
182         resistAnim.addFloat(params.translationTarget, params.translationProperty,
183                 params.startTranslation, endTranslation, RECENTS_TRANSLATE_RESIST_INTERPOLATOR);
184 
185         float prevScaleRate = (fullscreenScale - params.startScale)
186                 / (params.dp.heightPx - startRect.bottom);
187         // This is what the scale would be at the end of the drag if we didn't apply resistance.
188         float endScale = params.startScale - prevScaleRate * distanceToCover;
189         // Create an interpolator that resists the scale so the scale doesn't get smaller than
190         // RECENTS_SCALE_MAX_RESIST.
191         float startResist = Utilities.getProgress(params.resistanceParams.scaleStartResist,
192                 params.startScale, endScale);
193         float maxResist = Utilities.getProgress(params.resistanceParams.scaleMaxResist,
194                 params.startScale, endScale);
195         float stopResist =
196                 params.resistanceParams.stopScalingAtTop ? 1f - startRect.top / endRectF.top : 1f;
197         final TimeInterpolator scaleInterpolator = t -> {
198             if (t <= startResist) {
199                 return t;
200             }
201             if (t >= stopResist) {
202                 return maxResist;
203             }
204             float resistProgress = Utilities.getProgress(t, startResist, stopResist);
205             resistProgress = RECENTS_SCALE_RESIST_INTERPOLATOR.getInterpolation(resistProgress);
206             return startResist + resistProgress * (maxResist - startResist);
207         };
208         resistAnim.addFloat(params.scaleTarget, params.scaleProperty, params.startScale, endScale,
209                 scaleInterpolator);
210 
211         return resistAnim;
212     }
213 
214     /**
215      * Helper method to update or create a PendingAnimation suitable for animating
216      * a RecentsView interaction that started from the overview state.
217      */
createRecentsResistanceFromOverviewAnim( Launcher launcher, @Nullable PendingAnimation resistanceAnim)218     public static PendingAnimation createRecentsResistanceFromOverviewAnim(
219             Launcher launcher, @Nullable PendingAnimation resistanceAnim) {
220         RecentsView recentsView = launcher.getOverviewPanel();
221         RecentsParams params = new RecentsParams(launcher, recentsView.getPagedViewOrientedState(),
222                 launcher.getDeviceProfile(), recentsView, RECENTS_SCALE_PROPERTY, recentsView,
223                 TASK_SECONDARY_TRANSLATION)
224                 .setResistAnim(resistanceAnim)
225                 .setResistanceParams(RecentsResistanceParams.FROM_OVERVIEW)
226                 .setStartScale(recentsView.getScaleX());
227         return createRecentsResistanceAnim(params);
228     }
229 
230     /**
231      * Params to compute resistance when scaling/translating recents.
232      */
233     private static class RecentsParams<SCALE, TRANSLATION> {
234         // These are all required and can't have default values, hence are final.
235         public final Context context;
236         public final RecentsOrientedState recentsOrientedState;
237         public final DeviceProfile dp;
238         public final SCALE scaleTarget;
239         public final FloatProperty<SCALE> scaleProperty;
240         public final TRANSLATION translationTarget;
241         public final FloatProperty<TRANSLATION> translationProperty;
242 
243         // These are not required, or can have a default value that is generally correct.
244         @Nullable public PendingAnimation resistAnim = null;
245         public RecentsResistanceParams resistanceParams;
246         public float startScale = 1f;
247         public float startTranslation = 0f;
248 
RecentsParams(Context context, RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget, FloatProperty<SCALE> scaleProperty, TRANSLATION translationTarget, FloatProperty<TRANSLATION> translationProperty)249         private RecentsParams(Context context, RecentsOrientedState recentsOrientedState,
250                 DeviceProfile dp, SCALE scaleTarget, FloatProperty<SCALE> scaleProperty,
251                 TRANSLATION translationTarget, FloatProperty<TRANSLATION> translationProperty) {
252             this.context = context;
253             this.recentsOrientedState = recentsOrientedState;
254             this.dp = dp;
255             this.scaleTarget = scaleTarget;
256             this.scaleProperty = scaleProperty;
257             this.translationTarget = translationTarget;
258             this.translationProperty = translationProperty;
259             if (dp.isTablet) {
260                 resistanceParams = RecentsResistanceParams.FROM_APP_TABLET;
261             } else {
262                 resistanceParams = RecentsResistanceParams.FROM_APP;
263             }
264         }
265 
setResistAnim(PendingAnimation resistAnim)266         private RecentsParams setResistAnim(PendingAnimation resistAnim) {
267             this.resistAnim = resistAnim;
268             return this;
269         }
270 
setResistanceParams(RecentsResistanceParams resistanceParams)271         private RecentsParams setResistanceParams(RecentsResistanceParams resistanceParams) {
272             this.resistanceParams = resistanceParams;
273             return this;
274         }
275 
setStartScale(float startScale)276         private RecentsParams setStartScale(float startScale) {
277             this.startScale = startScale;
278             return this;
279         }
280 
setStartTranslation(float startTranslation)281         private RecentsParams setStartTranslation(float startTranslation) {
282             this.startTranslation = startTranslation;
283             return this;
284         }
285     }
286 }
287