From f1de89b16cae490c52b7dfa376d30694c4123869 Mon Sep 17 00:00:00 2001 From: George Mount Date: Wed, 10 Feb 2021 18:32:58 +0000 Subject: [PATCH] Placeholder implementation of stretch overscroll using offset Temporarily use an offset RenderEffect for the stretch overscroll. This makes it possible to visually validate the effect and start writing CTS tests to ensure it is functioning. Bug: 178807038 Test: Iab32812bfa626ae6932f52c1a9af78069b5dc6ac Change-Id: I5b0da0e669d36f4149cd58b4b3aff149a1fa7e14 --- core/java/android/widget/EdgeEffect.java | 80 +++++++++++++++++++----- 1 file changed, 64 insertions(+), 16 deletions(-) diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java index 681fd147a51f9..1b62266c12e21 100644 --- a/core/java/android/widget/EdgeEffect.java +++ b/core/java/android/widget/EdgeEffect.java @@ -25,8 +25,12 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.BlendMode; import android.graphics.Canvas; +import android.graphics.Matrix; import android.graphics.Paint; +import android.graphics.RecordingCanvas; import android.graphics.Rect; +import android.graphics.RenderEffect; +import android.graphics.RenderNode; import android.os.Build; import android.util.AttributeSet; import android.view.animation.AnimationUtils; @@ -149,6 +153,8 @@ public class EdgeEffect { private float mDisplacement = 0.5f; private float mTargetDisplacement = 0.5f; private @EdgeEffectType int mEdgeEffectType = TYPE_GLOW; + private Matrix mTmpMatrix = null; + private float[] mTmpPoints = null; /** * Construct a new EdgeEffect with a theme appropriate for the provided context. @@ -250,11 +256,18 @@ public class EdgeEffect { public void onPull(float deltaDistance, float displacement) { final long now = AnimationUtils.currentAnimationTimeMillis(); mTargetDisplacement = displacement; - if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) { + if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration + && mEdgeEffectType == TYPE_GLOW) { return; } if (mState != STATE_PULL) { - mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY); + if (mEdgeEffectType == TYPE_STRETCH) { + // Restore the mPullDistance to the fraction it is currently showing -- we want + // to "catch" the current stretch value. + mPullDistance = mDistance; + } else { + mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY); + } } mState = STATE_PULL; @@ -313,6 +326,15 @@ public class EdgeEffect { public float onPullDistance(float deltaDistance, float displacement) { float finalDistance = Math.max(0f, deltaDistance + mDistance); float delta = finalDistance - mDistance; + if (delta == 0f && mDistance == 0f) { + return 0f; // No pull, don't do anything. + } + + if (mState != STATE_PULL && mState != STATE_PULL_DECAY && mEdgeEffectType == TYPE_GLOW) { + // Catch the edge glow in the middle of an animation. + mPullDistance = mDistance; + mState = STATE_PULL; + } onPull(delta, displacement); return delta; } @@ -466,33 +488,59 @@ public class EdgeEffect { * Draw into the provided canvas. Assumes that the canvas has been rotated * accordingly and the size has been set. The effect will be drawn the full * width of X=0 to X=width, beginning from Y=0 and extending to some factor < - * 1.f of height. + * 1.f of height. The {@link #TYPE_STRETCH} effect will only be visible on a + * hardware canvas, e.g. {@link RenderNode#beginRecording()}. * * @param canvas Canvas to draw into * @return true if drawing should continue beyond this frame to continue the * animation */ public boolean draw(Canvas canvas) { - update(); + if (mEdgeEffectType == TYPE_GLOW) { + update(); + final int count = canvas.save(); - final int count = canvas.save(); + final float centerX = mBounds.centerX(); + final float centerY = mBounds.height() - mRadius; - final float centerX = mBounds.centerX(); - final float centerY = mBounds.height() - mRadius; + canvas.scale(1.f, Math.min(mGlowScaleY, 1.f) * mBaseGlowScale, centerX, 0); - canvas.scale(1.f, Math.min(mGlowScaleY, 1.f) * mBaseGlowScale, centerX, 0); + final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f; + float translateX = mBounds.width() * displacement / 2; - final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f; - float translateX = mBounds.width() * displacement / 2; + canvas.clipRect(mBounds); + canvas.translate(translateX, 0); + mPaint.setAlpha((int) (0xff * mGlowAlpha)); + canvas.drawCircle(centerX, centerY, mRadius, mPaint); + canvas.restoreToCount(count); + } else if (canvas instanceof RecordingCanvas) { + if (mState != STATE_PULL) { + update(); + } + RecordingCanvas recordingCanvas = (RecordingCanvas) canvas; + if (mTmpMatrix == null) { + mTmpMatrix = new Matrix(); + mTmpPoints = new float[4]; + } + //noinspection deprecation + recordingCanvas.getMatrix(mTmpMatrix); + mTmpPoints[0] = mBounds.width() * mDisplacement; + mTmpPoints[1] = mDistance * mBounds.height(); + mTmpPoints[2] = mTmpPoints[0]; + mTmpPoints[3] = 0; + mTmpMatrix.mapPoints(mTmpPoints); + float x = mTmpPoints[0] - mTmpPoints[2]; + float y = mTmpPoints[1] - mTmpPoints[3]; - canvas.clipRect(mBounds); - canvas.translate(translateX, 0); - mPaint.setAlpha((int) (0xff * mGlowAlpha)); - canvas.drawCircle(centerX, centerY, mRadius, mPaint); - canvas.restoreToCount(count); + RenderNode renderNode = recordingCanvas.mNode; + + // TODO: use stretchy RenderEffect and use internal API when it is ready + // TODO: wrap existing RenderEffect + renderNode.setRenderEffect(RenderEffect.createOffsetEffect(x, y)); + } boolean oneLastFrame = false; - if (mState == STATE_RECEDE && mGlowScaleY == 0) { + if (mState == STATE_RECEDE && mDistance == 0) { mState = STATE_IDLE; oneLastFrame = true; }