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

Commit a3f0c2b2 authored by Alan Viverette's avatar Alan Viverette
Browse files

Simplify ripple background drawing, fix ripple alphas

Eliminates an extra saveLayer on the background in the common case of
a rectangle-bounded ripple.

Ripples and backgrounds are now drawn at 50% opacity of the ripple
color, which ensures that both the ripple and background are visible
and that the pressed state has a correct combined alpha.

Also fixes a bug where hardware (RT) animation was getting turned off
prematurely.

BUG: 17405007
BUG: 17398089
BUG: 17394445
BUG: 17389859
Change-Id: Idb5808368fe563581a51a8cb9778275ee8d22f4c
parent 3f70141e
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -19,8 +19,8 @@
    <color name="background_material_dark">#ff212121</color>
    <color name="background_material_light">#fffafafa</color>

    <color name="ripple_material_light">#20444444</color>
    <color name="ripple_material_dark">#20ffffff</color>
    <color name="ripple_material_light">#40000000</color>
    <color name="ripple_material_dark">#40ffffff</color>

    <color name="button_material_dark">#ff5a595b</color>
    <color name="button_material_light">#ffd6d7d7</color>
+16 −27
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.graphics.Canvas;
import android.graphics.CanvasProperty;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
@@ -58,7 +59,7 @@ class Ripple {
    private final Rect mBounds;

    /** Full-opacity color for drawing this ripple. */
    private int mColor;
    private int mColorOpaque;

    /** Maximum ripple radius. */
    private float mOuterRadius;
@@ -120,7 +121,7 @@ class Ripple {
    }

    public void setup(int maxRadius, int color, float density) {
        mColor = color | 0xFF000000;
        mColorOpaque = color | 0xFF000000;

        if (maxRadius != RippleDrawable.RADIUS_AUTO) {
            mHasMaxRadius = true;
@@ -236,6 +237,9 @@ class Ripple {
        if (N > 0) {
            cancelHardwareAnimations(false);

            // We canceled old animations, but we're about to run new ones.
            mHardwareAnimating = true;

            for (int i = 0; i < N; i++) {
                pendingAnimations.get(i).setTarget(c);
                pendingAnimations.get(i).start();
@@ -253,9 +257,8 @@ class Ripple {
    private boolean drawSoftware(Canvas c, Paint p) {
        boolean hasContent = false;

        // Cache the paint alpha so we can restore it later.
        final int paintAlpha = p.getAlpha();
        final int alpha = (int) (paintAlpha * mOpacity + 0.5f);
        p.setColor(mColorOpaque);
        final int alpha = (int) (255 * mOpacity + 0.5f);
        final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
        if (alpha > 0 && radius > 0) {
            final float x = MathUtils.lerp(
@@ -268,8 +271,6 @@ class Ripple {
            hasContent = true;
        }

        p.setAlpha(paintAlpha);

        return hasContent;
    }

@@ -369,7 +370,7 @@ class Ripple {
        final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
        final Paint paint = getTempPaint();
        paint.setAntiAlias(true);
        paint.setColor(mColor);
        paint.setColor(mColorOpaque);
        paint.setAlpha((int) (255 * mOpacity + 0.5f));
        paint.setStyle(Style.FILL);
        mPropPaint = CanvasProperty.createPaint(paint);
@@ -402,6 +403,12 @@ class Ripple {

        mHardwareAnimating = true;

        // Set up the software values to match the hardware end values.
        mOpacity = 0;
        mTweenX = 1;
        mTweenY = 1;
        mTweenRadius = 1;

        invalidateSelf();
    }

@@ -412,7 +419,7 @@ class Ripple {
    public void jump() {
        mCanceled = true;
        endSoftwareAnimations();
        endHardwareAnimations();
        cancelHardwareAnimations(true);
        mCanceled = false;
    }

@@ -438,24 +445,6 @@ class Ripple {
        }
    }

    private void endHardwareAnimations() {
        final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
        final int N = runningAnimations.size();
        for (int i = 0; i < N; i++) {
            runningAnimations.get(i).end();
        }
        runningAnimations.clear();

        // Abort any pending animations. Since we always have a completion
        // listener on a pending animation, we also need to remove ourselves.
        if (!mPendingAnimations.isEmpty()) {
            mPendingAnimations.clear();
            removeSelf();
        }

        mHardwareAnimating = false;
    }

    private Paint getTempPaint() {
        if (mTempPaint == null) {
            mTempPaint = new Paint();
+36 −56
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.graphics.Canvas;
import android.graphics.CanvasProperty;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
@@ -46,8 +47,6 @@ class RippleBackground {
    private static final float WAVE_OUTER_SIZE_INFLUENCE_MAX = 200f;
    private static final float WAVE_OUTER_SIZE_INFLUENCE_MIN = 40f;

    private static final long RIPPLE_ENTER_DELAY = 80;

    // Hardware animators.
    private final ArrayList<RenderNodeAnimator> mRunningAnimations =
            new ArrayList<RenderNodeAnimator>();
@@ -60,7 +59,10 @@ class RippleBackground {
    private final Rect mBounds;

    /** Full-opacity color for drawing this ripple. */
    private int mColor;
    private int mColorOpaque;

    /** Maximum alpha value for drawing this ripple. */
    private int mColorAlpha;

    /** Maximum ripple radius. */
    private float mOuterRadius;
@@ -103,7 +105,8 @@ class RippleBackground {
    }

    public void setup(int maxRadius, int color, float density) {
        mColor = color | 0xFF000000;
        mColorOpaque = color | 0xFF000000;
        mColorAlpha = Color.alpha(color);

        if (maxRadius != RippleDrawable.RADIUS_AUTO) {
            mHasMaxRadius = true;
@@ -159,6 +162,11 @@ class RippleBackground {
        return hasContent;
    }

    public boolean shouldDraw() {
        final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f);
        return mCanUseHardware && mHardwareAnimating || outerAlpha > 0 && mOuterRadius > 0;
    }

    private boolean drawHardware(HardwareCanvas c) {
        // If we have any pending hardware animations, cancel any running
        // animations and start those now.
@@ -167,6 +175,9 @@ class RippleBackground {
        if (N > 0) {
            cancelHardwareAnimations(false);

            // We canceled old animations, but we're about to run new ones.
            mHardwareAnimating = true;

            for (int i = 0; i < N; i++) {
                pendingAnimations.get(i).setTarget(c);
                pendingAnimations.get(i).start();
@@ -184,10 +195,8 @@ class RippleBackground {
    private boolean drawSoftware(Canvas c, Paint p) {
        boolean hasContent = false;

        // Cache the paint alpha so we can restore it later.
        final int paintAlpha = p.getAlpha();

        final int outerAlpha = (int) (paintAlpha * mOuterOpacity + 0.5f);
        p.setColor(mColorOpaque);
        final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f);
        if (outerAlpha > 0 && mOuterRadius > 0) {
            p.setAlpha(outerAlpha);
            p.setStyle(Style.FILL);
@@ -195,8 +204,6 @@ class RippleBackground {
            hasContent = true;
        }

        p.setAlpha(paintAlpha);

        return hasContent;
    }

@@ -248,25 +255,25 @@ class RippleBackground {
        // Determine at what time the inner and outer opacity intersect.
        // inner(t) = mOpacity - t * WAVE_OPACITY_DECAY_VELOCITY / 1000
        // outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000
        final int outerInflection = Math.max(0, (int) (1000 * (1 - mOuterOpacity)
        final int inflectionDuration = Math.max(0, (int) (1000 * (1 - mOuterOpacity)
                / (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f));
        final int inflectionOpacity = (int) (255 * (mOuterOpacity + outerInflection
                * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);
        final int inflectionOpacity = (int) (mColorAlpha * (mOuterOpacity
                + inflectionDuration * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);

        if (mCanUseHardware) {
            exitHardware(opacityDuration, outerInflection, inflectionOpacity);
            exitHardware(opacityDuration, inflectionDuration, inflectionOpacity);
        } else {
            exitSoftware(opacityDuration, outerInflection, inflectionOpacity);
            exitSoftware(opacityDuration, inflectionDuration, inflectionOpacity);
        }
    }

    private void exitHardware(int opacityDuration, int outerInflection, int inflectionOpacity) {
    private void exitHardware(int opacityDuration, int inflectionDuration, int inflectionOpacity) {
        mPendingAnimations.clear();

        final Paint outerPaint = getTempPaint();
        outerPaint.setAntiAlias(true);
        outerPaint.setColor(mColor);
        outerPaint.setAlpha((int) (255 * mOuterOpacity + 0.5f));
        outerPaint.setColor(mColorOpaque);
        outerPaint.setAlpha((int) (mColorAlpha * mOuterOpacity + 0.5f));
        outerPaint.setStyle(Style.FILL);
        mPropOuterPaint = CanvasProperty.createPaint(outerPaint);
        mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius);
@@ -274,21 +281,21 @@ class RippleBackground {
        mPropOuterY = CanvasProperty.createFloat(mOuterY);

        final RenderNodeAnimator outerOpacityAnim;
        if (outerInflection > 0) {
        if (inflectionDuration > 0) {
            // Outer opacity continues to increase for a bit.
            outerOpacityAnim = new RenderNodeAnimator(
                    mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity);
            outerOpacityAnim.setDuration(outerInflection);
            outerOpacityAnim = new RenderNodeAnimator(mPropOuterPaint,
                    RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity);
            outerOpacityAnim.setDuration(inflectionDuration);
            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);

            // Chain the outer opacity exit animation.
            final int outerDuration = opacityDuration - outerInflection;
            final int outerDuration = opacityDuration - inflectionDuration;
            if (outerDuration > 0) {
                final RenderNodeAnimator outerFadeOutAnim = new RenderNodeAnimator(
                        mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
                outerFadeOutAnim.setDuration(outerDuration);
                outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
                outerFadeOutAnim.setStartDelay(outerInflection);
                outerFadeOutAnim.setStartDelay(inflectionDuration);
                outerFadeOutAnim.setStartValue(inflectionOpacity);
                outerFadeOutAnim.addListener(mAnimationListener);

@@ -320,7 +327,7 @@ class RippleBackground {
     */
    public void jump() {
        endSoftwareAnimations();
        endHardwareAnimations();
        cancelHardwareAnimations(true);
    }

    private void endSoftwareAnimations() {
@@ -330,23 +337,6 @@ class RippleBackground {
        }
    }

    private void endHardwareAnimations() {
        final ArrayList<RenderNodeAnimator> runningAnimations = mRunningAnimations;
        final int N = runningAnimations.size();
        for (int i = 0; i < N; i++) {
            runningAnimations.get(i).end();
        }
        runningAnimations.clear();

        // Abort any pending animations. Since we always have a completion
        // listener on a pending animation, we also need to remove ourselves.
        if (!mPendingAnimations.isEmpty()) {
            mPendingAnimations.clear();
        }

        mHardwareAnimating = false;
    }

    private Paint getTempPaint() {
        if (mTempPaint == null) {
            mTempPaint = new Paint();
@@ -354,18 +344,18 @@ class RippleBackground {
        return mTempPaint;
    }

    private void exitSoftware(int opacityDuration, int outerInflection, int inflectionOpacity) {
    private void exitSoftware(int opacityDuration, int inflectionDuration, int inflectionOpacity) {
        final ObjectAnimator outerOpacityAnim;
        if (outerInflection > 0) {
        if (inflectionDuration > 0) {
            // Outer opacity continues to increase for a bit.
            outerOpacityAnim = ObjectAnimator.ofFloat(this,
                    "outerOpacity", inflectionOpacity / 255.0f);
            outerOpacityAnim.setAutoCancel(true);
            outerOpacityAnim.setDuration(outerInflection);
            outerOpacityAnim.setDuration(inflectionDuration);
            outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);

            // Chain the outer opacity exit animation.
            final int outerDuration = opacityDuration - outerInflection;
            final int outerDuration = opacityDuration - inflectionDuration;
            if (outerDuration > 0) {
                outerOpacityAnim.addListener(new AnimatorListenerAdapter() {
                    @Override
@@ -446,14 +436,4 @@ class RippleBackground {
            mHardwareAnimating = false;
        }
    };

    /**
    * Interpolator with a smooth log deceleration
    */
    private static final class LogInterpolator implements TimeInterpolator {
        @Override
        public float getInterpolation(float input) {
            return 1 - (float) Math.pow(400, -input * 1.4);
        }
    }
}
+53 −91
Original line number Diff line number Diff line
@@ -242,7 +242,7 @@ public class RippleDrawable extends LayerDrawable {

    @Override
    protected boolean onStateChange(int[] stateSet) {
        super.onStateChange(stateSet);
        final boolean changed = super.onStateChange(stateSet);

        boolean enabled = false;
        boolean pressed = false;
@@ -263,19 +263,7 @@ public class RippleDrawable extends LayerDrawable {
        setRippleActive(enabled && pressed);
        setBackgroundActive(focused || (enabled && pressed));

        // Update the paint color. Only applicable when animated in software.
        if (mRipplePaint != null && mState.mColor != null) {
            final ColorStateList stateList = mState.mColor;
            final int newColor = stateList.getColorForState(stateSet, 0);
            final int oldColor = mRipplePaint.getColor();
            if (oldColor != newColor) {
                mRipplePaint.setColor(newColor);
                invalidateSelf();
                return true;
            }
        }

        return false;
        return changed;
    }

    private void setRippleActive(boolean active) {
@@ -587,6 +575,10 @@ public class RippleDrawable extends LayerDrawable {
            ripples[i].onHotspotBoundsChanged();
        }

        if (mRipple != null) {
            mRipple.onHotspotBoundsChanged();
        }

        if (mBackground != null) {
            mBackground.onHotspotBoundsChanged();
        }
@@ -617,17 +609,23 @@ public class RippleDrawable extends LayerDrawable {
        final boolean drawNonMaskContent = mLayerState.mNum > (hasMask ? 1 : 0);
        final boolean drawMask = hasMask && mMask.getOpacity() != PixelFormat.OPAQUE;
        final Rect bounds = getDirtyBounds();
        final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
        canvas.clipRect(bounds);

        // If we have content, draw it into a layer first.
        final int contentLayer = drawNonMaskContent ?
                drawContentLayer(canvas, bounds, SRC_OVER) : -1;
        final int contentLayer;
        if (drawNonMaskContent) {
            contentLayer = drawContentLayer(canvas, bounds, SRC_OVER);
        } else {
            contentLayer = -1;
        }

        // Next, try to draw the ripples (into a layer if necessary). If we need
        // to mask against the underlying content, set the xfermode to SRC_ATOP.
        final PorterDuffXfermode xfermode = (hasMask || !drawNonMaskContent) ? SRC_OVER : SRC_ATOP;

        // If we have a background and a non-opaque mask, draw the masking layer.
        final int backgroundLayer = drawBackgroundLayer(canvas, bounds, xfermode);
        final int backgroundLayer = drawBackgroundLayer(canvas, bounds, xfermode, drawMask);
        if (backgroundLayer >= 0) {
            if (drawMask) {
                drawMaskingLayer(canvas, bounds, DST_IN);
@@ -644,10 +642,13 @@ public class RippleDrawable extends LayerDrawable {
            canvas.restoreToCount(rippleLayer);
        }

        // Composite the layers if needed.
        if (contentLayer >= 0) {
            canvas.restoreToCount(contentLayer);
        // If we failed to draw anything, at least draw a color so that
        // invalidation works correctly.
        if (contentLayer < 0 && backgroundLayer < 0 && rippleLayer < 0) {
            canvas.drawColor(Color.TRANSPARENT);
        }

        canvas.restoreToCount(saveCount);
    }

    /**
@@ -711,81 +712,29 @@ public class RippleDrawable extends LayerDrawable {
        return restoreToCount;
    }

    private int drawBackgroundLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
        // Separate the ripple color and alpha channel. The alpha will be
        // applied when we merge the ripples down to the canvas.
        final int rippleARGB;
        if (mState.mColor != null) {
            rippleARGB = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
        } else {
            rippleARGB = Color.TRANSPARENT;
        }
    private int drawBackgroundLayer(
            Canvas canvas, Rect bounds, PorterDuffXfermode mode, boolean drawMask) {
        int saveCount = -1;

        if (mRipplePaint == null) {
            mRipplePaint = new Paint();
            mRipplePaint.setAntiAlias(true);
        if (mBackground != null && mBackground.shouldDraw()) {
            // TODO: We can avoid saveLayer here if we push the xfermode into
            // the background's render thread animator at exit() time.
            if (drawMask || mode != SRC_OVER) {
                saveCount = canvas.saveLayer(bounds.left, bounds.top, bounds.right,
                        bounds.bottom, getMaskingPaint(mode));
            }

        final int rippleAlpha = Color.alpha(rippleARGB);
        final Paint ripplePaint = mRipplePaint;
        ripplePaint.setColor(rippleARGB);
        ripplePaint.setAlpha(0xFF);

        boolean drewRipples = false;
        int restoreToCount = -1;
        int restoreTranslate = -1;

        // Draw background.
        final RippleBackground background = mBackground;
        if (background != null) {
            // If we're masking the ripple layer, make sure we have a layer
            // first. This will merge SRC_OVER (directly) onto the canvas.
            final Paint maskingPaint = getMaskingPaint(mode);
            maskingPaint.setAlpha(rippleAlpha);
            restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
                    bounds.right, bounds.bottom, maskingPaint);

            restoreTranslate = canvas.save();
            // Translate the canvas to the current hotspot bounds.
            canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY());

            drewRipples = background.draw(canvas, ripplePaint);
            final float x = mHotspotBounds.exactCenterX();
            final float y = mHotspotBounds.exactCenterY();
            canvas.translate(x, y);
            mBackground.draw(canvas, getRipplePaint());
            canvas.translate(-x, -y);
        }

        // Always restore the translation.
        if (restoreTranslate >= 0) {
            canvas.restoreToCount(restoreTranslate);
        }

        // If we created a layer with no content, merge it immediately.
        if (restoreToCount >= 0 && !drewRipples) {
            canvas.restoreToCount(restoreToCount);
            restoreToCount = -1;
        }

        return restoreToCount;
        return saveCount;
    }

    private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
        // Separate the ripple color and alpha channel. The alpha will be
        // applied when we merge the ripples down to the canvas.
        final int rippleARGB;
        if (mState.mColor != null) {
            rippleARGB = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
        } else {
            rippleARGB = Color.TRANSPARENT;
        }

        if (mRipplePaint == null) {
            mRipplePaint = new Paint();
            mRipplePaint.setAntiAlias(true);
        }

        final int rippleAlpha = Color.alpha(rippleARGB);
        final Paint ripplePaint = mRipplePaint;
        ripplePaint.setColor(rippleARGB);
        ripplePaint.setAlpha(0xFF);

        boolean drewRipples = false;
        int restoreToCount = -1;
        int restoreTranslate = -1;
@@ -807,16 +756,21 @@ public class RippleDrawable extends LayerDrawable {
            // first. This will merge SRC_OVER (directly) onto the canvas.
            if (restoreToCount < 0) {
                final Paint maskingPaint = getMaskingPaint(mode);
                maskingPaint.setAlpha(rippleAlpha);
                final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
                final int alpha = Color.alpha(color);
                maskingPaint.setAlpha(alpha / 2);

                // TODO: We can avoid saveLayer here if we're only drawing one
                // ripple and we don't have content or a translucent mask.
                restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
                        bounds.right, bounds.bottom, maskingPaint);

                restoreTranslate = canvas.save();
                // Translate the canvas to the current hotspot bounds.
                restoreTranslate = canvas.save();
                canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY());
            }

            drewRipples |= ripple.draw(canvas, ripplePaint);
            drewRipples |= ripple.draw(canvas, getRipplePaint());
        }

        // Always restore the translation.
@@ -845,6 +799,14 @@ public class RippleDrawable extends LayerDrawable {
        return restoreToCount;
    }

    private Paint getRipplePaint() {
        if (mRipplePaint == null) {
            mRipplePaint = new Paint();
            mRipplePaint.setAntiAlias(true);
        }
        return mRipplePaint;
    }

    private Paint getMaskingPaint(PorterDuffXfermode xfermode) {
        if (mMaskingPaint == null) {
            mMaskingPaint = new Paint();