package com.external.HorizontalVariableListView.widget;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
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 {
// Time it will take the effect to fully recede in ms
/** The Constant RECEDE_TIME. */
private static final int RECEDE_TIME = 1000;
// Time it will take before a pulled glow begins receding
/** The Constant PULL_TIME. */
private static final int PULL_TIME = 167;
// Time it will take for a pulled glow to decay to partial strength before release
/** The Constant PULL_DECAY_TIME. */
private static final int PULL_DECAY_TIME = 1000;
/** The Constant MAX_ALPHA. */
private static final float MAX_ALPHA = 0.8f;
/** The Constant HELD_EDGE_SCALE_Y. */
private static final float HELD_EDGE_SCALE_Y = 0.5f;
/** The Constant MAX_GLOW_HEIGHT. */
private static final float MAX_GLOW_HEIGHT = 3.f;
/** The Constant PULL_GLOW_BEGIN. */
private static final float PULL_GLOW_BEGIN = 1.f;
/** The Constant PULL_EDGE_BEGIN. */
private static final float PULL_EDGE_BEGIN = 0.6f;
// Minimum velocity that will be absorbed
/** The Constant MIN_VELOCITY. */
private static final int MIN_VELOCITY = 100;
/** The Constant EPSILON. */
private static final float EPSILON = 0.001f;
/** The m edge. */
private final Drawable mEdge;
/** The m glow. */
private final Drawable mGlow;
/** The m width. */
private int mWidth;
/** The m height. */
private int mHeight;
/** The m edge alpha. */
private float mEdgeAlpha;
/** The m edge scale y. */
private float mEdgeScaleY;
/** The m glow alpha. */
private float mGlowAlpha;
/** The m glow scale y. */
private float mGlowScaleY;
/** The m edge alpha start. */
private float mEdgeAlphaStart;
/** The m edge alpha finish. */
private float mEdgeAlphaFinish;
/** The m edge scale y start. */
private float mEdgeScaleYStart;
/** The m edge scale y finish. */
private float mEdgeScaleYFinish;
/** The m glow alpha start. */
private float mGlowAlphaStart;
/** The m glow alpha finish. */
private float mGlowAlphaFinish;
/** The m glow scale y start. */
private float mGlowScaleYStart;
/** The m glow scale y finish. */
private float mGlowScaleYFinish;
/** The m start time. */
private long mStartTime;
/** The m duration. */
private float mDuration;
/** The m interpolator. */
private final Interpolator mInterpolator;
/** The Constant STATE_IDLE. */
private static final int STATE_IDLE = 0;
/** The Constant STATE_PULL. */
private static final int STATE_PULL = 1;
/** The Constant STATE_ABSORB. */
private static final int STATE_ABSORB = 2;
/** The Constant STATE_RECEDE. */
private static final int STATE_RECEDE = 3;
/** The Constant STATE_PULL_DECAY. */
private static final int STATE_PULL_DECAY = 4;
// How much dragging should effect the height of the edge image.
// Number determined by user testing.
/** The Constant PULL_DISTANCE_EDGE_FACTOR. */
private static final int PULL_DISTANCE_EDGE_FACTOR = 1;
// How much dragging should effect the height of the glow image.
// Number determined by user testing.
/** The Constant PULL_DISTANCE_GLOW_FACTOR. */
private static final int PULL_DISTANCE_GLOW_FACTOR = 1;
/** The Constant PULL_DISTANCE_ALPHA_GLOW_FACTOR. */
private static final float PULL_DISTANCE_ALPHA_GLOW_FACTOR = 0.8f;
/** The Constant VELOCITY_EDGE_FACTOR. */
private static final int VELOCITY_EDGE_FACTOR = 8;
/** The Constant VELOCITY_GLOW_FACTOR. */
private static final int VELOCITY_GLOW_FACTOR = 16;
/** The m state. */
private int mState = STATE_IDLE;
/** The m pull distance. */
private float mPullDistance;
/**
* Instantiates a new edge glow.
*
* @param edge
* the edge
* @param glow
* the glow
*/
public EdgeGlow( Drawable edge, Drawable glow ) {
mEdge = edge;
mGlow = glow;
mInterpolator = new DecelerateInterpolator();
}
public void setColorFilter( int color, PorterDuff.Mode mode ) {
if ( null != mEdge ) mEdge.setColorFilter( color, mode );
if ( null != mGlow ) mGlow.setColorFilter( color, mode );
}
/**
* Sets the size.
*
* @param width
* the width
* @param height
* the height
*/
public void setSize( int width, int height ) {
mWidth = width;
mHeight = height;
}
/**
* Checks if is finished.
*
* @return true, if is finished
*/
public boolean isFinished() {
return mState == STATE_IDLE;
}
/**
* Finish.
*/
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, MAX_ALPHA ) );
mEdgeScaleY = mEdgeScaleYStart = Math.max( HELD_EDGE_SCALE_Y, Math.min( distance * PULL_DISTANCE_EDGE_FACTOR, 1.f ) );
mGlowAlpha = mGlowAlphaStart = Math.min( MAX_ALPHA, mGlowAlpha
+ ( Math.abs( deltaDistance ) * PULL_DISTANCE_ALPHA_GLOW_FACTOR ) );
float glowChange = Math.abs( deltaDistance );
if ( deltaDistance > 0 && mPullDistance < 0 ) {
glowChange = -glowChange;
}
if ( mPullDistance == 0 ) {
mGlowScaleY = 0;
}
// Do not allow glow to get larger than MAX_GLOW_HEIGHT.
mGlowScaleY = mGlowScaleYStart = Math.min( MAX_GLOW_HEIGHT, Math.max( 0, glowChange * PULL_DISTANCE_GLOW_FACTOR ) );
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.f;
mGlowAlphaFinish = 0.f;
mGlowScaleYFinish = 0.f;
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 = 0.1f + ( velocity * 0.03f );
// The edge should always be at least partially visible, regardless
// of velocity.
mEdgeAlphaStart = 0.f;
mEdgeScaleY = mEdgeScaleYStart = 0.f;
// The glow depends more on the velocity, and therefore starts out
// nearly invisible.
mGlowAlphaStart = 0.5f;
mGlowScaleYStart = 0.f;
// Factor the velocity by 8. Testing on device shows this works best to
// reflect the strength of the user's scrolling.
mEdgeAlphaFinish = Math.max( 0, Math.min( velocity * VELOCITY_EDGE_FACTOR, 1 ) );
// Edge should never get larger than the size of its asset.
mEdgeScaleYFinish = Math.max( HELD_EDGE_SCALE_Y, Math.min( velocity * VELOCITY_EDGE_FACTOR, 1.f ) );
// Growth for the size of the glow should be quadratic to properly
// respond
// to a user's scrolling speed. The faster the scrolling speed, the more
// intense the effect should be for both the size and the saturation.
mGlowScaleYFinish = Math.min( 0.025f + ( velocity * ( velocity / 100 ) * 0.00015f ), 1.75f );
// Alpha should change for the glow as well as size.
mGlowAlphaFinish = Math.max( mGlowAlphaStart, Math.min( velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA ) );
}
/**
* 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 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 );
if ( mEdge != null ) {
final int edgeHeight = mEdge.getIntrinsicHeight();
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;
}
/**
* Update.
*/
private void update() {
final long time = AnimationUtils.currentAnimationTimeMillis();
final float t = Math.min( ( 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;
// After absorb, the glow and edge should fade to nothing.
mEdgeAlphaFinish = 0.f;
mEdgeScaleYFinish = 0.f;
mGlowAlphaFinish = 0.f;
mGlowScaleYFinish = 0.f;
break;
case STATE_PULL:
mState = STATE_PULL_DECAY;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mDuration = PULL_DECAY_TIME;
mEdgeAlphaStart = mEdgeAlpha;
mEdgeScaleYStart = mEdgeScaleY;
mGlowAlphaStart = mGlowAlpha;
mGlowScaleYStart = mGlowScaleY;
// After pull, the glow and edge should fade to nothing.
mEdgeAlphaFinish = 0.f;
mEdgeScaleYFinish = 0.f;
mGlowAlphaFinish = 0.f;
mGlowScaleYFinish = 0.f;
break;
case STATE_PULL_DECAY:
// When receding, we want edge to decrease more slowly
// than the glow.
float factor = mGlowScaleYFinish != 0 ? 1 / ( mGlowScaleYFinish * mGlowScaleYFinish ) : Float.MAX_VALUE;
mEdgeScaleY = mEdgeScaleYStart + ( mEdgeScaleYFinish - mEdgeScaleYStart ) * interp * factor;
break;
case STATE_RECEDE:
mState = STATE_IDLE;
break;
}
}
}
}