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 android.animation.Animator;
19 import android.content.res.Resources;
20 import android.graphics.PointF;
21 import android.graphics.RectF;
22 
23 import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
24 import androidx.dynamicanimation.animation.FloatPropertyCompat;
25 import androidx.dynamicanimation.animation.SpringAnimation;
26 import androidx.dynamicanimation.animation.SpringForce;
27 
28 import com.android.launcher3.R;
29 import com.android.launcher3.Utilities;
30 import com.android.launcher3.anim.FlingSpringAnim;
31 
32 import java.util.ArrayList;
33 import java.util.List;
34 
35 
36 /**
37  * Applies spring forces to animate from a starting rect to a target rect,
38  * while providing update callbacks to the caller.
39  */
40 public class RectFSpringAnim {
41 
42     private static final FloatPropertyCompat<RectFSpringAnim> RECT_CENTER_X =
43             new FloatPropertyCompat<RectFSpringAnim>("rectCenterXSpring") {
44                 @Override
45                 public float getValue(RectFSpringAnim anim) {
46                     return anim.mCurrentCenterX;
47                 }
48 
49                 @Override
50                 public void setValue(RectFSpringAnim anim, float currentCenterX) {
51                     anim.mCurrentCenterX = currentCenterX;
52                     anim.onUpdate();
53                 }
54             };
55 
56     private static final FloatPropertyCompat<RectFSpringAnim> RECT_Y =
57             new FloatPropertyCompat<RectFSpringAnim>("rectYSpring") {
58                 @Override
59                 public float getValue(RectFSpringAnim anim) {
60                     return anim.mCurrentY;
61                 }
62 
63                 @Override
64                 public void setValue(RectFSpringAnim anim, float y) {
65                     anim.mCurrentY = y;
66                     anim.onUpdate();
67                 }
68             };
69 
70     private static final FloatPropertyCompat<RectFSpringAnim> RECT_SCALE_PROGRESS =
71             new FloatPropertyCompat<RectFSpringAnim>("rectScaleProgress") {
72                 @Override
73                 public float getValue(RectFSpringAnim object) {
74                     return object.mCurrentScaleProgress;
75                 }
76 
77                 @Override
78                 public void setValue(RectFSpringAnim object, float value) {
79                     object.mCurrentScaleProgress = value;
80                     object.onUpdate();
81                 }
82             };
83 
84     private final RectF mStartRect;
85     private final RectF mTargetRect;
86     private final RectF mCurrentRect = new RectF();
87     private final List<OnUpdateListener> mOnUpdateListeners = new ArrayList<>();
88     private final List<Animator.AnimatorListener> mAnimatorListeners = new ArrayList<>();
89 
90     private float mCurrentCenterX;
91     private float mCurrentY;
92     // If true, tracking the bottom of the rects, else tracking the top.
93     private boolean mTrackingBottomY;
94     private float mCurrentScaleProgress;
95     private FlingSpringAnim mRectXAnim;
96     private FlingSpringAnim mRectYAnim;
97     private SpringAnimation mRectScaleAnim;
98     private boolean mAnimsStarted;
99     private boolean mRectXAnimEnded;
100     private boolean mRectYAnimEnded;
101     private boolean mRectScaleAnimEnded;
102 
103     private float mMinVisChange;
104     private float mYOvershoot;
105 
RectFSpringAnim(RectF startRect, RectF targetRect, Resources resources)106     public RectFSpringAnim(RectF startRect, RectF targetRect, Resources resources) {
107         mStartRect = startRect;
108         mTargetRect = targetRect;
109         mCurrentCenterX = mStartRect.centerX();
110 
111         mTrackingBottomY = startRect.bottom < targetRect.bottom;
112         mCurrentY = mTrackingBottomY ? mStartRect.bottom : mStartRect.top;
113 
114         mMinVisChange = resources.getDimensionPixelSize(R.dimen.swipe_up_fling_min_visible_change);
115         mYOvershoot = resources.getDimensionPixelSize(R.dimen.swipe_up_y_overshoot);
116     }
117 
118     public void onTargetPositionChanged() {
119         if (mRectXAnim != null && mRectXAnim.getTargetPosition() != mTargetRect.centerX()) {
120             mRectXAnim.updatePosition(mCurrentCenterX, mTargetRect.centerX());
121         }
122 
123         if (mRectYAnim != null) {
124             if (mTrackingBottomY && mRectYAnim.getTargetPosition() != mTargetRect.bottom) {
125                 mRectYAnim.updatePosition(mCurrentY, mTargetRect.bottom);
126             } else if (!mTrackingBottomY && mRectYAnim.getTargetPosition() != mTargetRect.top) {
127                 mRectYAnim.updatePosition(mCurrentY, mTargetRect.top);
128             }
129         }
130     }
131 
132     public void addOnUpdateListener(OnUpdateListener onUpdateListener) {
133         mOnUpdateListeners.add(onUpdateListener);
134     }
135 
136     public void addAnimatorListener(Animator.AnimatorListener animatorListener) {
137         mAnimatorListeners.add(animatorListener);
138     }
139 
140     public void start(PointF velocityPxPerMs) {
141         // Only tell caller that we ended if both x and y animations have ended.
142         OnAnimationEndListener onXEndListener = ((animation, canceled, centerX, velocityX) -> {
143             mRectXAnimEnded = true;
144             maybeOnEnd();
145         });
146         OnAnimationEndListener onYEndListener = ((animation, canceled, centerY, velocityY) -> {
147             mRectYAnimEnded = true;
148             maybeOnEnd();
149         });
150 
151         float startX = mCurrentCenterX;
152         float endX = mTargetRect.centerX();
153         float minXValue = Math.min(startX, endX);
154         float maxXValue = Math.max(startX, endX);
155         mRectXAnim = new FlingSpringAnim(this, RECT_CENTER_X, startX, endX,
156                 velocityPxPerMs.x * 1000, mMinVisChange, minXValue, maxXValue, 1f, onXEndListener);
157 
158         float startVelocityY = velocityPxPerMs.y * 1000;
159         // Scale the Y velocity based on the initial velocity to tune the curves.
160         float springVelocityFactor = 0.1f + 0.9f * Math.abs(startVelocityY) / 20000.0f;
161         float startY = mCurrentY;
162         float endY = mTrackingBottomY ? mTargetRect.bottom : mTargetRect.top;
163         float minYValue = Math.min(startY, endY - mYOvershoot);
164         float maxYValue = Math.max(startY, endY);
165         mRectYAnim = new FlingSpringAnim(this, RECT_Y, startY, endY, startVelocityY,
166                 mMinVisChange, minYValue, maxYValue, springVelocityFactor, onYEndListener);
167 
168         float minVisibleChange = 1f / mStartRect.height();
169         mRectScaleAnim = new SpringAnimation(this, RECT_SCALE_PROGRESS)
170                 .setSpring(new SpringForce(1f)
171                 .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
172                 .setStiffness(SpringForce.STIFFNESS_LOW))
173                 .setStartVelocity(velocityPxPerMs.y * minVisibleChange)
174                 .setMaxValue(1f)
175                 .setMinimumVisibleChange(minVisibleChange)
176                 .addEndListener((animation, canceled, value, velocity) -> {
177                     mRectScaleAnimEnded = true;
178                     maybeOnEnd();
179                 });
180 
181         mRectXAnim.start();
182         mRectYAnim.start();
183         mRectScaleAnim.start();
184         mAnimsStarted = true;
185         for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
186             animatorListener.onAnimationStart(null);
187         }
188     }
189 
end()190     public void end() {
191         if (mAnimsStarted) {
192             mRectXAnim.end();
193             mRectYAnim.end();
194             if (mRectScaleAnim.canSkipToEnd()) {
195                 mRectScaleAnim.skipToEnd();
196             }
197         }
198     }
199 
onUpdate()200     private void onUpdate() {
201         if (!mOnUpdateListeners.isEmpty()) {
202             float currentWidth = Utilities.mapRange(mCurrentScaleProgress, mStartRect.width(),
203                     mTargetRect.width());
204             float currentHeight = Utilities.mapRange(mCurrentScaleProgress, mStartRect.height(),
205                     mTargetRect.height());
206             if (mTrackingBottomY) {
207                 mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentY - currentHeight,
208                         mCurrentCenterX + currentWidth / 2, mCurrentY);
209             } else {
210                 mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentY,
211                         mCurrentCenterX + currentWidth / 2, mCurrentY + currentHeight);
212             }
213             for (OnUpdateListener onUpdateListener : mOnUpdateListeners) {
214                 onUpdateListener.onUpdate(mCurrentRect, mCurrentScaleProgress);
215             }
216         }
217     }
218 
maybeOnEnd()219     private void maybeOnEnd() {
220         if (mAnimsStarted && mRectXAnimEnded && mRectYAnimEnded && mRectScaleAnimEnded) {
221             mAnimsStarted = false;
222             for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
223                 animatorListener.onAnimationEnd(null);
224             }
225         }
226     }
227 
cancel()228     public void cancel() {
229         if (mAnimsStarted) {
230             for (OnUpdateListener onUpdateListener : mOnUpdateListeners) {
231                 onUpdateListener.onCancel();
232             }
233         }
234         end();
235     }
236 
237     public interface OnUpdateListener {
onUpdate(RectF currentRect, float progress)238         void onUpdate(RectF currentRect, float progress);
239 
onCancel()240         default void onCancel() { }
241     }
242 }
243