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