• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.internal.widget.remotecompose.core.operations.layout.animation;
17 
18 import android.annotation.NonNull;
19 
20 import com.android.internal.widget.remotecompose.core.Operation;
21 import com.android.internal.widget.remotecompose.core.PaintContext;
22 import com.android.internal.widget.remotecompose.core.RemoteContext;
23 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
24 import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent;
25 import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
26 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
27 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
28 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
29 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.GeneralEasing;
30 
31 /**
32  * Basic interpolation manager between two ComponentMeasures
33  *
34  * <p>Handles position, size and visibility
35  */
36 public class AnimateMeasure {
37     private long mStartTime = System.currentTimeMillis();
38     private final @NonNull Component mComponent;
39     private final @NonNull ComponentMeasure mOriginal;
40     private final @NonNull ComponentMeasure mTarget;
41     private float mDuration;
42     private float mDurationVisibilityChange = mDuration;
43     private @NonNull AnimationSpec.ANIMATION mEnterAnimation = AnimationSpec.ANIMATION.FADE_IN;
44     private @NonNull AnimationSpec.ANIMATION mExitAnimation = AnimationSpec.ANIMATION.FADE_OUT;
45     private int mMotionEasingType = GeneralEasing.CUBIC_STANDARD;
46     private int mVisibilityEasingType = GeneralEasing.CUBIC_ACCELERATE;
47 
48     private float mP = 0f;
49     private float mVp = 0f;
50 
51     @NonNull
52     private FloatAnimation mMotionEasing =
53             new FloatAnimation(mMotionEasingType, mDuration / 1000f, null, 0f, Float.NaN);
54 
55     @NonNull
56     private FloatAnimation mVisibilityEasing =
57             new FloatAnimation(
58                     mVisibilityEasingType, mDurationVisibilityChange / 1000f, null, 0f, Float.NaN);
59 
60     private ParticleAnimation mParticleAnimation;
61 
AnimateMeasure( long startTime, @NonNull Component component, @NonNull ComponentMeasure original, @NonNull ComponentMeasure target, float duration, float durationVisibilityChange, @NonNull AnimationSpec.ANIMATION enterAnimation, @NonNull AnimationSpec.ANIMATION exitAnimation, int motionEasingType, int visibilityEasingType)62     public AnimateMeasure(
63             long startTime,
64             @NonNull Component component,
65             @NonNull ComponentMeasure original,
66             @NonNull ComponentMeasure target,
67             float duration,
68             float durationVisibilityChange,
69             @NonNull AnimationSpec.ANIMATION enterAnimation,
70             @NonNull AnimationSpec.ANIMATION exitAnimation,
71             int motionEasingType,
72             int visibilityEasingType) {
73         this.mStartTime = startTime;
74         this.mComponent = component;
75         this.mOriginal = original;
76         this.mTarget = target;
77         this.mDuration = duration;
78         this.mDurationVisibilityChange = durationVisibilityChange;
79         this.mEnterAnimation = enterAnimation;
80         this.mExitAnimation = exitAnimation;
81         this.mMotionEasingType = motionEasingType;
82         this.mVisibilityEasingType = visibilityEasingType;
83 
84         float motionDuration = mDuration / 1000f;
85         float visibilityDuration = mDurationVisibilityChange / 1000f;
86 
87         mMotionEasing = new FloatAnimation(mMotionEasingType, motionDuration, null, 0f, Float.NaN);
88         mVisibilityEasing =
89                 new FloatAnimation(mVisibilityEasingType, visibilityDuration, null, 0f, Float.NaN);
90 
91         mMotionEasing.setTargetValue(1f);
92         mVisibilityEasing.setTargetValue(1f);
93 
94         component.mVisibility = target.getVisibility();
95     }
96 
97     /**
98      * Update the current bounds/visibility/etc given the current time
99      *
100      * @param currentTime the time we use to evaluate the animation
101      */
update(long currentTime)102     public void update(long currentTime) {
103         long elapsed = currentTime - mStartTime;
104         float motionProgress = elapsed / (float) mDuration;
105         float visibilityProgress = elapsed / (float) mDurationVisibilityChange;
106         mP = mMotionEasing.get(motionProgress);
107         mVp = mVisibilityEasing.get(visibilityProgress);
108     }
109 
110     @NonNull public PaintBundle paint = new PaintBundle();
111 
112     /**
113      * Apply the layout portion of the animation if any
114      *
115      * @param context
116      */
apply(@onNull RemoteContext context)117     public void apply(@NonNull RemoteContext context) {
118         update(context.currentTime);
119         mComponent.setX(getX());
120         mComponent.setY(getY());
121         mComponent.setWidth(getWidth());
122         mComponent.setHeight(getHeight());
123         mComponent.updateVariables(context);
124 
125         float w = mComponent.getWidth();
126         float h = mComponent.getHeight();
127         for (Operation op : mComponent.mList) {
128             if (op instanceof PaddingModifierOperation) {
129                 PaddingModifierOperation pop = (PaddingModifierOperation) op;
130                 w -= pop.getLeft() + pop.getRight();
131                 h -= pop.getTop() + pop.getBottom();
132             }
133             if (op instanceof DecoratorComponent) {
134                 ((DecoratorComponent) op).layout(context, mComponent, w, h);
135             }
136         }
137     }
138 
139     /**
140      * Paint the transition animation for the component owned
141      *
142      * @param context
143      */
paint(@onNull PaintContext context)144     public void paint(@NonNull PaintContext context) {
145         if (mOriginal.getVisibility() != mTarget.getVisibility()) {
146             if (mTarget.isGone()) {
147                 switch (mExitAnimation) {
148                     case PARTICLE:
149                         // particleAnimation(context, component, original, target, vp)
150                         if (mParticleAnimation == null) {
151                             mParticleAnimation = new ParticleAnimation();
152                         }
153                         mParticleAnimation.animate(context, mComponent, mOriginal, mTarget, mVp);
154                         break;
155                     case FADE_OUT:
156                         context.save();
157                         context.savePaint();
158                         paint.reset();
159                         paint.setColor(0f, 0f, 0f, 1f - mVp);
160                         context.applyPaint(paint);
161                         context.saveLayer(
162                                 mComponent.getX(),
163                                 mComponent.getY(),
164                                 mComponent.getWidth(),
165                                 mComponent.getHeight());
166                         mComponent.paintingComponent(context);
167                         context.restore();
168                         context.restorePaint();
169                         context.restore();
170                         break;
171                     case SLIDE_LEFT:
172                         context.save();
173                         context.translate(-mVp * mComponent.getParent().getWidth(), 0f);
174                         context.saveLayer(
175                                 mComponent.getX(),
176                                 mComponent.getY(),
177                                 mComponent.getWidth(),
178                                 mComponent.getHeight());
179                         mComponent.paintingComponent(context);
180                         context.restore();
181                         context.restore();
182                         break;
183                     case SLIDE_RIGHT:
184                         context.save();
185                         context.savePaint();
186                         paint.reset();
187                         paint.setColor(0f, 0f, 0f, 1f);
188                         context.applyPaint(paint);
189                         context.translate(mVp * mComponent.getParent().getWidth(), 0f);
190                         context.saveLayer(
191                                 mComponent.getX(),
192                                 mComponent.getY(),
193                                 mComponent.getWidth(),
194                                 mComponent.getHeight());
195                         mComponent.paintingComponent(context);
196                         context.restore();
197                         context.restorePaint();
198                         context.restore();
199                         break;
200                     case SLIDE_TOP:
201                         context.save();
202                         context.translate(0f, -mVp * mComponent.getParent().getHeight());
203                         context.saveLayer(
204                                 mComponent.getX(),
205                                 mComponent.getY(),
206                                 mComponent.getWidth(),
207                                 mComponent.getHeight());
208                         mComponent.paintingComponent(context);
209                         context.restore();
210                         context.restore();
211                         break;
212                     case SLIDE_BOTTOM:
213                         context.save();
214                         context.translate(0f, mVp * mComponent.getParent().getHeight());
215                         context.saveLayer(
216                                 mComponent.getX(),
217                                 mComponent.getY(),
218                                 mComponent.getWidth(),
219                                 mComponent.getHeight());
220                         mComponent.paintingComponent(context);
221                         context.restore();
222                         context.restore();
223                         break;
224                     default:
225                         //            particleAnimation(context, component, original, target, vp)
226                         if (mParticleAnimation == null) {
227                             mParticleAnimation = new ParticleAnimation();
228                         }
229                         mParticleAnimation.animate(context, mComponent, mOriginal, mTarget, mVp);
230                         break;
231                 }
232             } else if (mOriginal.isGone() && mTarget.isVisible()) {
233                 switch (mEnterAnimation) {
234                     case ROTATE:
235                         float px = mTarget.getX() + mTarget.getW() / 2f;
236                         float py = mTarget.getY() + mTarget.getH() / 2f;
237 
238                         context.save();
239                         context.savePaint();
240                         context.matrixRotate(mVp * 360f, px, py);
241                         context.matrixScale(1f * mVp, 1f * mVp, px, py);
242                         paint.reset();
243                         paint.setColor(0f, 0f, 0f, mVp);
244                         context.applyPaint(paint);
245                         context.saveLayer(
246                                 mComponent.getX(),
247                                 mComponent.getY(),
248                                 mComponent.getWidth(),
249                                 mComponent.getHeight());
250                         mComponent.paintingComponent(context);
251                         context.restore();
252                         context.restorePaint();
253                         context.restore();
254                         break;
255                     case FADE_IN:
256                         context.save();
257                         context.savePaint();
258                         paint.reset();
259                         paint.setColor(0f, 0f, 0f, mVp);
260                         context.applyPaint(paint);
261                         context.saveLayer(
262                                 mComponent.getX(),
263                                 mComponent.getY(),
264                                 mComponent.getWidth(),
265                                 mComponent.getHeight());
266                         mComponent.paintingComponent(context);
267                         context.restore();
268                         context.restorePaint();
269                         context.restore();
270                         break;
271                     case SLIDE_LEFT:
272                         context.save();
273                         context.translate((1f - mVp) * mComponent.getParent().getWidth(), 0f);
274                         context.saveLayer(
275                                 mComponent.getX(),
276                                 mComponent.getY(),
277                                 mComponent.getWidth(),
278                                 mComponent.getHeight());
279                         mComponent.paintingComponent(context);
280                         context.restore();
281                         context.restore();
282                         break;
283                     case SLIDE_RIGHT:
284                         context.save();
285                         context.translate(-(1f - mVp) * mComponent.getParent().getWidth(), 0f);
286                         context.saveLayer(
287                                 mComponent.getX(),
288                                 mComponent.getY(),
289                                 mComponent.getWidth(),
290                                 mComponent.getHeight());
291                         mComponent.paintingComponent(context);
292                         context.restore();
293                         context.restore();
294                         break;
295                     case SLIDE_TOP:
296                         context.save();
297                         context.translate(0f, (1f - mVp) * mComponent.getParent().getHeight());
298                         context.saveLayer(
299                                 mComponent.getX(),
300                                 mComponent.getY(),
301                                 mComponent.getWidth(),
302                                 mComponent.getHeight());
303                         mComponent.paintingComponent(context);
304                         context.restore();
305                         context.restore();
306                         break;
307                     case SLIDE_BOTTOM:
308                         context.save();
309                         context.translate(0f, -(1f - mVp) * mComponent.getParent().getHeight());
310                         context.saveLayer(
311                                 mComponent.getX(),
312                                 mComponent.getY(),
313                                 mComponent.getWidth(),
314                                 mComponent.getHeight());
315                         mComponent.paintingComponent(context);
316                         context.restore();
317                         context.restore();
318                         break;
319                     default:
320                         break;
321                 }
322             } else {
323                 mComponent.paintingComponent(context);
324             }
325         } else if (mTarget.isVisible()) {
326             mComponent.paintingComponent(context);
327         }
328 
329         if (mP >= 1f && mVp >= 1f) {
330             mComponent.mVisibility = mTarget.getVisibility();
331         }
332     }
333 
isDone()334     public boolean isDone() {
335         return mP >= 1f && mVp >= 1f;
336     }
337 
getX()338     public float getX() {
339         return mOriginal.getX() * (1 - mP) + mTarget.getX() * mP;
340     }
341 
getY()342     public float getY() {
343         return mOriginal.getY() * (1 - mP) + mTarget.getY() * mP;
344     }
345 
getWidth()346     public float getWidth() {
347         return mOriginal.getW() * (1 - mP) + mTarget.getW() * mP;
348     }
349 
getHeight()350     public float getHeight() {
351         return mOriginal.getH() * (1 - mP) + mTarget.getH() * mP;
352     }
353 
354     /**
355      * Returns the visibility for this measure
356      *
357      * @return the current visibility (possibly interpolated)
358      */
getVisibility()359     public float getVisibility() {
360         if (mOriginal.getVisibility() == mTarget.getVisibility()) {
361             return 1f;
362         } else if (mTarget.isVisible()) {
363             return mVp;
364         } else {
365             return 1 - mVp;
366         }
367     }
368 
369     /**
370      * Set the target values from the given measure
371      *
372      * @param measure the target measure
373      * @param currentTime the current time
374      */
updateTarget(@onNull ComponentMeasure measure, long currentTime)375     public void updateTarget(@NonNull ComponentMeasure measure, long currentTime) {
376         mOriginal.setX(getX());
377         mOriginal.setY(getY());
378         mOriginal.setW(getWidth());
379         mOriginal.setH(getHeight());
380         float targetX = mTarget.getX();
381         float targetY = mTarget.getY();
382         float targetW = mTarget.getW();
383         float targetH = mTarget.getH();
384         int targetVisibility = mTarget.getVisibility();
385         if (targetX != measure.getX()
386                 || targetY != measure.getY()
387                 || targetW != measure.getW()
388                 || targetH != measure.getH()
389                 || targetVisibility != measure.getVisibility()) {
390             mTarget.setX(measure.getX());
391             mTarget.setY(measure.getY());
392             mTarget.setW(measure.getW());
393             mTarget.setH(measure.getH());
394             mTarget.setVisibility(measure.getVisibility());
395             // We shouldn't reset the leftover animation time here
396             // 1/ if we are eg fading out a component, and an updateTarget comes on, we don't want
397             //    to restart the full animation time
398             // 2/ if no visibility change but quick updates come in (eg live resize) it seems
399             //    better as well to not restart the animation time and only allows the original
400             //    time to wrap up
401             // mStartTime = currentTime;
402         }
403     }
404 }
405