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