diff options
author | Alan Viverette <alanv@google.com> | 2014-11-07 15:31:16 -0800 |
---|---|---|
committer | Alan Viverette <alanv@google.com> | 2014-11-07 15:31:16 -0800 |
commit | cc3c573334a9cd2124a8a0ccf2f37884e36f83fa (patch) | |
tree | c995e150bbb94eb977f68d11f2b2773fe3b2029b /graphics/java | |
parent | 975590f22916e184204678758bb339c1d2b6b57f (diff) | |
download | frameworks_base-cc3c573334a9cd2124a8a0ccf2f37884e36f83fa.zip frameworks_base-cc3c573334a9cd2124a8a0ccf2f37884e36f83fa.tar.gz frameworks_base-cc3c573334a9cd2124a8a0ccf2f37884e36f83fa.tar.bz2 |
Reduce number of saveLayer calls in RippleDrawable
Removes an extra saveLayer call by rendering ripples as overlapping. We
are now down to zero saveLayers when drawing unmasked ripples or ripples
masked against opaque content/masks, one saveLayer for ripples masked
against content and two saveLayers when masked against an explicit mask
layer.
BUG: 18226391
Change-Id: I0fc09d21fbc462fbcfe4c26fc7b18737f584043e
Diffstat (limited to 'graphics/java')
3 files changed, 113 insertions, 175 deletions
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java index 864e119..6731366 100644 --- a/graphics/java/android/graphics/drawable/Ripple.java +++ b/graphics/java/android/graphics/drawable/Ripple.java @@ -26,6 +26,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; +import android.graphics.Xfermode; import android.util.MathUtils; import android.view.HardwareCanvas; import android.view.RenderNodeAnimator; @@ -58,8 +59,10 @@ class Ripple { /** Bounds used for computing max radius. */ private final Rect mBounds; - /** Full-opacity color for drawing this ripple. */ - private int mColorOpaque; + /** ARGB color for drawing this ripple. */ + private int mColor; + + private Xfermode mXfermode; /** Maximum ripple radius. */ private float mOuterRadius; @@ -120,9 +123,7 @@ class Ripple { mStartingY = startingY; } - public void setup(int maxRadius, int color, float density) { - mColorOpaque = color | 0xFF000000; - + public void setup(int maxRadius, float density) { if (maxRadius != RippleDrawable.RADIUS_AUTO) { mHasMaxRadius = true; mOuterRadius = maxRadius; @@ -216,6 +217,10 @@ class Ripple { * Draws the ripple centered at (0,0) using the specified paint. */ public boolean draw(Canvas c, Paint p) { + // Store the color and xfermode, we might need them later. + mColor = p.getColor(); + mXfermode = p.getXfermode(); + final boolean canUseHardware = c.isHardwareAccelerated(); if (mCanUseHardware != canUseHardware && mCanUseHardware) { // We've switched from hardware to non-hardware mode. Panic. @@ -261,8 +266,8 @@ class Ripple { private boolean drawSoftware(Canvas c, Paint p) { boolean hasContent = false; - p.setColor(mColorOpaque); - final int alpha = (int) (255 * mOpacity + 0.5f); + final int paintAlpha = p.getAlpha(); + final int alpha = (int) (paintAlpha * mOpacity + 0.5f); final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius); if (alpha > 0 && radius > 0) { final float x = MathUtils.lerp( @@ -270,8 +275,8 @@ class Ripple { final float y = MathUtils.lerp( mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY); p.setAlpha(alpha); - p.setStyle(Style.FILL); c.drawCircle(x, y, radius, p); + p.setAlpha(paintAlpha); hasContent = true; } @@ -374,8 +379,9 @@ class Ripple { final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius); final Paint paint = getTempPaint(); paint.setAntiAlias(true); - paint.setColor(mColorOpaque); - paint.setAlpha((int) (255 * mOpacity + 0.5f)); + paint.setColor(mColor); + paint.setXfermode(mXfermode); + paint.setAlpha((int) (Color.alpha(mColor) * mOpacity + 0.5f)); paint.setStyle(Style.FILL); mPropPaint = CanvasProperty.createPaint(paint); mPropRadius = CanvasProperty.createFloat(startRadius); diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java index 21d865f..69847b5 100644 --- a/graphics/java/android/graphics/drawable/RippleBackground.java +++ b/graphics/java/android/graphics/drawable/RippleBackground.java @@ -26,6 +26,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; +import android.graphics.Xfermode; import android.util.MathUtils; import android.view.HardwareCanvas; import android.view.RenderNodeAnimator; @@ -60,11 +61,10 @@ class RippleBackground { /** Bounds used for computing max radius. */ private final Rect mBounds; - /** Full-opacity color for drawing this ripple. */ - private int mColorOpaque; + /** ARGB color for drawing this ripple. */ + private int mColor; - /** Maximum alpha value for drawing this ripple. */ - private int mColorAlpha; + private Xfermode mXfermode; /** Maximum ripple radius. */ private float mOuterRadius; @@ -106,10 +106,7 @@ class RippleBackground { mBounds = bounds; } - public void setup(int maxRadius, int color, float density) { - mColorOpaque = color | 0xFF000000; - mColorAlpha = Color.alpha(color) / 2; - + public void setup(int maxRadius, float density) { if (maxRadius != RippleDrawable.RADIUS_AUTO) { mHasMaxRadius = true; mOuterRadius = maxRadius; @@ -124,10 +121,6 @@ class RippleBackground { mDensity = density; } - public boolean isHardwareAnimating() { - return mHardwareAnimating; - } - public void onHotspotBoundsChanged() { if (!mHasMaxRadius) { final float halfWidth = mBounds.width() / 2.0f; @@ -151,6 +144,10 @@ class RippleBackground { * Draws the ripple centered at (0,0) using the specified paint. */ public boolean draw(Canvas c, Paint p) { + // Store the color and xfermode, we might need them later. + mColor = p.getColor(); + mXfermode = p.getXfermode(); + final boolean canUseHardware = c.isHardwareAccelerated(); if (mCanUseHardware != canUseHardware && mCanUseHardware) { // We've switched from hardware to non-hardware mode. Panic. @@ -169,8 +166,7 @@ class RippleBackground { } public boolean shouldDraw() { - final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f); - return mCanUseHardware && mHardwareAnimating || outerAlpha > 0 && mOuterRadius > 0; + return (mCanUseHardware && mHardwareAnimating) || (mOuterOpacity > 0 && mOuterRadius > 0); } private boolean drawHardware(HardwareCanvas c) { @@ -201,12 +197,13 @@ class RippleBackground { private boolean drawSoftware(Canvas c, Paint p) { boolean hasContent = false; - p.setColor(mColorOpaque); - final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f); - if (outerAlpha > 0 && mOuterRadius > 0) { - p.setAlpha(outerAlpha); - p.setStyle(Style.FILL); - c.drawCircle(mOuterX, mOuterY, mOuterRadius, p); + final int paintAlpha = p.getAlpha(); + final int alpha = (int) (paintAlpha * mOuterOpacity + 0.5f); + final float radius = mOuterRadius; + if (alpha > 0 && radius > 0) { + p.setAlpha(alpha); + c.drawCircle(mOuterX, mOuterY, radius, p); + p.setAlpha(paintAlpha); hasContent = true; } @@ -262,7 +259,7 @@ class RippleBackground { // outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000 final int inflectionDuration = Math.max(0, (int) (1000 * (1 - mOuterOpacity) / (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f)); - final int inflectionOpacity = (int) (mColorAlpha * (mOuterOpacity + final int inflectionOpacity = (int) (Color.alpha(mColor) * (mOuterOpacity + inflectionDuration * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f); if (mCanUseHardware) { @@ -277,8 +274,9 @@ class RippleBackground { final Paint outerPaint = getTempPaint(); outerPaint.setAntiAlias(true); - outerPaint.setColor(mColorOpaque); - outerPaint.setAlpha((int) (mColorAlpha * mOuterOpacity + 0.5f)); + outerPaint.setXfermode(mXfermode); + outerPaint.setColor(mColor); + outerPaint.setAlpha((int) (Color.alpha(mColor) * mOuterOpacity + 0.5f)); outerPaint.setStyle(Style.FILL); mPropOuterPaint = CanvasProperty.createPaint(outerPaint); mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius); diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index e658279..8cbc239 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -16,6 +16,11 @@ package android.graphics.drawable; +import com.android.internal.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.ColorStateList; @@ -34,11 +39,6 @@ import android.graphics.Rect; import android.util.AttributeSet; import android.util.DisplayMetrics; -import com.android.internal.R; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - import java.io.IOException; import java.util.Arrays; @@ -157,13 +157,6 @@ public class RippleDrawable extends LayerDrawable { private boolean mOverrideBounds; /** - * Whether the next draw MUST draw something to canvas. Used to work around - * a bug in hardware invalidation following a render thread-accelerated - * animation. - */ - private boolean mNeedsDraw; - - /** * Constructor used for drawable inflation. */ RippleDrawable() { @@ -203,21 +196,15 @@ public class RippleDrawable extends LayerDrawable { public void jumpToCurrentState() { super.jumpToCurrentState(); - boolean needsDraw = false; - if (mRipple != null) { - needsDraw |= mRipple.isHardwareAnimating(); mRipple.jump(); } if (mBackground != null) { - needsDraw |= mBackground.isHardwareAnimating(); mBackground.jump(); } - needsDraw |= cancelExitingRipples(); - - mNeedsDraw = needsDraw; + cancelExitingRipples(); invalidateSelf(); } @@ -497,8 +484,7 @@ public class RippleDrawable extends LayerDrawable { mBackground = new RippleBackground(this, mHotspotBounds); } - final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT); - mBackground.setup(mState.mMaxRadius, color, mDensity); + mBackground.setup(mState.mMaxRadius, mDensity); mBackground.enter(focused); } @@ -534,8 +520,7 @@ public class RippleDrawable extends LayerDrawable { mRipple = new Ripple(this, mHotspotBounds, x, y); } - final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT); - mRipple.setup(mState.mMaxRadius, color, mDensity); + mRipple.setup(mState.mMaxRadius, mDensity); mRipple.enter(); } @@ -559,23 +544,17 @@ public class RippleDrawable extends LayerDrawable { * background. Nothing will be drawn after this method is called. */ private void clearHotspots() { - boolean needsDraw = false; - if (mRipple != null) { - needsDraw |= mRipple.isHardwareAnimating(); mRipple.cancel(); mRipple = null; } if (mBackground != null) { - needsDraw |= mBackground.isHardwareAnimating(); mBackground.cancel(); mBackground = null; } - needsDraw |= cancelExitingRipples(); - - mNeedsDraw = needsDraw; + cancelExitingRipples(); invalidateSelf(); } @@ -631,56 +610,41 @@ public class RippleDrawable extends LayerDrawable { } } + /** + * Optimized for drawing ripples with a mask layer and optional content. + */ @Override public void draw(@NonNull Canvas canvas) { final boolean hasMask = mMask != null; - final boolean drawNonMaskContent = mLayerState.mNum > (hasMask ? 1 : 0); - final boolean drawMask = hasMask && mMask.getOpacity() != PixelFormat.OPAQUE; + final boolean hasRipples = mRipple != null || mExitingRipplesCount > 0 + || (mBackground != null && mBackground.shouldDraw()); + + // Clip to the dirty bounds, which will be the drawable bounds if we + // have a mask or content and the ripple bounds if we're projecting. 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; - 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, drawMask); - if (backgroundLayer >= 0) { - if (drawMask) { + // If we have content, draw it first. If we have ripples and no mask, + // we'll draw it into a SRC_OVER layer so that we can mask ripples + // against it using SRC_IN. + final boolean hasContentLayer = drawContent(canvas, bounds, hasRipples, hasMask); + + // Next, try to draw the ripples. If we have a non-opaque mask, we'll + // draw the ripples into a SRC_OVER layer, draw the mask into a DST_IN + // layer, and blend. + if (hasRipples) { + final boolean hasNonOpaqueMask = hasMask && mMask.getOpacity() != PixelFormat.OPAQUE; + final boolean hasRippleLayer = drawBackgroundAndRipples(canvas, bounds, + hasNonOpaqueMask, hasContentLayer); + + // If drawing ripples created a layer, we have a non-opaque mask + // that needs to be blended on top of the ripples with DST_IN. + if (hasRippleLayer) { drawMaskingLayer(canvas, bounds, DST_IN); } - canvas.restoreToCount(backgroundLayer); } - // If we have ripples and a non-opaque mask, draw the masking layer. - final int rippleLayer = drawRippleLayer(canvas, bounds, xfermode); - if (rippleLayer >= 0) { - if (drawMask) { - drawMaskingLayer(canvas, bounds, DST_IN); - } - canvas.restoreToCount(rippleLayer); - } - - // If we failed to draw anything and we just canceled animations, at - // least draw a color so that hardware invalidation works correctly. - if (contentLayer < 0 && backgroundLayer < 0 && rippleLayer < 0 && mNeedsDraw) { - canvas.drawColor(Color.TRANSPARENT); - - // Request another draw so we can avoid adding a transparent layer - // during the next display list refresh. - invalidateSelf(); - } - mNeedsDraw = false; - canvas.restoreToCount(saveCount); } @@ -714,28 +678,27 @@ public class RippleDrawable extends LayerDrawable { return -1; } - private int drawContentLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) { + private boolean drawContent(Canvas canvas, Rect bounds, boolean hasRipples, boolean hasMask) { final ChildDrawable[] array = mLayerState.mChildren; final int count = mLayerState.mNum; - // We don't need a layer if we don't expect to draw any ripples or - // a background, we have an explicit mask, or if the non-mask content - // is all opaque. boolean needsLayer = false; - if ((mExitingRipplesCount > 0 || (mBackground != null && mBackground.shouldDraw())) - && mMask == null) { + + if (hasRipples && !hasMask) { + // If we only have opaque content, we don't really need a layer + // because the ripples will be clipped to the drawable bounds. for (int i = 0; i < count; i++) { - if (array[i].mId != R.id.mask - && array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) { + if (array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) { needsLayer = true; break; } } } - final Paint maskingPaint = getMaskingPaint(mode); - final int restoreToCount = needsLayer ? canvas.saveLayer(bounds.left, bounds.top, - bounds.right, bounds.bottom, maskingPaint) : -1; + if (needsLayer) { + canvas.saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, + getMaskingPaint(SRC_OVER)); + } // Draw everything except the mask. for (int i = 0; i < count; i++) { @@ -744,82 +707,52 @@ public class RippleDrawable extends LayerDrawable { } } - return restoreToCount; + return needsLayer; } - private int drawBackgroundLayer( - Canvas canvas, Rect bounds, PorterDuffXfermode mode, boolean drawMask) { - int saveCount = -1; + private boolean drawBackgroundAndRipples( + Canvas canvas, Rect bounds, boolean hasNonOpaqueMask, boolean hasContentLayer) { + if (hasNonOpaqueMask) { + final Paint p = getMaskingPaint(SRC_OVER); + canvas.saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, p); + } - 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 PorterDuffXfermode mode = hasContentLayer ? SRC_ATOP : SRC_OVER; + final float x = mHotspotBounds.exactCenterX(); + final float y = mHotspotBounds.exactCenterY(); + canvas.translate(x, y); - final float x = mHotspotBounds.exactCenterX(); - final float y = mHotspotBounds.exactCenterY(); - canvas.translate(x, y); - mBackground.draw(canvas, getRipplePaint()); - canvas.translate(-x, -y); - } + final Paint p = getRipplePaint(); + p.setXfermode(mode); - return saveCount; - } + // Grab the color for the current state and cut the alpha channel in + // half so that the ripple and background together yield full alpha. + final int color = mState.mColor.getColorForState(getState(), Color.BLACK); + final int alpha = (Color.alpha(color) / 2) << 24; + p.setColor(color & 0xFFFFFF | alpha); - private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) { - boolean drewRipples = false; - int restoreToCount = -1; - int restoreTranslate = -1; + final RippleBackground background = mBackground; + if (background != null && background.shouldDraw()) { + background.draw(canvas, p); + } - // Draw ripples and update the animating ripples array. final int count = mExitingRipplesCount; - final Ripple[] ripples = mExitingRipples; - for (int i = 0; i <= count; i++) { - final Ripple ripple; - if (i < count) { - ripple = ripples[i]; - } else if (mRipple != null) { - ripple = mRipple; - } else { - continue; - } - - // If we're masking the ripple layer, make sure we have a layer - // first. This will merge SRC_OVER (directly) onto the canvas. - if (restoreToCount < 0) { - final Paint maskingPaint = getMaskingPaint(mode); - 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); - - // Translate the canvas to the current hotspot bounds. - restoreTranslate = canvas.save(); - canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY()); + if (count > 0) { + final Ripple[] ripples = mExitingRipples; + for (int i = 0; i < count; i++) { + ripples[i].draw(canvas, p); } - - drewRipples |= ripple.draw(canvas, getRipplePaint()); } - // Always restore the translation. - if (restoreTranslate >= 0) { - canvas.restoreToCount(restoreTranslate); + final Ripple active = mRipple; + if (active != null) { + active.draw(canvas, p); } - // If we created a layer with no content, merge it immediately. - if (restoreToCount >= 0 && !drewRipples) { - canvas.restoreToCount(restoreToCount); - restoreToCount = -1; - } + canvas.translate(-x, -y); - return restoreToCount; + // Returns true if a layer was created. + return hasNonOpaqueMask; } private int drawMaskingLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) { @@ -838,6 +771,7 @@ public class RippleDrawable extends LayerDrawable { if (mRipplePaint == null) { mRipplePaint = new Paint(); mRipplePaint.setAntiAlias(true); + mRipplePaint.setStyle(Paint.Style.FILL); } return mRipplePaint; } |