FileDocCategorySizeDatePackage
EdgeEffect.javaAPI DocAndroid 5.1 API15025Thu Mar 12 22:22:10 GMT 2015android.widget

EdgeEffect

public class EdgeEffect extends Object
This class performs the graphical effect used at the edges of scrollable widgets when the user scrolls beyond the content bounds in 2D space.

EdgeEffect is stateful. Custom widgets using EdgeEffect should create an instance for each edge that should show the effect, feed it input data using the methods {@link #onAbsorb(int)}, {@link #onPull(float)}, and {@link #onRelease()}, and draw the effect using {@link #draw(Canvas)} in the widget's overridden {@link android.view.View#draw(Canvas)} method. If {@link #isFinished()} returns false after drawing, the edge effect's animation is not yet complete and the widget should schedule another drawing pass to continue the animation.

When drawing, widgets should draw their main content and child views first, usually by invoking super.draw(canvas) from an overridden draw method. (This will invoke onDraw and dispatch drawing to child views as needed.) The edge effect may then be drawn on top of the view's content using the {@link #draw(Canvas)} method.

Fields Summary
private static final String
TAG
private static final int
RECEDE_TIME
private static final int
PULL_TIME
private static final int
PULL_DECAY_TIME
private static final float
MAX_ALPHA
private static final float
MAX_GLOW_SCALE
private static final float
PULL_GLOW_BEGIN
private static final int
MIN_VELOCITY
private static final int
MAX_VELOCITY
private static final float
EPSILON
private static final double
ANGLE
private static final float
SIN
private static final float
COS
private float
mGlowAlpha
private float
mGlowScaleY
private float
mGlowAlphaStart
private float
mGlowAlphaFinish
private float
mGlowScaleYStart
private float
mGlowScaleYFinish
private long
mStartTime
private float
mDuration
private final android.view.animation.Interpolator
mInterpolator
private static final int
STATE_IDLE
private static final int
STATE_PULL
private static final int
STATE_ABSORB
private static final int
STATE_RECEDE
private static final int
STATE_PULL_DECAY
private static final float
PULL_DISTANCE_ALPHA_GLOW_FACTOR
private static final int
VELOCITY_GLOW_FACTOR
private int
mState
private float
mPullDistance
private final android.graphics.Rect
mBounds
private final android.graphics.Paint
mPaint
private float
mRadius
private float
mBaseGlowScale
private float
mDisplacement
private float
mTargetDisplacement
Constructors Summary
public EdgeEffect(android.content.Context context)
Construct a new EdgeEffect with a theme appropriate for the provided context.

param
context Context used to provide theming and resource information for the EdgeEffect


                                  
       
        mPaint.setAntiAlias(true);
        final TypedArray a = context.obtainStyledAttributes(
                com.android.internal.R.styleable.EdgeEffect);
        final int themeColor = a.getColor(
                com.android.internal.R.styleable.EdgeEffect_colorEdgeEffect, 0xff666666);
        a.recycle();
        mPaint.setColor((themeColor & 0xffffff) | 0x33000000);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
        mInterpolator = new DecelerateInterpolator();
    
Methods Summary
public booleandraw(android.graphics.Canvas canvas)
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, beginning 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

        update();

        final int count = canvas.save();

        final float centerX = mBounds.centerX();
        final float centerY = mBounds.height() - mRadius;

        canvas.scale(1.f, Math.min(mGlowScaleY, 1.f) * mBaseGlowScale, centerX, 0);

        final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f;
        float translateX = mBounds.width() * displacement / 2;

        canvas.clipRect(mBounds);
        canvas.translate(translateX, 0);
        mPaint.setAlpha((int) (0xff * mGlowAlpha));
        canvas.drawCircle(centerX, centerY, mRadius, mPaint);
        canvas.restoreToCount(count);

        boolean oneLastFrame = false;
        if (mState == STATE_RECEDE && mGlowScaleY == 0) {
            mState = STATE_IDLE;
            oneLastFrame = true;
        }

        return mState != STATE_IDLE || oneLastFrame;
    
public voidfinish()
Immediately finish the current animation. After this call {@link #isFinished()} will return true.

        mState = STATE_IDLE;
    
public intgetColor()
Return the color of this edge effect in argb.

return
The color of this edge effect in argb

        return mPaint.getColor();
    
public intgetMaxHeight()
Return the maximum height that the edge effect will be drawn at given the original {@link #setSize(int, int) input size}.

return
The maximum height of the edge effect

        return (int) (mBounds.height() * MAX_GLOW_SCALE + 0.5f);
    
public booleanisFinished()
Reports if this EdgeEffect's animation is finished. If this method returns false after a call to {@link #draw(Canvas)} the host widget should schedule another drawing pass to continue the animation.

return
true if animation is finished, false if drawing should continue on the next frame.

        return mState == STATE_IDLE;
    
public voidonAbsorb(int velocity)
Call when the effect absorbs an impact at the given velocity. Used when a fling reaches the scroll boundary.

When using a {@link android.widget.Scroller} or {@link android.widget.OverScroller}, the method getCurrVelocity will provide a reasonable approximation to use here.

param
velocity Velocity at impact in pixels per second.

        mState = STATE_ABSORB;
        velocity = Math.min(Math.max(MIN_VELOCITY, Math.abs(velocity)), MAX_VELOCITY);

        mStartTime = AnimationUtils.currentAnimationTimeMillis();
        mDuration = 0.15f + (velocity * 0.02f);

        // The glow depends more on the velocity, and therefore starts out
        // nearly invisible.
        mGlowAlphaStart = 0.3f;
        mGlowScaleYStart = Math.max(mGlowScaleY, 0.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) / 2, 1.f);
        // Alpha should change for the glow as well as size.
        mGlowAlphaFinish = Math.max(
                mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
        mTargetDisplacement = 0.5f;
    
public voidonPull(float deltaDistance)
A view should call this when content is pulled away from an edge by the user. This will update the state of the current visual effect and its associated animation. The host view should always {@link android.view.View#invalidate()} after this and draw the results accordingly.

Views using EdgeEffect should favor {@link #onPull(float, float)} when the displacement of the pull point is known.

param
deltaDistance Change in distance since the last call. Values may be 0 (no change) to 1.f (full length of the view) or negative values to express change back toward the edge reached to initiate the effect.

        onPull(deltaDistance, 0.5f);
    
public voidonPull(float deltaDistance, float displacement)
A view should call this when content is pulled away from an edge by the user. This will update the state of the current visual effect and its associated animation. The host view should always {@link android.view.View#invalidate()} after this and draw the results accordingly.

param
deltaDistance Change in distance since the last call. Values may be 0 (no change) to 1.f (full length of the view) or negative values to express change back toward the edge reached to initiate the effect.
param
displacement The displacement from the starting side of the effect of the point initiating the pull. In the case of touch this is the finger position. Values may be from 0-1.

        final long now = AnimationUtils.currentAnimationTimeMillis();
        mTargetDisplacement = displacement;
        if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) {
            return;
        }
        if (mState != STATE_PULL) {
            mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY);
        }
        mState = STATE_PULL;

        mStartTime = now;
        mDuration = PULL_TIME;

        mPullDistance += deltaDistance;

        final float absdd = Math.abs(deltaDistance);
        mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA,
                mGlowAlpha + (absdd * PULL_DISTANCE_ALPHA_GLOW_FACTOR));

        if (mPullDistance == 0) {
            mGlowScaleY = mGlowScaleYStart = 0;
        } else {
            final float scale = Math.max(0, 1 - 1 /
                    FloatMath.sqrt(Math.abs(mPullDistance) * mBounds.height()) - 0.3f) / 0.7f;

            mGlowScaleY = mGlowScaleYStart = scale;
        }

        mGlowAlphaFinish = mGlowAlpha;
        mGlowScaleYFinish = mGlowScaleY;
    
public voidonRelease()
Call when the object is released after being pulled. This will begin the "decay" phase of the effect. After calling this method the host view should {@link android.view.View#invalidate()} and thereby draw the results accordingly.

        mPullDistance = 0;

        if (mState != STATE_PULL && mState != STATE_PULL_DECAY) {
            return;
        }

        mState = STATE_RECEDE;
        mGlowAlphaStart = mGlowAlpha;
        mGlowScaleYStart = mGlowScaleY;

        mGlowAlphaFinish = 0.f;
        mGlowScaleYFinish = 0.f;

        mStartTime = AnimationUtils.currentAnimationTimeMillis();
        mDuration = RECEDE_TIME;
    
public voidsetColor(int color)
Set the color of this edge effect in argb.

param
color Color in argb

        mPaint.setColor(color);
    
public voidsetSize(int width, int height)
Set the size of this edge effect in pixels.

param
width Effect width in pixels
param
height Effect height in pixels

        final float r = width * 0.75f / SIN;
        final float y = COS * r;
        final float h = r - y;
        final float or = height * 0.75f / SIN;
        final float oy = COS * or;
        final float oh = or - oy;

        mRadius = r;
        mBaseGlowScale = h > 0 ? Math.min(oh / h, 1.f) : 1.f;

        mBounds.set(mBounds.left, mBounds.top, width, (int) Math.min(height, h));
    
private voidupdate()

        final long time = AnimationUtils.currentAnimationTimeMillis();
        final float t = Math.min((time - mStartTime) / mDuration, 1.f);

        final float interp = mInterpolator.getInterpolation(t);

        mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
        mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
        mDisplacement = (mDisplacement + mTargetDisplacement) / 2;

        if (t >= 1.f - EPSILON) {
            switch (mState) {
                case STATE_ABSORB:
                    mState = STATE_RECEDE;
                    mStartTime = AnimationUtils.currentAnimationTimeMillis();
                    mDuration = RECEDE_TIME;

                    mGlowAlphaStart = mGlowAlpha;
                    mGlowScaleYStart = mGlowScaleY;

                    // After absorb, the glow should fade to nothing.
                    mGlowAlphaFinish = 0.f;
                    mGlowScaleYFinish = 0.f;
                    break;
                case STATE_PULL:
                    mState = STATE_PULL_DECAY;
                    mStartTime = AnimationUtils.currentAnimationTimeMillis();
                    mDuration = PULL_DECAY_TIME;

                    mGlowAlphaStart = mGlowAlpha;
                    mGlowScaleYStart = mGlowScaleY;

                    // After pull, the glow should fade to nothing.
                    mGlowAlphaFinish = 0.f;
                    mGlowScaleYFinish = 0.f;
                    break;
                case STATE_PULL_DECAY:
                    mState = STATE_RECEDE;
                    break;
                case STATE_RECEDE:
                    mState = STATE_IDLE;
                    break;
            }
        }