Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 89b5b918 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Revert edge glow effect and restore damped overscroll" into ub-launcher3-dorval-polish

parents a80b1842 8d769d66
Loading
Loading
Loading
Loading
+78 −59
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import android.animation.TimeInterpolator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Bundle;
@@ -49,7 +48,6 @@ import android.view.animation.Interpolator;

import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.pageindicators.PageIndicator;
import com.android.launcher3.util.LauncherEdgeEffect;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;

@@ -70,6 +68,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
    public static final int PAGE_SNAP_ANIMATION_DURATION = 750;
    protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;

    // Overscroll constants
    private final static int OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION = 270;
    private static final float OVERSCROLL_ACCELERATE_FACTOR = 2;
    private static final float OVERSCROLL_DAMP_FACTOR = 0.07f;

    private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
    // The page is moved more than halfway, automatically move to the next page on touch up.
    private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f;
@@ -145,6 +148,13 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc

    protected boolean mWasInOverscroll = false;

    // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise
    // it is equal to the scaled overscroll position. We use a separate value so as to prevent
    // the screens from continuing to translate beyond the normal bounds.
    protected int mOverScrollX;

    protected int mUnboundedScrollX;

    // Page Indicator
    @Thunk int mPageIndicatorViewId;
    protected PageIndicator mPageIndicator;
@@ -184,10 +194,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
    protected final Rect mInsets = new Rect();
    protected final boolean mIsRtl;

    // Edge effect
    private final LauncherEdgeEffect mEdgeGlowLeft = new LauncherEdgeEffect();
    private final LauncherEdgeEffect mEdgeGlowRight = new LauncherEdgeEffect();

    public PagedView(Context context) {
        this(context, null);
    }
@@ -229,8 +235,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
        setWillNotDraw(false);

        int edgeEffectColor = Themes.getAttrColor(getContext(), android.R.attr.colorEdgeEffect);
        mEdgeGlowLeft.setColor(edgeEffectColor);
        mEdgeGlowRight.setColor(edgeEffectColor);
    }

    protected void setDefaultInterpolator(Interpolator interpolator) {
@@ -476,7 +480,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
    }

    protected int getUnboundedScrollX() {
        return getScrollX();
        return mUnboundedScrollX;
    }

    @Override
@@ -499,6 +503,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
            x = Math.max(x, mFreeScrollMinScrollX);
        }

        mUnboundedScrollX = x;

        boolean isXBeforeFirstPage = mIsRtl ? (x > mMaxScrollX) : (x < 0);
        boolean isXAfterLastPage = mIsRtl ? (x < 0) : (x > mMaxScrollX);
        if (isXBeforeFirstPage) {
@@ -526,6 +532,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
                overScroll(0);
                mWasInOverscroll = false;
            }
            mOverScrollX = x;
            super.scrollTo(x, y);
        }

@@ -565,7 +572,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
        if (mScroller.computeScrollOffset()) {
            // Don't bother scrolling if the page does not need to be moved
            if (getUnboundedScrollX() != mScroller.getCurrX()
                    || getScrollY() != mScroller.getCurrY()) {
                    || getScrollY() != mScroller.getCurrY()
                    || mOverScrollX != mScroller.getCurrX()) {
                float scaleX = mFreeScroll ? getScaleX() : 1f;
                int scrollX = (int) (mScroller.getCurrX() * (1 / scaleX));
                scrollTo(scrollX, mScroller.getCurrY());
@@ -972,47 +980,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
        range[1] = Math.max(0, getChildCount() - 1);
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        if (getPageCount() > 0) {
            if (!mEdgeGlowLeft.isFinished()) {
                final int restoreCount = canvas.save();
                Rect display = mViewport;
                canvas.translate(display.left, display.top);
                canvas.rotate(270);

                getEdgeVerticalPosition(sTmpIntPoint);
                canvas.translate(display.top - sTmpIntPoint[1], 0);
                mEdgeGlowLeft.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width());
                if (mEdgeGlowLeft.draw(canvas)) {
                    postInvalidateOnAnimation();
                }
                canvas.restoreToCount(restoreCount);
            }
            if (!mEdgeGlowRight.isFinished()) {
                final int restoreCount = canvas.save();
                Rect display = mViewport;
                canvas.translate(display.left + mPageScrolls[mIsRtl ? 0 : (getPageCount() - 1)], display.top);
                canvas.rotate(90);

                getEdgeVerticalPosition(sTmpIntPoint);

                canvas.translate(sTmpIntPoint[0] - display.top, -display.width());
                mEdgeGlowRight.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width());
                if (mEdgeGlowRight.draw(canvas)) {
                    postInvalidateOnAnimation();
                }
                canvas.restoreToCount(restoreCount);
            }
        }
    }

    /**
     * Returns the top and bottom position for the edge effect.
     */
    protected abstract void getEdgeVerticalPosition(int[] pos);

    @Override
    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
        int page = indexToPage(indexOfChild(child));
@@ -1335,6 +1302,29 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
        }
    }

    // This curve determines how the effect of scrolling over the limits of the page dimishes
    // as the user pulls further and further from the bounds
    private float overScrollInfluenceCurve(float f) {
        f -= 1.0f;
        return f * f * f + 1.0f;
    }

    protected float acceleratedOverFactor(float amount) {
        int screenSize = getViewportWidth();

        // We want to reach the max over scroll effect when the user has
        // over scrolled half the size of the screen
        float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize);

        if (Float.compare(f, 0f) == 0) return 0;

        // Clamp this factor, f, to -1 < f < 1
        if (Math.abs(f) >= 1) {
            f /= Math.abs(f);
        }
        return f;
    }

    // While layout transitions are occurring, a child's position may stray from its baseline
    // position. This method returns the magnitude of this stray at any given time.
    public int getLayoutTransitionOffsetForPage(int index) {
@@ -1356,13 +1346,25 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc

    protected void dampedOverScroll(float amount) {
        int screenSize = getViewportWidth();

        float f = (amount / screenSize);
        if (f < 0) {
            mEdgeGlowLeft.onPull(-f);
        } else if (f > 0) {
            mEdgeGlowRight.onPull(f);

        if (Float.compare(f, 0f) == 0) return;

        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));

        // Clamp this factor, f, to -1 < f < 1
        if (Math.abs(f) >= 1) {
            f /= Math.abs(f);
        }

        int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize);
        if (amount < 0) {
            mOverScrollX = overScrollAmount;
            super.scrollTo(mOverScrollX, getScrollY());
        } else {
            return;
            mOverScrollX = mMaxScrollX + overScrollAmount;
            super.scrollTo(mOverScrollX, getScrollY());
        }
        invalidate();
    }
@@ -1371,6 +1373,14 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
        dampedOverScroll(amount);
    }

    protected float maxOverScroll() {
        // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not
        // exceed). Used to find out how much extra wallpaper we need for the over scroll effect
        float f = 1.0f;
        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
        return OVERSCROLL_DAMP_FACTOR * f;
    }

    /**
     * return true if freescroll has been enabled, false otherwise
     */
@@ -1715,8 +1725,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
        mCancelTap = false;
        mTouchState = TOUCH_STATE_REST;
        mActivePointerId = INVALID_POINTER;
        mEdgeGlowLeft.onRelease();
        mEdgeGlowRight.onRelease();
    }

    /**
@@ -1830,7 +1838,18 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
    }

    protected void snapToDestination() {
        snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION);
        snapToPage(getPageNearestToCenterOfScreen(), getPageSnapDuration());
    }

    protected boolean isInOverScroll() {
        return (mOverScrollX > mMaxScrollX || mOverScrollX < 0);
    }

    protected int getPageSnapDuration() {
        if (isInOverScroll()) {
            return OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION;
        }
        return PAGE_SNAP_ANIMATION_DURATION;
    }

    public static class ScrollInterpolator implements Interpolator {
+2 −24
Original line number Diff line number Diff line
@@ -300,8 +300,7 @@ public class Workspace extends PagedView
    boolean mScrollInteractionBegan;
    boolean mStartedSendingScrollEvents;
    float mLastOverlayScroll = 0;
    // Total over scrollX in the overlay direction.
    private int mUnboundedScrollX;

    private boolean mForceDrawAdjacentPages = false;
    // Total over scrollX in the overlay direction.
    private float mOverlayTranslation;
@@ -1321,18 +1320,10 @@ public class Workspace extends PagedView
        onOverlayScrollChanged(0);
    }

    @Override
    protected int getUnboundedScrollX() {
        if (isScrollingOverlay()) {
            return mUnboundedScrollX;
        }

        return super.getUnboundedScrollX();
    }

    private boolean isScrollingOverlay() {
        return mLauncherOverlay != null &&
                ((mIsRtl && mUnboundedScrollX > mMaxScrollX) || (!mIsRtl && mUnboundedScrollX < 0));
                ((mIsRtl && getUnboundedScrollX() > mMaxScrollX) || (!mIsRtl && getUnboundedScrollX() < 0));
    }

    @Override
@@ -1351,12 +1342,6 @@ public class Workspace extends PagedView
        }
    }

    @Override
    public void scrollTo(int x, int y) {
        mUnboundedScrollX = x;
        super.scrollTo(x, y);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
@@ -1529,13 +1514,6 @@ public class Workspace extends PagedView
        }
    }

    @Override
    protected void getEdgeVerticalPosition(int[] pos) {
        View child = getChildAt(getPageCount() - 1);
        pos[0] = child.getTop();
        pos[1] = child.getBottom();
    }

    @Override
    protected void notifyPageSwitchListener() {
        super.notifyPageSwitchListener();
+0 −6
Original line number Diff line number Diff line
@@ -701,10 +701,4 @@ public class FolderPagedView extends PagedView {
    public int itemsPerPage() {
        return mMaxItemsPerPage;
    }

    @Override
    protected void getEdgeVerticalPosition(int[] pos) {
        pos[0] = 0;
        pos[1] = getViewportHeight();
    }
}
+0 −365
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 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 com.android.launcher3.util;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;

/**
 * This class differs from the framework {@link android.widget.EdgeEffect}:
 *   1) It does not use PorterDuffXfermode
 *   2) The width to radius factor is smaller (0.5 instead of 0.75)
 */
public class LauncherEdgeEffect {

    // Time it will take the effect to fully recede in ms
    private static final int RECEDE_TIME = 600;

    // Time it will take before a pulled glow begins receding in ms
    private static final int PULL_TIME = 167;

    // Time it will take in ms for a pulled glow to decay to partial strength before release
    private static final int PULL_DECAY_TIME = 2000;

    private static final float MAX_ALPHA = 0.5f;

    private static final float MAX_GLOW_SCALE = 2.f;

    private static final float PULL_GLOW_BEGIN = 0.f;

    // Minimum velocity that will be absorbed
    private static final int MIN_VELOCITY = 100;
    // Maximum velocity, clamps at this value
    private static final int MAX_VELOCITY = 10000;

    private static final float EPSILON = 0.001f;

    private static final double ANGLE = Math.PI / 6;
    private static final float SIN = (float) Math.sin(ANGLE);
    private static final float COS = (float) Math.cos(ANGLE);

    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 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 static final float PULL_DISTANCE_ALPHA_GLOW_FACTOR = 0.8f;

    private static final int VELOCITY_GLOW_FACTOR = 6;

    private int mState = STATE_IDLE;

    private float mPullDistance;

    private final Rect mBounds = new Rect();
    private final Paint mPaint = new Paint();
    private float mRadius;
    private float mBaseGlowScale;
    private float mDisplacement = 0.5f;
    private float mTargetDisplacement = 0.5f;

    /**
     * Construct a new EdgeEffect with a theme appropriate for the provided context.
     */
    public LauncherEdgeEffect() {
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
        mInterpolator = new DecelerateInterpolator();
    }

    /**
     * Set the size of this edge effect in pixels.
     *
     * @param width Effect width in pixels
     * @param height Effect height in pixels
     */
    public void setSize(int width, int height) {
        final float r = width * 0.5f / 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));
    }

    /**
     * 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.
     */
    public boolean isFinished() {
        return mState == STATE_IDLE;
    }

    /**
     * Immediately finish the current animation.
     * After this call {@link #isFinished()} will return true.
     */
    public void finish() {
        mState = STATE_IDLE;
    }

    /**
     * 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.
     *
     * <p>Views using EdgeEffect should favor {@link #onPull(float, float)} when the displacement
     * of the pull point is known.</p>
     *
     * @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.
     */
    public void onPull(float deltaDistance) {
        onPull(deltaDistance, 0.5f);
    }

    /**
     * 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.
     */
    public void onPull(float deltaDistance, float displacement) {
        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 = (float) (Math.max(0, 1 - 1 /
                    Math.sqrt(Math.abs(mPullDistance) * mBounds.height()) - 0.3d) / 0.7d);

            mGlowScaleY = mGlowScaleYStart = scale;
        }

        mGlowAlphaFinish = mGlowAlpha;
        mGlowScaleYFinish = mGlowScaleY;
    }

    /**
     * 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.
     */
    public void onRelease() {
        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;
    }

    /**
     * Call when the effect absorbs an impact at the given velocity.
     * Used when a fling reaches the scroll boundary.
     *
     * <p>When using a {@link android.widget.Scroller} or {@link android.widget.OverScroller},
     * the method <code>getCurrVelocity</code> will provide a reasonable approximation
     * to use here.</p>
     *
     * @param velocity Velocity at impact in pixels per second.
     */
    public void onAbsorb(int velocity) {
        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;
    }

    /**
     * Set the color of this edge effect in argb.
     *
     * @param color Color in argb
     */
    public void setColor(int color) {
        mPaint.setColor(color);
    }

    /**
     * Return the color of this edge effect in argb.
     * @return The color of this edge effect in argb
     */
    public int getColor() {
        return mPaint.getColor();
    }

    /**
     * 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
     */
    public boolean draw(Canvas canvas) {
        update();

        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;
        mPaint.setAlpha((int) (0xff * mGlowAlpha));
        canvas.drawCircle(centerX + translateX, centerY, mRadius, mPaint);

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

        return mState != STATE_IDLE || oneLastFrame;
    }

    /**
     * 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
     */
    public int getMaxHeight() {
        return (int) (mBounds.height() * MAX_GLOW_SCALE + 0.5f);
    }

    private void update() {
        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;
            }
        }
    }
}