blob: 1f7daabd426a19aa9d281eff63c9599bde958c6e [file] [log] [blame]
Adam Powell0a77ce22010-08-25 14:37:03 -07001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import android.graphics.Canvas;
20import android.graphics.drawable.Drawable;
Adam Powell0a77ce22010-08-25 14:37:03 -070021import android.view.animation.AnimationUtils;
22import android.view.animation.DecelerateInterpolator;
23import android.view.animation.Interpolator;
24
25/**
26 * This class performs the glow effect used at the edges of scrollable widgets.
27 * @hide
28 */
29public class EdgeGlow {
30 private static final String TAG = "EdgeGlow";
31
Adam Powell0a77ce22010-08-25 14:37:03 -070032 // Time it will take the effect to fully recede in ms
33 private static final int RECEDE_TIME = 1000;
34
35 // Time it will take before a pulled glow begins receding
36 private static final int PULL_TIME = 250;
37
38 // Time it will take for a pulled glow to decay to partial strength before release
Mindy Pereira40d35792010-09-20 09:12:41 -070039 private static final int PULL_DECAY_TIME = 10000;
Adam Powell0a77ce22010-08-25 14:37:03 -070040
Mindy Pereira40d35792010-09-20 09:12:41 -070041 private static final float MAX_ALPHA = 1.f;
Adam Powell0a77ce22010-08-25 14:37:03 -070042 private static final float HELD_EDGE_ALPHA = 0.7f;
43 private static final float HELD_EDGE_SCALE_Y = 0.5f;
44 private static final float HELD_GLOW_ALPHA = 0.5f;
45 private static final float HELD_GLOW_SCALE_Y = 0.5f;
46
Mindy Pereira40d35792010-09-20 09:12:41 -070047 private static final float MAX_GLOW_HEIGHT = 3.f;
Adam Powell59168822010-09-01 17:47:16 -070048
Mindy Pereira40d35792010-09-20 09:12:41 -070049 private static final float PULL_GLOW_BEGIN = 1.f;
Adam Powell59168822010-09-01 17:47:16 -070050 private static final float PULL_EDGE_BEGIN = 0.6f;
Adam Powell0a77ce22010-08-25 14:37:03 -070051
52 // Minimum velocity that will be absorbed
Mindy Pereira40d35792010-09-20 09:12:41 -070053 private static final int MIN_VELOCITY = 100;
54
Adam Powell0a77ce22010-08-25 14:37:03 -070055 private static final float EPSILON = 0.001f;
56
Mindy Pereira40d35792010-09-20 09:12:41 -070057 private final Drawable mEdge;
58 private final Drawable mGlow;
Adam Powell0a77ce22010-08-25 14:37:03 -070059 private int mWidth;
60 private int mHeight;
61
62 private float mEdgeAlpha;
63 private float mEdgeScaleY;
64 private float mGlowAlpha;
65 private float mGlowScaleY;
66
67 private float mEdgeAlphaStart;
68 private float mEdgeAlphaFinish;
69 private float mEdgeScaleYStart;
70 private float mEdgeScaleYFinish;
71 private float mGlowAlphaStart;
72 private float mGlowAlphaFinish;
73 private float mGlowScaleYStart;
74 private float mGlowScaleYFinish;
75
76 private long mStartTime;
Mindy Pereira40d35792010-09-20 09:12:41 -070077 private float mDuration;
Adam Powell0a77ce22010-08-25 14:37:03 -070078
Mindy Pereira40d35792010-09-20 09:12:41 -070079 private final Interpolator mInterpolator;
Adam Powell0a77ce22010-08-25 14:37:03 -070080
81 private static final int STATE_IDLE = 0;
82 private static final int STATE_PULL = 1;
83 private static final int STATE_ABSORB = 2;
84 private static final int STATE_RECEDE = 3;
85 private static final int STATE_PULL_DECAY = 4;
86
Mindy Pereira40d35792010-09-20 09:12:41 -070087 // How much dragging should effect the height of the edge image.
88 // Number determined by user testing.
89 private static final int PULL_DISTANCE_EDGE_FACTOR = 5;
90
91 // How much dragging should effect the height of the glow image.
92 // Number determined by user testing.
Mindy Pereirae6c47472010-10-01 16:37:06 -070093 private static final int PULL_DISTANCE_GLOW_FACTOR = 5;
Mindy Pereira40d35792010-09-20 09:12:41 -070094
95 private static final int VELOCITY_EDGE_FACTOR = 8;
96 private static final int VELOCITY_GLOW_FACTOR = 16;
97
Adam Powell0a77ce22010-08-25 14:37:03 -070098 private int mState = STATE_IDLE;
99
100 private float mPullDistance;
101
102 public EdgeGlow(Drawable edge, Drawable glow) {
103 mEdge = edge;
104 mGlow = glow;
105
106 mInterpolator = new DecelerateInterpolator();
107 }
108
109 public void setSize(int width, int height) {
110 mWidth = width;
111 mHeight = height;
112 }
113
114 public boolean isFinished() {
115 return mState == STATE_IDLE;
116 }
117
Adam Powell59168822010-09-01 17:47:16 -0700118 public void finish() {
119 mState = STATE_IDLE;
120 }
121
Adam Powell0a77ce22010-08-25 14:37:03 -0700122 /**
123 * Call when the object is pulled by the user.
Mindy Pereira40d35792010-09-20 09:12:41 -0700124 *
Adam Powell0a77ce22010-08-25 14:37:03 -0700125 * @param deltaDistance Change in distance since the last call
126 */
127 public void onPull(float deltaDistance) {
128 final long now = AnimationUtils.currentAnimationTimeMillis();
129 if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) {
130 return;
131 }
132 if (mState != STATE_PULL) {
133 mGlowScaleY = PULL_GLOW_BEGIN;
134 }
135 mState = STATE_PULL;
136
137 mStartTime = now;
138 mDuration = PULL_TIME;
139
140 mPullDistance += deltaDistance;
141 float distance = Math.abs(mPullDistance);
142
Mindy Pereira40d35792010-09-20 09:12:41 -0700143 mEdgeAlpha = mEdgeAlphaStart = Math.max(PULL_EDGE_BEGIN, Math.min(distance, MAX_ALPHA));
144 mEdgeScaleY = mEdgeScaleYStart = Math.max(
145 HELD_EDGE_SCALE_Y, Math.min(distance * PULL_DISTANCE_EDGE_FACTOR, 1.f));
Adam Powell0a77ce22010-08-25 14:37:03 -0700146
Mindy Pereira40d35792010-09-20 09:12:41 -0700147 mGlowAlpha = mGlowAlphaStart = Math.max(
148 0.5f, Math.min(mGlowAlpha + Math.abs(deltaDistance), MAX_ALPHA));
Adam Powell0a77ce22010-08-25 14:37:03 -0700149
150 float glowChange = Math.abs(deltaDistance);
151 if (deltaDistance > 0 && mPullDistance < 0) {
152 glowChange = -glowChange;
153 }
154 if (mPullDistance == 0) {
155 mGlowScaleY = 0;
156 }
Mindy Pereira40d35792010-09-20 09:12:41 -0700157 mGlowScaleY = mGlowScaleYStart = Math.max(
158 0, mGlowScaleY + glowChange * PULL_DISTANCE_GLOW_FACTOR);
Adam Powell0a77ce22010-08-25 14:37:03 -0700159
160 mEdgeAlphaFinish = mEdgeAlpha;
161 mEdgeScaleYFinish = mEdgeScaleY;
162 mGlowAlphaFinish = mGlowAlpha;
163 mGlowScaleYFinish = mGlowScaleY;
Adam Powell0a77ce22010-08-25 14:37:03 -0700164 }
165
166 /**
167 * Call when the object is released after being pulled.
168 */
169 public void onRelease() {
170 mPullDistance = 0;
171
172 if (mState != STATE_PULL && mState != STATE_PULL_DECAY) {
173 return;
174 }
Adam Powell0a77ce22010-08-25 14:37:03 -0700175
176 mState = STATE_RECEDE;
177 mEdgeAlphaStart = mEdgeAlpha;
178 mEdgeScaleYStart = mEdgeScaleY;
179 mGlowAlphaStart = mGlowAlpha;
180 mGlowScaleYStart = mGlowScaleY;
181
182 mEdgeAlphaFinish = 0.f;
Mindy Pereira40d35792010-09-20 09:12:41 -0700183 mEdgeScaleYFinish = 0.f;
Adam Powell0a77ce22010-08-25 14:37:03 -0700184 mGlowAlphaFinish = 0.f;
Mindy Pereira40d35792010-09-20 09:12:41 -0700185 mGlowScaleYFinish = 0.f;
Adam Powell0a77ce22010-08-25 14:37:03 -0700186
187 mStartTime = AnimationUtils.currentAnimationTimeMillis();
188 mDuration = RECEDE_TIME;
189 }
190
191 /**
192 * Call when the effect absorbs an impact at the given velocity.
Mindy Pereira40d35792010-09-20 09:12:41 -0700193 *
Adam Powell0a77ce22010-08-25 14:37:03 -0700194 * @param velocity Velocity at impact in pixels per second.
195 */
196 public void onAbsorb(int velocity) {
197 mState = STATE_ABSORB;
Adam Powell0a77ce22010-08-25 14:37:03 -0700198 velocity = Math.max(MIN_VELOCITY, Math.abs(velocity));
199
200 mStartTime = AnimationUtils.currentAnimationTimeMillis();
Mindy Pereira40d35792010-09-20 09:12:41 -0700201 mDuration = 0.1f + (velocity * 0.03f);
Adam Powell0a77ce22010-08-25 14:37:03 -0700202
Mindy Pereira40d35792010-09-20 09:12:41 -0700203 // The edge should always be at least partially visible, regardless
204 // of velocity.
Adam Powell0a77ce22010-08-25 14:37:03 -0700205 mEdgeAlphaStart = 0.5f;
206 mEdgeScaleYStart = 0.2f;
Mindy Pereira40d35792010-09-20 09:12:41 -0700207 // The glow depends more on the velocity, and therefore starts out
208 // nearly invisible.
Adam Powell0a77ce22010-08-25 14:37:03 -0700209 mGlowAlphaStart = 0.5f;
210 mGlowScaleYStart = 0.f;
211
Mindy Pereira40d35792010-09-20 09:12:41 -0700212 // Factor the velocity by 8. Testing on device shows this works best to
213 // reflect the strength of the user's scrolling.
214 mEdgeAlphaFinish = Math.max(0, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1));
215 // Edge should never get larger than the size of its asset.
Adam Powell0a77ce22010-08-25 14:37:03 -0700216 mEdgeScaleYFinish = 1.f;
Mindy Pereira40d35792010-09-20 09:12:41 -0700217
218 // Growth for the size of the glow should be quadratic to properly
219 // respond
220 // to a user's scrolling speed. The faster the scrolling speed, the more
221 // intense the effect should be for both the size and the saturation.
222 mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f), 1.75f);
223 // Alpha should change for the glow as well as size.
224 mGlowAlphaFinish = Math.max(
225 mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
Adam Powell0a77ce22010-08-25 14:37:03 -0700226 }
227
Mindy Pereira40d35792010-09-20 09:12:41 -0700228
Adam Powell0a77ce22010-08-25 14:37:03 -0700229 /**
Mindy Pereira40d35792010-09-20 09:12:41 -0700230 * Draw into the provided canvas. Assumes that the canvas has been rotated
231 * accordingly and the size has been set. The effect will be drawn the full
232 * width of X=0 to X=width, emitting from Y=0 and extending to some factor <
233 * 1.f of height.
Adam Powell0a77ce22010-08-25 14:37:03 -0700234 *
235 * @param canvas Canvas to draw into
Mindy Pereira40d35792010-09-20 09:12:41 -0700236 * @return true if drawing should continue beyond this frame to continue the
237 * animation
Adam Powell0a77ce22010-08-25 14:37:03 -0700238 */
239 public boolean draw(Canvas canvas) {
240 update();
241
242 final int edgeHeight = mEdge.getIntrinsicHeight();
243 final int glowHeight = mGlow.getIntrinsicHeight();
244
Adam Powell59168822010-09-01 17:47:16 -0700245 final float distScale = (float) mHeight / mWidth;
246
Adam Powell0a77ce22010-08-25 14:37:03 -0700247 mGlow.setAlpha((int) (Math.max(0, Math.min(mGlowAlpha, 1)) * 255));
Mindy Pereira40d35792010-09-20 09:12:41 -0700248 // Width of the image should be 3 * the width of the screen.
249 // Should start off screen to the left.
250 mGlow.setBounds(-mWidth, 0, mWidth * 2, (int) Math.min(
251 glowHeight * mGlowScaleY * distScale * 0.6f, mHeight * MAX_GLOW_HEIGHT));
Adam Powell0a77ce22010-08-25 14:37:03 -0700252 mGlow.draw(canvas);
253
254 mEdge.setAlpha((int) (Math.max(0, Math.min(mEdgeAlpha, 1)) * 255));
Mindy Pereira40d35792010-09-20 09:12:41 -0700255 mEdge.setBounds(0, 0, mWidth, (int) (edgeHeight * mEdgeScaleY));
Adam Powell0a77ce22010-08-25 14:37:03 -0700256 mEdge.draw(canvas);
Adam Powell0a77ce22010-08-25 14:37:03 -0700257
258 return mState != STATE_IDLE;
259 }
260
261 private void update() {
262 final long time = AnimationUtils.currentAnimationTimeMillis();
Mindy Pereira40d35792010-09-20 09:12:41 -0700263 final float t = Math.min((time - mStartTime) / mDuration, 1.f);
Adam Powell0a77ce22010-08-25 14:37:03 -0700264
265 final float interp = mInterpolator.getInterpolation(t);
266
267 mEdgeAlpha = mEdgeAlphaStart + (mEdgeAlphaFinish - mEdgeAlphaStart) * interp;
268 mEdgeScaleY = mEdgeScaleYStart + (mEdgeScaleYFinish - mEdgeScaleYStart) * interp;
269 mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
270 mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
271
272 if (t >= 1.f - EPSILON) {
273 switch (mState) {
274 case STATE_ABSORB:
275 mState = STATE_RECEDE;
276 mStartTime = AnimationUtils.currentAnimationTimeMillis();
277 mDuration = RECEDE_TIME;
278
279 mEdgeAlphaStart = mEdgeAlpha;
280 mEdgeScaleYStart = mEdgeScaleY;
281 mGlowAlphaStart = mGlowAlpha;
282 mGlowScaleYStart = mGlowScaleY;
283
284 mEdgeAlphaFinish = 0.f;
Mindy Pereira40d35792010-09-20 09:12:41 -0700285 mEdgeScaleYFinish = mEdgeScaleY;
Adam Powell0a77ce22010-08-25 14:37:03 -0700286 mGlowAlphaFinish = 0.f;
287 mGlowScaleYFinish = mGlowScaleY;
Adam Powell0a77ce22010-08-25 14:37:03 -0700288 break;
289 case STATE_PULL:
290 mState = STATE_PULL_DECAY;
291 mStartTime = AnimationUtils.currentAnimationTimeMillis();
292 mDuration = PULL_DECAY_TIME;
293
294 mEdgeAlphaStart = mEdgeAlpha;
295 mEdgeScaleYStart = mEdgeScaleY;
296 mGlowAlphaStart = mGlowAlpha;
297 mGlowScaleYStart = mGlowScaleY;
298
Mindy Pereira40d35792010-09-20 09:12:41 -0700299 // After a pull, the glow should fade to nothing.
300 mEdgeAlphaFinish = 0.f;
301 mEdgeScaleYFinish = 0.f;
302 mGlowAlphaFinish = 0.f;
303 mGlowScaleYFinish = 0.f;
Adam Powell0a77ce22010-08-25 14:37:03 -0700304 break;
305 case STATE_PULL_DECAY:
306 // Do nothing; wait for release
307 break;
308 case STATE_RECEDE:
309 mState = STATE_IDLE;
Adam Powell0a77ce22010-08-25 14:37:03 -0700310 break;
311 }
312 }
313 }
314}