• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.Utilities.dpToPx;
19 import static com.android.launcher3.anim.Interpolators.LINEAR;
20 
21 import android.animation.Animator;
22 import android.animation.AnimatorListenerAdapter;
23 import android.animation.ObjectAnimator;
24 import android.animation.ValueAnimator;
25 import android.content.Context;
26 import android.graphics.PointF;
27 import android.graphics.RectF;
28 import android.util.PathParser;
29 import android.util.Property;
30 import android.view.animation.Interpolator;
31 
32 import androidx.core.view.animation.PathInterpolatorCompat;
33 import androidx.dynamicanimation.animation.FloatPropertyCompat;
34 import androidx.dynamicanimation.animation.SpringAnimation;
35 import androidx.dynamicanimation.animation.SpringForce;
36 
37 import com.android.launcher3.R;
38 import com.android.launcher3.Utilities;
39 import com.android.launcher3.util.DynamicResource;
40 import com.android.systemui.plugins.ResourceProvider;
41 
42 import java.util.ArrayList;
43 import java.util.List;
44 import java.util.Locale;
45 
46 /**
47  * Applies spring forces to animate from a starting rect to a target rect,
48  * while providing update callbacks to the caller.
49  */
50 public class RectFSpringAnim2 extends RectFSpringAnim {
51 
52     private static final FloatPropertyCompat<RectFSpringAnim2> RECT_CENTER_X =
53             new FloatPropertyCompat<RectFSpringAnim2>("rectCenterXSpring") {
54                 @Override
55                 public float getValue(RectFSpringAnim2 anim) {
56                     return anim.mCurrentCenterX;
57                 }
58 
59                 @Override
60                 public void setValue(RectFSpringAnim2 anim, float currentCenterX) {
61                     anim.mCurrentCenterX = currentCenterX;
62                     anim.onUpdate();
63                 }
64             };
65 
66     private static final FloatPropertyCompat<RectFSpringAnim2> RECT_Y =
67             new FloatPropertyCompat<RectFSpringAnim2>("rectYSpring") {
68                 @Override
69                 public float getValue(RectFSpringAnim2 anim) {
70                     return anim.mCurrentCenterY;
71                 }
72 
73                 @Override
74                 public void setValue(RectFSpringAnim2 anim, float y) {
75                     anim.mCurrentCenterY = y;
76                     anim.onUpdate();
77                 }
78             };
79 
80     private static final Property<RectFSpringAnim2, Float> PROGRESS =
81             new Property<RectFSpringAnim2, Float>(Float.class, "rectFProgress") {
82                 @Override
83                 public Float get(RectFSpringAnim2 rectFSpringAnim) {
84                     return rectFSpringAnim.mProgress;
85                 }
86 
87                 @Override
88                 public void set(RectFSpringAnim2 rectFSpringAnim, Float progress) {
89                     rectFSpringAnim.mProgress = progress;
90                     rectFSpringAnim.onUpdate();
91                 }
92             };
93 
94     private final RectF mStartRect;
95     private final RectF mTargetRect;
96     private final RectF mCurrentRect = new RectF();
97     private final List<OnUpdateListener> mOnUpdateListeners = new ArrayList<>();
98     private final List<Animator.AnimatorListener> mAnimatorListeners = new ArrayList<>();
99 
100     private float mCurrentCenterX;
101     private float mCurrentCenterY;
102 
103     private float mTargetX;
104     private float mTargetY;
105 
106     // If true, tracking the bottom of the rects, else tracking the top.
107     private float mProgress;
108     private SpringAnimation mRectXAnim;
109     private SpringAnimation mRectYAnim;
110     private ValueAnimator mRectScaleAnim;
111     private boolean mAnimsStarted;
112     private boolean mRectXAnimEnded;
113     private boolean mRectYAnimEnded;
114     private boolean mRectScaleAnimEnded;
115 
116     private final float mXDamping;
117     private final float mXStiffness;
118 
119     private final float mYDamping;
120     private float mYStiffness;
121 
122     private long mDuration;
123 
124     private final Interpolator mCloseInterpolator;
125 
126     private AppCloseConfig mValues;
127     final float mStartRadius;
128     final float mEndRadius;
129 
130     final float mHomeTransYEnd;
131     final float mScaleStart;
132 
RectFSpringAnim2(RectF startRect, RectF targetRect, Context context, float startRadius, float endRadius)133     public RectFSpringAnim2(RectF startRect, RectF targetRect, Context context, float startRadius,
134             float endRadius) {
135         super(startRect, targetRect, context);
136         mStartRect = startRect;
137         mTargetRect = targetRect;
138 
139         mCurrentCenterY = mStartRect.centerY();
140         mCurrentCenterX = mStartRect.centerX();
141 
142         mTargetY = mTargetRect.centerY();
143         mTargetX = mTargetRect.centerX();
144 
145         ResourceProvider rp = DynamicResource.provider(context);
146         mXDamping = rp.getFloat(R.dimen.swipe_up_rect_2_x_damping_ratio);
147         mXStiffness = rp.getFloat(R.dimen.swipe_up_rect_2_x_stiffness);
148 
149         mYDamping = rp.getFloat(R.dimen.swipe_up_rect_2_y_damping_ratio);
150         mYStiffness = rp.getFloat(R.dimen.swipe_up_rect_2_y_stiffness);
151         mDuration = Math.round(rp.getFloat(R.dimen.swipe_up_duration));
152 
153         mHomeTransYEnd = dpToPx(rp.getFloat(R.dimen.swipe_up_trans_y_dp));
154         mScaleStart = rp.getFloat(R.dimen.swipe_up_scale_start);
155 
156         mCloseInterpolator = getAppCloseInterpolator(context);
157 
158         // End on a "round-enough" radius so that the shape reveal doesn't have to do too much
159         // rounding at the end of the animation.
160         mStartRadius = startRadius;
161         mEndRadius = endRadius;
162 
163         setCanRelease(true);
164     }
165 
onTargetPositionChanged()166     public void onTargetPositionChanged() {
167         if (mRectXAnim != null && mTargetX != mTargetRect.centerX()) {
168             mTargetX = mTargetRect.centerX();
169             mRectXAnim.animateToFinalPosition(mTargetX);
170         }
171 
172         if (mRectYAnim != null) {
173             if (mTargetY != mTargetRect.centerY()) {
174                 mTargetY = mTargetRect.centerY();
175                 mRectYAnim.animateToFinalPosition(mTargetY);
176             }
177         }
178     }
179 
addOnUpdateListener(OnUpdateListener onUpdateListener)180     public void addOnUpdateListener(OnUpdateListener onUpdateListener) {
181         mOnUpdateListeners.add(onUpdateListener);
182     }
183 
addAnimatorListener(Animator.AnimatorListener animatorListener)184     public void addAnimatorListener(Animator.AnimatorListener animatorListener) {
185         mAnimatorListeners.add(animatorListener);
186     }
187 
188     /**
189      * Starts the fling/spring animation.
190      * @param context The activity context.
191      * @param velocityPxPerMs Velocity of swipe in px/ms.
192      */
start(Context context, PointF velocityPxPerMs)193     public void start(Context context, PointF velocityPxPerMs) {
194         mRectXAnim = new SpringAnimation(this, RECT_CENTER_X)
195                 .setStartValue(mCurrentCenterX)
196                 .setStartVelocity(velocityPxPerMs.x * 1000)
197                 .setSpring(new SpringForce(mTargetX)
198                         .setStiffness(mXStiffness)
199                         .setDampingRatio(mXDamping));
200         mRectXAnim.addEndListener(((animation, canceled, centerX, velocityX) -> {
201             mRectXAnimEnded = true;
202             maybeOnEnd();
203         }));
204 
205         mRectYAnim = new SpringAnimation(this, RECT_Y)
206                 .setStartValue(mCurrentCenterY)
207                 .setStartVelocity(velocityPxPerMs.y * 1000)
208                 .setSpring(new SpringForce(mTargetY)
209                         .setStiffness(mYStiffness)
210                         .setDampingRatio(mYDamping));
211         mRectYAnim.addEndListener(((animation, canceled, centerY, velocityY) -> {
212             mRectYAnimEnded = true;
213             maybeOnEnd();
214         }));
215 
216         mRectScaleAnim = ObjectAnimator.ofFloat(this, PROGRESS, 0, 1f)
217                 .setDuration(mDuration);
218         mRectScaleAnim.setInterpolator(mCloseInterpolator);
219         mRectScaleAnim.addListener(new AnimatorListenerAdapter() {
220             @Override
221             public void onAnimationEnd(Animator animation) {
222                 mRectScaleAnimEnded = true;
223                 maybeOnEnd();
224             }
225         });
226 
227         mValues = buildConfig();
228         mRectScaleAnim.addUpdateListener(mValues);
229 
230         setCanRelease(false);
231         mAnimsStarted = true;
232 
233         mRectXAnim.start();
234         mRectYAnim.start();
235         mRectScaleAnim.start();
236         for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
237             animatorListener.onAnimationStart(null);
238         }
239     }
240 
buildConfig()241     private AppCloseConfig buildConfig() {
242         return new AppCloseConfig() {
243             FloatProp mHomeTransY = new FloatProp(0, mHomeTransYEnd, 0, mDuration, LINEAR);
244             FloatProp mHomeScale = new FloatProp(mScaleStart, 1f, 0, mDuration, LINEAR);
245             FloatProp mWindowFadeOut = new FloatProp(1f, 0f, 0, 116, LINEAR);
246             // There should be a slight overlap b/w window fading out and fg fading in.
247             // (fg startDelay < window fade out duration)
248             FloatProp mFgFadeIn = new FloatProp(0, 255f, 100, mDuration - 100, LINEAR);
249             FloatProp mRadius = new FloatProp(mStartRadius, mEndRadius, 0, mDuration, LINEAR);
250             FloatProp mThreePointInterpolation = new FloatProp(0, 1, 0, mDuration, LINEAR);
251 
252             @Override
253             public float getWorkspaceTransY() {
254                 return mHomeTransY.value;
255             }
256 
257             @Override
258             public float getWorkspaceScale() {
259                 return mHomeScale.value;
260             }
261 
262             @Override
263             public float getWindowAlpha() {
264                 return mWindowFadeOut.value;
265             }
266 
267             @Override
268             public int getFgAlpha() {
269                 return (int) mFgFadeIn.value;
270             }
271 
272             @Override
273             public float getCornerRadius() {
274                 return mRadius.value;
275             }
276 
277             @Override
278             public float getInterpolatedProgress() {
279                 return mThreePointInterpolation.value;
280             }
281 
282             @Override
283             public void onUpdate(float percent, boolean initOnly) {}
284         };
285     }
286 
end()287     public void end() {
288         if (mAnimsStarted) {
289             if (mRectXAnim.canSkipToEnd()) {
290                 mRectXAnim.skipToEnd();
291             }
292             if (mRectYAnim.canSkipToEnd()) {
293                 mRectYAnim.skipToEnd();
294             }
295             mRectScaleAnim.end();
296         }
297         mRectXAnimEnded = true;
298         mRectYAnimEnded = true;
299         mRectScaleAnimEnded = true;
300         maybeOnEnd();
301     }
302 
isEnded()303     private boolean isEnded() {
304         return mRectXAnimEnded && mRectYAnimEnded && mRectScaleAnimEnded;
305     }
306 
onUpdate()307     private void onUpdate() {
308         if (isEnded()) {
309             // Prevent further updates from being called. This can happen between callbacks for
310             // ending the x/y/scale animations.
311             return;
312         }
313 
314         if (!mOnUpdateListeners.isEmpty()) {
315             float rectProgress = mProgress;
316             float currentWidth = Utilities.mapRange(rectProgress, mStartRect.width(),
317                     mTargetRect.width());
318             float currentHeight = Utilities.mapRange(rectProgress, mStartRect.height(),
319                     mTargetRect.height());
320 
321             mCurrentRect.set(mCurrentCenterX - currentWidth / 2,
322                     mCurrentCenterY - currentHeight / 2,
323                     mCurrentCenterX + currentWidth / 2,
324                     mCurrentCenterY + currentHeight / 2);
325 
326             float currentPlayTime = mRectScaleAnimEnded ? mRectScaleAnim.getDuration()
327                     : mRectScaleAnim.getCurrentPlayTime();
328             float linearProgress = Math.min(1f, currentPlayTime / mRectScaleAnim.getDuration());
329             for (OnUpdateListener onUpdateListener : mOnUpdateListeners) {
330                 onUpdateListener.onUpdate(mValues, mCurrentRect, linearProgress);
331             }
332         }
333     }
334 
maybeOnEnd()335     private void maybeOnEnd() {
336         if (mAnimsStarted && isEnded()) {
337             mAnimsStarted = false;
338             setCanRelease(true);
339             for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
340                 animatorListener.onAnimationEnd(null);
341             }
342         }
343     }
344 
cancel()345     public void cancel() {
346         if (mAnimsStarted) {
347             for (OnUpdateListener onUpdateListener : mOnUpdateListeners) {
348                 onUpdateListener.onCancel();
349             }
350         }
351         end();
352     }
353 
getAppCloseInterpolator(Context context)354     private Interpolator getAppCloseInterpolator(Context context) {
355         ResourceProvider rp = DynamicResource.provider(context);
356         String path = String.format(Locale.ENGLISH,
357                 "M 0,0 C %f, %f, %f, %f, %f, %f C %f, %f, %f, %f, 1, 1",
358                 rp.getFloat(R.dimen.c1_a),
359                 rp.getFloat(R.dimen.c1_b),
360                 rp.getFloat(R.dimen.c1_c),
361                 rp.getFloat(R.dimen.c1_d),
362                 rp.getFloat(R.dimen.mp_x),
363                 rp.getFloat(R.dimen.mp_y),
364                 rp.getFloat(R.dimen.c2_a),
365                 rp.getFloat(R.dimen.c2_b),
366                 rp.getFloat(R.dimen.c2_c),
367                 rp.getFloat(R.dimen.c2_d));
368         return PathInterpolatorCompat.create(PathParser.createPathFromPathData(path));
369     }
370 }
371