/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.widget; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; /** * This class performs the glow effect used at the edges of scrollable widgets. * @hide */ public class EdgeGlow { private static final String TAG = "EdgeGlow"; // Time it will take the effect to fully recede in ms private static final int RECEDE_TIME = 1000; // Time it will take before a pulled glow begins receding private static final int PULL_TIME = 250; // Time it will take for a pulled glow to decay to partial strength before release private static final int PULL_DECAY_TIME = 1000; private static final float HELD_EDGE_ALPHA = 0.7f; private static final float HELD_EDGE_SCALE_Y = 0.5f; private static final float HELD_GLOW_ALPHA = 0.5f; private static final float HELD_GLOW_SCALE_Y = 0.5f; private static final float MAX_GLOW_HEIGHT = 0.33f; private static final float PULL_GLOW_BEGIN = 0.5f; private static final float PULL_EDGE_BEGIN = 0.6f; // Minimum velocity that will be absorbed private static final int MIN_VELOCITY = 750; private static final float EPSILON = 0.001f; private Drawable mEdge; private Drawable mGlow; private int mWidth; private int mHeight; private float mEdgeAlpha; private float mEdgeScaleY; private float mGlowAlpha; private float mGlowScaleY; private float mEdgeAlphaStart; private float mEdgeAlphaFinish; private float mEdgeScaleYStart; private float mEdgeScaleYFinish; private float mGlowAlphaStart; private float mGlowAlphaFinish; private float mGlowScaleYStart; private float mGlowScaleYFinish; private long mStartTime; private int mDuration; private Interpolator mInterpolator; private static final int STATE_IDLE = 0; private static final int STATE_PULL = 1; private static final int STATE_ABSORB = 2; private static final int STATE_RECEDE = 3; private static final int STATE_PULL_DECAY = 4; private int mState = STATE_IDLE; private float mPullDistance; public EdgeGlow(Drawable edge, Drawable glow) { mEdge = edge; mGlow = glow; mInterpolator = new DecelerateInterpolator(); } public void setSize(int width, int height) { mWidth = width; mHeight = height; } public boolean isFinished() { return mState == STATE_IDLE; } public void finish() { mState = STATE_IDLE; } /** * Call when the object is pulled by the user. * @param deltaDistance Change in distance since the last call */ public void onPull(float deltaDistance) { final long now = AnimationUtils.currentAnimationTimeMillis(); if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) { return; } if (mState != STATE_PULL) { mGlowScaleY = PULL_GLOW_BEGIN; } mState = STATE_PULL; mStartTime = now; mDuration = PULL_TIME; mPullDistance += deltaDistance; float distance = Math.abs(mPullDistance); mEdgeAlpha = mEdgeAlphaStart = Math.max(PULL_EDGE_BEGIN, Math.min(distance, 1.f)); mEdgeScaleY = mEdgeScaleYStart = Math.max(HELD_EDGE_SCALE_Y, Math.min(distance, 2.f)); mGlowAlpha = mGlowAlphaStart = Math.max(0.5f, Math.min(mGlowAlpha + Math.abs(deltaDistance), 1.f)); float glowChange = Math.abs(deltaDistance); if (deltaDistance > 0 && mPullDistance < 0) { glowChange = -glowChange; } if (mPullDistance == 0) { mGlowScaleY = 0; } mGlowScaleY = mGlowScaleYStart = Math.max(0, mGlowScaleY + glowChange * 2); mEdgeAlphaFinish = mEdgeAlpha; mEdgeScaleYFinish = mEdgeScaleY; mGlowAlphaFinish = mGlowAlpha; mGlowScaleYFinish = mGlowScaleY; } /** * Call when the object is released after being pulled. */ public void onRelease() { mPullDistance = 0; if (mState != STATE_PULL && mState != STATE_PULL_DECAY) { return; } mState = STATE_RECEDE; mEdgeAlphaStart = mEdgeAlpha; mEdgeScaleYStart = mEdgeScaleY; mGlowAlphaStart = mGlowAlpha; mGlowScaleYStart = mGlowScaleY; mEdgeAlphaFinish = 0.f; mEdgeScaleYFinish = 0.1f; mGlowAlphaFinish = 0.f; mGlowScaleYFinish = 0.1f; mStartTime = AnimationUtils.currentAnimationTimeMillis(); mDuration = RECEDE_TIME; } /** * Call when the effect absorbs an impact at the given velocity. * @param velocity Velocity at impact in pixels per second. */ public void onAbsorb(int velocity) { mState = STATE_ABSORB; velocity = Math.max(MIN_VELOCITY, Math.abs(velocity)); mStartTime = AnimationUtils.currentAnimationTimeMillis(); mDuration = (int) (velocity * 0.03f); mEdgeAlphaStart = 0.5f; mEdgeScaleYStart = 0.2f; mGlowAlphaStart = 0.5f; mGlowScaleYStart = 0.f; mEdgeAlphaFinish = Math.max(0, Math.min(velocity * 0.01f, 1)); mEdgeScaleYFinish = 1.f; mGlowAlphaFinish = 1.f; mGlowScaleYFinish = Math.min(velocity * 0.001f, 1); } /** * Draw into the provided canvas. * Assumes that the canvas has been rotated accordingly and the size has been set. * The effect will be drawn the full width of X=0 to X=width, emitting from Y=0 and extending * to some factor < 1.f of height. * * @param canvas Canvas to draw into * @return true if drawing should continue beyond this frame to continue the animation */ public boolean draw(Canvas canvas) { update(); final int edgeHeight = mEdge.getIntrinsicHeight(); final int glowHeight = mGlow.getIntrinsicHeight(); final float distScale = (float) mHeight / mWidth; mGlow.setAlpha((int) (Math.max(0, Math.min(mGlowAlpha, 1)) * 255)); mGlow.setBounds(0, 0, mWidth, (int) Math.min(glowHeight * mGlowScaleY * distScale * 0.6f, mHeight * MAX_GLOW_HEIGHT)); mGlow.draw(canvas); mEdge.setAlpha((int) (Math.max(0, Math.min(mEdgeAlpha, 1)) * 255)); mEdge.setBounds(0, 0, mWidth, (int) (edgeHeight * mEdgeScaleY)); mEdge.draw(canvas); return mState != STATE_IDLE; } private void update() { final long time = AnimationUtils.currentAnimationTimeMillis(); final float t = Math.min((float) (time - mStartTime) / mDuration, 1.f); final float interp = mInterpolator.getInterpolation(t); mEdgeAlpha = mEdgeAlphaStart + (mEdgeAlphaFinish - mEdgeAlphaStart) * interp; mEdgeScaleY = mEdgeScaleYStart + (mEdgeScaleYFinish - mEdgeScaleYStart) * interp; mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp; mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp; if (t >= 1.f - EPSILON) { switch (mState) { case STATE_ABSORB: mState = STATE_RECEDE; mStartTime = AnimationUtils.currentAnimationTimeMillis(); mDuration = RECEDE_TIME; mEdgeAlphaStart = mEdgeAlpha; mEdgeScaleYStart = mEdgeScaleY; mGlowAlphaStart = mGlowAlpha; mGlowScaleYStart = mGlowScaleY; mEdgeAlphaFinish = 0.f; mEdgeScaleYFinish = 0.1f; mGlowAlphaFinish = 0.f; mGlowScaleYFinish = mGlowScaleY; break; case STATE_PULL: mState = STATE_PULL_DECAY; mStartTime = AnimationUtils.currentAnimationTimeMillis(); mDuration = PULL_DECAY_TIME; mEdgeAlphaStart = mEdgeAlpha; mEdgeScaleYStart = mEdgeScaleY; mGlowAlphaStart = mGlowAlpha; mGlowScaleYStart = mGlowScaleY; mEdgeAlphaFinish = Math.min(mEdgeAlphaStart, HELD_EDGE_ALPHA); mEdgeScaleYFinish = Math.min(mEdgeScaleYStart, HELD_EDGE_SCALE_Y); mGlowAlphaFinish = Math.min(mGlowAlphaStart, HELD_GLOW_ALPHA); mGlowScaleYFinish = Math.min(mGlowScaleY, HELD_GLOW_SCALE_Y); break; case STATE_PULL_DECAY: // Do nothing; wait for release break; case STATE_RECEDE: mState = STATE_IDLE; break; } } } }