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
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 681fd14..1b62266 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -25,8 +25,12 @@
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 @@
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 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 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 @@
* 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;
}