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
1989935e41c593a599e8955388b27fb926e60e5e94Adam Powellimport com.android.internal.R;
2089935e41c593a599e8955388b27fb926e60e5e94Adam Powell
214e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereiraimport android.content.Context;
2289935e41c593a599e8955388b27fb926e60e5e94Adam Powellimport android.content.res.Resources;
23637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powellimport android.graphics.Canvas;
24637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powellimport android.graphics.drawable.Drawable;
25637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powellimport android.view.animation.AnimationUtils;
26637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powellimport android.view.animation.DecelerateInterpolator;
27637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powellimport android.view.animation.Interpolator;
28637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
29637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell/**
3089935e41c593a599e8955388b27fb926e60e5e94Adam Powell * This class performs the graphical effect used at the edges of scrollable widgets
3189935e41c593a599e8955388b27fb926e60e5e94Adam Powell * when the user scrolls beyond the content bounds in 2D space.
3289935e41c593a599e8955388b27fb926e60e5e94Adam Powell *
3389935e41c593a599e8955388b27fb926e60e5e94Adam Powell * <p>EdgeEffect is stateful. Custom widgets using EdgeEffect should create an
3489935e41c593a599e8955388b27fb926e60e5e94Adam Powell * instance for each edge that should show the effect, feed it input data using
3589935e41c593a599e8955388b27fb926e60e5e94Adam Powell * the methods {@link #onAbsorb(int)}, {@link #onPull(float)}, and {@link #onRelease()},
3689935e41c593a599e8955388b27fb926e60e5e94Adam Powell * and draw the effect using {@link #draw(Canvas)} in the widget's overridden
3789935e41c593a599e8955388b27fb926e60e5e94Adam Powell * {@link android.view.View#draw(Canvas)} method. If {@link #isFinished()} returns
3889935e41c593a599e8955388b27fb926e60e5e94Adam Powell * false after drawing, the edge effect's animation is not yet complete and the widget
3989935e41c593a599e8955388b27fb926e60e5e94Adam Powell * should schedule another drawing pass to continue the animation.</p>
4089935e41c593a599e8955388b27fb926e60e5e94Adam Powell *
4189935e41c593a599e8955388b27fb926e60e5e94Adam Powell * <p>When drawing, widgets should draw their main content and child views first,
4289935e41c593a599e8955388b27fb926e60e5e94Adam Powell * usually by invoking <code>super.draw(canvas)</code> from an overridden <code>draw</code>
4389935e41c593a599e8955388b27fb926e60e5e94Adam Powell * method. (This will invoke onDraw and dispatch drawing to child views as needed.)
4489935e41c593a599e8955388b27fb926e60e5e94Adam Powell * The edge effect may then be drawn on top of the view's content using the
4589935e41c593a599e8955388b27fb926e60e5e94Adam Powell * {@link #draw(Canvas)} method.</p>
46637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell */
4789935e41c593a599e8955388b27fb926e60e5e94Adam Powellpublic class EdgeEffect {
4889935e41c593a599e8955388b27fb926e60e5e94Adam Powell    private static final String TAG = "EdgeEffect";
49637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
50637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    // Time it will take the effect to fully recede in ms
51637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int RECEDE_TIME = 1000;
52637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
5389935e41c593a599e8955388b27fb926e60e5e94Adam Powell    // Time it will take before a pulled glow begins receding in ms
54637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int PULL_TIME = 167;
55637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
5689935e41c593a599e8955388b27fb926e60e5e94Adam Powell    // Time it will take in ms for a pulled glow to decay to partial strength before release
57637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int PULL_DECAY_TIME = 1000;
58637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
59637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final float MAX_ALPHA = 0.8f;
60637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final float HELD_EDGE_ALPHA = 0.7f;
61637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final float HELD_EDGE_SCALE_Y = 0.5f;
62637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final float HELD_GLOW_ALPHA = 0.5f;
63637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final float HELD_GLOW_SCALE_Y = 0.5f;
64637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
654e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira    private static final float MAX_GLOW_HEIGHT = 4.f;
66637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
67637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final float PULL_GLOW_BEGIN = 1.f;
68637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final float PULL_EDGE_BEGIN = 0.6f;
69637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
70637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    // Minimum velocity that will be absorbed
71637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int MIN_VELOCITY = 100;
72637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
73637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final float EPSILON = 0.001f;
74637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
75637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private final Drawable mEdge;
76637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private final Drawable mGlow;
77637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private int mWidth;
78637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private int mHeight;
794e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira    private final int MIN_WIDTH = 300;
804e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira    private final int mMinWidth;
81637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
82637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mEdgeAlpha;
83637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mEdgeScaleY;
84637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mGlowAlpha;
85637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mGlowScaleY;
86637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
87637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mEdgeAlphaStart;
88637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mEdgeAlphaFinish;
89637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mEdgeScaleYStart;
90637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mEdgeScaleYFinish;
91637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mGlowAlphaStart;
92637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mGlowAlphaFinish;
93637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mGlowScaleYStart;
94637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mGlowScaleYFinish;
95637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
96637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private long mStartTime;
97637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mDuration;
98637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
99637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private final Interpolator mInterpolator;
100637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
101637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int STATE_IDLE = 0;
102637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int STATE_PULL = 1;
103637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int STATE_ABSORB = 2;
104637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int STATE_RECEDE = 3;
105637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int STATE_PULL_DECAY = 4;
106637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
107637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    // How much dragging should effect the height of the edge image.
108637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    // Number determined by user testing.
1094e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira    private static final int PULL_DISTANCE_EDGE_FACTOR = 7;
110637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
111637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    // How much dragging should effect the height of the glow image.
112637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    // Number determined by user testing.
1134e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira    private static final int PULL_DISTANCE_GLOW_FACTOR = 7;
1144e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira    private static final float PULL_DISTANCE_ALPHA_GLOW_FACTOR = 1.1f;
115637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
116637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int VELOCITY_EDGE_FACTOR = 8;
117637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private static final int VELOCITY_GLOW_FACTOR = 16;
118637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
119637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private int mState = STATE_IDLE;
120637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
121637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private float mPullDistance;
122637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
12389935e41c593a599e8955388b27fb926e60e5e94Adam Powell    /**
12489935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * Construct a new EdgeEffect with a theme appropriate for the provided context.
12589935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * @param context Context used to provide theming and resource information for the EdgeEffect
12689935e41c593a599e8955388b27fb926e60e5e94Adam Powell     */
12789935e41c593a599e8955388b27fb926e60e5e94Adam Powell    public EdgeEffect(Context context) {
12889935e41c593a599e8955388b27fb926e60e5e94Adam Powell        final Resources res = context.getResources();
12989935e41c593a599e8955388b27fb926e60e5e94Adam Powell        mEdge = res.getDrawable(R.drawable.overscroll_edge);
13089935e41c593a599e8955388b27fb926e60e5e94Adam Powell        mGlow = res.getDrawable(R.drawable.overscroll_glow);
131637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
1321373a8eb581fe3c8e9a036e69042015f98a7e346Christopher Tate        mMinWidth = (int) (res.getDisplayMetrics().density * MIN_WIDTH + 0.5f);
133637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mInterpolator = new DecelerateInterpolator();
134637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    }
135637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
13689935e41c593a599e8955388b27fb926e60e5e94Adam Powell    /**
13789935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * Set the size of this edge effect in pixels.
13889935e41c593a599e8955388b27fb926e60e5e94Adam Powell     *
13989935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * @param width Effect width in pixels
14089935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * @param height Effect height in pixels
14189935e41c593a599e8955388b27fb926e60e5e94Adam Powell     */
142637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    public void setSize(int width, int height) {
143637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mWidth = width;
144637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mHeight = height;
145637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    }
146637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
14789935e41c593a599e8955388b27fb926e60e5e94Adam Powell    /**
14889935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * Reports if this EdgeEffect's animation is finished. If this method returns false
14989935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * after a call to {@link #draw(Canvas)} the host widget should schedule another
15089935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * drawing pass to continue the animation.
15189935e41c593a599e8955388b27fb926e60e5e94Adam Powell     *
15289935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * @return true if animation is finished, false if drawing should continue on the next frame.
15389935e41c593a599e8955388b27fb926e60e5e94Adam Powell     */
154637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    public boolean isFinished() {
155637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        return mState == STATE_IDLE;
156637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    }
157637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
15889935e41c593a599e8955388b27fb926e60e5e94Adam Powell    /**
15989935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * Immediately finish the current animation.
16089935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * After this call {@link #isFinished()} will return true.
16189935e41c593a599e8955388b27fb926e60e5e94Adam Powell     */
162637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    public void finish() {
163637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mState = STATE_IDLE;
164637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    }
165637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
166637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    /**
16789935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * A view should call this when content is pulled away from an edge by the user.
16889935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * This will update the state of the current visual effect and its associated animation.
16989935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * The host view should always {@link android.view.View#invalidate()} after this
17089935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * and draw the results accordingly.
171637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     *
17289935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to
17389935e41c593a599e8955388b27fb926e60e5e94Adam Powell     *                      1.f (full length of the view) or negative values to express change
17489935e41c593a599e8955388b27fb926e60e5e94Adam Powell     *                      back toward the edge reached to initiate the effect.
175637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     */
176637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    public void onPull(float deltaDistance) {
177637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        final long now = AnimationUtils.currentAnimationTimeMillis();
178637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) {
179637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell            return;
180637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        }
181637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        if (mState != STATE_PULL) {
182637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell            mGlowScaleY = PULL_GLOW_BEGIN;
183637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        }
184637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mState = STATE_PULL;
185637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
186637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mStartTime = now;
187637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mDuration = PULL_TIME;
188637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
189637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mPullDistance += deltaDistance;
190637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        float distance = Math.abs(mPullDistance);
191637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
192637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeAlpha = mEdgeAlphaStart = Math.max(PULL_EDGE_BEGIN, Math.min(distance, MAX_ALPHA));
193637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeScaleY = mEdgeScaleYStart = Math.max(
194637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                HELD_EDGE_SCALE_Y, Math.min(distance * PULL_DISTANCE_EDGE_FACTOR, 1.f));
195637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
196637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA,
197637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                mGlowAlpha +
198637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                (Math.abs(deltaDistance) * PULL_DISTANCE_ALPHA_GLOW_FACTOR));
199637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
200637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        float glowChange = Math.abs(deltaDistance);
201637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        if (deltaDistance > 0 && mPullDistance < 0) {
202637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell            glowChange = -glowChange;
203637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        }
204637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        if (mPullDistance == 0) {
205637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell            mGlowScaleY = 0;
206637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        }
207637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
208637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // Do not allow glow to get larger than MAX_GLOW_HEIGHT.
209637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowScaleY = mGlowScaleYStart = Math.min(MAX_GLOW_HEIGHT, Math.max(
210637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                0, mGlowScaleY + glowChange * PULL_DISTANCE_GLOW_FACTOR));
211637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
212637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeAlphaFinish = mEdgeAlpha;
213637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeScaleYFinish = mEdgeScaleY;
214637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowAlphaFinish = mGlowAlpha;
215637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowScaleYFinish = mGlowScaleY;
216637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    }
217637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
218637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    /**
219637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     * Call when the object is released after being pulled.
22089935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * This will begin the "decay" phase of the effect. After calling this method
22189935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * the host view should {@link android.view.View#invalidate()} and thereby
22289935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * draw the results accordingly.
223637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     */
224637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    public void onRelease() {
225637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mPullDistance = 0;
226637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
227637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        if (mState != STATE_PULL && mState != STATE_PULL_DECAY) {
228637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell            return;
229637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        }
230637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
231637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mState = STATE_RECEDE;
232637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeAlphaStart = mEdgeAlpha;
233637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeScaleYStart = mEdgeScaleY;
234637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowAlphaStart = mGlowAlpha;
235637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowScaleYStart = mGlowScaleY;
236637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
237637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeAlphaFinish = 0.f;
238637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeScaleYFinish = 0.f;
239637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowAlphaFinish = 0.f;
240637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowScaleYFinish = 0.f;
241637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
242637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mStartTime = AnimationUtils.currentAnimationTimeMillis();
243637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mDuration = RECEDE_TIME;
244637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    }
245637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
246637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    /**
247637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     * Call when the effect absorbs an impact at the given velocity.
24889935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * Used when a fling reaches the scroll boundary.
24989935e41c593a599e8955388b27fb926e60e5e94Adam Powell     *
25089935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * <p>When using a {@link android.widget.Scroller} or {@link android.widget.OverScroller},
25189935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * the method <code>getCurrVelocity</code> will provide a reasonable approximation
25289935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * to use here.</p>
253637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     *
254637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     * @param velocity Velocity at impact in pixels per second.
255637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     */
256637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    public void onAbsorb(int velocity) {
257637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mState = STATE_ABSORB;
258637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        velocity = Math.max(MIN_VELOCITY, Math.abs(velocity));
259637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
260637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mStartTime = AnimationUtils.currentAnimationTimeMillis();
261637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mDuration = 0.1f + (velocity * 0.03f);
262637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
263637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // The edge should always be at least partially visible, regardless
264637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // of velocity.
265637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeAlphaStart = 0.f;
266637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeScaleY = mEdgeScaleYStart = 0.f;
267637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // The glow depends more on the velocity, and therefore starts out
268637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // nearly invisible.
269637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowAlphaStart = 0.5f;
270637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowScaleYStart = 0.f;
271637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
272637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // Factor the velocity by 8. Testing on device shows this works best to
273637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // reflect the strength of the user's scrolling.
274637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeAlphaFinish = Math.max(0, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1));
275637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // Edge should never get larger than the size of its asset.
276637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeScaleYFinish = Math.max(
277637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                HELD_EDGE_SCALE_Y, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1.f));
278637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
279637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // Growth for the size of the glow should be quadratic to properly
280637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // respond
281637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // to a user's scrolling speed. The faster the scrolling speed, the more
282637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // intense the effect should be for both the size and the saturation.
283637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f), 1.75f);
284637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        // Alpha should change for the glow as well as size.
285637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowAlphaFinish = Math.max(
286637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
287637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    }
288637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
289637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
290637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    /**
291637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     * Draw into the provided canvas. Assumes that the canvas has been rotated
292637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     * accordingly and the size has been set. The effect will be drawn the full
29389935e41c593a599e8955388b27fb926e60e5e94Adam Powell     * width of X=0 to X=width, beginning from Y=0 and extending to some factor <
294637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     * 1.f of height.
295637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     *
296637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     * @param canvas Canvas to draw into
297637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     * @return true if drawing should continue beyond this frame to continue the
298637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     *         animation
299637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell     */
300637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    public boolean draw(Canvas canvas) {
301637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        update();
302637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
303637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        final int edgeHeight = mEdge.getIntrinsicHeight();
304a5531d784b9b79fe3bf9b30d99e9c99ea3947c6dMindy Pereira        final int edgeWidth = mEdge.getIntrinsicWidth();
305637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        final int glowHeight = mGlow.getIntrinsicHeight();
306a5531d784b9b79fe3bf9b30d99e9c99ea3947c6dMindy Pereira        final int glowWidth = mGlow.getIntrinsicWidth();
307637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
308637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlow.setAlpha((int) (Math.max(0, Math.min(mGlowAlpha, 1)) * 255));
309a5531d784b9b79fe3bf9b30d99e9c99ea3947c6dMindy Pereira
3104e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira        int glowBottom = (int) Math.min(
311a5531d784b9b79fe3bf9b30d99e9c99ea3947c6dMindy Pereira                glowHeight * mGlowScaleY * glowHeight/ glowWidth * 0.6f,
3124e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira                glowHeight * MAX_GLOW_HEIGHT);
3134e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira        if (mWidth < mMinWidth) {
3144e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira            // Center the glow and clip it.
315ffc4196c46419469f088ca23a0d0e31cf7979e30Jim Miller            int glowLeft = (mWidth - mMinWidth)/2;
3164e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira            mGlow.setBounds(glowLeft, 0, mWidth - glowLeft, glowBottom);
3174e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira        } else {
3184e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira            // Stretch the glow to fit.
3194e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira            mGlow.setBounds(0, 0, mWidth, glowBottom);
3204e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira        }
3214e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira
322637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlow.draw(canvas);
323637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
324637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdge.setAlpha((int) (Math.max(0, Math.min(mEdgeAlpha, 1)) * 255));
325a5531d784b9b79fe3bf9b30d99e9c99ea3947c6dMindy Pereira
3264e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira        int edgeBottom = (int) (edgeHeight * mEdgeScaleY);
3274e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira        if (mWidth < mMinWidth) {
3284e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira            // Center the edge and clip it.
329ffc4196c46419469f088ca23a0d0e31cf7979e30Jim Miller            int edgeLeft = (mWidth - mMinWidth)/2;
3304e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira            mEdge.setBounds(edgeLeft, 0, mWidth - edgeLeft, edgeBottom);
3314e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira        } else {
3324e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira            // Stretch the edge to fit.
3334e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira            mEdge.setBounds(0, 0, mWidth, edgeBottom);
3344e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira        }
335637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdge.draw(canvas);
336637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
337637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        return mState != STATE_IDLE;
338637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    }
339637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
340637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    private void update() {
341637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        final long time = AnimationUtils.currentAnimationTimeMillis();
342637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        final float t = Math.min((time - mStartTime) / mDuration, 1.f);
343637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
344637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        final float interp = mInterpolator.getInterpolation(t);
345637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
346637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeAlpha = mEdgeAlphaStart + (mEdgeAlphaFinish - mEdgeAlphaStart) * interp;
347637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mEdgeScaleY = mEdgeScaleYStart + (mEdgeScaleYFinish - mEdgeScaleYStart) * interp;
348637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
349637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
350637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
351637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        if (t >= 1.f - EPSILON) {
352637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell            switch (mState) {
353637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                case STATE_ABSORB:
354637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mState = STATE_RECEDE;
355637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mStartTime = AnimationUtils.currentAnimationTimeMillis();
356637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mDuration = RECEDE_TIME;
357637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
358637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mEdgeAlphaStart = mEdgeAlpha;
359637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mEdgeScaleYStart = mEdgeScaleY;
360637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mGlowAlphaStart = mGlowAlpha;
361637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mGlowScaleYStart = mGlowScaleY;
362637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
363637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    // After absorb, the glow and edge should fade to nothing.
364637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mEdgeAlphaFinish = 0.f;
365637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mEdgeScaleYFinish = 0.f;
366637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mGlowAlphaFinish = 0.f;
367637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mGlowScaleYFinish = 0.f;
368637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    break;
369637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                case STATE_PULL:
370637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mState = STATE_PULL_DECAY;
371637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mStartTime = AnimationUtils.currentAnimationTimeMillis();
372637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mDuration = PULL_DECAY_TIME;
373637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
374637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mEdgeAlphaStart = mEdgeAlpha;
375637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mEdgeScaleYStart = mEdgeScaleY;
376637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mGlowAlphaStart = mGlowAlpha;
377637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mGlowScaleYStart = mGlowScaleY;
378637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell
379637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    // After pull, the glow and edge should fade to nothing.
380637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mEdgeAlphaFinish = 0.f;
381637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mEdgeScaleYFinish = 0.f;
382637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mGlowAlphaFinish = 0.f;
383637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mGlowScaleYFinish = 0.f;
384637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    break;
385637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                case STATE_PULL_DECAY:
386637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    // When receding, we want edge to decrease more slowly
387637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    // than the glow.
388637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    float factor = mGlowScaleYFinish != 0 ? 1
389637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                            / (mGlowScaleYFinish * mGlowScaleYFinish)
390637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                            : Float.MAX_VALUE;
391637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mEdgeScaleY = mEdgeScaleYStart +
392637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                        (mEdgeScaleYFinish - mEdgeScaleYStart) *
393637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                            interp * factor;
3940b1ab3a2776a1fff5b2abbddd3bc256e355e30efDaniel Mladenovic                    mState = STATE_RECEDE;
395637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    break;
396637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                case STATE_RECEDE:
397637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    mState = STATE_IDLE;
398637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell                    break;
399637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell            }
400637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell        }
401637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell    }
402637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell}
403