From 40d35794ca73a93d6f6fa590e34cb8ce7d70b587 Mon Sep 17 00:00:00 2001 From: Mindy Pereira Date: Mon, 20 Sep 2010 09:12:41 -0700 Subject: [PATCH] DO NOT MERGE Tweaked EdgeGlow to better match user expectations. Change-Id: I7864dd4d3e15a9dc11429d5d3268f9b078f3eaad Constants updated to match user testing. Recede no longer flashes out but degrades all the way to zero. Growth of the absorb flash is now quadratic as this better matches the "physics" of the user flicking the list. Size of the pull and absorb graphics changed to give the user a more dramatic indication of what has happened. --- core/java/android/widget/EdgeGlow.java | 106 ++++++++++++------ .../res/res/drawable-hdpi/overscroll_edge.png | Bin 4515 -> 3582 bytes 2 files changed, 69 insertions(+), 37 deletions(-) diff --git a/core/java/android/widget/EdgeGlow.java b/core/java/android/widget/EdgeGlow.java index 93222e1c00fc7..e24f49543496d 100644 --- a/core/java/android/widget/EdgeGlow.java +++ b/core/java/android/widget/EdgeGlow.java @@ -36,25 +36,26 @@ public class EdgeGlow { private static final int PULL_TIME = 250; // Time it will take for a pulled glow to decay to partial strength before release - private static final int PULL_DECAY_TIME = 1000; + private static final int PULL_DECAY_TIME = 10000; + private static final float MAX_ALPHA = 1.f; private static final float HELD_EDGE_ALPHA = 0.7f; private static final float HELD_EDGE_SCALE_Y = 0.5f; private static final float HELD_GLOW_ALPHA = 0.5f; private static final float HELD_GLOW_SCALE_Y = 0.5f; - private static final float MAX_GLOW_HEIGHT = 0.33f; + private static final float MAX_GLOW_HEIGHT = 3.f; - private static final float PULL_GLOW_BEGIN = 0.5f; + private static final float PULL_GLOW_BEGIN = 1.f; private static final float PULL_EDGE_BEGIN = 0.6f; // Minimum velocity that will be absorbed - private static final int MIN_VELOCITY = 750; - + private static final int MIN_VELOCITY = 100; + private static final float EPSILON = 0.001f; - private Drawable mEdge; - private Drawable mGlow; + private final Drawable mEdge; + private final Drawable mGlow; private int mWidth; private int mHeight; @@ -73,9 +74,9 @@ public class EdgeGlow { private float mGlowScaleYFinish; private long mStartTime; - private int mDuration; + private float mDuration; - private Interpolator mInterpolator; + private final Interpolator mInterpolator; private static final int STATE_IDLE = 0; private static final int STATE_PULL = 1; @@ -83,6 +84,17 @@ public class EdgeGlow { private static final int STATE_RECEDE = 3; private static final int STATE_PULL_DECAY = 4; + // How much dragging should effect the height of the edge image. + // Number determined by user testing. + private static final int PULL_DISTANCE_EDGE_FACTOR = 5; + + // How much dragging should effect the height of the glow image. + // Number determined by user testing. + private static final int PULL_DISTANCE_GLOW_FACTOR = 10; + + private static final int VELOCITY_EDGE_FACTOR = 8; + private static final int VELOCITY_GLOW_FACTOR = 16; + private int mState = STATE_IDLE; private float mPullDistance; @@ -109,6 +121,7 @@ public class EdgeGlow { /** * Call when the object is pulled by the user. + * * @param deltaDistance Change in distance since the last call */ public void onPull(float deltaDistance) { @@ -127,11 +140,12 @@ public class EdgeGlow { mPullDistance += deltaDistance; float distance = Math.abs(mPullDistance); - mEdgeAlpha = mEdgeAlphaStart = Math.max(PULL_EDGE_BEGIN, Math.min(distance, 1.f)); - mEdgeScaleY = mEdgeScaleYStart = Math.max(HELD_EDGE_SCALE_Y, Math.min(distance, 2.f)); + mEdgeAlpha = mEdgeAlphaStart = Math.max(PULL_EDGE_BEGIN, Math.min(distance, MAX_ALPHA)); + mEdgeScaleY = mEdgeScaleYStart = Math.max( + HELD_EDGE_SCALE_Y, Math.min(distance * PULL_DISTANCE_EDGE_FACTOR, 1.f)); - mGlowAlpha = mGlowAlphaStart = Math.max(0.5f, - Math.min(mGlowAlpha + Math.abs(deltaDistance), 1.f)); + mGlowAlpha = mGlowAlphaStart = Math.max( + 0.5f, Math.min(mGlowAlpha + Math.abs(deltaDistance), MAX_ALPHA)); float glowChange = Math.abs(deltaDistance); if (deltaDistance > 0 && mPullDistance < 0) { @@ -140,7 +154,8 @@ public class EdgeGlow { if (mPullDistance == 0) { mGlowScaleY = 0; } - mGlowScaleY = mGlowScaleYStart = Math.max(0, mGlowScaleY + glowChange * 2); + mGlowScaleY = mGlowScaleYStart = Math.max( + 0, mGlowScaleY + glowChange * PULL_DISTANCE_GLOW_FACTOR); mEdgeAlphaFinish = mEdgeAlpha; mEdgeScaleYFinish = mEdgeScaleY; @@ -165,9 +180,9 @@ public class EdgeGlow { mGlowScaleYStart = mGlowScaleY; mEdgeAlphaFinish = 0.f; - mEdgeScaleYFinish = 0.1f; + mEdgeScaleYFinish = 0.f; mGlowAlphaFinish = 0.f; - mGlowScaleYFinish = 0.1f; + mGlowScaleYFinish = 0.f; mStartTime = AnimationUtils.currentAnimationTimeMillis(); mDuration = RECEDE_TIME; @@ -175,6 +190,7 @@ public class EdgeGlow { /** * Call when the effect absorbs an impact at the given velocity. + * * @param velocity Velocity at impact in pixels per second. */ public void onAbsorb(int velocity) { @@ -182,27 +198,43 @@ public class EdgeGlow { velocity = Math.max(MIN_VELOCITY, Math.abs(velocity)); mStartTime = AnimationUtils.currentAnimationTimeMillis(); - mDuration = (int) (velocity * 0.03f); + mDuration = 0.1f + (velocity * 0.03f); + // The edge should always be at least partially visible, regardless + // of velocity. mEdgeAlphaStart = 0.5f; mEdgeScaleYStart = 0.2f; + // The glow depends more on the velocity, and therefore starts out + // nearly invisible. mGlowAlphaStart = 0.5f; mGlowScaleYStart = 0.f; - mEdgeAlphaFinish = Math.max(0, Math.min(velocity * 0.01f, 1)); + // Factor the velocity by 8. Testing on device shows this works best to + // reflect the strength of the user's scrolling. + mEdgeAlphaFinish = Math.max(0, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1)); + // Edge should never get larger than the size of its asset. mEdgeScaleYFinish = 1.f; - mGlowAlphaFinish = 1.f; - mGlowScaleYFinish = Math.min(velocity * 0.001f, 1); + + // Growth for the size of the glow should be quadratic to properly + // respond + // to a user's scrolling speed. The faster the scrolling speed, the more + // intense the effect should be for both the size and the saturation. + mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f), 1.75f); + // Alpha should change for the glow as well as size. + mGlowAlphaFinish = Math.max( + mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA)); } + /** - * 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, emitting from Y=0 and extending - * to some factor < 1.f of height. + * 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, emitting from Y=0 and extending to some factor < + * 1.f of height. * * @param canvas Canvas to draw into - * @return true if drawing should continue beyond this frame to continue the animation + * @return true if drawing should continue beyond this frame to continue the + * animation */ public boolean draw(Canvas canvas) { update(); @@ -213,15 +245,14 @@ public class EdgeGlow { final float distScale = (float) mHeight / mWidth; mGlow.setAlpha((int) (Math.max(0, Math.min(mGlowAlpha, 1)) * 255)); - mGlow.setBounds(0, 0, mWidth, (int) Math.min(glowHeight * mGlowScaleY * distScale * 0.6f, - mHeight * MAX_GLOW_HEIGHT)); + // Width of the image should be 3 * the width of the screen. + // Should start off screen to the left. + mGlow.setBounds(-mWidth, 0, mWidth * 2, (int) Math.min( + glowHeight * mGlowScaleY * distScale * 0.6f, mHeight * MAX_GLOW_HEIGHT)); mGlow.draw(canvas); mEdge.setAlpha((int) (Math.max(0, Math.min(mEdgeAlpha, 1)) * 255)); - mEdge.setBounds(0, - 0, - mWidth, - (int) (edgeHeight * mEdgeScaleY)); + mEdge.setBounds(0, 0, mWidth, (int) (edgeHeight * mEdgeScaleY)); mEdge.draw(canvas); return mState != STATE_IDLE; @@ -229,7 +260,7 @@ public class EdgeGlow { private void update() { final long time = AnimationUtils.currentAnimationTimeMillis(); - final float t = Math.min((float) (time - mStartTime) / mDuration, 1.f); + final float t = Math.min((time - mStartTime) / mDuration, 1.f); final float interp = mInterpolator.getInterpolation(t); @@ -251,7 +282,7 @@ public class EdgeGlow { mGlowScaleYStart = mGlowScaleY; mEdgeAlphaFinish = 0.f; - mEdgeScaleYFinish = 0.1f; + mEdgeScaleYFinish = mEdgeScaleY; mGlowAlphaFinish = 0.f; mGlowScaleYFinish = mGlowScaleY; break; @@ -265,10 +296,11 @@ public class EdgeGlow { mGlowAlphaStart = mGlowAlpha; mGlowScaleYStart = mGlowScaleY; - mEdgeAlphaFinish = Math.min(mEdgeAlphaStart, HELD_EDGE_ALPHA); - mEdgeScaleYFinish = Math.min(mEdgeScaleYStart, HELD_EDGE_SCALE_Y); - mGlowAlphaFinish = Math.min(mGlowAlphaStart, HELD_GLOW_ALPHA); - mGlowScaleYFinish = Math.min(mGlowScaleY, HELD_GLOW_SCALE_Y); + // After a pull, the glow should fade to nothing. + mEdgeAlphaFinish = 0.f; + mEdgeScaleYFinish = 0.f; + mGlowAlphaFinish = 0.f; + mGlowScaleYFinish = 0.f; break; case STATE_PULL_DECAY: // Do nothing; wait for release diff --git a/core/res/res/drawable-hdpi/overscroll_edge.png b/core/res/res/drawable-hdpi/overscroll_edge.png index f8e40ecfd7cd330029b9dc70758d38285e9e4888..5969093efc25365b76fc34621a5f52620bbd6965 100644 GIT binary patch delta 3580 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 Bj3fX6