1637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell/*
2637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * Copyright (C) 2010 The Android Open Source Project
3637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell *
4637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * Licensed under the Apache License, Version 2.0 (the "License");
5637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * you may not use this file except in compliance with the License.
6637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * You may obtain a copy of the License at
7637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell *
8637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell *      http://www.apache.org/licenses/LICENSE-2.0
9637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell *
10637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * Unless required by applicable law or agreed to in writing, software
11637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * distributed under the License is distributed on an "AS IS" BASIS,
12637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * See the License for the specific language governing permissions and
14637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * limitations under the License.
15637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell */
16637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
17637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powellpackage android.widget;
18637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
199d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guyimport android.graphics.Rect;
2089935e41c593a599e8955388b27fb926e60e5e94Adam Powellimport com.android.internal.R;
2189935e41c593a599e8955388b27fb926e60e5e94Adam Powell
224e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereiraimport android.content.Context;
2389935e41c593a599e8955388b27fb926e60e5e94Adam Powellimport android.content.res.Resources;
24637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powellimport android.graphics.Canvas;
25637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powellimport android.graphics.drawable.Drawable;
26637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powellimport android.view.animation.AnimationUtils;
27637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powellimport android.view.animation.DecelerateInterpolator;
28637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powellimport android.view.animation.Interpolator;
29637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
30637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell/**
3189935e41c593a599e8955388b27fb926e60e5e94Adam Powell * This class performs the graphical effect used at the edges of scrollable widgets
3289935e41c593a599e8955388b27fb926e60e5e94Adam Powell * when the user scrolls beyond the content bounds in 2D space.
3389935e41c593a599e8955388b27fb926e60e5e94Adam Powell *
3489935e41c593a599e8955388b27fb926e60e5e94Adam Powell * <p>EdgeEffect is stateful. Custom widgets using EdgeEffect should create an
3589935e41c593a599e8955388b27fb926e60e5e94Adam Powell * instance for each edge that should show the effect, feed it input data using
3689935e41c593a599e8955388b27fb926e60e5e94Adam Powell * the methods {@link #onAbsorb(int)}, {@link #onPull(float)}, and {@link #onRelease()},
3789935e41c593a599e8955388b27fb926e60e5e94Adam Powell * and draw the effect using {@link #draw(Canvas)} in the widget's overridden
3889935e41c593a599e8955388b27fb926e60e5e94Adam Powell * {@link android.view.View#draw(Canvas)} method. If {@link #isFinished()} returns
3989935e41c593a599e8955388b27fb926e60e5e94Adam Powell * false after drawing, the edge effect's animation is not yet complete and the widget
4089935e41c593a599e8955388b27fb926e60e5e94Adam Powell * should schedule another drawing pass to continue the animation.</p>
4189935e41c593a599e8955388b27fb926e60e5e94Adam Powell *
4289935e41c593a599e8955388b27fb926e60e5e94Adam Powell * <p>When drawing, widgets should draw their main content and child views first,
4389935e41c593a599e8955388b27fb926e60e5e94Adam Powell * usually by invoking <code>super.draw(canvas)</code> from an overridden <code>draw</code>
4489935e41c593a599e8955388b27fb926e60e5e94Adam Powell * method. (This will invoke onDraw and dispatch drawing to child views as needed.)
4589935e41c593a599e8955388b27fb926e60e5e94Adam Powell * The edge effect may then be drawn on top of the view's content using the
4689935e41c593a599e8955388b27fb926e60e5e94Adam Powell * {@link #draw(Canvas)} method.</p>
47637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell */
4889935e41c593a599e8955388b27fb926e60e5e94Adam Powellpublic class EdgeEffect {
499d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy    @SuppressWarnings("UnusedDeclaration")
5089935e41c593a599e8955388b27fb926e60e5e94Adam Powell    private static final String TAG = "EdgeEffect";
51637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
52637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    // Time it will take the effect to fully recede in ms
53637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int RECEDE_TIME = 1000;
54637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
5589935e41c593a599e8955388b27fb926e60e5e94Adam Powell    // Time it will take before a pulled glow begins receding in ms
56637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int PULL_TIME = 167;
57637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
5889935e41c593a599e8955388b27fb926e60e5e94Adam Powell    // Time it will take in ms for a pulled glow to decay to partial strength before release
59637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int PULL_DECAY_TIME = 1000;
60637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
61539ee8716b4f81260bab2e9f3dc5d88d81c99985Adam Powell    private static final float MAX_ALPHA = 1.f;
62637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final float HELD_EDGE_SCALE_Y = 0.5f;
63637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
644e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira    private static final float MAX_GLOW_HEIGHT = 4.f;
65637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
66637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final float PULL_GLOW_BEGIN = 1.f;
67637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final float PULL_EDGE_BEGIN = 0.6f;
68637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
69637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    // Minimum velocity that will be absorbed
70637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int MIN_VELOCITY = 100;
71637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
72637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final float EPSILON = 0.001f;
73637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
74637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private final Drawable mEdge;
75637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private final Drawable mGlow;
76637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private int mWidth;
77637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private int mHeight;
789d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy    private int mX;
799d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy    private int mY;
809d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy    private static final int MIN_WIDTH = 300;
814e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira    private final int mMinWidth;
82637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
83637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mEdgeAlpha;
84637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mEdgeScaleY;
85637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mGlowAlpha;
86637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mGlowScaleY;
87637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
88637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mEdgeAlphaStart;
89637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mEdgeAlphaFinish;
90637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mEdgeScaleYStart;
91637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mEdgeScaleYFinish;
92637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mGlowAlphaStart;
93637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mGlowAlphaFinish;
94637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mGlowScaleYStart;
95637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mGlowScaleYFinish;
96637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
97637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private long mStartTime;
98637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mDuration;
99637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
100637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private final Interpolator mInterpolator;
101637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
102637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int STATE_IDLE = 0;
103637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int STATE_PULL = 1;
104637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int STATE_ABSORB = 2;
105637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int STATE_RECEDE = 3;
106637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int STATE_PULL_DECAY = 4;
107637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
108637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    // How much dragging should effect the height of the edge image.
109637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    // Number determined by user testing.
1104e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira    private static final int PULL_DISTANCE_EDGE_FACTOR = 7;
111637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
112637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    // How much dragging should effect the height of the glow image.
113637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    // Number determined by user testing.
1144e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira    private static final int PULL_DISTANCE_GLOW_FACTOR = 7;
1154e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira    private static final float PULL_DISTANCE_ALPHA_GLOW_FACTOR = 1.1f;
116637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
117637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int VELOCITY_EDGE_FACTOR = 8;
118637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int VELOCITY_GLOW_FACTOR = 16;
119637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
120637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private int mState = STATE_IDLE;
121637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
122637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mPullDistance;
1239d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy
1249d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy    private final Rect mBounds = new Rect();
125637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
126a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy    private final int mEdgeHeight;
127a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy    private final int mGlowHeight;
128a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy    private final int mGlowWidth;
129a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy    private final int mMaxEffectHeight;
130a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy
13189935e41c593a599e8955388b27fb926e60e5e94Adam Powell    /**
13289935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * Construct a new EdgeEffect with a theme appropriate for the provided context.
13389935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * @param context Context used to provide theming and resource information for the EdgeEffect
13489935e41c593a599e8955388b27fb926e60e5e94Adam Powell     */
13589935e41c593a599e8955388b27fb926e60e5e94Adam Powell    public EdgeEffect(Context context) {
13689935e41c593a599e8955388b27fb926e60e5e94Adam Powell        final Resources res = context.getResources();
13789935e41c593a599e8955388b27fb926e60e5e94Adam Powell        mEdge = res.getDrawable(R.drawable.overscroll_edge);
13889935e41c593a599e8955388b27fb926e60e5e94Adam Powell        mGlow = res.getDrawable(R.drawable.overscroll_glow);
139637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
140a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy        mEdgeHeight = mEdge.getIntrinsicHeight();
141a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy        mGlowHeight = mGlow.getIntrinsicHeight();
142a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy        mGlowWidth = mGlow.getIntrinsicWidth();
143a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy
144a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy        mMaxEffectHeight = (int) (Math.min(
145a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy                mGlowHeight * MAX_GLOW_HEIGHT * mGlowHeight / mGlowWidth * 0.6f,
146a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy                mGlowHeight * MAX_GLOW_HEIGHT) + 0.5f);
147a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy
1481373a8eb581fe3c8e9a036e69042015f98a7e346Christopher Tate        mMinWidth = (int) (res.getDisplayMetrics().density * MIN_WIDTH + 0.5f);
149637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mInterpolator = new DecelerateInterpolator();
150637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    }
151637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
15289935e41c593a599e8955388b27fb926e60e5e94Adam Powell    /**
15389935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * Set the size of this edge effect in pixels.
15489935e41c593a599e8955388b27fb926e60e5e94Adam Powell     *
15589935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * @param width Effect width in pixels
15689935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * @param height Effect height in pixels
15789935e41c593a599e8955388b27fb926e60e5e94Adam Powell     */
158637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    public void setSize(int width, int height) {
159637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mWidth = width;
160637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mHeight = height;
161637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    }
162637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
16389935e41c593a599e8955388b27fb926e60e5e94Adam Powell    /**
1649d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy     * Set the position of this edge effect in pixels. This position is
165a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy     * only used by {@link #getBounds(boolean)}.
1669d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy     *
1679d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy     * @param x The position of the edge effect on the X axis
1689d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy     * @param y The position of the edge effect on the Y axis
1699d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy     */
1709d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy    void setPosition(int x, int y) {
1719d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy        mX = x;
1729d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy        mY = y;
1739d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy    }
1749d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy
1759d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy    /**
17689935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * Reports if this EdgeEffect's animation is finished. If this method returns false
17789935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * after a call to {@link #draw(Canvas)} the host widget should schedule another
17889935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * drawing pass to continue the animation.
17989935e41c593a599e8955388b27fb926e60e5e94Adam Powell     *
18089935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * @return true if animation is finished, false if drawing should continue on the next frame.
18189935e41c593a599e8955388b27fb926e60e5e94Adam Powell     */
182637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    public boolean isFinished() {
183637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        return mState == STATE_IDLE;
184637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    }
185637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
18689935e41c593a599e8955388b27fb926e60e5e94Adam Powell    /**
18789935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * Immediately finish the current animation.
18889935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * After this call {@link #isFinished()} will return true.
18989935e41c593a599e8955388b27fb926e60e5e94Adam Powell     */
190637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    public void finish() {
191637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mState = STATE_IDLE;
192637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    }
193637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
194637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    /**
19589935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * A view should call this when content is pulled away from an edge by the user.
19689935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * This will update the state of the current visual effect and its associated animation.
19789935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * The host view should always {@link android.view.View#invalidate()} after this
19889935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * and draw the results accordingly.
199637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     *
20089935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to
20189935e41c593a599e8955388b27fb926e60e5e94Adam Powell     *                      1.f (full length of the view) or negative values to express change
20289935e41c593a599e8955388b27fb926e60e5e94Adam Powell     *                      back toward the edge reached to initiate the effect.
203637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     */
204637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    public void onPull(float deltaDistance) {
205637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        final long now = AnimationUtils.currentAnimationTimeMillis();
206637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) {
207637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell            return;
208637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        }
209637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        if (mState != STATE_PULL) {
210637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell            mGlowScaleY = PULL_GLOW_BEGIN;
211637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        }
212637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mState = STATE_PULL;
213637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
214637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mStartTime = now;
215637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mDuration = PULL_TIME;
216637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
217637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mPullDistance += deltaDistance;
218637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        float distance = Math.abs(mPullDistance);
219637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
220637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeAlpha = mEdgeAlphaStart = Math.max(PULL_EDGE_BEGIN, Math.min(distance, MAX_ALPHA));
221637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeScaleY = mEdgeScaleYStart = Math.max(
222637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                HELD_EDGE_SCALE_Y, Math.min(distance * PULL_DISTANCE_EDGE_FACTOR, 1.f));
223637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
224637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA,
225637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                mGlowAlpha +
226637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                (Math.abs(deltaDistance) * PULL_DISTANCE_ALPHA_GLOW_FACTOR));
227637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
228637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        float glowChange = Math.abs(deltaDistance);
229637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        if (deltaDistance > 0 && mPullDistance < 0) {
230637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell            glowChange = -glowChange;
231637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        }
232637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        if (mPullDistance == 0) {
233637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell            mGlowScaleY = 0;
234637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        }
235637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
236637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // Do not allow glow to get larger than MAX_GLOW_HEIGHT.
237637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowScaleY = mGlowScaleYStart = Math.min(MAX_GLOW_HEIGHT, Math.max(
238637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                0, mGlowScaleY + glowChange * PULL_DISTANCE_GLOW_FACTOR));
239637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
240637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeAlphaFinish = mEdgeAlpha;
241637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeScaleYFinish = mEdgeScaleY;
242637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowAlphaFinish = mGlowAlpha;
243637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowScaleYFinish = mGlowScaleY;
244637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    }
245637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
246637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    /**
247637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     * Call when the object is released after being pulled.
24889935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * This will begin the "decay" phase of the effect. After calling this method
24989935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * the host view should {@link android.view.View#invalidate()} and thereby
25089935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * draw the results accordingly.
251637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     */
252637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    public void onRelease() {
253637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mPullDistance = 0;
254637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
255637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        if (mState != STATE_PULL && mState != STATE_PULL_DECAY) {
256637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell            return;
257637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        }
258637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
259637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mState = STATE_RECEDE;
260637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeAlphaStart = mEdgeAlpha;
261637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeScaleYStart = mEdgeScaleY;
262637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowAlphaStart = mGlowAlpha;
263637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowScaleYStart = mGlowScaleY;
264637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
265637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeAlphaFinish = 0.f;
266637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeScaleYFinish = 0.f;
267637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowAlphaFinish = 0.f;
268637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowScaleYFinish = 0.f;
269637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
270637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mStartTime = AnimationUtils.currentAnimationTimeMillis();
271637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mDuration = RECEDE_TIME;
272637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    }
273637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
274637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    /**
275637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     * Call when the effect absorbs an impact at the given velocity.
27689935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * Used when a fling reaches the scroll boundary.
27789935e41c593a599e8955388b27fb926e60e5e94Adam Powell     *
27889935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * <p>When using a {@link android.widget.Scroller} or {@link android.widget.OverScroller},
27989935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * the method <code>getCurrVelocity</code> will provide a reasonable approximation
28089935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * to use here.</p>
281637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     *
282637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     * @param velocity Velocity at impact in pixels per second.
283637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     */
284637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    public void onAbsorb(int velocity) {
285637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mState = STATE_ABSORB;
286637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        velocity = Math.max(MIN_VELOCITY, Math.abs(velocity));
287637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
288637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mStartTime = AnimationUtils.currentAnimationTimeMillis();
289637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mDuration = 0.1f + (velocity * 0.03f);
290637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
291637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // The edge should always be at least partially visible, regardless
292637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // of velocity.
293637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeAlphaStart = 0.f;
294637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeScaleY = mEdgeScaleYStart = 0.f;
295637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // The glow depends more on the velocity, and therefore starts out
296637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // nearly invisible.
297637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowAlphaStart = 0.5f;
298637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowScaleYStart = 0.f;
299637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
300637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // Factor the velocity by 8. Testing on device shows this works best to
301637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // reflect the strength of the user's scrolling.
302637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeAlphaFinish = Math.max(0, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1));
303637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // Edge should never get larger than the size of its asset.
304637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeScaleYFinish = Math.max(
305637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                HELD_EDGE_SCALE_Y, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1.f));
306637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
307637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // Growth for the size of the glow should be quadratic to properly
308637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // respond
309637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // to a user's scrolling speed. The faster the scrolling speed, the more
310637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // intense the effect should be for both the size and the saturation.
311637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f), 1.75f);
312637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // Alpha should change for the glow as well as size.
313637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowAlphaFinish = Math.max(
314637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
315637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    }
316637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
317637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
318637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    /**
319637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     * Draw into the provided canvas. Assumes that the canvas has been rotated
320637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     * accordingly and the size has been set. The effect will be drawn the full
32189935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * width of X=0 to X=width, beginning from Y=0 and extending to some factor <
322637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     * 1.f of height.
323637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     *
324637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     * @param canvas Canvas to draw into
325637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     * @return true if drawing should continue beyond this frame to continue the
326637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     *         animation
327637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     */
328637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    public boolean draw(Canvas canvas) {
329637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        update();
330637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
331637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlow.setAlpha((int) (Math.max(0, Math.min(mGlowAlpha, 1)) * 255));
332a5531d784b9b79fe3bf9b30d99e9c99ea3947c6dMindy Pereira
3334e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira        int glowBottom = (int) Math.min(
334a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy                mGlowHeight * mGlowScaleY * mGlowHeight / mGlowWidth * 0.6f,
335a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy                mGlowHeight * MAX_GLOW_HEIGHT);
3364e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira        if (mWidth < mMinWidth) {
3374e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira            // Center the glow and clip it.
338ffc4196c46419469f088ca23a0d0e31cf7979e30Jim Miller            int glowLeft = (mWidth - mMinWidth)/2;
3394e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira            mGlow.setBounds(glowLeft, 0, mWidth - glowLeft, glowBottom);
3404e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira        } else {
3414e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira            // Stretch the glow to fit.
3424e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira            mGlow.setBounds(0, 0, mWidth, glowBottom);
3434e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira        }
3444e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira
345637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlow.draw(canvas);
346637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
347637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdge.setAlpha((int) (Math.max(0, Math.min(mEdgeAlpha, 1)) * 255));
348a5531d784b9b79fe3bf9b30d99e9c99ea3947c6dMindy Pereira
349a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy        int edgeBottom = (int) (mEdgeHeight * mEdgeScaleY);
3504e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira        if (mWidth < mMinWidth) {
3514e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira            // Center the edge and clip it.
352ffc4196c46419469f088ca23a0d0e31cf7979e30Jim Miller            int edgeLeft = (mWidth - mMinWidth)/2;
3534e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira            mEdge.setBounds(edgeLeft, 0, mWidth - edgeLeft, edgeBottom);
3544e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira        } else {
3554e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira            // Stretch the edge to fit.
3564e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira            mEdge.setBounds(0, 0, mWidth, edgeBottom);
3574e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira        }
358637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdge.draw(canvas);
359637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
3609d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy        if (mState == STATE_RECEDE && glowBottom == 0 && edgeBottom == 0) {
3619d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy            mState = STATE_IDLE;
3629d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy        }
3639d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy
364637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        return mState != STATE_IDLE;
365637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    }
366637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
3679d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy    /**
3689d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy     * Returns the bounds of the edge effect.
369a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy     *
370a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy     * @hide
3719d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy     */
372a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy    public Rect getBounds(boolean reverse) {
373a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy        mBounds.set(0, 0, mWidth, mMaxEffectHeight);
374a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy        mBounds.offset(mX, mY - (reverse ? mMaxEffectHeight : 0));
375a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy
3769d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy        return mBounds;
3779d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy    }
3789d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy
379637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private void update() {
380637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        final long time = AnimationUtils.currentAnimationTimeMillis();
381637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        final float t = Math.min((time - mStartTime) / mDuration, 1.f);
382637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
383637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        final float interp = mInterpolator.getInterpolation(t);
384637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
385637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeAlpha = mEdgeAlphaStart + (mEdgeAlphaFinish - mEdgeAlphaStart) * interp;
386637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeScaleY = mEdgeScaleYStart + (mEdgeScaleYFinish - mEdgeScaleYStart) * interp;
387637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
388637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
389637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
390637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        if (t >= 1.f - EPSILON) {
391637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell            switch (mState) {
392637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                case STATE_ABSORB:
393637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mState = STATE_RECEDE;
394637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mStartTime = AnimationUtils.currentAnimationTimeMillis();
395637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mDuration = RECEDE_TIME;
396637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
397637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mEdgeAlphaStart = mEdgeAlpha;
398637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mEdgeScaleYStart = mEdgeScaleY;
399637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mGlowAlphaStart = mGlowAlpha;
400637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mGlowScaleYStart = mGlowScaleY;
401637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
402637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    // After absorb, the glow and edge should fade to nothing.
403637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mEdgeAlphaFinish = 0.f;
404637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mEdgeScaleYFinish = 0.f;
405637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mGlowAlphaFinish = 0.f;
406637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mGlowScaleYFinish = 0.f;
407637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    break;
408637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                case STATE_PULL:
409637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mState = STATE_PULL_DECAY;
410637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mStartTime = AnimationUtils.currentAnimationTimeMillis();
411637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mDuration = PULL_DECAY_TIME;
412637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
413637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mEdgeAlphaStart = mEdgeAlpha;
414637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mEdgeScaleYStart = mEdgeScaleY;
415637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mGlowAlphaStart = mGlowAlpha;
416637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mGlowScaleYStart = mGlowScaleY;
417637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
418637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    // After pull, the glow and edge should fade to nothing.
419637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mEdgeAlphaFinish = 0.f;
420637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mEdgeScaleYFinish = 0.f;
421637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mGlowAlphaFinish = 0.f;
422637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mGlowScaleYFinish = 0.f;
423637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    break;
424637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                case STATE_PULL_DECAY:
425637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    // When receding, we want edge to decrease more slowly
426637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    // than the glow.
427637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    float factor = mGlowScaleYFinish != 0 ? 1
428637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                            / (mGlowScaleYFinish * mGlowScaleYFinish)
429637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                            : Float.MAX_VALUE;
430637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mEdgeScaleY = mEdgeScaleYStart +
431637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                        (mEdgeScaleYFinish - mEdgeScaleYStart) *
432637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                            interp * factor;
4330b1ab3a2776a1fff5b2abbddd3bc256e355e30efDaniel Mladenovic                    mState = STATE_RECEDE;
434637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    break;
435637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                case STATE_RECEDE:
436637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mState = STATE_IDLE;
437637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    break;
438637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell            }
439637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        }
440637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    }
441637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell}
442