Loading graphics/java/android/graphics/drawable/Ripple.java +16 −10 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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( Loading @@ -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; } Loading Loading @@ -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); Loading graphics/java/android/graphics/drawable/RippleBackground.java +21 −23 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading @@ -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) { Loading Loading @@ -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; } Loading Loading @@ -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) { Loading @@ -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); Loading graphics/java/android/graphics/drawable/RippleDrawable.java +76 −142 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -156,13 +156,6 @@ public class RippleDrawable extends LayerDrawable { /** Whether bounds are being overridden. */ 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. */ Loading Loading @@ -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(); } Loading Loading @@ -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); } Loading Loading @@ -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(); } Loading @@ -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(); } Loading Loading @@ -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); } Loading Loading @@ -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++) { Loading @@ -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; 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)); 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); } final PorterDuffXfermode mode = hasContentLayer ? SRC_ATOP : SRC_OVER; final float x = mHotspotBounds.exactCenterX(); final float y = mHotspotBounds.exactCenterY(); canvas.translate(x, y); mBackground.draw(canvas, getRipplePaint()); canvas.translate(-x, -y); } return saveCount; } final Paint p = getRipplePaint(); p.setXfermode(mode); private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) { boolean drewRipples = false; int restoreToCount = -1; int restoreTranslate = -1; // 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); // 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; final RippleBackground background = mBackground; if (background != null && background.shouldDraw()) { background.draw(canvas, p); } // 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()); final int count = mExitingRipplesCount; 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) { Loading @@ -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; } Loading Loading
graphics/java/android/graphics/drawable/Ripple.java +16 −10 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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( Loading @@ -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; } Loading Loading @@ -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); Loading
graphics/java/android/graphics/drawable/RippleBackground.java +21 −23 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading @@ -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) { Loading Loading @@ -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; } Loading Loading @@ -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) { Loading @@ -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); Loading
graphics/java/android/graphics/drawable/RippleDrawable.java +76 −142 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -156,13 +156,6 @@ public class RippleDrawable extends LayerDrawable { /** Whether bounds are being overridden. */ 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. */ Loading Loading @@ -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(); } Loading Loading @@ -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); } Loading Loading @@ -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(); } Loading @@ -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(); } Loading Loading @@ -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); } Loading Loading @@ -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++) { Loading @@ -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; 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)); 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); } final PorterDuffXfermode mode = hasContentLayer ? SRC_ATOP : SRC_OVER; final float x = mHotspotBounds.exactCenterX(); final float y = mHotspotBounds.exactCenterY(); canvas.translate(x, y); mBackground.draw(canvas, getRipplePaint()); canvas.translate(-x, -y); } return saveCount; } final Paint p = getRipplePaint(); p.setXfermode(mode); private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) { boolean drewRipples = false; int restoreToCount = -1; int restoreTranslate = -1; // 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); // 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; final RippleBackground background = mBackground; if (background != null && background.shouldDraw()) { background.draw(canvas, p); } // 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()); final int count = mExitingRipplesCount; 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) { Loading @@ -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; } Loading