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 1980756e38882720860db52f1fcc21fa1505a02abfTor Norbyeimport android.annotation.ColorInt; 20c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powellimport android.content.res.TypedArray; 21c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powellimport android.graphics.Paint; 22c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powellimport android.graphics.PorterDuff; 23c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powellimport android.graphics.PorterDuffXfermode; 249d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guyimport android.graphics.Rect; 2589935e41c593a599e8955388b27fb926e60e5e94Adam Powell 264e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereiraimport android.content.Context; 27637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powellimport android.graphics.Canvas; 28637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powellimport android.view.animation.AnimationUtils; 29637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powellimport android.view.animation.DecelerateInterpolator; 30637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powellimport android.view.animation.Interpolator; 31637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 32637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell/** 3389935e41c593a599e8955388b27fb926e60e5e94Adam Powell * This class performs the graphical effect used at the edges of scrollable widgets 3489935e41c593a599e8955388b27fb926e60e5e94Adam Powell * when the user scrolls beyond the content bounds in 2D space. 3589935e41c593a599e8955388b27fb926e60e5e94Adam Powell * 3689935e41c593a599e8955388b27fb926e60e5e94Adam Powell * <p>EdgeEffect is stateful. Custom widgets using EdgeEffect should create an 3789935e41c593a599e8955388b27fb926e60e5e94Adam Powell * instance for each edge that should show the effect, feed it input data using 3889935e41c593a599e8955388b27fb926e60e5e94Adam Powell * the methods {@link #onAbsorb(int)}, {@link #onPull(float)}, and {@link #onRelease()}, 3989935e41c593a599e8955388b27fb926e60e5e94Adam Powell * and draw the effect using {@link #draw(Canvas)} in the widget's overridden 4089935e41c593a599e8955388b27fb926e60e5e94Adam Powell * {@link android.view.View#draw(Canvas)} method. If {@link #isFinished()} returns 4189935e41c593a599e8955388b27fb926e60e5e94Adam Powell * false after drawing, the edge effect's animation is not yet complete and the widget 4289935e41c593a599e8955388b27fb926e60e5e94Adam Powell * should schedule another drawing pass to continue the animation.</p> 4389935e41c593a599e8955388b27fb926e60e5e94Adam Powell * 4489935e41c593a599e8955388b27fb926e60e5e94Adam Powell * <p>When drawing, widgets should draw their main content and child views first, 4589935e41c593a599e8955388b27fb926e60e5e94Adam Powell * usually by invoking <code>super.draw(canvas)</code> from an overridden <code>draw</code> 4689935e41c593a599e8955388b27fb926e60e5e94Adam Powell * method. (This will invoke onDraw and dispatch drawing to child views as needed.) 4789935e41c593a599e8955388b27fb926e60e5e94Adam Powell * The edge effect may then be drawn on top of the view's content using the 4889935e41c593a599e8955388b27fb926e60e5e94Adam Powell * {@link #draw(Canvas)} method.</p> 49637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell */ 5089935e41c593a599e8955388b27fb926e60e5e94Adam Powellpublic class EdgeEffect { 519d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy @SuppressWarnings("UnusedDeclaration") 5289935e41c593a599e8955388b27fb926e60e5e94Adam Powell private static final String TAG = "EdgeEffect"; 53637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 54637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell // Time it will take the effect to fully recede in ms 55710c456ddb32fe05e13965183e7018015da52eaeAdam Powell private static final int RECEDE_TIME = 600; 56637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 5789935e41c593a599e8955388b27fb926e60e5e94Adam Powell // Time it will take before a pulled glow begins receding in ms 58637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private static final int PULL_TIME = 167; 59637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 60710c456ddb32fe05e13965183e7018015da52eaeAdam Powell // Time it will take in ms for a pulled glow to decay to partial strength before release 61710c456ddb32fe05e13965183e7018015da52eaeAdam Powell private static final int PULL_DECAY_TIME = 2000; 62710c456ddb32fe05e13965183e7018015da52eaeAdam Powell 63710c456ddb32fe05e13965183e7018015da52eaeAdam Powell private static final float MAX_ALPHA = 0.5f; 64637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 652897a6fdedd23efe96cd373886da78f1f1d18442Adam Powell private static final float MAX_GLOW_SCALE = 2.f; 66637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 67c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell private static final float PULL_GLOW_BEGIN = 0.f; 68637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 69637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell // Minimum velocity that will be absorbed 70637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private static final int MIN_VELOCITY = 100; 712d1acfc9f7e1502a5dbb8cab54289d6fbb880467Christian Robertson // Maximum velocity, clamps at this value 722d1acfc9f7e1502a5dbb8cab54289d6fbb880467Christian Robertson private static final int MAX_VELOCITY = 10000; 73637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 74637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private static final float EPSILON = 0.001f; 75637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 762897a6fdedd23efe96cd373886da78f1f1d18442Adam Powell private static final double ANGLE = Math.PI / 6; 772897a6fdedd23efe96cd373886da78f1f1d18442Adam Powell private static final float SIN = (float) Math.sin(ANGLE); 782897a6fdedd23efe96cd373886da78f1f1d18442Adam Powell private static final float COS = (float) Math.cos(ANGLE); 79c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell 80637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private float mGlowAlpha; 81637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private float mGlowScaleY; 82637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 83637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private float mGlowAlphaStart; 84637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private float mGlowAlphaFinish; 85637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private float mGlowScaleYStart; 86637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private float mGlowScaleYFinish; 87637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 88637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private long mStartTime; 89637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private float mDuration; 90637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 91637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private final Interpolator mInterpolator; 92637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 93637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private static final int STATE_IDLE = 0; 94637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private static final int STATE_PULL = 1; 95637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private static final int STATE_ABSORB = 2; 96637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private static final int STATE_RECEDE = 3; 97637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private static final int STATE_PULL_DECAY = 4; 98637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 99710c456ddb32fe05e13965183e7018015da52eaeAdam Powell private static final float PULL_DISTANCE_ALPHA_GLOW_FACTOR = 0.8f; 100637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 101710c456ddb32fe05e13965183e7018015da52eaeAdam Powell private static final int VELOCITY_GLOW_FACTOR = 6; 102637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 103637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private int mState = STATE_IDLE; 104637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 105637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private float mPullDistance; 1069d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy 1079d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy private final Rect mBounds = new Rect(); 108c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell private final Paint mPaint = new Paint(); 109c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell private float mRadius; 110710c456ddb32fe05e13965183e7018015da52eaeAdam Powell private float mBaseGlowScale; 111c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell private float mDisplacement = 0.5f; 112c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell private float mTargetDisplacement = 0.5f; 113a8bfeaf4f49fa33e96f37302f9c9b99c94aa1581Romain Guy 11489935e41c593a599e8955388b27fb926e60e5e94Adam Powell /** 11589935e41c593a599e8955388b27fb926e60e5e94Adam Powell * Construct a new EdgeEffect with a theme appropriate for the provided context. 11689935e41c593a599e8955388b27fb926e60e5e94Adam Powell * @param context Context used to provide theming and resource information for the EdgeEffect 11789935e41c593a599e8955388b27fb926e60e5e94Adam Powell */ 11889935e41c593a599e8955388b27fb926e60e5e94Adam Powell public EdgeEffect(Context context) { 119c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell mPaint.setAntiAlias(true); 120c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell final TypedArray a = context.obtainStyledAttributes( 121c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell com.android.internal.R.styleable.EdgeEffect); 122c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell final int themeColor = a.getColor( 123c6c744da75cfb79ba758a60baa3029495016fcfeAdam Powell com.android.internal.R.styleable.EdgeEffect_colorEdgeEffect, 0xff666666); 124c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell a.recycle(); 1252897a6fdedd23efe96cd373886da78f1f1d18442Adam Powell mPaint.setColor((themeColor & 0xffffff) | 0x33000000); 126c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell mPaint.setStyle(Paint.Style.FILL); 127c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)); 128637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mInterpolator = new DecelerateInterpolator(); 129637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell } 130637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 13189935e41c593a599e8955388b27fb926e60e5e94Adam Powell /** 13289935e41c593a599e8955388b27fb926e60e5e94Adam Powell * Set the size of this edge effect in pixels. 13389935e41c593a599e8955388b27fb926e60e5e94Adam Powell * 13489935e41c593a599e8955388b27fb926e60e5e94Adam Powell * @param width Effect width in pixels 13589935e41c593a599e8955388b27fb926e60e5e94Adam Powell * @param height Effect height in pixels 13689935e41c593a599e8955388b27fb926e60e5e94Adam Powell */ 137637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell public void setSize(int width, int height) { 1382897a6fdedd23efe96cd373886da78f1f1d18442Adam Powell final float r = width * 0.75f / SIN; 1392897a6fdedd23efe96cd373886da78f1f1d18442Adam Powell final float y = COS * r; 140c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell final float h = r - y; 141710c456ddb32fe05e13965183e7018015da52eaeAdam Powell final float or = height * 0.75f / SIN; 142710c456ddb32fe05e13965183e7018015da52eaeAdam Powell final float oy = COS * or; 143710c456ddb32fe05e13965183e7018015da52eaeAdam Powell final float oh = or - oy; 144710c456ddb32fe05e13965183e7018015da52eaeAdam Powell 145c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell mRadius = r; 146710c456ddb32fe05e13965183e7018015da52eaeAdam Powell mBaseGlowScale = h > 0 ? Math.min(oh / h, 1.f) : 1.f; 147637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 148c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell mBounds.set(mBounds.left, mBounds.top, width, (int) Math.min(height, h)); 1499d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy } 1509d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy 1519d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy /** 15289935e41c593a599e8955388b27fb926e60e5e94Adam Powell * Reports if this EdgeEffect's animation is finished. If this method returns false 15389935e41c593a599e8955388b27fb926e60e5e94Adam Powell * after a call to {@link #draw(Canvas)} the host widget should schedule another 15489935e41c593a599e8955388b27fb926e60e5e94Adam Powell * drawing pass to continue the animation. 15589935e41c593a599e8955388b27fb926e60e5e94Adam Powell * 15689935e41c593a599e8955388b27fb926e60e5e94Adam Powell * @return true if animation is finished, false if drawing should continue on the next frame. 15789935e41c593a599e8955388b27fb926e60e5e94Adam Powell */ 158637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell public boolean isFinished() { 159637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell return mState == STATE_IDLE; 160637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell } 161637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 16289935e41c593a599e8955388b27fb926e60e5e94Adam Powell /** 16389935e41c593a599e8955388b27fb926e60e5e94Adam Powell * Immediately finish the current animation. 16489935e41c593a599e8955388b27fb926e60e5e94Adam Powell * After this call {@link #isFinished()} will return true. 16589935e41c593a599e8955388b27fb926e60e5e94Adam Powell */ 166637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell public void finish() { 167637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mState = STATE_IDLE; 168637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell } 169637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 170637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell /** 17189935e41c593a599e8955388b27fb926e60e5e94Adam Powell * A view should call this when content is pulled away from an edge by the user. 17289935e41c593a599e8955388b27fb926e60e5e94Adam Powell * This will update the state of the current visual effect and its associated animation. 17389935e41c593a599e8955388b27fb926e60e5e94Adam Powell * The host view should always {@link android.view.View#invalidate()} after this 17489935e41c593a599e8955388b27fb926e60e5e94Adam Powell * and draw the results accordingly. 175637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * 176c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell * <p>Views using EdgeEffect should favor {@link #onPull(float, float)} when the displacement 177c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell * of the pull point is known.</p> 178c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell * 17989935e41c593a599e8955388b27fb926e60e5e94Adam Powell * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to 18089935e41c593a599e8955388b27fb926e60e5e94Adam Powell * 1.f (full length of the view) or negative values to express change 18189935e41c593a599e8955388b27fb926e60e5e94Adam Powell * back toward the edge reached to initiate the effect. 182637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell */ 183637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell public void onPull(float deltaDistance) { 184c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell onPull(deltaDistance, 0.5f); 185c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell } 186c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell 187c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell /** 188c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell * A view should call this when content is pulled away from an edge by the user. 189c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell * This will update the state of the current visual effect and its associated animation. 190c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell * The host view should always {@link android.view.View#invalidate()} after this 191c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell * and draw the results accordingly. 192c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell * 193c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to 194c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell * 1.f (full length of the view) or negative values to express change 195c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell * back toward the edge reached to initiate the effect. 196c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell * @param displacement The displacement from the starting side of the effect of the point 197c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell * initiating the pull. In the case of touch this is the finger position. 198c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell * Values may be from 0-1. 199c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell */ 200c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell public void onPull(float deltaDistance, float displacement) { 201637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell final long now = AnimationUtils.currentAnimationTimeMillis(); 202c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell mTargetDisplacement = displacement; 203637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) { 204637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell return; 205637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell } 206637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell if (mState != STATE_PULL) { 207c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY); 208637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell } 209637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mState = STATE_PULL; 210637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 211637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mStartTime = now; 212637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mDuration = PULL_TIME; 213637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 214637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mPullDistance += deltaDistance; 215637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 2162897a6fdedd23efe96cd373886da78f1f1d18442Adam Powell final float absdd = Math.abs(deltaDistance); 217637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA, 2182897a6fdedd23efe96cd373886da78f1f1d18442Adam Powell mGlowAlpha + (absdd * PULL_DISTANCE_ALPHA_GLOW_FACTOR)); 219637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 220637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell if (mPullDistance == 0) { 2212897a6fdedd23efe96cd373886da78f1f1d18442Adam Powell mGlowScaleY = mGlowScaleYStart = 0; 2222897a6fdedd23efe96cd373886da78f1f1d18442Adam Powell } else { 223e573aa9371fc507075219cd078117f96ba8b3b02Neil Fuller final float scale = (float) (Math.max(0, 1 - 1 / 224e573aa9371fc507075219cd078117f96ba8b3b02Neil Fuller Math.sqrt(Math.abs(mPullDistance) * mBounds.height()) - 0.3d) / 0.7d); 225637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 2262897a6fdedd23efe96cd373886da78f1f1d18442Adam Powell mGlowScaleY = mGlowScaleYStart = scale; 2272897a6fdedd23efe96cd373886da78f1f1d18442Adam Powell } 228637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 229637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mGlowAlphaFinish = mGlowAlpha; 230637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mGlowScaleYFinish = mGlowScaleY; 231637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell } 232637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 233637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell /** 234637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * Call when the object is released after being pulled. 23589935e41c593a599e8955388b27fb926e60e5e94Adam Powell * This will begin the "decay" phase of the effect. After calling this method 23689935e41c593a599e8955388b27fb926e60e5e94Adam Powell * the host view should {@link android.view.View#invalidate()} and thereby 23789935e41c593a599e8955388b27fb926e60e5e94Adam Powell * draw the results accordingly. 238637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell */ 239637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell public void onRelease() { 240637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mPullDistance = 0; 241637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 242637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell if (mState != STATE_PULL && mState != STATE_PULL_DECAY) { 243637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell return; 244637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell } 245637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 246637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mState = STATE_RECEDE; 247637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mGlowAlphaStart = mGlowAlpha; 248637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mGlowScaleYStart = mGlowScaleY; 249637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 250637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mGlowAlphaFinish = 0.f; 251637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mGlowScaleYFinish = 0.f; 252637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 253637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mStartTime = AnimationUtils.currentAnimationTimeMillis(); 254637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mDuration = RECEDE_TIME; 255637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell } 256637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 257637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell /** 258637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * Call when the effect absorbs an impact at the given velocity. 25989935e41c593a599e8955388b27fb926e60e5e94Adam Powell * Used when a fling reaches the scroll boundary. 26089935e41c593a599e8955388b27fb926e60e5e94Adam Powell * 26189935e41c593a599e8955388b27fb926e60e5e94Adam Powell * <p>When using a {@link android.widget.Scroller} or {@link android.widget.OverScroller}, 26289935e41c593a599e8955388b27fb926e60e5e94Adam Powell * the method <code>getCurrVelocity</code> will provide a reasonable approximation 26389935e41c593a599e8955388b27fb926e60e5e94Adam Powell * to use here.</p> 264637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * 265637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * @param velocity Velocity at impact in pixels per second. 266637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell */ 267637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell public void onAbsorb(int velocity) { 268637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mState = STATE_ABSORB; 2692d1acfc9f7e1502a5dbb8cab54289d6fbb880467Christian Robertson velocity = Math.min(Math.max(MIN_VELOCITY, Math.abs(velocity)), MAX_VELOCITY); 270637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 271637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mStartTime = AnimationUtils.currentAnimationTimeMillis(); 2722d1acfc9f7e1502a5dbb8cab54289d6fbb880467Christian Robertson mDuration = 0.15f + (velocity * 0.02f); 273637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 274637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell // The glow depends more on the velocity, and therefore starts out 275637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell // nearly invisible. 2762d1acfc9f7e1502a5dbb8cab54289d6fbb880467Christian Robertson mGlowAlphaStart = 0.3f; 277c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell mGlowScaleYStart = Math.max(mGlowScaleY, 0.f); 278637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 279637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 280637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell // Growth for the size of the glow should be quadratic to properly 281637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell // respond 282637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell // to a user's scrolling speed. The faster the scrolling speed, the more 283637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell // intense the effect should be for both the size and the saturation. 284c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f) / 2, 1.f); 285637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell // Alpha should change for the glow as well as size. 286637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mGlowAlphaFinish = Math.max( 287637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA)); 288c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell mTargetDisplacement = 0.5f; 289637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell } 290637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 291a279918c5b709ce15edcccbd176108a0b0a07750Brian Attwell /** 292a279918c5b709ce15edcccbd176108a0b0a07750Brian Attwell * Set the color of this edge effect in argb. 293a279918c5b709ce15edcccbd176108a0b0a07750Brian Attwell * 294a279918c5b709ce15edcccbd176108a0b0a07750Brian Attwell * @param color Color in argb 295a279918c5b709ce15edcccbd176108a0b0a07750Brian Attwell */ 29680756e38882720860db52f1fcc21fa1505a02abfTor Norbye public void setColor(@ColorInt int color) { 297a279918c5b709ce15edcccbd176108a0b0a07750Brian Attwell mPaint.setColor(color); 298a279918c5b709ce15edcccbd176108a0b0a07750Brian Attwell } 299a279918c5b709ce15edcccbd176108a0b0a07750Brian Attwell 300a279918c5b709ce15edcccbd176108a0b0a07750Brian Attwell /** 301a279918c5b709ce15edcccbd176108a0b0a07750Brian Attwell * Return the color of this edge effect in argb. 302a279918c5b709ce15edcccbd176108a0b0a07750Brian Attwell * @return The color of this edge effect in argb 303a279918c5b709ce15edcccbd176108a0b0a07750Brian Attwell */ 30480756e38882720860db52f1fcc21fa1505a02abfTor Norbye @ColorInt 305a279918c5b709ce15edcccbd176108a0b0a07750Brian Attwell public int getColor() { 306a279918c5b709ce15edcccbd176108a0b0a07750Brian Attwell return mPaint.getColor(); 307a279918c5b709ce15edcccbd176108a0b0a07750Brian Attwell } 308637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 309637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell /** 310637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * Draw into the provided canvas. Assumes that the canvas has been rotated 311637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * accordingly and the size has been set. The effect will be drawn the full 31289935e41c593a599e8955388b27fb926e60e5e94Adam Powell * width of X=0 to X=width, beginning from Y=0 and extending to some factor < 313637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * 1.f of height. 314637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * 315637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * @param canvas Canvas to draw into 316637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * @return true if drawing should continue beyond this frame to continue the 317637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell * animation 318637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell */ 319637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell public boolean draw(Canvas canvas) { 320637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell update(); 321637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 322c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell final int count = canvas.save(); 3234e30d89ceda832300f80bf73f4f58cd2b51bf112Mindy Pereira 324c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell final float centerX = mBounds.centerX(); 3258eeb729cf20b770823514d83d3c2b5391a3d51e1Chris Craik final float centerY = mBounds.height() - mRadius; 3262897a6fdedd23efe96cd373886da78f1f1d18442Adam Powell 327710c456ddb32fe05e13965183e7018015da52eaeAdam Powell canvas.scale(1.f, Math.min(mGlowScaleY, 1.f) * mBaseGlowScale, centerX, 0); 328637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 329c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f; 3302897a6fdedd23efe96cd373886da78f1f1d18442Adam Powell float translateX = mBounds.width() * displacement / 2; 3312897a6fdedd23efe96cd373886da78f1f1d18442Adam Powell 3329be22455ac2548dd822bdfa7e4091561eac67d57Adam Powell canvas.clipRect(mBounds); 3332897a6fdedd23efe96cd373886da78f1f1d18442Adam Powell canvas.translate(translateX, 0); 334710c456ddb32fe05e13965183e7018015da52eaeAdam Powell mPaint.setAlpha((int) (0xff * mGlowAlpha)); 3358eeb729cf20b770823514d83d3c2b5391a3d51e1Chris Craik canvas.drawCircle(centerX, centerY, mRadius, mPaint); 336c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell canvas.restoreToCount(count); 337c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell 338c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell boolean oneLastFrame = false; 339c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell if (mState == STATE_RECEDE && mGlowScaleY == 0) { 3409d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy mState = STATE_IDLE; 341c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell oneLastFrame = true; 3429d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy } 3439d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy 344c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell return mState != STATE_IDLE || oneLastFrame; 345637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell } 346637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 3479d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy /** 348c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell * Return the maximum height that the edge effect will be drawn at given the original 349c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell * {@link #setSize(int, int) input size}. 350c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell * @return The maximum height of the edge effect 3519d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy */ 352c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell public int getMaxHeight() { 3532897a6fdedd23efe96cd373886da78f1f1d18442Adam Powell return (int) (mBounds.height() * MAX_GLOW_SCALE + 0.5f); 3549d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy } 3559d849a2f6351caed83105b90cab79223ec2bfbd3Romain Guy 356637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell private void update() { 357637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell final long time = AnimationUtils.currentAnimationTimeMillis(); 358637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell final float t = Math.min((time - mStartTime) / mDuration, 1.f); 359637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 360637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell final float interp = mInterpolator.getInterpolation(t); 361637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 362637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp; 363637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp; 364c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell mDisplacement = (mDisplacement + mTargetDisplacement) / 2; 365637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 366637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell if (t >= 1.f - EPSILON) { 367637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell switch (mState) { 368637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell case STATE_ABSORB: 369637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mState = STATE_RECEDE; 370637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mStartTime = AnimationUtils.currentAnimationTimeMillis(); 371637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mDuration = RECEDE_TIME; 372637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 373637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mGlowAlphaStart = mGlowAlpha; 374637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mGlowScaleYStart = mGlowScaleY; 375637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell 376c501db9f44f7967961f0ba61a0b3b63055ac1190Adam Powell // After absorb, the glow should fade to nothing. 377637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mGlowAlphaFinish = 0.f; 378637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mGlowScaleYFinish = 0.f; 379637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell break; 380637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell case STATE_PULL: 381710c456ddb32fe05e13965183e7018015da52eaeAdam Powell mState = STATE_PULL_DECAY; 382710c456ddb32fe05e13965183e7018015da52eaeAdam Powell mStartTime = AnimationUtils.currentAnimationTimeMillis(); 383710c456ddb32fe05e13965183e7018015da52eaeAdam Powell mDuration = PULL_DECAY_TIME; 384710c456ddb32fe05e13965183e7018015da52eaeAdam Powell 385710c456ddb32fe05e13965183e7018015da52eaeAdam Powell mGlowAlphaStart = mGlowAlpha; 386710c456ddb32fe05e13965183e7018015da52eaeAdam Powell mGlowScaleYStart = mGlowScaleY; 387710c456ddb32fe05e13965183e7018015da52eaeAdam Powell 388710c456ddb32fe05e13965183e7018015da52eaeAdam Powell // After pull, the glow should fade to nothing. 389710c456ddb32fe05e13965183e7018015da52eaeAdam Powell mGlowAlphaFinish = 0.f; 390710c456ddb32fe05e13965183e7018015da52eaeAdam Powell mGlowScaleYFinish = 0.f; 391637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell break; 392637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell case STATE_PULL_DECAY: 3930b1ab3a2776a1fff5b2abbddd3bc256e355e30efDaniel Mladenovic mState = STATE_RECEDE; 394637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell break; 395637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell case STATE_RECEDE: 396637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell mState = STATE_IDLE; 397637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell break; 398637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell } 399637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell } 400637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell } 401637d337b58d8eec6de19230a5dd5ca5581c0478dAdam Powell} 402