diff options
author | Alan Viverette <alanv@google.com> | 2014-11-12 02:04:20 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2014-11-12 02:04:22 +0000 |
commit | b724314516dc15ab7afb62f7a6e63d94f4022011 (patch) | |
tree | 31e00e0ef9eaaabb86dd44c54e7e7e0c8780de89 /graphics | |
parent | 3f558e7285d8632b81cde4a9f1d0829e02be5fa0 (diff) | |
parent | cc3c573334a9cd2124a8a0ccf2f37884e36f83fa (diff) | |
download | frameworks_base-b724314516dc15ab7afb62f7a6e63d94f4022011.zip frameworks_base-b724314516dc15ab7afb62f7a6e63d94f4022011.tar.gz frameworks_base-b724314516dc15ab7afb62f7a6e63d94f4022011.tar.bz2 |
Merge "Reduce number of saveLayer calls in RippleDrawable" into lmp-mr1-dev
Diffstat (limited to 'graphics')
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; } |