From b9610108d5b8e5ed9908071c8a07587e1cab8ea7 Mon Sep 17 00:00:00 2001 From: Lucas Dupin Date: Mon, 15 Mar 2021 23:45:33 -0700 Subject: [PATCH 1/2] Fix issue where ripple would move The correct behavior is for the circle to move inside the RenderNode bounds. The RenderNode itself should not move at all. Fixes: 182174260 Test: visual Change-Id: I254138cada2586312530122fcb9ba4518d864cdf --- .../drawable/RippleAnimationSession.java | 45 ++++--------------- .../graphics/drawable/RippleDrawable.java | 8 ++-- .../graphics/drawable/RippleShader.java | 10 ++++- 3 files changed, 20 insertions(+), 43 deletions(-) diff --git a/graphics/java/android/graphics/drawable/RippleAnimationSession.java b/graphics/java/android/graphics/drawable/RippleAnimationSession.java index f3bf63bb3660d..877d07f0d9284 100644 --- a/graphics/java/android/graphics/drawable/RippleAnimationSession.java +++ b/graphics/java/android/graphics/drawable/RippleAnimationSession.java @@ -38,8 +38,7 @@ */ public final class RippleAnimationSession { private static final String TAG = "RippleAnimationSession"; - private static final int ENTER_ANIM_DURATION = 300; - private static final int SLIDE_ANIM_DURATION = 450; + private static final int ENTER_ANIM_DURATION = 450; private static final int EXIT_ANIM_DURATION = 300; private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); private static final TimeInterpolator PATH_INTERPOLATOR = @@ -50,16 +49,13 @@ public final class RippleAnimationSession { private Runnable mOnUpdate; private long mStartTime; private boolean mForceSoftware; - private final float mWidth, mHeight; private final ValueAnimator mSparkle = ValueAnimator.ofFloat(0, 1); private final ArraySet mActiveAnimations = new ArraySet<>(3); RippleAnimationSession(@NonNull AnimationProperties properties, - boolean forceSoftware, float width, float height) { + boolean forceSoftware) { mProperties = properties; mForceSoftware = forceSoftware; - mWidth = width; - mHeight = height; mSparkle.addUpdateListener(anim -> { final long now = AnimationUtils.currentAnimationTimeMillis(); @@ -134,7 +130,7 @@ public void onAnimationEnd(Animator animation) { private long computeDelay() { final long timePassed = AnimationUtils.currentAnimationTimeMillis() - mStartTime; - return Math.max((long) SLIDE_ANIM_DURATION - timePassed, 0); + return Math.max((long) ENTER_ANIM_DURATION - timePassed, 0); } private void notifyUpdate() { @@ -175,54 +171,29 @@ private void enterHardware(RecordingCanvas canvas) { props = getCanvasProperties(); RenderNodeAnimator expand = new RenderNodeAnimator(props.getProgress(), .5f); - RenderNodeAnimator slideX = - new RenderNodeAnimator(props.getX(), mWidth / 2); - RenderNodeAnimator slideY = - new RenderNodeAnimator(props.getY(), mHeight / 2); expand.setTarget(canvas); - slideX.setTarget(canvas); - slideY.setTarget(canvas); - startAnimation(expand, slideX, slideY); + startAnimation(expand); } - private void startAnimation(Animator expand, - Animator slideX, Animator slideY) { - expand.setDuration(SLIDE_ANIM_DURATION); - slideX.setDuration(SLIDE_ANIM_DURATION); - slideY.setDuration(SLIDE_ANIM_DURATION); - slideX.addListener(new AnimatorListener(this)); + private void startAnimation(Animator expand) { + expand.setDuration(ENTER_ANIM_DURATION); + expand.addListener(new AnimatorListener(this)); expand.setInterpolator(LINEAR_INTERPOLATOR); - slideX.setInterpolator(PATH_INTERPOLATOR); - slideY.setInterpolator(PATH_INTERPOLATOR); expand.start(); - slideX.start(); - slideY.start(); if (!mSparkle.isRunning()) { mSparkle.start(); mActiveAnimations.add(mSparkle); } mActiveAnimations.add(expand); - mActiveAnimations.add(slideX); - mActiveAnimations.add(slideY); } private void enterSoftware() { ValueAnimator expand = ValueAnimator.ofFloat(0f, 0.5f); - ValueAnimator slideX = ValueAnimator.ofFloat( - mProperties.getX(), mWidth / 2); - ValueAnimator slideY = ValueAnimator.ofFloat( - mProperties.getY(), mHeight / 2); expand.addUpdateListener(updatedAnimation -> { notifyUpdate(); mProperties.getShader().setProgress((Float) expand.getAnimatedValue()); }); - slideX.addUpdateListener(anim -> { - float x = (float) slideX.getAnimatedValue(); - float y = (float) slideY.getAnimatedValue(); - mProperties.setOrigin(x, y); - mProperties.getShader().setOrigin(x, y); - }); - startAnimation(expand, slideX, slideY); + startAnimation(expand); } @NonNull AnimationProperties getProperties() { diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index d6bbee90d73b0..c6d996234fb32 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -842,7 +842,7 @@ private void drawPatterned(@NonNull Canvas canvas) { if (shouldAnimate && mRunningAnimations.size() <= MAX_RIPPLES) { RippleAnimationSession.AnimationProperties properties = createAnimationProperties(x, y, w, h); - mRunningAnimations.add(new RippleAnimationSession(properties, !useCanvasProps, w, h) + mRunningAnimations.add(new RippleAnimationSession(properties, !useCanvasProps) .setOnAnimationUpdated(() -> invalidateSelf(false)) .setOnSessionEnd(session -> { mRunningAnimations.remove(session); @@ -912,14 +912,14 @@ private RippleAnimationSession.AnimationProperties createAnimation ? mState.mColor.getColorForState(getState(), Color.BLACK) : mMaskColorFilter.getColor(); shader.setColor(color); - shader.setOrigin(x, y); + shader.setOrigin(w / 2, y / 2); + shader.setTouch(x, y); shader.setResolution(w, h); shader.setSecondsOffset(0); shader.setRadius(radius); shader.setProgress(.0f); properties = new RippleAnimationSession.AnimationProperties<>( - x, y, radius, p, 0f, - shader); + w / 2, h / 2, radius, p, 0f, shader); if (mMaskShader == null) { shader.setShader(null); } else { diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java index 657a32c1ac467..8eddbea4d1f86 100644 --- a/graphics/java/android/graphics/drawable/RippleShader.java +++ b/graphics/java/android/graphics/drawable/RippleShader.java @@ -23,6 +23,7 @@ final class RippleShader extends RuntimeShader { private static final String SHADER_UNIFORMS = "uniform vec2 in_origin;\n" + + "uniform vec2 in_touch;\n" + "uniform float in_progress;\n" + "uniform float in_maxRadius;\n" + "uniform vec2 in_resolution;\n" @@ -79,12 +80,13 @@ final class RippleShader extends RuntimeShader { + " float fadeIn = subProgress(0., 0.175, in_progress);\n" + " float fadeOutNoise = subProgress(0.375, 1., in_progress);\n" + " float fadeOutRipple = subProgress(0.375, 0.75, in_progress);\n" - + " float ring = getRingMask(p, in_origin, in_maxRadius, fadeIn);\n" + + " vec2 center = mix(in_touch, in_origin, fadeIn);\n" + + " float ring = getRingMask(p, center, in_maxRadius, fadeIn);\n" + " float alpha = min(fadeIn, 1. - fadeOutNoise);\n" + " float sparkle = sparkles(p, in_progress * 0.25 + in_secondsOffset)\n" + " * ring * alpha;\n" + " float fade = min(fadeIn, 1.-fadeOutRipple);\n" - + " vec4 circle = in_color * (softCircle(p, in_origin, in_maxRadius " + + " vec4 circle = in_color * (softCircle(p, center, in_maxRadius " + " * fadeIn, 0.2) * fade);\n" + " float mask = in_hasMask == 1. ? sample(in_shader).a > 0. ? 1. : 0. : 1.;\n" + " return mix(circle, vec4(sparkle), sparkle) * mask;\n" @@ -117,6 +119,10 @@ public void setOrigin(float x, float y) { setUniform("in_origin", new float[] {x, y}); } + public void setTouch(float x, float y) { + setUniform("in_touch", new float[] {x, y}); + } + public void setProgress(float progress) { setUniform("in_progress", progress); } From 0feb52d5a9c7d6c7b87a2da9b2a8fef16aa6c378 Mon Sep 17 00:00:00 2001 From: Lucas Dupin Date: Tue, 16 Mar 2021 00:37:48 -0700 Subject: [PATCH 2/2] Slow down sparkles and make them more sparse Sparkles were moving too fast, and were also very dense. They were looking like TV static. Test: visual Bug: 182173639 Change-Id: I9dc1de92e7cc9d56f999a9719f1fae8ff41404c5 --- .../graphics/drawable/RippleAnimationSession.java | 14 ++++---------- .../android/graphics/drawable/RippleDrawable.java | 2 +- .../android/graphics/drawable/RippleShader.java | 13 ++++++------- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/graphics/java/android/graphics/drawable/RippleAnimationSession.java b/graphics/java/android/graphics/drawable/RippleAnimationSession.java index 877d07f0d9284..b57f7af14a0e8 100644 --- a/graphics/java/android/graphics/drawable/RippleAnimationSession.java +++ b/graphics/java/android/graphics/drawable/RippleAnimationSession.java @@ -50,7 +50,6 @@ public final class RippleAnimationSession { private long mStartTime; private boolean mForceSoftware; private final ValueAnimator mSparkle = ValueAnimator.ofFloat(0, 1); - private final ArraySet mActiveAnimations = new ArraySet<>(3); RippleAnimationSession(@NonNull AnimationProperties properties, boolean forceSoftware) { @@ -60,12 +59,11 @@ public final class RippleAnimationSession { mSparkle.addUpdateListener(anim -> { final long now = AnimationUtils.currentAnimationTimeMillis(); final long elapsed = now - mStartTime - ENTER_ANIM_DURATION; - final float phase = (float) elapsed / 1000f; - mProperties.getShader().setSecondsOffset(phase); + final float phase = (float) elapsed / 30000f; + mProperties.getShader().setNoisePhase(phase); notifyUpdate(); }); mSparkle.setDuration(ENTER_ANIM_DURATION); - mSparkle.setStartDelay(ENTER_ANIM_DURATION); mSparkle.setInterpolator(LINEAR_INTERPOLATOR); mSparkle.setRepeatCount(ValueAnimator.INFINITE); } @@ -81,7 +79,6 @@ public final class RippleAnimationSession { } @NonNull RippleAnimationSession exit(Canvas canvas) { - mSparkle.end(); if (isHwAccelerated(canvas)) exitHardware((RecordingCanvas) canvas); else exitSoftware(); return this; @@ -89,7 +86,6 @@ public final class RippleAnimationSession { private void onAnimationEnd(Animator anim) { notifyUpdate(); - mActiveAnimations.remove(anim); } @NonNull RippleAnimationSession setOnSessionEnd( @@ -119,13 +115,13 @@ private void exitSoftware() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); + mSparkle.end(); Consumer onEnd = mOnSessionEnd; if (onEnd != null) onEnd.accept(RippleAnimationSession.this); } }); expand.setInterpolator(LINEAR_INTERPOLATOR); expand.start(); - mActiveAnimations.add(expand); } private long computeDelay() { @@ -153,6 +149,7 @@ private void exitHardware(RecordingCanvas canvas) { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); + mSparkle.end(); Consumer onEnd = mOnSessionEnd; if (onEnd != null) onEnd.accept(RippleAnimationSession.this); } @@ -163,7 +160,6 @@ public void onAnimationEnd(Animator animation) { long delay = computeDelay(); exit.setStartDelay(delay); exit.start(); - mActiveAnimations.add(exit); } private void enterHardware(RecordingCanvas canvas) { @@ -182,9 +178,7 @@ private void startAnimation(Animator expand) { expand.start(); if (!mSparkle.isRunning()) { mSparkle.start(); - mActiveAnimations.add(mSparkle); } - mActiveAnimations.add(expand); } private void enterSoftware() { diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index c6d996234fb32..fb2b9b343f0ad 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -915,7 +915,7 @@ private RippleAnimationSession.AnimationProperties createAnimation shader.setOrigin(w / 2, y / 2); shader.setTouch(x, y); shader.setResolution(w, h); - shader.setSecondsOffset(0); + shader.setNoisePhase(0); shader.setRadius(radius); shader.setProgress(.0f); properties = new RippleAnimationSession.AnimationProperties<>( diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java index 8eddbea4d1f86..ea9ba325a826c 100644 --- a/graphics/java/android/graphics/drawable/RippleShader.java +++ b/graphics/java/android/graphics/drawable/RippleShader.java @@ -28,7 +28,7 @@ final class RippleShader extends RuntimeShader { + "uniform float in_maxRadius;\n" + "uniform vec2 in_resolution;\n" + "uniform float in_hasMask;\n" - + "uniform float in_secondsOffset;\n" + + "uniform float in_noisePhase;\n" + "uniform vec4 in_color;\n" + "uniform shader in_shader;\n"; private static final String SHADER_LIB = @@ -49,7 +49,7 @@ final class RippleShader extends RuntimeShader { + " float s = 0.0;\n" + " for (float i = 0; i < 4; i += 1) {\n" + " float l = i * 0.25;\n" - + " float h = l + 0.025;\n" + + " float h = l + 0.005;\n" + " float o = abs(sin(0.1 * PI * (t + i)));\n" + " s += threshold(n + o, l, h);\n" + " }\n" @@ -83,9 +83,8 @@ final class RippleShader extends RuntimeShader { + " vec2 center = mix(in_touch, in_origin, fadeIn);\n" + " float ring = getRingMask(p, center, in_maxRadius, fadeIn);\n" + " float alpha = min(fadeIn, 1. - fadeOutNoise);\n" - + " float sparkle = sparkles(p, in_progress * 0.25 + in_secondsOffset)\n" - + " * ring * alpha;\n" - + " float fade = min(fadeIn, 1.-fadeOutRipple);\n" + + " float sparkle = sparkles(p, in_noisePhase) * ring * alpha;\n" + + " float fade = min(fadeIn, 1. - fadeOutRipple);\n" + " vec4 circle = in_color * (softCircle(p, center, in_maxRadius " + " * fadeIn, 0.2) * fade);\n" + " float mask = in_hasMask == 1. ? sample(in_shader).a > 0. ? 1. : 0. : 1.;\n" @@ -111,8 +110,8 @@ public void setRadius(float radius) { /** * Continuous offset used as noise phase. */ - public void setSecondsOffset(float t) { - setUniform("in_secondsOffset", t); + public void setNoisePhase(float t) { + setUniform("in_noisePhase", t); } public void setOrigin(float x, float y) {