diff options
author | Alan Viverette <alanv@google.com> | 2014-09-04 21:29:18 -0700 |
---|---|---|
committer | Alan Viverette <alanv@google.com> | 2014-09-05 19:15:44 +0000 |
commit | a3f0c2b21a73a82a919abe247c4046d114f3712c (patch) | |
tree | 7ce1375b1a49a17224a1d1f061a1b48ed2768c5c /graphics | |
parent | 3f70141ed0defa413f48bf9d906fad72b8401bda (diff) | |
download | frameworks_base-a3f0c2b21a73a82a919abe247c4046d114f3712c.zip frameworks_base-a3f0c2b21a73a82a919abe247c4046d114f3712c.tar.gz frameworks_base-a3f0c2b21a73a82a919abe247c4046d114f3712c.tar.bz2 |
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
Diffstat (limited to 'graphics')
3 files changed, 105 insertions, 174 deletions
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java index 43922b8..6f95f91 100644 --- a/graphics/java/android/graphics/drawable/Ripple.java +++ b/graphics/java/android/graphics/drawable/Ripple.java @@ -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(); diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java index 80ecea3..bc6f5fb 100644 --- a/graphics/java/android/graphics/drawable/RippleBackground.java +++ b/graphics/java/android/graphics/drawable/RippleBackground.java @@ -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); - } - } } diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index fa762b7..b05fb61 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -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; - } - - 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; - - // 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); - } + private int drawBackgroundLayer( + Canvas canvas, Rect bounds, PorterDuffXfermode mode, boolean drawMask) { + int saveCount = -1; - // Always restore the translation. - if (restoreTranslate >= 0) { - canvas.restoreToCount(restoreTranslate); - } + 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)); + } - // If we created a layer with no content, merge it immediately. - if (restoreToCount >= 0 && !drewRipples) { - canvas.restoreToCount(restoreToCount); - restoreToCount = -1; + final float x = mHotspotBounds.exactCenterX(); + final float y = mHotspotBounds.exactCenterY(); + canvas.translate(x, y); + mBackground.draw(canvas, getRipplePaint()); + canvas.translate(-x, -y); } - 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(); |